From 814a5b0e72ede42c013e7411ff4740ddd0554234 Mon Sep 17 00:00:00 2001 From: alexey1312 Date: Mon, 16 Mar 2026 20:59:04 +0500 Subject: [PATCH 1/3] feat: expand Figma REST API coverage to 46/46 endpoints (100%) Add 38 new endpoints covering the complete Figma REST API v0.36.0: - User: GET /me - Files: HEAD /files/:key, GET /files/:key/images, GET /files/:key/versions, GET /files/:key/variables/published - Comments: GET/POST/DELETE /files/:key/comments - Components: GET /components/:key, GET /teams/:id/components - Component Sets: GET /component_sets/:key, GET /files/:key/component_sets, GET /teams/:id/component_sets - Styles: GET /styles/:key, GET /teams/:id/styles - Projects: GET /teams/:id/projects, GET /projects/:id/files - Reactions: GET/POST/DELETE /files/:key/comments/:id/reactions - Dev Resources: GET/POST/PUT/DELETE /files/:key/dev_resources - Webhooks (v2): GET/POST/PUT/DELETE /v2/webhooks, GET /v2/teams/:id/webhooks, GET /v2/webhooks/:id/requests - Analytics: activity_logs, payments, library component/style/variable actions and usages Refactor baseURL from https://api.figma.com/v1/ to https://api.figma.com/ so endpoints can target both v1 and v2 paths. This is an internal change with no impact on the public API. Add PaginationParams for cursor-based team endpoints, EmptyResponse for DELETE operations, and 22 new JSON fixtures with 290 total test cases. --- README.md | 33 +++++++-- .../Endpoint/ComponentsEndpoint.swift | 1 + .../Endpoint/DeleteCommentEndpoint.swift | 28 ++++++++ .../Endpoint/DeleteDevResourceEndpoint.swift | 28 ++++++++ .../Endpoint/DeleteReactionEndpoint.swift | 38 ++++++++++ .../Endpoint/DeleteWebhookEndpoint.swift | 24 +++++++ .../Endpoint/FileMetadataEndpoint.swift | 1 + .../Endpoint/GetActivityLogsEndpoint.swift | 29 ++++++++ .../Endpoint/GetCommentsEndpoint.swift | 31 ++++++++ .../GetComponentActionsEndpoint.swift | 33 +++++++++ .../Endpoint/GetComponentEndpoint.swift | 30 ++++++++ .../Endpoint/GetComponentSetEndpoint.swift | 30 ++++++++ .../Endpoint/GetComponentUsagesEndpoint.swift | 33 +++++++++ .../Endpoint/GetDevResourcesEndpoint.swift | 35 ++++++++++ .../GetFileComponentSetsEndpoint.swift | 39 +++++++++++ .../Endpoint/GetFileMetaEndpoint.swift | 34 +++++++++ .../Endpoint/GetFileVersionsEndpoint.swift | 31 ++++++++ .../Endpoint/GetImageFillsEndpoint.swift | 35 ++++++++++ Sources/FigmaAPI/Endpoint/GetMeEndpoint.swift | 15 ++++ .../Endpoint/GetPaymentsEndpoint.swift | 17 +++++ .../Endpoint/GetProjectFilesEndpoint.swift | 31 ++++++++ .../GetPublishedVariablesEndpoint.swift | 28 ++++++++ .../Endpoint/GetReactionsEndpoint.swift | 35 ++++++++++ .../Endpoint/GetStyleActionsEndpoint.swift | 29 ++++++++ .../FigmaAPI/Endpoint/GetStyleEndpoint.swift | 30 ++++++++ .../Endpoint/GetStyleUsagesEndpoint.swift | 29 ++++++++ .../GetTeamComponentSetsEndpoint.swift | 37 ++++++++++ .../Endpoint/GetTeamComponentsEndpoint.swift | 37 ++++++++++ .../Endpoint/GetTeamProjectsEndpoint.swift | 31 ++++++++ .../Endpoint/GetTeamStylesEndpoint.swift | 37 ++++++++++ .../Endpoint/GetTeamWebhooksEndpoint.swift | 28 ++++++++ .../Endpoint/GetVariableActionsEndpoint.swift | 29 ++++++++ .../Endpoint/GetWebhookEndpoint.swift | 22 ++++++ .../Endpoint/GetWebhookRequestsEndpoint.swift | 31 ++++++++ .../Endpoint/GetWebhooksEndpoint.swift | 25 +++++++ Sources/FigmaAPI/Endpoint/ImageEndpoint.swift | 1 + Sources/FigmaAPI/Endpoint/NodesEndpoint.swift | 1 + .../FigmaAPI/Endpoint/PaginationParams.swift | 18 +++++ .../Endpoint/PostCommentEndpoint.swift | 33 +++++++++ .../Endpoint/PostDevResourceEndpoint.swift | 33 +++++++++ .../Endpoint/PostReactionEndpoint.swift | 37 ++++++++++ .../Endpoint/PostWebhookEndpoint.swift | 27 +++++++ .../Endpoint/PutDevResourceEndpoint.swift | 33 +++++++++ .../Endpoint/PutWebhookEndpoint.swift | 30 ++++++++ .../FigmaAPI/Endpoint/StylesEndpoint.swift | 1 + .../Endpoint/UpdateVariablesEndpoint.swift | 1 + .../FigmaAPI/Endpoint/VariablesEndpoint.swift | 1 + Sources/FigmaAPI/FigmaClient.swift | 2 +- Sources/FigmaAPI/Model/ActivityLog.swift | 16 +++++ Sources/FigmaAPI/Model/Comment.swift | 29 ++++++++ Sources/FigmaAPI/Model/ComponentSet.swift | 14 ++++ Sources/FigmaAPI/Model/DevResource.swift | 13 ++++ Sources/FigmaAPI/Model/EmptyResponse.swift | 1 + Sources/FigmaAPI/Model/FileMeta.swift | 4 ++ Sources/FigmaAPI/Model/FileVersion.swift | 13 ++++ Sources/FigmaAPI/Model/LibraryAnalytics.swift | 41 +++++++++++ Sources/FigmaAPI/Model/PaymentInfo.swift | 14 ++++ Sources/FigmaAPI/Model/PostCommentBody.swift | 44 ++++++++++++ .../FigmaAPI/Model/PostDevResourceBody.swift | 19 +++++ Sources/FigmaAPI/Model/PostReactionBody.swift | 7 ++ Sources/FigmaAPI/Model/PostWebhookBody.swift | 21 ++++++ Sources/FigmaAPI/Model/Project.swift | 17 +++++ .../FigmaAPI/Model/PutDevResourceBody.swift | 18 +++++ Sources/FigmaAPI/Model/PutWebhookBody.swift | 18 +++++ Sources/FigmaAPI/Model/Reaction.swift | 10 +++ Sources/FigmaAPI/Model/User.swift | 11 +++ Sources/FigmaAPI/Model/Webhook.swift | 20 ++++++ Sources/FigmaAPI/Model/WebhookRequest.swift | 22 ++++++ .../ComponentsEndpointTests.swift | 2 +- .../DeleteCommentEndpointTests.swift | 38 ++++++++++ .../DeleteDevResourceEndpointTests.swift | 37 ++++++++++ .../DeleteReactionEndpointTests.swift | 48 +++++++++++++ .../DeleteWebhookEndpointTests.swift | 37 ++++++++++ .../EndpointMakeRequestTests.swift | 6 +- .../FileMetadataEndpointTests.swift | 4 +- .../Fixtures/ActivityLogsResponse.json | 20 ++++++ .../Fixtures/CommentsResponse.json | 40 +++++++++++ .../Fixtures/DevResourcesResponse.json | 18 +++++ .../Fixtures/FileComponentSetsResponse.json | 20 ++++++ .../Fixtures/FileVersionsResponse.json | 28 ++++++++ .../Fixtures/GetComponentResponse.json | 16 +++++ .../Fixtures/GetComponentSetResponse.json | 16 +++++ .../FigmaAPITests/Fixtures/GetMeResponse.json | 6 ++ .../Fixtures/GetStyleResponse.json | 8 +++ .../Fixtures/ImageFillsResponse.json | 8 +++ .../Fixtures/LibraryActionsResponse.json | 14 ++++ .../Fixtures/LibraryUsagesResponse.json | 13 ++++ .../Fixtures/PaymentsResponse.json | 13 ++++ .../Fixtures/PostCommentResponse.json | 21 ++++++ .../Fixtures/PostDevResourceResponse.json | 7 ++ .../Fixtures/ProjectFilesResponse.json | 16 +++++ .../Fixtures/PublishedVariablesResponse.json | 28 ++++++++ .../Fixtures/ReactionsResponse.json | 24 +++++++ .../Fixtures/TeamProjectsResponse.json | 12 ++++ .../Fixtures/WebhookRequestsResponse.json | 21 ++++++ .../Fixtures/WebhookResponse.json | 11 +++ .../Fixtures/WebhooksResponse.json | 26 +++++++ .../GetActivityLogsEndpointTests.swift | 60 ++++++++++++++++ .../GetCommentsEndpointTests.swift | 55 +++++++++++++++ .../GetComponentActionsEndpointTests.swift | 56 +++++++++++++++ .../GetComponentEndpointTests.swift | 52 ++++++++++++++ .../GetComponentSetEndpointTests.swift | 52 ++++++++++++++ .../GetComponentUsagesEndpointTests.swift | 55 +++++++++++++++ .../GetDevResourcesEndpointTests.swift | 54 ++++++++++++++ .../GetFileComponentSetsEndpointTests.swift | 51 ++++++++++++++ .../GetFileMetaEndpointTests.swift | 56 +++++++++++++++ .../GetFileVersionsEndpointTests.swift | 51 ++++++++++++++ .../GetImageFillsEndpointTests.swift | 32 +++++++++ Tests/FigmaAPITests/GetMeEndpointTests.swift | 33 +++++++++ .../GetPaymentsEndpointTests.swift | 44 ++++++++++++ .../GetProjectFilesEndpointTests.swift | 54 ++++++++++++++ .../GetPublishedVariablesEndpointTests.swift | 38 ++++++++++ .../GetReactionsEndpointTests.swift | 54 ++++++++++++++ .../GetStyleActionsEndpointTests.swift | 50 +++++++++++++ .../FigmaAPITests/GetStyleEndpointTests.swift | 51 ++++++++++++++ .../GetStyleUsagesEndpointTests.swift | 49 +++++++++++++ .../GetTeamComponentSetsEndpointTests.swift | 70 +++++++++++++++++++ .../GetTeamComponentsEndpointTests.swift | 70 +++++++++++++++++++ .../GetTeamProjectsEndpointTests.swift | 52 ++++++++++++++ .../GetTeamStylesEndpointTests.swift | 70 +++++++++++++++++++ .../GetTeamWebhooksEndpointTests.swift | 31 ++++++++ .../GetVariableActionsEndpointTests.swift | 50 +++++++++++++ .../GetWebhookEndpointTests.swift | 37 ++++++++++ .../GetWebhookRequestsEndpointTests.swift | 56 +++++++++++++++ .../GetWebhooksEndpointTests.swift | 58 +++++++++++++++ Tests/FigmaAPITests/ImageEndpointTests.swift | 8 +-- Tests/FigmaAPITests/Mocks/MockClient.swift | 2 +- Tests/FigmaAPITests/NodesEndpointTests.swift | 6 +- .../PostCommentEndpointTests.swift | 66 +++++++++++++++++ .../PostDevResourceEndpointTests.swift | 68 ++++++++++++++++++ .../PostReactionEndpointTests.swift | 62 ++++++++++++++++ .../PostWebhookEndpointTests.swift | 55 +++++++++++++++ .../PutDevResourceEndpointTests.swift | 67 ++++++++++++++++++ .../PutWebhookEndpointTests.swift | 52 ++++++++++++++ Tests/FigmaAPITests/StylesEndpointTests.swift | 4 +- .../UpdateVariablesEndpointTests.swift | 8 +-- .../VariablesEndpointTests.swift | 2 +- 137 files changed, 3945 insertions(+), 28 deletions(-) create mode 100644 Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetCommentsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetComponentActionsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetComponentEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetComponentSetEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetComponentUsagesEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetDevResourcesEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetFileComponentSetsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetFileMetaEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetFileVersionsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetImageFillsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetMeEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetProjectFilesEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetPublishedVariablesEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetReactionsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetStyleActionsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetStyleEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetStyleUsagesEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetTeamComponentSetsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetTeamComponentsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetTeamProjectsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetTeamStylesEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetVariableActionsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetWebhookEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetWebhookRequestsEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/PaginationParams.swift create mode 100644 Sources/FigmaAPI/Endpoint/PostCommentEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/PostReactionEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/PostWebhookEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift create mode 100644 Sources/FigmaAPI/Endpoint/PutWebhookEndpoint.swift create mode 100644 Sources/FigmaAPI/Model/ActivityLog.swift create mode 100644 Sources/FigmaAPI/Model/Comment.swift create mode 100644 Sources/FigmaAPI/Model/ComponentSet.swift create mode 100644 Sources/FigmaAPI/Model/DevResource.swift create mode 100644 Sources/FigmaAPI/Model/EmptyResponse.swift create mode 100644 Sources/FigmaAPI/Model/FileMeta.swift create mode 100644 Sources/FigmaAPI/Model/FileVersion.swift create mode 100644 Sources/FigmaAPI/Model/LibraryAnalytics.swift create mode 100644 Sources/FigmaAPI/Model/PaymentInfo.swift create mode 100644 Sources/FigmaAPI/Model/PostCommentBody.swift create mode 100644 Sources/FigmaAPI/Model/PostDevResourceBody.swift create mode 100644 Sources/FigmaAPI/Model/PostReactionBody.swift create mode 100644 Sources/FigmaAPI/Model/PostWebhookBody.swift create mode 100644 Sources/FigmaAPI/Model/Project.swift create mode 100644 Sources/FigmaAPI/Model/PutDevResourceBody.swift create mode 100644 Sources/FigmaAPI/Model/PutWebhookBody.swift create mode 100644 Sources/FigmaAPI/Model/Reaction.swift create mode 100644 Sources/FigmaAPI/Model/User.swift create mode 100644 Sources/FigmaAPI/Model/Webhook.swift create mode 100644 Sources/FigmaAPI/Model/WebhookRequest.swift create mode 100644 Tests/FigmaAPITests/DeleteCommentEndpointTests.swift create mode 100644 Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift create mode 100644 Tests/FigmaAPITests/DeleteReactionEndpointTests.swift create mode 100644 Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift create mode 100644 Tests/FigmaAPITests/Fixtures/ActivityLogsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/CommentsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/DevResourcesResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/FileComponentSetsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/FileVersionsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/GetComponentResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/GetComponentSetResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/GetMeResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/GetStyleResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/ImageFillsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/LibraryActionsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/LibraryUsagesResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/PaymentsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/PostCommentResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/ProjectFilesResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/PublishedVariablesResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/ReactionsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/TeamProjectsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/WebhookRequestsResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/WebhookResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/WebhooksResponse.json create mode 100644 Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetCommentsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetComponentActionsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetComponentEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetComponentSetEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetComponentUsagesEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetDevResourcesEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetFileComponentSetsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetFileMetaEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetFileVersionsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetImageFillsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetMeEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetPaymentsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetProjectFilesEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetPublishedVariablesEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetReactionsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetStyleActionsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetStyleEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetStyleUsagesEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetTeamComponentSetsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetTeamComponentsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetTeamProjectsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetTeamStylesEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetTeamWebhooksEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetVariableActionsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetWebhookEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetWebhookRequestsEndpointTests.swift create mode 100644 Tests/FigmaAPITests/GetWebhooksEndpointTests.swift create mode 100644 Tests/FigmaAPITests/PostCommentEndpointTests.swift create mode 100644 Tests/FigmaAPITests/PostDevResourceEndpointTests.swift create mode 100644 Tests/FigmaAPITests/PostReactionEndpointTests.swift create mode 100644 Tests/FigmaAPITests/PostWebhookEndpointTests.swift create mode 100644 Tests/FigmaAPITests/PutDevResourceEndpointTests.swift create mode 100644 Tests/FigmaAPITests/PutWebhookEndpointTests.swift diff --git a/README.md b/README.md index 9e5c9a8..f6bab5f 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ A Swift client for the [Figma REST API](https://www.figma.com/developers/api) wi ## Features -- 8 Figma API endpoints (files, components, nodes, images, styles, variables) +- Full Figma REST API coverage (46 endpoints) — files, components, styles, variables, comments, webhooks, dev resources, analytics, and more - Token-bucket rate limiting with fair round-robin scheduling - Exponential backoff retry with jitter and `Retry-After` support -- Figma Variables API (read + write codeSyntax) +- Figma Variables API (read local, read published, write codeSyntax) +- Support for both API v1 and v2 (webhooks) - GitHub Releases endpoint (for version checking) - Swift 6 strict concurrency @@ -47,12 +48,18 @@ Then add `FigmaAPI` to your target dependencies: ```swift import FigmaAPI -// Create a client with your Figma personal access token +// Create a client +let figma = FigmaClient(accessToken: "your-figma-token", timeout: nil) + +// Wrap with rate limiting and retry +let rateLimiter = SharedRateLimiter() let client = RateLimitedClient( - inner: FigmaClient(accessToken: "your-figma-token") + client: figma, + rateLimiter: rateLimiter, + configID: "default" ) -// Fetch components +// Fetch components from a file let components = try await client.request( ComponentsEndpoint(fileId: "your-file-id") ) @@ -64,8 +71,22 @@ let variables = try await client.request( // Export images as SVG let images = try await client.request( - ImageEndpoint(fileId: "your-file-id", nodeIds: ["1:2", "3:4"], format: .svg) + ImageEndpoint(fileId: "your-file-id", nodeIds: ["1:2", "3:4"], params: SVGParams()) ) + +// Get current user +let me = try await client.request(GetMeEndpoint()) + +// Post a comment +let comment = try await client.request( + PostCommentEndpoint( + fileId: "your-file-id", + body: PostCommentBody(message: "Looks great!") + ) +) + +// List webhooks (v2 API) +let webhooks = try await client.request(GetWebhooksEndpoint()) ``` ## License diff --git a/Sources/FigmaAPI/Endpoint/ComponentsEndpoint.swift b/Sources/FigmaAPI/Endpoint/ComponentsEndpoint.swift index b7f9ce1..34736c4 100644 --- a/Sources/FigmaAPI/Endpoint/ComponentsEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/ComponentsEndpoint.swift @@ -18,6 +18,7 @@ public struct ComponentsEndpoint: BaseEndpoint { public func makeRequest(baseURL: URL) -> URLRequest { let url = baseURL + .appendingPathComponent("v1") .appendingPathComponent("files") .appendingPathComponent(fileId) .appendingPathComponent("components") diff --git a/Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift new file mode 100644 index 0000000..14469da --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift @@ -0,0 +1,28 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct DeleteCommentEndpoint: BaseEndpoint { + public typealias Content = EmptyResponse + + private let fileId: String + private let commentId: String + + public init(fileId: String, commentId: String) { + self.fileId = fileId + self.commentId = commentId + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("comments") + .appendingPathComponent(commentId) + var request = URLRequest(url: url) + request.httpMethod = "DELETE" + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift new file mode 100644 index 0000000..3c65456 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift @@ -0,0 +1,28 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct DeleteDevResourceEndpoint: BaseEndpoint { + public typealias Content = EmptyResponse + + private let fileId: String + private let resourceId: String + + public init(fileId: String, resourceId: String) { + self.fileId = fileId + self.resourceId = resourceId + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("dev_resources") + .appendingPathComponent(resourceId) + var request = URLRequest(url: url) + request.httpMethod = "DELETE" + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift new file mode 100644 index 0000000..1be124f --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift @@ -0,0 +1,38 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct DeleteReactionEndpoint: BaseEndpoint { + public typealias Content = EmptyResponse + + private let fileId: String + private let commentId: String + private let emoji: String + + public init(fileId: String, commentId: String, emoji: String) { + self.fileId = fileId + self.commentId = commentId + self.emoji = emoji + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("comments") + .appendingPathComponent(commentId) + .appendingPathComponent("reactions") + guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + throw URLError(.badURL) + } + comps.queryItems = [URLQueryItem(name: "emoji", value: emoji)] + guard let finalURL = comps.url else { + throw URLError(.badURL) + } + var request = URLRequest(url: finalURL) + request.httpMethod = "DELETE" + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift new file mode 100644 index 0000000..64380d4 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift @@ -0,0 +1,24 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct DeleteWebhookEndpoint: BaseEndpoint { + public typealias Content = EmptyResponse + + private let webhookId: String + + public init(webhookId: String) { + self.webhookId = webhookId + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v2") + .appendingPathComponent("webhooks") + .appendingPathComponent(webhookId) + var request = URLRequest(url: url) + request.httpMethod = "DELETE" + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/FileMetadataEndpoint.swift b/Sources/FigmaAPI/Endpoint/FileMetadataEndpoint.swift index dc813e7..815ec6a 100644 --- a/Sources/FigmaAPI/Endpoint/FileMetadataEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/FileMetadataEndpoint.swift @@ -16,6 +16,7 @@ public struct FileMetadataEndpoint: BaseEndpoint { public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL + .appendingPathComponent("v1") .appendingPathComponent("files") .appendingPathComponent(fileId) diff --git a/Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift new file mode 100644 index 0000000..6139b39 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift @@ -0,0 +1,29 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetActivityLogsEndpoint: BaseEndpoint { + public typealias Content = [ActivityLog] + + public init() {} + + func content(from root: ActivityLogsResponse) -> [ActivityLog] { + root.activityLogs + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("activity_logs") + return URLRequest(url: url) + } +} + +struct ActivityLogsResponse: Decodable { + let activityLogs: [ActivityLog] + + private enum CodingKeys: String, CodingKey { + case activityLogs = "activity_logs" + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetCommentsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetCommentsEndpoint.swift new file mode 100644 index 0000000..81b901d --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetCommentsEndpoint.swift @@ -0,0 +1,31 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetCommentsEndpoint: BaseEndpoint { + public typealias Content = [Comment] + + private let fileId: String + + public init(fileId: String) { + self.fileId = fileId + } + + func content(from root: CommentsResponse) -> [Comment] { + root.comments + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("comments") + return URLRequest(url: url) + } +} + +struct CommentsResponse: Decodable { + let comments: [Comment] +} diff --git a/Sources/FigmaAPI/Endpoint/GetComponentActionsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetComponentActionsEndpoint.swift new file mode 100644 index 0000000..841d1bf --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetComponentActionsEndpoint.swift @@ -0,0 +1,33 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetComponentActionsEndpoint: BaseEndpoint { + public typealias Content = [LibraryAnalyticsAction] + + private let fileKey: String + + public init(fileKey: String) { + self.fileKey = fileKey + } + + func content(from root: LibraryActionsResponse) -> [LibraryAnalyticsAction] { + root.actions + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("analytics") + .appendingPathComponent("libraries") + .appendingPathComponent(fileKey) + .appendingPathComponent("component") + .appendingPathComponent("actions") + return URLRequest(url: url) + } +} + +struct LibraryActionsResponse: Decodable { + let actions: [LibraryAnalyticsAction] +} diff --git a/Sources/FigmaAPI/Endpoint/GetComponentEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetComponentEndpoint.swift new file mode 100644 index 0000000..b20d3e8 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetComponentEndpoint.swift @@ -0,0 +1,30 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetComponentEndpoint: BaseEndpoint { + public typealias Content = Component + + private let key: String + + public init(key: String) { + self.key = key + } + + func content(from root: GetComponentResponse) -> Component { + root.meta + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("components") + .appendingPathComponent(key) + return URLRequest(url: url) + } +} + +struct GetComponentResponse: Decodable { + let meta: Component +} diff --git a/Sources/FigmaAPI/Endpoint/GetComponentSetEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetComponentSetEndpoint.swift new file mode 100644 index 0000000..6df7bfb --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetComponentSetEndpoint.swift @@ -0,0 +1,30 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetComponentSetEndpoint: BaseEndpoint { + public typealias Content = ComponentSet + + private let key: String + + public init(key: String) { + self.key = key + } + + func content(from root: GetComponentSetResponse) -> ComponentSet { + root.meta + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("component_sets") + .appendingPathComponent(key) + return URLRequest(url: url) + } +} + +struct GetComponentSetResponse: Decodable { + let meta: ComponentSet +} diff --git a/Sources/FigmaAPI/Endpoint/GetComponentUsagesEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetComponentUsagesEndpoint.swift new file mode 100644 index 0000000..175556a --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetComponentUsagesEndpoint.swift @@ -0,0 +1,33 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetComponentUsagesEndpoint: BaseEndpoint { + public typealias Content = [LibraryAnalyticsUsage] + + private let fileKey: String + + public init(fileKey: String) { + self.fileKey = fileKey + } + + func content(from root: LibraryUsagesResponse) -> [LibraryAnalyticsUsage] { + root.usages + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("analytics") + .appendingPathComponent("libraries") + .appendingPathComponent(fileKey) + .appendingPathComponent("component") + .appendingPathComponent("usages") + return URLRequest(url: url) + } +} + +struct LibraryUsagesResponse: Decodable { + let usages: [LibraryAnalyticsUsage] +} diff --git a/Sources/FigmaAPI/Endpoint/GetDevResourcesEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetDevResourcesEndpoint.swift new file mode 100644 index 0000000..f0a6e6e --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetDevResourcesEndpoint.swift @@ -0,0 +1,35 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetDevResourcesEndpoint: BaseEndpoint { + public typealias Content = [DevResource] + + private let fileId: String + + public init(fileId: String) { + self.fileId = fileId + } + + func content(from root: DevResourcesResponse) -> [DevResource] { + root.devResources + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("dev_resources") + return URLRequest(url: url) + } +} + +struct DevResourcesResponse: Decodable { + let devResources: [DevResource] + + private enum CodingKeys: String, CodingKey { + case devResources = "dev_resources" + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetFileComponentSetsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetFileComponentSetsEndpoint.swift new file mode 100644 index 0000000..613eb87 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetFileComponentSetsEndpoint.swift @@ -0,0 +1,39 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetFileComponentSetsEndpoint: BaseEndpoint { + public typealias Content = [ComponentSet] + + private let fileId: String + + public init(fileId: String) { + self.fileId = fileId + } + + func content(from root: FileComponentSetsResponse) -> [ComponentSet] { + root.meta.componentSets + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("component_sets") + return URLRequest(url: url) + } +} + +struct FileComponentSetsResponse: Decodable { + let meta: FileComponentSetsMeta +} + +struct FileComponentSetsMeta: Decodable { + let componentSets: [ComponentSet] + + private enum CodingKeys: String, CodingKey { + case componentSets = "component_sets" + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetFileMetaEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetFileMetaEndpoint.swift new file mode 100644 index 0000000..2b8e817 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetFileMetaEndpoint.swift @@ -0,0 +1,34 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetFileMetaEndpoint: Endpoint { + public typealias Content = FileMeta + + private let fileId: String + + public init(fileId: String) { + self.fileId = fileId + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + var request = URLRequest(url: url) + request.httpMethod = "HEAD" + return request + } + + public func content(from response: URLResponse?, with body: Data) throws -> FileMeta { + guard let http = response as? HTTPURLResponse else { + throw URLError(.badServerResponse) + } + return FileMeta( + lastModified: http.value(forHTTPHeaderField: "Last-Modified"), + version: http.value(forHTTPHeaderField: "X-Figma-Version") + ) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetFileVersionsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetFileVersionsEndpoint.swift new file mode 100644 index 0000000..9550767 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetFileVersionsEndpoint.swift @@ -0,0 +1,31 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetFileVersionsEndpoint: BaseEndpoint { + public typealias Content = [FileVersion] + + private let fileId: String + + public init(fileId: String) { + self.fileId = fileId + } + + func content(from root: FileVersionsResponse) -> [FileVersion] { + root.versions + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("versions") + return URLRequest(url: url) + } +} + +struct FileVersionsResponse: Decodable { + let versions: [FileVersion] +} diff --git a/Sources/FigmaAPI/Endpoint/GetImageFillsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetImageFillsEndpoint.swift new file mode 100644 index 0000000..9f13f44 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetImageFillsEndpoint.swift @@ -0,0 +1,35 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetImageFillsEndpoint: BaseEndpoint { + public typealias Content = [String: String] + + private let fileId: String + + public init(fileId: String) { + self.fileId = fileId + } + + func content(from root: ImageFillsResponse) -> [String: String] { + root.meta.images + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("images") + return URLRequest(url: url) + } +} + +struct ImageFillsResponse: Decodable { + let meta: ImageFillsMeta +} + +struct ImageFillsMeta: Decodable { + let images: [String: String] +} diff --git a/Sources/FigmaAPI/Endpoint/GetMeEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetMeEndpoint.swift new file mode 100644 index 0000000..d0ab972 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetMeEndpoint.swift @@ -0,0 +1,15 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetMeEndpoint: BaseEndpoint { + public typealias Content = User + + public init() {} + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL.appendingPathComponent("v1").appendingPathComponent("me") + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift new file mode 100644 index 0000000..40d1b9f --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift @@ -0,0 +1,17 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetPaymentsEndpoint: BaseEndpoint { + public typealias Content = PaymentInfo + + public init() {} + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("payments") + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetProjectFilesEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetProjectFilesEndpoint.swift new file mode 100644 index 0000000..4e2e695 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetProjectFilesEndpoint.swift @@ -0,0 +1,31 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetProjectFilesEndpoint: BaseEndpoint { + public typealias Content = [ProjectFile] + + private let projectId: String + + public init(projectId: String) { + self.projectId = projectId + } + + func content(from root: ProjectFilesResponse) -> [ProjectFile] { + root.files + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("projects") + .appendingPathComponent(projectId) + .appendingPathComponent("files") + return URLRequest(url: url) + } +} + +struct ProjectFilesResponse: Decodable { + let files: [ProjectFile] +} diff --git a/Sources/FigmaAPI/Endpoint/GetPublishedVariablesEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetPublishedVariablesEndpoint.swift new file mode 100644 index 0000000..8ce8d3c --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetPublishedVariablesEndpoint.swift @@ -0,0 +1,28 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetPublishedVariablesEndpoint: BaseEndpoint { + public typealias Content = VariablesMeta + + private let fileId: String + + public init(fileId: String) { + self.fileId = fileId + } + + func content(from root: VariablesResponse) -> VariablesMeta { + root.meta + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("variables") + .appendingPathComponent("published") + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetReactionsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetReactionsEndpoint.swift new file mode 100644 index 0000000..79cc9ee --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetReactionsEndpoint.swift @@ -0,0 +1,35 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetReactionsEndpoint: BaseEndpoint { + public typealias Content = [Reaction] + + private let fileId: String + private let commentId: String + + public init(fileId: String, commentId: String) { + self.fileId = fileId + self.commentId = commentId + } + + func content(from root: ReactionsResponse) -> [Reaction] { + root.reactions + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("comments") + .appendingPathComponent(commentId) + .appendingPathComponent("reactions") + return URLRequest(url: url) + } +} + +struct ReactionsResponse: Decodable { + let reactions: [Reaction] +} diff --git a/Sources/FigmaAPI/Endpoint/GetStyleActionsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetStyleActionsEndpoint.swift new file mode 100644 index 0000000..6d03829 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetStyleActionsEndpoint.swift @@ -0,0 +1,29 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetStyleActionsEndpoint: BaseEndpoint { + public typealias Content = [LibraryAnalyticsAction] + + private let fileKey: String + + public init(fileKey: String) { + self.fileKey = fileKey + } + + func content(from root: LibraryActionsResponse) -> [LibraryAnalyticsAction] { + root.actions + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("analytics") + .appendingPathComponent("libraries") + .appendingPathComponent(fileKey) + .appendingPathComponent("style") + .appendingPathComponent("actions") + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetStyleEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetStyleEndpoint.swift new file mode 100644 index 0000000..d72785e --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetStyleEndpoint.swift @@ -0,0 +1,30 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetStyleEndpoint: BaseEndpoint { + public typealias Content = Style + + private let key: String + + public init(key: String) { + self.key = key + } + + func content(from root: GetStyleResponse) -> Style { + root.meta + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("styles") + .appendingPathComponent(key) + return URLRequest(url: url) + } +} + +struct GetStyleResponse: Decodable { + let meta: Style +} diff --git a/Sources/FigmaAPI/Endpoint/GetStyleUsagesEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetStyleUsagesEndpoint.swift new file mode 100644 index 0000000..ceb8a23 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetStyleUsagesEndpoint.swift @@ -0,0 +1,29 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetStyleUsagesEndpoint: BaseEndpoint { + public typealias Content = [LibraryAnalyticsUsage] + + private let fileKey: String + + public init(fileKey: String) { + self.fileKey = fileKey + } + + func content(from root: LibraryUsagesResponse) -> [LibraryAnalyticsUsage] { + root.usages + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("analytics") + .appendingPathComponent("libraries") + .appendingPathComponent(fileKey) + .appendingPathComponent("style") + .appendingPathComponent("usages") + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetTeamComponentSetsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetTeamComponentSetsEndpoint.swift new file mode 100644 index 0000000..7f2b26c --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetTeamComponentSetsEndpoint.swift @@ -0,0 +1,37 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetTeamComponentSetsEndpoint: BaseEndpoint { + public typealias Content = [ComponentSet] + + private let teamId: String + private let pagination: PaginationParams? + + public init(teamId: String, pagination: PaginationParams? = nil) { + self.teamId = teamId + self.pagination = pagination + } + + func content(from root: FileComponentSetsResponse) -> [ComponentSet] { + root.meta.componentSets + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("teams") + .appendingPathComponent(teamId) + .appendingPathComponent("component_sets") + guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + throw URLError(.badURL) + } + let items = pagination?.queryItems ?? [] + if !items.isEmpty { comps.queryItems = items } + guard let finalURL = comps.url else { + throw URLError(.badURL) + } + return URLRequest(url: finalURL) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetTeamComponentsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetTeamComponentsEndpoint.swift new file mode 100644 index 0000000..c496d20 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetTeamComponentsEndpoint.swift @@ -0,0 +1,37 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetTeamComponentsEndpoint: BaseEndpoint { + public typealias Content = [Component] + + private let teamId: String + private let pagination: PaginationParams? + + public init(teamId: String, pagination: PaginationParams? = nil) { + self.teamId = teamId + self.pagination = pagination + } + + func content(from root: ComponentsResponse) -> [Component] { + root.meta.components + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("teams") + .appendingPathComponent(teamId) + .appendingPathComponent("components") + guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + throw URLError(.badURL) + } + let items = pagination?.queryItems ?? [] + if !items.isEmpty { comps.queryItems = items } + guard let finalURL = comps.url else { + throw URLError(.badURL) + } + return URLRequest(url: finalURL) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetTeamProjectsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetTeamProjectsEndpoint.swift new file mode 100644 index 0000000..8fbcf34 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetTeamProjectsEndpoint.swift @@ -0,0 +1,31 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetTeamProjectsEndpoint: BaseEndpoint { + public typealias Content = [Project] + + private let teamId: String + + public init(teamId: String) { + self.teamId = teamId + } + + func content(from root: TeamProjectsResponse) -> [Project] { + root.projects + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("teams") + .appendingPathComponent(teamId) + .appendingPathComponent("projects") + return URLRequest(url: url) + } +} + +struct TeamProjectsResponse: Decodable { + let projects: [Project] +} diff --git a/Sources/FigmaAPI/Endpoint/GetTeamStylesEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetTeamStylesEndpoint.swift new file mode 100644 index 0000000..965db09 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetTeamStylesEndpoint.swift @@ -0,0 +1,37 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetTeamStylesEndpoint: BaseEndpoint { + public typealias Content = [Style] + + private let teamId: String + private let pagination: PaginationParams? + + public init(teamId: String, pagination: PaginationParams? = nil) { + self.teamId = teamId + self.pagination = pagination + } + + func content(from root: StylesResponse) -> [Style] { + root.meta.styles + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("teams") + .appendingPathComponent(teamId) + .appendingPathComponent("styles") + guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + throw URLError(.badURL) + } + let items = pagination?.queryItems ?? [] + if !items.isEmpty { comps.queryItems = items } + guard let finalURL = comps.url else { + throw URLError(.badURL) + } + return URLRequest(url: finalURL) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift new file mode 100644 index 0000000..57d5c0b --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift @@ -0,0 +1,28 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +@available(*, deprecated, message: "Use GetWebhooksEndpoint instead") +public struct GetTeamWebhooksEndpoint: BaseEndpoint { + public typealias Content = [Webhook] + + private let teamId: String + + public init(teamId: String) { + self.teamId = teamId + } + + func content(from root: WebhooksResponse) -> [Webhook] { + root.webhooks + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v2") + .appendingPathComponent("teams") + .appendingPathComponent(teamId) + .appendingPathComponent("webhooks") + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetVariableActionsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetVariableActionsEndpoint.swift new file mode 100644 index 0000000..8d785fa --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetVariableActionsEndpoint.swift @@ -0,0 +1,29 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetVariableActionsEndpoint: BaseEndpoint { + public typealias Content = [LibraryAnalyticsAction] + + private let fileKey: String + + public init(fileKey: String) { + self.fileKey = fileKey + } + + func content(from root: LibraryActionsResponse) -> [LibraryAnalyticsAction] { + root.actions + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("analytics") + .appendingPathComponent("libraries") + .appendingPathComponent(fileKey) + .appendingPathComponent("variable") + .appendingPathComponent("actions") + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetWebhookEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetWebhookEndpoint.swift new file mode 100644 index 0000000..b3b93b3 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetWebhookEndpoint.swift @@ -0,0 +1,22 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetWebhookEndpoint: BaseEndpoint { + public typealias Content = Webhook + + private let webhookId: String + + public init(webhookId: String) { + self.webhookId = webhookId + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v2") + .appendingPathComponent("webhooks") + .appendingPathComponent(webhookId) + return URLRequest(url: url) + } +} diff --git a/Sources/FigmaAPI/Endpoint/GetWebhookRequestsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetWebhookRequestsEndpoint.swift new file mode 100644 index 0000000..f668b92 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetWebhookRequestsEndpoint.swift @@ -0,0 +1,31 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetWebhookRequestsEndpoint: BaseEndpoint { + public typealias Content = [WebhookRequest] + + private let webhookId: String + + public init(webhookId: String) { + self.webhookId = webhookId + } + + func content(from root: WebhookRequestsResponse) -> [WebhookRequest] { + root.requests + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v2") + .appendingPathComponent("webhooks") + .appendingPathComponent(webhookId) + .appendingPathComponent("requests") + return URLRequest(url: url) + } +} + +struct WebhookRequestsResponse: Decodable { + let requests: [WebhookRequest] +} diff --git a/Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift new file mode 100644 index 0000000..42f3673 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift @@ -0,0 +1,25 @@ +import Foundation +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct GetWebhooksEndpoint: BaseEndpoint { + public typealias Content = [Webhook] + + public init() {} + + func content(from root: WebhooksResponse) -> [Webhook] { + root.webhooks + } + + public func makeRequest(baseURL: URL) -> URLRequest { + let url = baseURL + .appendingPathComponent("v2") + .appendingPathComponent("webhooks") + return URLRequest(url: url) + } +} + +struct WebhooksResponse: Decodable { + let webhooks: [Webhook] +} diff --git a/Sources/FigmaAPI/Endpoint/ImageEndpoint.swift b/Sources/FigmaAPI/Endpoint/ImageEndpoint.swift index 7eff782..bbd0b0b 100644 --- a/Sources/FigmaAPI/Endpoint/ImageEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/ImageEndpoint.swift @@ -81,6 +81,7 @@ public struct ImageEndpoint: BaseEndpoint { public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL + .appendingPathComponent("v1") .appendingPathComponent("images") .appendingPathComponent(fileId) diff --git a/Sources/FigmaAPI/Endpoint/NodesEndpoint.swift b/Sources/FigmaAPI/Endpoint/NodesEndpoint.swift index a04f0ed..052de4e 100644 --- a/Sources/FigmaAPI/Endpoint/NodesEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/NodesEndpoint.swift @@ -20,6 +20,7 @@ public struct NodesEndpoint: BaseEndpoint { public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL + .appendingPathComponent("v1") .appendingPathComponent("files") .appendingPathComponent(fileId) .appendingPathComponent("nodes") diff --git a/Sources/FigmaAPI/Endpoint/PaginationParams.swift b/Sources/FigmaAPI/Endpoint/PaginationParams.swift new file mode 100644 index 0000000..4ecad4d --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/PaginationParams.swift @@ -0,0 +1,18 @@ +import Foundation + +public struct PaginationParams: Sendable { + public let pageSize: Int? + public let cursor: String? + + public init(pageSize: Int? = nil, cursor: String? = nil) { + self.pageSize = pageSize + self.cursor = cursor + } + + public var queryItems: [URLQueryItem] { + var items: [URLQueryItem] = [] + if let pageSize { items.append(.init(name: "page_size", value: "\(pageSize)")) } + if let cursor { items.append(.init(name: "cursor", value: cursor)) } + return items + } +} diff --git a/Sources/FigmaAPI/Endpoint/PostCommentEndpoint.swift b/Sources/FigmaAPI/Endpoint/PostCommentEndpoint.swift new file mode 100644 index 0000000..3726bf7 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/PostCommentEndpoint.swift @@ -0,0 +1,33 @@ +import Foundation +import YYJSON + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct PostCommentEndpoint: BaseEndpoint { + public typealias Content = Comment + + private let fileId: String + private let body: PostCommentBody + + public init(fileId: String, body: PostCommentBody) { + self.fileId = fileId + self.body = body + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("comments") + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try YYJSONEncoder().encode(body) + + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift b/Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift new file mode 100644 index 0000000..2bc2f7c --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift @@ -0,0 +1,33 @@ +import Foundation +import YYJSON + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct PostDevResourceEndpoint: BaseEndpoint { + public typealias Content = DevResource + + private let fileId: String + private let body: PostDevResourceBody + + public init(fileId: String, body: PostDevResourceBody) { + self.fileId = fileId + self.body = body + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("dev_resources") + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try YYJSONEncoder().encode(body) + + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/PostReactionEndpoint.swift b/Sources/FigmaAPI/Endpoint/PostReactionEndpoint.swift new file mode 100644 index 0000000..992f882 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/PostReactionEndpoint.swift @@ -0,0 +1,37 @@ +import Foundation +import YYJSON + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct PostReactionEndpoint: BaseEndpoint { + public typealias Content = EmptyResponse + + private let fileId: String + private let commentId: String + private let body: PostReactionBody + + public init(fileId: String, commentId: String, body: PostReactionBody) { + self.fileId = fileId + self.commentId = commentId + self.body = body + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("comments") + .appendingPathComponent(commentId) + .appendingPathComponent("reactions") + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try YYJSONEncoder().encode(body) + + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/PostWebhookEndpoint.swift b/Sources/FigmaAPI/Endpoint/PostWebhookEndpoint.swift new file mode 100644 index 0000000..0a4bda0 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/PostWebhookEndpoint.swift @@ -0,0 +1,27 @@ +import Foundation +import YYJSON + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct PostWebhookEndpoint: BaseEndpoint { + public typealias Content = Webhook + + private let body: PostWebhookBody + + public init(body: PostWebhookBody) { + self.body = body + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v2") + .appendingPathComponent("webhooks") + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try YYJSONEncoder().encode(body) + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift b/Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift new file mode 100644 index 0000000..75e2106 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift @@ -0,0 +1,33 @@ +import Foundation +import YYJSON + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct PutDevResourceEndpoint: BaseEndpoint { + public typealias Content = DevResource + + private let fileId: String + private let body: PutDevResourceBody + + public init(fileId: String, body: PutDevResourceBody) { + self.fileId = fileId + self.body = body + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v1") + .appendingPathComponent("files") + .appendingPathComponent(fileId) + .appendingPathComponent("dev_resources") + + var request = URLRequest(url: url) + request.httpMethod = "PUT" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try YYJSONEncoder().encode(body) + + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/PutWebhookEndpoint.swift b/Sources/FigmaAPI/Endpoint/PutWebhookEndpoint.swift new file mode 100644 index 0000000..a6d4795 --- /dev/null +++ b/Sources/FigmaAPI/Endpoint/PutWebhookEndpoint.swift @@ -0,0 +1,30 @@ +import Foundation +import YYJSON + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +public struct PutWebhookEndpoint: BaseEndpoint { + public typealias Content = Webhook + + private let webhookId: String + private let body: PutWebhookBody + + public init(webhookId: String, body: PutWebhookBody) { + self.webhookId = webhookId + self.body = body + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { + let url = baseURL + .appendingPathComponent("v2") + .appendingPathComponent("webhooks") + .appendingPathComponent(webhookId) + var request = URLRequest(url: url) + request.httpMethod = "PUT" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = try YYJSONEncoder().encode(body) + return request + } +} diff --git a/Sources/FigmaAPI/Endpoint/StylesEndpoint.swift b/Sources/FigmaAPI/Endpoint/StylesEndpoint.swift index 032e892..6770c78 100644 --- a/Sources/FigmaAPI/Endpoint/StylesEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/StylesEndpoint.swift @@ -18,6 +18,7 @@ public struct StylesEndpoint: BaseEndpoint { public func makeRequest(baseURL: URL) -> URLRequest { let url = baseURL + .appendingPathComponent("v1") .appendingPathComponent("files") .appendingPathComponent(fileId) .appendingPathComponent("styles") diff --git a/Sources/FigmaAPI/Endpoint/UpdateVariablesEndpoint.swift b/Sources/FigmaAPI/Endpoint/UpdateVariablesEndpoint.swift index f120295..45bd42b 100644 --- a/Sources/FigmaAPI/Endpoint/UpdateVariablesEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/UpdateVariablesEndpoint.swift @@ -20,6 +20,7 @@ public struct UpdateVariablesEndpoint: BaseEndpoint { public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL + .appendingPathComponent("v1") .appendingPathComponent("files") .appendingPathComponent(fileId) .appendingPathComponent("variables") diff --git a/Sources/FigmaAPI/Endpoint/VariablesEndpoint.swift b/Sources/FigmaAPI/Endpoint/VariablesEndpoint.swift index a602526..bea222a 100644 --- a/Sources/FigmaAPI/Endpoint/VariablesEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/VariablesEndpoint.swift @@ -18,6 +18,7 @@ public struct VariablesEndpoint: BaseEndpoint { public func makeRequest(baseURL: URL) -> URLRequest { let url = baseURL + .appendingPathComponent("v1") .appendingPathComponent("files") .appendingPathComponent(fileId) .appendingPathComponent("variables") diff --git a/Sources/FigmaAPI/FigmaClient.swift b/Sources/FigmaAPI/FigmaClient.swift index 142d01b..4f813b2 100644 --- a/Sources/FigmaAPI/FigmaClient.swift +++ b/Sources/FigmaAPI/FigmaClient.swift @@ -5,7 +5,7 @@ import Foundation public final class FigmaClient: BaseClient, @unchecked Sendable { // swiftlint:disable:next force_unwrapping - private static let figmaBaseURL = URL(string: "https://api.figma.com/v1/")! + private static let figmaBaseURL = URL(string: "https://api.figma.com/")! public init(accessToken: String, timeout: TimeInterval?) { let config = URLSessionConfiguration.ephemeral diff --git a/Sources/FigmaAPI/Model/ActivityLog.swift b/Sources/FigmaAPI/Model/ActivityLog.swift new file mode 100644 index 0000000..d1dcbf8 --- /dev/null +++ b/Sources/FigmaAPI/Model/ActivityLog.swift @@ -0,0 +1,16 @@ +public struct ActivityLog: Decodable, Sendable { + public let id: String + public let timestamp: String + public let actorId: String? + public let actionType: String + public let entityType: String + public let details: [String: String]? + + private enum CodingKeys: String, CodingKey { + case id, timestamp + case actorId = "actor_id" + case actionType = "action_type" + case entityType = "entity_type" + case details + } +} diff --git a/Sources/FigmaAPI/Model/Comment.swift b/Sources/FigmaAPI/Model/Comment.swift new file mode 100644 index 0000000..dc12135 --- /dev/null +++ b/Sources/FigmaAPI/Model/Comment.swift @@ -0,0 +1,29 @@ +public struct Comment: Decodable, Sendable { + public let id: String + public let message: String + public let createdAt: String + public let resolvedAt: String? + public let user: User + public let orderId: String? + public let parentId: String? + public let clientMeta: ClientMeta? + + private enum CodingKeys: String, CodingKey { + case id, message, user + case createdAt = "created_at" + case resolvedAt = "resolved_at" + case orderId = "order_id" + case parentId = "parent_id" + case clientMeta = "client_meta" + } +} + +public struct ClientMeta: Decodable, Sendable { + public let nodeId: String? + public let nodeOffset: Vector? + + private enum CodingKeys: String, CodingKey { + case nodeId = "node_id" + case nodeOffset = "node_offset" + } +} diff --git a/Sources/FigmaAPI/Model/ComponentSet.swift b/Sources/FigmaAPI/Model/ComponentSet.swift new file mode 100644 index 0000000..58b0b0d --- /dev/null +++ b/Sources/FigmaAPI/Model/ComponentSet.swift @@ -0,0 +1,14 @@ +public struct ComponentSet: Codable, Sendable { + public let key: String + public let nodeId: String + public let name: String + public let description: String? + public let containingFrame: ContainingFrame + + private enum CodingKeys: String, CodingKey { + case key + case nodeId = "node_id" + case name, description + case containingFrame = "containing_frame" + } +} diff --git a/Sources/FigmaAPI/Model/DevResource.swift b/Sources/FigmaAPI/Model/DevResource.swift new file mode 100644 index 0000000..2bfccc4 --- /dev/null +++ b/Sources/FigmaAPI/Model/DevResource.swift @@ -0,0 +1,13 @@ +public struct DevResource: Codable, Sendable { + public let id: String + public let name: String + public let url: String + public let nodeId: String + public let devStatus: String? + + private enum CodingKeys: String, CodingKey { + case id, name, url + case nodeId = "node_id" + case devStatus = "dev_status" + } +} diff --git a/Sources/FigmaAPI/Model/EmptyResponse.swift b/Sources/FigmaAPI/Model/EmptyResponse.swift new file mode 100644 index 0000000..b9b87ce --- /dev/null +++ b/Sources/FigmaAPI/Model/EmptyResponse.swift @@ -0,0 +1 @@ +public struct EmptyResponse: Decodable, Sendable {} diff --git a/Sources/FigmaAPI/Model/FileMeta.swift b/Sources/FigmaAPI/Model/FileMeta.swift new file mode 100644 index 0000000..75529e1 --- /dev/null +++ b/Sources/FigmaAPI/Model/FileMeta.swift @@ -0,0 +1,4 @@ +public struct FileMeta: Sendable { + public let lastModified: String? + public let version: String? +} diff --git a/Sources/FigmaAPI/Model/FileVersion.swift b/Sources/FigmaAPI/Model/FileVersion.swift new file mode 100644 index 0000000..c18a479 --- /dev/null +++ b/Sources/FigmaAPI/Model/FileVersion.swift @@ -0,0 +1,13 @@ +public struct FileVersion: Codable, Sendable { + public let id: String + public let createdAt: String + public let label: String? + public let description: String? + public let user: User + + private enum CodingKeys: String, CodingKey { + case id + case createdAt = "created_at" + case label, description, user + } +} diff --git a/Sources/FigmaAPI/Model/LibraryAnalytics.swift b/Sources/FigmaAPI/Model/LibraryAnalytics.swift new file mode 100644 index 0000000..e9e6d08 --- /dev/null +++ b/Sources/FigmaAPI/Model/LibraryAnalytics.swift @@ -0,0 +1,41 @@ +public struct LibraryAnalyticsAction: Decodable, Sendable { + public let componentKey: String? + public let componentName: String? + public let styleKey: String? + public let styleName: String? + public let variableKey: String? + public let variableName: String? + public let actionType: String + public let actionCount: Int + + private enum CodingKeys: String, CodingKey { + case componentKey = "component_key" + case componentName = "component_name" + case styleKey = "style_key" + case styleName = "style_name" + case variableKey = "variable_key" + case variableName = "variable_name" + case actionType = "action_type" + case actionCount = "action_count" + } +} + +public struct LibraryAnalyticsUsage: Decodable, Sendable { + public let componentKey: String? + public let componentName: String? + public let styleKey: String? + public let styleName: String? + public let variableKey: String? + public let variableName: String? + public let usageCount: Int + + private enum CodingKeys: String, CodingKey { + case componentKey = "component_key" + case componentName = "component_name" + case styleKey = "style_key" + case styleName = "style_name" + case variableKey = "variable_key" + case variableName = "variable_name" + case usageCount = "usage_count" + } +} diff --git a/Sources/FigmaAPI/Model/PaymentInfo.swift b/Sources/FigmaAPI/Model/PaymentInfo.swift new file mode 100644 index 0000000..bddf816 --- /dev/null +++ b/Sources/FigmaAPI/Model/PaymentInfo.swift @@ -0,0 +1,14 @@ +public struct PaymentInfo: Decodable, Sendable { + public let status: Int + public let users: [PaymentUser]? +} + +public struct PaymentUser: Decodable, Sendable { + public let userId: String + public let status: String + + private enum CodingKeys: String, CodingKey { + case userId = "user_id" + case status + } +} diff --git a/Sources/FigmaAPI/Model/PostCommentBody.swift b/Sources/FigmaAPI/Model/PostCommentBody.swift new file mode 100644 index 0000000..fa2b501 --- /dev/null +++ b/Sources/FigmaAPI/Model/PostCommentBody.swift @@ -0,0 +1,44 @@ +import Foundation + +public struct PostCommentBody: Encodable, Sendable { + public let message: String + public let clientMeta: PostClientMeta? + public let commentId: String? + + public init(message: String, clientMeta: PostClientMeta? = nil, commentId: String? = nil) { + self.message = message + self.clientMeta = clientMeta + self.commentId = commentId + } + + private enum CodingKeys: String, CodingKey { + case message + case clientMeta = "client_meta" + case commentId = "comment_id" + } +} + +public struct PostClientMeta: Encodable, Sendable { + public let nodeId: String? + public let nodeOffset: PostVector? + + public init(nodeId: String? = nil, nodeOffset: PostVector? = nil) { + self.nodeId = nodeId + self.nodeOffset = nodeOffset + } + + private enum CodingKeys: String, CodingKey { + case nodeId = "node_id" + case nodeOffset = "node_offset" + } +} + +public struct PostVector: Encodable, Sendable { + public let x: Double + public let y: Double + + public init(x: Double, y: Double) { + self.x = x + self.y = y + } +} diff --git a/Sources/FigmaAPI/Model/PostDevResourceBody.swift b/Sources/FigmaAPI/Model/PostDevResourceBody.swift new file mode 100644 index 0000000..6303268 --- /dev/null +++ b/Sources/FigmaAPI/Model/PostDevResourceBody.swift @@ -0,0 +1,19 @@ +public struct PostDevResourceBody: Encodable, Sendable { + public let name: String + public let url: String + public let nodeId: String + public let devStatus: String? + + public init(name: String, url: String, nodeId: String, devStatus: String? = nil) { + self.name = name + self.url = url + self.nodeId = nodeId + self.devStatus = devStatus + } + + private enum CodingKeys: String, CodingKey { + case name, url + case nodeId = "node_id" + case devStatus = "dev_status" + } +} diff --git a/Sources/FigmaAPI/Model/PostReactionBody.swift b/Sources/FigmaAPI/Model/PostReactionBody.swift new file mode 100644 index 0000000..90ee6d4 --- /dev/null +++ b/Sources/FigmaAPI/Model/PostReactionBody.swift @@ -0,0 +1,7 @@ +public struct PostReactionBody: Encodable, Sendable { + public let emoji: String + + public init(emoji: String) { + self.emoji = emoji + } +} diff --git a/Sources/FigmaAPI/Model/PostWebhookBody.swift b/Sources/FigmaAPI/Model/PostWebhookBody.swift new file mode 100644 index 0000000..a17f216 --- /dev/null +++ b/Sources/FigmaAPI/Model/PostWebhookBody.swift @@ -0,0 +1,21 @@ +public struct PostWebhookBody: Encodable, Sendable { + public let eventType: String + public let teamId: String + public let endpoint: String + public let passcode: String + public let description: String? + + public init(eventType: String, teamId: String, endpoint: String, passcode: String, description: String? = nil) { + self.eventType = eventType + self.teamId = teamId + self.endpoint = endpoint + self.passcode = passcode + self.description = description + } + + private enum CodingKeys: String, CodingKey { + case eventType = "event_type" + case teamId = "team_id" + case endpoint, passcode, description + } +} diff --git a/Sources/FigmaAPI/Model/Project.swift b/Sources/FigmaAPI/Model/Project.swift new file mode 100644 index 0000000..85d9710 --- /dev/null +++ b/Sources/FigmaAPI/Model/Project.swift @@ -0,0 +1,17 @@ +public struct Project: Decodable, Sendable { + public let id: String + public let name: String +} + +public struct ProjectFile: Decodable, Sendable { + public let key: String + public let name: String + public let thumbnailUrl: String? + public let lastModified: String + + private enum CodingKeys: String, CodingKey { + case key, name + case thumbnailUrl = "thumbnail_url" + case lastModified = "last_modified" + } +} diff --git a/Sources/FigmaAPI/Model/PutDevResourceBody.swift b/Sources/FigmaAPI/Model/PutDevResourceBody.swift new file mode 100644 index 0000000..c353252 --- /dev/null +++ b/Sources/FigmaAPI/Model/PutDevResourceBody.swift @@ -0,0 +1,18 @@ +public struct PutDevResourceBody: Encodable, Sendable { + public let id: String + public let name: String? + public let url: String? + public let devStatus: String? + + public init(id: String, name: String? = nil, url: String? = nil, devStatus: String? = nil) { + self.id = id + self.name = name + self.url = url + self.devStatus = devStatus + } + + private enum CodingKeys: String, CodingKey { + case id, name, url + case devStatus = "dev_status" + } +} diff --git a/Sources/FigmaAPI/Model/PutWebhookBody.swift b/Sources/FigmaAPI/Model/PutWebhookBody.swift new file mode 100644 index 0000000..ef77442 --- /dev/null +++ b/Sources/FigmaAPI/Model/PutWebhookBody.swift @@ -0,0 +1,18 @@ +public struct PutWebhookBody: Encodable, Sendable { + public let eventType: String? + public let endpoint: String? + public let passcode: String? + public let description: String? + + public init(eventType: String? = nil, endpoint: String? = nil, passcode: String? = nil, description: String? = nil) { + self.eventType = eventType + self.endpoint = endpoint + self.passcode = passcode + self.description = description + } + + private enum CodingKeys: String, CodingKey { + case eventType = "event_type" + case endpoint, passcode, description + } +} diff --git a/Sources/FigmaAPI/Model/Reaction.swift b/Sources/FigmaAPI/Model/Reaction.swift new file mode 100644 index 0000000..bde2bfc --- /dev/null +++ b/Sources/FigmaAPI/Model/Reaction.swift @@ -0,0 +1,10 @@ +public struct Reaction: Decodable, Sendable { + public let emoji: String + public let user: User + public let createdAt: String + + private enum CodingKeys: String, CodingKey { + case emoji, user + case createdAt = "created_at" + } +} diff --git a/Sources/FigmaAPI/Model/User.swift b/Sources/FigmaAPI/Model/User.swift new file mode 100644 index 0000000..70d5329 --- /dev/null +++ b/Sources/FigmaAPI/Model/User.swift @@ -0,0 +1,11 @@ +public struct User: Codable, Sendable { + public let id: String + public let handle: String + public let email: String? + public let imgUrl: String? + + private enum CodingKeys: String, CodingKey { + case id, handle, email + case imgUrl = "img_url" + } +} diff --git a/Sources/FigmaAPI/Model/Webhook.swift b/Sources/FigmaAPI/Model/Webhook.swift new file mode 100644 index 0000000..fa5d8f4 --- /dev/null +++ b/Sources/FigmaAPI/Model/Webhook.swift @@ -0,0 +1,20 @@ +public struct Webhook: Codable, Sendable { + public let id: String + public let teamId: String + public let eventType: String + public let clientId: String? + public let endpoint: String + public let passcode: String? + public let status: String + public let description: String? + public let protocolVersion: String? + + private enum CodingKeys: String, CodingKey { + case id + case teamId = "team_id" + case eventType = "event_type" + case clientId = "client_id" + case endpoint, passcode, status, description + case protocolVersion = "protocol_version" + } +} diff --git a/Sources/FigmaAPI/Model/WebhookRequest.swift b/Sources/FigmaAPI/Model/WebhookRequest.swift new file mode 100644 index 0000000..9c96a0c --- /dev/null +++ b/Sources/FigmaAPI/Model/WebhookRequest.swift @@ -0,0 +1,22 @@ +public struct WebhookRequest: Decodable, Sendable { + public let id: String + public let endpoint: String + public let payload: WebhookPayload? + public let error: String? + public let createdAt: String + + private enum CodingKeys: String, CodingKey { + case id, endpoint, payload, error + case createdAt = "created_at" + } +} + +public struct WebhookPayload: Decodable, Sendable { + public let eventType: String? + public let timestamp: String? + + private enum CodingKeys: String, CodingKey { + case eventType = "event_type" + case timestamp + } +} diff --git a/Tests/FigmaAPITests/ComponentsEndpointTests.swift b/Tests/FigmaAPITests/ComponentsEndpointTests.swift index 489cc9a..2a764c4 100644 --- a/Tests/FigmaAPITests/ComponentsEndpointTests.swift +++ b/Tests/FigmaAPITests/ComponentsEndpointTests.swift @@ -7,7 +7,7 @@ final class ComponentsEndpointTests: XCTestCase { func testMakeRequestConstructsCorrectURL() throws { let endpoint = ComponentsEndpoint(fileId: "abc123") - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = endpoint.makeRequest(baseURL: baseURL) diff --git a/Tests/FigmaAPITests/DeleteCommentEndpointTests.swift b/Tests/FigmaAPITests/DeleteCommentEndpointTests.swift new file mode 100644 index 0000000..45c7fcf --- /dev/null +++ b/Tests/FigmaAPITests/DeleteCommentEndpointTests.swift @@ -0,0 +1,38 @@ +@testable import FigmaAPI +import XCTest + +final class DeleteCommentEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = DeleteCommentEndpoint(fileId: "abc123", commentId: "comment1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/comments/comment1" + ) + } + + func testMakeRequestUsesDELETEMethod() throws { + let endpoint = DeleteCommentEndpoint(fileId: "abc123", commentId: "comment1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "DELETE") + } + + // MARK: - Response Parsing + + func testContentParsesEmptyResponse() throws { + let data = Data("{}".utf8) + let endpoint = DeleteCommentEndpoint(fileId: "test", commentId: "c1") + let response = try endpoint.content(from: nil, with: data) + + // EmptyResponse just needs to decode without error + XCTAssertNotNil(response) + } +} diff --git a/Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift b/Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift new file mode 100644 index 0000000..6f2b253 --- /dev/null +++ b/Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift @@ -0,0 +1,37 @@ +@testable import FigmaAPI +import XCTest + +final class DeleteDevResourceEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = DeleteDevResourceEndpoint(fileId: "abc123", resourceId: "res1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/dev_resources/res1" + ) + } + + func testMakeRequestUsesDELETEMethod() throws { + let endpoint = DeleteDevResourceEndpoint(fileId: "abc123", resourceId: "res1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "DELETE") + } + + // MARK: - Response Parsing + + func testContentParsesEmptyResponse() throws { + let data = Data("{}".utf8) + let endpoint = DeleteDevResourceEndpoint(fileId: "test", resourceId: "res1") + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } +} diff --git a/Tests/FigmaAPITests/DeleteReactionEndpointTests.swift b/Tests/FigmaAPITests/DeleteReactionEndpointTests.swift new file mode 100644 index 0000000..ea56015 --- /dev/null +++ b/Tests/FigmaAPITests/DeleteReactionEndpointTests.swift @@ -0,0 +1,48 @@ +@testable import FigmaAPI +import XCTest + +final class DeleteReactionEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = DeleteReactionEndpoint(fileId: "abc123", commentId: "comment1", emoji: "👍") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertTrue( + request.url?.absoluteString.starts(with: + "https://api.figma.com/v1/files/abc123/comments/comment1/reactions") ?? false + ) + } + + func testMakeRequestIncludesEmojiQueryParameter() throws { + let endpoint = DeleteReactionEndpoint(fileId: "abc123", commentId: "comment1", emoji: "👍") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let components = try XCTUnwrap(URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)) + let emojiItem = components.queryItems?.first(where: { $0.name == "emoji" }) + XCTAssertEqual(emojiItem?.value, "\u{1F44D}") + } + + func testMakeRequestUsesDELETEMethod() throws { + let endpoint = DeleteReactionEndpoint(fileId: "abc123", commentId: "comment1", emoji: "👍") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "DELETE") + } + + // MARK: - Response Parsing + + func testContentParsesEmptyResponse() throws { + let data = Data("{}".utf8) + let endpoint = DeleteReactionEndpoint(fileId: "test", commentId: "c1", emoji: "👍") + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } +} diff --git a/Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift b/Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift new file mode 100644 index 0000000..69b211c --- /dev/null +++ b/Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift @@ -0,0 +1,37 @@ +@testable import FigmaAPI +import XCTest + +final class DeleteWebhookEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = DeleteWebhookEndpoint(webhookId: "wh1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v2/webhooks/wh1" + ) + } + + func testMakeRequestUsesDELETEMethod() throws { + let endpoint = DeleteWebhookEndpoint(webhookId: "wh1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "DELETE") + } + + // MARK: - Response Parsing + + func testContentParsesEmptyResponse() throws { + let data = Data("{}".utf8) + let endpoint = DeleteWebhookEndpoint(webhookId: "wh1") + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } +} diff --git a/Tests/FigmaAPITests/EndpointMakeRequestTests.swift b/Tests/FigmaAPITests/EndpointMakeRequestTests.swift index e288906..2ce9058 100644 --- a/Tests/FigmaAPITests/EndpointMakeRequestTests.swift +++ b/Tests/FigmaAPITests/EndpointMakeRequestTests.swift @@ -12,7 +12,7 @@ final class EndpointMakeRequestTests: XCTestCase { func testImageEndpointMakeRequestSucceeds() throws { // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let endpoint = ImageEndpoint(fileId: "abc123", nodeIds: ["1:2"], params: SVGParams()) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -23,7 +23,7 @@ final class EndpointMakeRequestTests: XCTestCase { func testNodesEndpointMakeRequestSucceeds() throws { // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let endpoint = NodesEndpoint(fileId: "abc123", nodeIds: ["1:2", "3:4"]) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -34,7 +34,7 @@ final class EndpointMakeRequestTests: XCTestCase { func testFileMetadataEndpointMakeRequestSucceeds() throws { // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let endpoint = FileMetadataEndpoint(fileId: "abc123") let request = try endpoint.makeRequest(baseURL: baseURL) diff --git a/Tests/FigmaAPITests/FileMetadataEndpointTests.swift b/Tests/FigmaAPITests/FileMetadataEndpointTests.swift index fb6facd..e32e8df 100644 --- a/Tests/FigmaAPITests/FileMetadataEndpointTests.swift +++ b/Tests/FigmaAPITests/FileMetadataEndpointTests.swift @@ -8,7 +8,7 @@ final class FileMetadataEndpointTests: XCTestCase { func testMakeRequestConstructsCorrectURL() throws { let endpoint = FileMetadataEndpoint(fileId: "abc123") // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -21,7 +21,7 @@ final class FileMetadataEndpointTests: XCTestCase { func testMakeRequestIncludesDepthParameter() throws { let endpoint = FileMetadataEndpoint(fileId: "test") // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) diff --git a/Tests/FigmaAPITests/Fixtures/ActivityLogsResponse.json b/Tests/FigmaAPITests/Fixtures/ActivityLogsResponse.json new file mode 100644 index 0000000..838597b --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/ActivityLogsResponse.json @@ -0,0 +1,20 @@ +{ + "activity_logs": [ + { + "id": "log1", + "timestamp": "2024-01-15T10:30:00Z", + "actor_id": "user1", + "action_type": "FILE_OPEN", + "entity_type": "FILE", + "details": {"file_key": "abc123"} + }, + { + "id": "log2", + "timestamp": "2024-01-15T11:00:00Z", + "actor_id": null, + "action_type": "FILE_DELETE", + "entity_type": "FILE", + "details": null + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/CommentsResponse.json b/Tests/FigmaAPITests/Fixtures/CommentsResponse.json new file mode 100644 index 0000000..bc44818 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/CommentsResponse.json @@ -0,0 +1,40 @@ +{ + "comments": [ + { + "id": "comment1", + "message": "Great design!", + "created_at": "2024-01-15T10:30:00Z", + "resolved_at": null, + "user": { + "id": "user1", + "handle": "Designer", + "email": "designer@example.com", + "img_url": "https://example.com/avatar1.png" + }, + "order_id": "1", + "parent_id": null, + "client_meta": { + "node_id": "10:1", + "node_offset": { + "x": 100.0, + "y": 200.0 + } + } + }, + { + "id": "comment2", + "message": "I agree!", + "created_at": "2024-01-15T11:00:00Z", + "resolved_at": "2024-01-16T09:00:00Z", + "user": { + "id": "user2", + "handle": "Developer", + "email": null, + "img_url": null + }, + "order_id": "2", + "parent_id": "comment1", + "client_meta": null + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/DevResourcesResponse.json b/Tests/FigmaAPITests/Fixtures/DevResourcesResponse.json new file mode 100644 index 0000000..7dc3d0e --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/DevResourcesResponse.json @@ -0,0 +1,18 @@ +{ + "dev_resources": [ + { + "id": "res1", + "name": "Storybook", + "url": "https://storybook.example.com/button", + "node_id": "10:1", + "dev_status": "ready_for_dev" + }, + { + "id": "res2", + "name": "GitHub PR", + "url": "https://github.com/example/repo/pull/123", + "node_id": "10:2", + "dev_status": null + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/FileComponentSetsResponse.json b/Tests/FigmaAPITests/Fixtures/FileComponentSetsResponse.json new file mode 100644 index 0000000..33af6d9 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/FileComponentSetsResponse.json @@ -0,0 +1,20 @@ +{ + "meta": { + "component_sets": [ + { + "key": "cs1", + "node_id": "20:1", + "name": "Button", + "description": "Buttons", + "containing_frame": { + "node_id": "1:1", + "name": "Buttons", + "page_id": "0:1", + "page_name": "Components", + "background_color": null, + "containing_component_set": null + } + } + ] + } +} diff --git a/Tests/FigmaAPITests/Fixtures/FileVersionsResponse.json b/Tests/FigmaAPITests/Fixtures/FileVersionsResponse.json new file mode 100644 index 0000000..cae7b16 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/FileVersionsResponse.json @@ -0,0 +1,28 @@ +{ + "versions": [ + { + "id": "ver123", + "created_at": "2024-01-15T10:30:00Z", + "label": "v1.0", + "description": "Initial release", + "user": { + "id": "user1", + "handle": "Designer", + "email": "designer@example.com", + "img_url": "https://example.com/avatar1.png" + } + }, + { + "id": "ver456", + "created_at": "2024-02-20T14:00:00Z", + "label": null, + "description": null, + "user": { + "id": "user2", + "handle": "Developer", + "email": null, + "img_url": null + } + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/GetComponentResponse.json b/Tests/FigmaAPITests/Fixtures/GetComponentResponse.json new file mode 100644 index 0000000..71aba9a --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/GetComponentResponse.json @@ -0,0 +1,16 @@ +{ + "meta": { + "key": "comp_key_123", + "node_id": "10:1", + "name": "Button/Primary", + "description": "Primary action button", + "containing_frame": { + "node_id": "1:1", + "name": "Buttons", + "page_id": "0:1", + "page_name": "Components", + "background_color": null, + "containing_component_set": null + } + } +} diff --git a/Tests/FigmaAPITests/Fixtures/GetComponentSetResponse.json b/Tests/FigmaAPITests/Fixtures/GetComponentSetResponse.json new file mode 100644 index 0000000..f6e94de --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/GetComponentSetResponse.json @@ -0,0 +1,16 @@ +{ + "meta": { + "key": "cs_key_456", + "node_id": "20:1", + "name": "Button", + "description": "Button component set with variants", + "containing_frame": { + "node_id": "1:1", + "name": "Buttons", + "page_id": "0:1", + "page_name": "Components", + "background_color": null, + "containing_component_set": null + } + } +} diff --git a/Tests/FigmaAPITests/Fixtures/GetMeResponse.json b/Tests/FigmaAPITests/Fixtures/GetMeResponse.json new file mode 100644 index 0000000..404040b --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/GetMeResponse.json @@ -0,0 +1,6 @@ +{ + "id": "user123", + "handle": "John Doe", + "email": "john@example.com", + "img_url": "https://example.com/avatar.png" +} diff --git a/Tests/FigmaAPITests/Fixtures/GetStyleResponse.json b/Tests/FigmaAPITests/Fixtures/GetStyleResponse.json new file mode 100644 index 0000000..592c0b7 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/GetStyleResponse.json @@ -0,0 +1,8 @@ +{ + "meta": { + "style_type": "FILL", + "node_id": "5:1", + "name": "Colors/Primary", + "description": "Primary brand color" + } +} diff --git a/Tests/FigmaAPITests/Fixtures/ImageFillsResponse.json b/Tests/FigmaAPITests/Fixtures/ImageFillsResponse.json new file mode 100644 index 0000000..a46185b --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/ImageFillsResponse.json @@ -0,0 +1,8 @@ +{ + "meta": { + "images": { + "0:1": "https://s3-alpha.figma.com/img/abc/123/image1.png", + "0:2": "https://s3-alpha.figma.com/img/def/456/image2.png" + } + } +} diff --git a/Tests/FigmaAPITests/Fixtures/LibraryActionsResponse.json b/Tests/FigmaAPITests/Fixtures/LibraryActionsResponse.json new file mode 100644 index 0000000..a5b3dcf --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/LibraryActionsResponse.json @@ -0,0 +1,14 @@ +{ + "actions": [ + { + "component_key": "comp1", + "component_name": "Button", + "style_key": null, + "style_name": null, + "variable_key": null, + "variable_name": null, + "action_type": "INSERT", + "action_count": 42 + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/LibraryUsagesResponse.json b/Tests/FigmaAPITests/Fixtures/LibraryUsagesResponse.json new file mode 100644 index 0000000..35640c1 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/LibraryUsagesResponse.json @@ -0,0 +1,13 @@ +{ + "usages": [ + { + "component_key": "comp1", + "component_name": "Button", + "style_key": null, + "style_name": null, + "variable_key": null, + "variable_name": null, + "usage_count": 150 + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/PaymentsResponse.json b/Tests/FigmaAPITests/Fixtures/PaymentsResponse.json new file mode 100644 index 0000000..91579e3 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/PaymentsResponse.json @@ -0,0 +1,13 @@ +{ + "status": 200, + "users": [ + { + "user_id": "user1", + "status": "active" + }, + { + "user_id": "user2", + "status": "inactive" + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/PostCommentResponse.json b/Tests/FigmaAPITests/Fixtures/PostCommentResponse.json new file mode 100644 index 0000000..8630510 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/PostCommentResponse.json @@ -0,0 +1,21 @@ +{ + "id": "comment3", + "message": "New comment", + "created_at": "2024-03-01T12:00:00Z", + "resolved_at": null, + "user": { + "id": "user1", + "handle": "Designer", + "email": "designer@example.com", + "img_url": "https://example.com/avatar1.png" + }, + "order_id": "3", + "parent_id": null, + "client_meta": { + "node_id": "5:1", + "node_offset": { + "x": 50.0, + "y": 75.0 + } + } +} diff --git a/Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json b/Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json new file mode 100644 index 0000000..71ad8bd --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json @@ -0,0 +1,7 @@ +{ + "id": "res3", + "name": "New Resource", + "url": "https://example.com/resource", + "node_id": "15:1", + "dev_status": "in_progress" +} diff --git a/Tests/FigmaAPITests/Fixtures/ProjectFilesResponse.json b/Tests/FigmaAPITests/Fixtures/ProjectFilesResponse.json new file mode 100644 index 0000000..f792de6 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/ProjectFilesResponse.json @@ -0,0 +1,16 @@ +{ + "files": [ + { + "key": "file1", + "name": "Components", + "thumbnail_url": "https://example.com/thumb1.png", + "last_modified": "2024-01-15T10:30:00Z" + }, + { + "key": "file2", + "name": "Icons", + "thumbnail_url": null, + "last_modified": "2024-02-20T14:00:00Z" + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/PublishedVariablesResponse.json b/Tests/FigmaAPITests/Fixtures/PublishedVariablesResponse.json new file mode 100644 index 0000000..74bac0a --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/PublishedVariablesResponse.json @@ -0,0 +1,28 @@ +{ + "meta": { + "variableCollections": { + "VariableCollectionId:2:1": { + "defaultModeId": "2:0", + "id": "VariableCollectionId:2:1", + "name": "Published Colors", + "modes": [ + { "modeId": "2:0", "name": "Default" } + ], + "variableIds": [ + "VariableID:2:2" + ] + } + }, + "variables": { + "VariableID:2:2": { + "id": "VariableID:2:2", + "name": "brand/primary", + "variableCollectionId": "VariableCollectionId:2:1", + "valuesByMode": { + "2:0": { "r": 0.0, "g": 0.5, "b": 1.0, "a": 1.0 } + }, + "description": "Primary brand color" + } + } + } +} diff --git a/Tests/FigmaAPITests/Fixtures/ReactionsResponse.json b/Tests/FigmaAPITests/Fixtures/ReactionsResponse.json new file mode 100644 index 0000000..e100d07 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/ReactionsResponse.json @@ -0,0 +1,24 @@ +{ + "reactions": [ + { + "emoji": "👍", + "user": { + "id": "user1", + "handle": "Designer", + "email": "designer@example.com", + "img_url": "https://example.com/avatar1.png" + }, + "created_at": "2024-01-15T10:30:00Z" + }, + { + "emoji": "🎉", + "user": { + "id": "user2", + "handle": "Developer", + "email": null, + "img_url": null + }, + "created_at": "2024-01-15T11:00:00Z" + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/TeamProjectsResponse.json b/Tests/FigmaAPITests/Fixtures/TeamProjectsResponse.json new file mode 100644 index 0000000..1edbbe9 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/TeamProjectsResponse.json @@ -0,0 +1,12 @@ +{ + "projects": [ + { + "id": "proj1", + "name": "Design System" + }, + { + "id": "proj2", + "name": "Marketing" + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/WebhookRequestsResponse.json b/Tests/FigmaAPITests/Fixtures/WebhookRequestsResponse.json new file mode 100644 index 0000000..b0a73e9 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/WebhookRequestsResponse.json @@ -0,0 +1,21 @@ +{ + "requests": [ + { + "id": "req1", + "endpoint": "https://example.com/webhook", + "payload": { + "event_type": "FILE_UPDATE", + "timestamp": "2024-01-15T10:30:00Z" + }, + "error": null, + "created_at": "2024-01-15T10:30:01Z" + }, + { + "id": "req2", + "endpoint": "https://example.com/webhook", + "payload": null, + "error": "Connection timeout", + "created_at": "2024-01-15T11:00:00Z" + } + ] +} diff --git a/Tests/FigmaAPITests/Fixtures/WebhookResponse.json b/Tests/FigmaAPITests/Fixtures/WebhookResponse.json new file mode 100644 index 0000000..f0439b8 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/WebhookResponse.json @@ -0,0 +1,11 @@ +{ + "id": "wh1", + "team_id": "team123", + "event_type": "FILE_UPDATE", + "client_id": null, + "endpoint": "https://example.com/webhook", + "passcode": "secret123", + "status": "ACTIVE", + "description": "File update webhook", + "protocol_version": "2" +} diff --git a/Tests/FigmaAPITests/Fixtures/WebhooksResponse.json b/Tests/FigmaAPITests/Fixtures/WebhooksResponse.json new file mode 100644 index 0000000..abd344f --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/WebhooksResponse.json @@ -0,0 +1,26 @@ +{ + "webhooks": [ + { + "id": "wh1", + "team_id": "team123", + "event_type": "FILE_UPDATE", + "client_id": null, + "endpoint": "https://example.com/webhook", + "passcode": "secret123", + "status": "ACTIVE", + "description": "File update webhook", + "protocol_version": "2" + }, + { + "id": "wh2", + "team_id": "team123", + "event_type": "FILE_DELETE", + "client_id": "client456", + "endpoint": "https://example.com/webhook2", + "passcode": null, + "status": "PAUSED", + "description": null, + "protocol_version": "1" + } + ] +} diff --git a/Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift b/Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift new file mode 100644 index 0000000..2c127fc --- /dev/null +++ b/Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift @@ -0,0 +1,60 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetActivityLogsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetActivityLogsEndpoint() + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/activity_logs" + ) + } + + // MARK: - Response Parsing + + func testContentParsesActivityLogsResponse() throws { + let response: ActivityLogsResponse = try FixtureLoader.load("ActivityLogsResponse") + + let endpoint = GetActivityLogsEndpoint() + let logs = endpoint.content(from: response) + + XCTAssertEqual(logs.count, 2) + + let firstLog = logs[0] + XCTAssertEqual(firstLog.id, "log1") + XCTAssertEqual(firstLog.timestamp, "2024-01-15T10:30:00Z") + XCTAssertEqual(firstLog.actorId, "user1") + XCTAssertEqual(firstLog.actionType, "FILE_OPEN") + XCTAssertEqual(firstLog.entityType, "FILE") + XCTAssertEqual(firstLog.details?["file_key"], "abc123") + + let secondLog = logs[1] + XCTAssertNil(secondLog.actorId) + XCTAssertNil(secondLog.details) + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("ActivityLogsResponse") + + let endpoint = GetActivityLogsEndpoint() + let logs = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(logs.count, 2) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetActivityLogsEndpoint() + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetCommentsEndpointTests.swift b/Tests/FigmaAPITests/GetCommentsEndpointTests.swift new file mode 100644 index 0000000..e60cbca --- /dev/null +++ b/Tests/FigmaAPITests/GetCommentsEndpointTests.swift @@ -0,0 +1,55 @@ +@testable import FigmaAPI +import XCTest + +final class GetCommentsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetCommentsEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/comments" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let response: CommentsResponse = try FixtureLoader.load("CommentsResponse") + + let endpoint = GetCommentsEndpoint(fileId: "test") + let comments = endpoint.content(from: response) + + XCTAssertEqual(comments.count, 2) + } + + func testContentParsesCommentWithClientMeta() throws { + let response: CommentsResponse = try FixtureLoader.load("CommentsResponse") + + let endpoint = GetCommentsEndpoint(fileId: "test") + let comments = endpoint.content(from: response) + + let first = comments[0] + XCTAssertEqual(first.id, "comment1") + XCTAssertEqual(first.message, "Great design!") + XCTAssertEqual(first.clientMeta?.nodeId, "10:1") + XCTAssertEqual(first.clientMeta?.nodeOffset?.x, 100.0) + XCTAssertEqual(first.clientMeta?.nodeOffset?.y, 200.0) + } + + func testContentParsesResolvedComment() throws { + let response: CommentsResponse = try FixtureLoader.load("CommentsResponse") + + let endpoint = GetCommentsEndpoint(fileId: "test") + let comments = endpoint.content(from: response) + + let second = comments[1] + XCTAssertEqual(second.id, "comment2") + XCTAssertEqual(second.resolvedAt, "2024-01-16T09:00:00Z") + XCTAssertEqual(second.parentId, "comment1") + } +} diff --git a/Tests/FigmaAPITests/GetComponentActionsEndpointTests.swift b/Tests/FigmaAPITests/GetComponentActionsEndpointTests.swift new file mode 100644 index 0000000..c110425 --- /dev/null +++ b/Tests/FigmaAPITests/GetComponentActionsEndpointTests.swift @@ -0,0 +1,56 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetComponentActionsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetComponentActionsEndpoint(fileKey: "file123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/analytics/libraries/file123/component/actions" + ) + } + + // MARK: - Response Parsing + + func testContentParsesLibraryActionsResponse() throws { + let response: LibraryActionsResponse = try FixtureLoader.load("LibraryActionsResponse") + + let endpoint = GetComponentActionsEndpoint(fileKey: "test") + let actions = endpoint.content(from: response) + + XCTAssertEqual(actions.count, 1) + + let action = actions[0] + XCTAssertEqual(action.componentKey, "comp1") + XCTAssertEqual(action.componentName, "Button") + XCTAssertNil(action.styleKey) + XCTAssertNil(action.variableKey) + XCTAssertEqual(action.actionType, "INSERT") + XCTAssertEqual(action.actionCount, 42) + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("LibraryActionsResponse") + + let endpoint = GetComponentActionsEndpoint(fileKey: "test") + let actions = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(actions.count, 1) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetComponentActionsEndpoint(fileKey: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetComponentEndpointTests.swift b/Tests/FigmaAPITests/GetComponentEndpointTests.swift new file mode 100644 index 0000000..a2cc382 --- /dev/null +++ b/Tests/FigmaAPITests/GetComponentEndpointTests.swift @@ -0,0 +1,52 @@ +@testable import FigmaAPI +import XCTest + +final class GetComponentEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetComponentEndpoint(key: "comp_key_123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/components/comp_key_123" + ) + } + + // MARK: - Response Parsing + + func testContentParsesGetComponentResponse() throws { + let response: GetComponentResponse = try FixtureLoader.load("GetComponentResponse") + + let endpoint = GetComponentEndpoint(key: "comp_key_123") + let component = endpoint.content(from: response) + + XCTAssertEqual(component.key, "comp_key_123") + XCTAssertEqual(component.nodeId, "10:1") + XCTAssertEqual(component.name, "Button/Primary") + XCTAssertEqual(component.description, "Primary action button") + XCTAssertEqual(component.containingFrame.name, "Buttons") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("GetComponentResponse") + + let endpoint = GetComponentEndpoint(key: "test") + let component = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(component.key, "comp_key_123") + XCTAssertEqual(component.name, "Button/Primary") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetComponentEndpoint(key: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetComponentSetEndpointTests.swift b/Tests/FigmaAPITests/GetComponentSetEndpointTests.swift new file mode 100644 index 0000000..4df890d --- /dev/null +++ b/Tests/FigmaAPITests/GetComponentSetEndpointTests.swift @@ -0,0 +1,52 @@ +@testable import FigmaAPI +import XCTest + +final class GetComponentSetEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetComponentSetEndpoint(key: "cs_key_456") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/component_sets/cs_key_456" + ) + } + + // MARK: - Response Parsing + + func testContentParsesGetComponentSetResponse() throws { + let response: GetComponentSetResponse = try FixtureLoader.load("GetComponentSetResponse") + + let endpoint = GetComponentSetEndpoint(key: "cs_key_456") + let componentSet = endpoint.content(from: response) + + XCTAssertEqual(componentSet.key, "cs_key_456") + XCTAssertEqual(componentSet.nodeId, "20:1") + XCTAssertEqual(componentSet.name, "Button") + XCTAssertEqual(componentSet.description, "Button component set with variants") + XCTAssertEqual(componentSet.containingFrame.name, "Buttons") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("GetComponentSetResponse") + + let endpoint = GetComponentSetEndpoint(key: "test") + let componentSet = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(componentSet.key, "cs_key_456") + XCTAssertEqual(componentSet.name, "Button") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetComponentSetEndpoint(key: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetComponentUsagesEndpointTests.swift b/Tests/FigmaAPITests/GetComponentUsagesEndpointTests.swift new file mode 100644 index 0000000..510d737 --- /dev/null +++ b/Tests/FigmaAPITests/GetComponentUsagesEndpointTests.swift @@ -0,0 +1,55 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetComponentUsagesEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetComponentUsagesEndpoint(fileKey: "file123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/analytics/libraries/file123/component/usages" + ) + } + + // MARK: - Response Parsing + + func testContentParsesLibraryUsagesResponse() throws { + let response: LibraryUsagesResponse = try FixtureLoader.load("LibraryUsagesResponse") + + let endpoint = GetComponentUsagesEndpoint(fileKey: "test") + let usages = endpoint.content(from: response) + + XCTAssertEqual(usages.count, 1) + + let usage = usages[0] + XCTAssertEqual(usage.componentKey, "comp1") + XCTAssertEqual(usage.componentName, "Button") + XCTAssertNil(usage.styleKey) + XCTAssertNil(usage.variableKey) + XCTAssertEqual(usage.usageCount, 150) + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("LibraryUsagesResponse") + + let endpoint = GetComponentUsagesEndpoint(fileKey: "test") + let usages = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(usages.count, 1) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetComponentUsagesEndpoint(fileKey: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetDevResourcesEndpointTests.swift b/Tests/FigmaAPITests/GetDevResourcesEndpointTests.swift new file mode 100644 index 0000000..e806767 --- /dev/null +++ b/Tests/FigmaAPITests/GetDevResourcesEndpointTests.swift @@ -0,0 +1,54 @@ +@testable import FigmaAPI +import XCTest + +final class GetDevResourcesEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetDevResourcesEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/dev_resources" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let response: DevResourcesResponse = try FixtureLoader.load("DevResourcesResponse") + + let endpoint = GetDevResourcesEndpoint(fileId: "test") + let resources = endpoint.content(from: response) + + XCTAssertEqual(resources.count, 2) + } + + func testContentParsesFirstResource() throws { + let response: DevResourcesResponse = try FixtureLoader.load("DevResourcesResponse") + + let endpoint = GetDevResourcesEndpoint(fileId: "test") + let resources = endpoint.content(from: response) + + let first = resources[0] + XCTAssertEqual(first.id, "res1") + XCTAssertEqual(first.name, "Storybook") + XCTAssertEqual(first.url, "https://storybook.example.com/button") + XCTAssertEqual(first.nodeId, "10:1") + XCTAssertEqual(first.devStatus, "ready_for_dev") + } + + func testContentParsesResourceWithNullDevStatus() throws { + let response: DevResourcesResponse = try FixtureLoader.load("DevResourcesResponse") + + let endpoint = GetDevResourcesEndpoint(fileId: "test") + let resources = endpoint.content(from: response) + + let second = resources[1] + XCTAssertEqual(second.id, "res2") + XCTAssertNil(second.devStatus) + } +} diff --git a/Tests/FigmaAPITests/GetFileComponentSetsEndpointTests.swift b/Tests/FigmaAPITests/GetFileComponentSetsEndpointTests.swift new file mode 100644 index 0000000..ed7c8ff --- /dev/null +++ b/Tests/FigmaAPITests/GetFileComponentSetsEndpointTests.swift @@ -0,0 +1,51 @@ +@testable import FigmaAPI +import XCTest + +final class GetFileComponentSetsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetFileComponentSetsEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/component_sets" + ) + } + + // MARK: - Response Parsing + + func testContentParsesFileComponentSetsResponse() throws { + let response: FileComponentSetsResponse = try FixtureLoader.load("FileComponentSetsResponse") + + let endpoint = GetFileComponentSetsEndpoint(fileId: "test") + let componentSets = endpoint.content(from: response) + + XCTAssertEqual(componentSets.count, 1) + XCTAssertEqual(componentSets[0].key, "cs1") + XCTAssertEqual(componentSets[0].name, "Button") + XCTAssertEqual(componentSets[0].description, "Buttons") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("FileComponentSetsResponse") + + let endpoint = GetFileComponentSetsEndpoint(fileId: "test") + let componentSets = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(componentSets.count, 1) + XCTAssertEqual(componentSets[0].key, "cs1") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetFileComponentSetsEndpoint(fileId: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetFileMetaEndpointTests.swift b/Tests/FigmaAPITests/GetFileMetaEndpointTests.swift new file mode 100644 index 0000000..aa7cf92 --- /dev/null +++ b/Tests/FigmaAPITests/GetFileMetaEndpointTests.swift @@ -0,0 +1,56 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetFileMetaEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetFileMetaEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123" + ) + } + + func testMakeRequestUsesHEADMethod() throws { + let endpoint = GetFileMetaEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "HEAD") + } + + // MARK: - Response Parsing + + func testContentExtractsHeaderValues() throws { + let endpoint = GetFileMetaEndpoint(fileId: "abc123") + let url = try XCTUnwrap(URL(string: "https://api.figma.com/v1/files/abc123")) + let response = HTTPURLResponse( + url: url, + statusCode: 200, + httpVersion: nil, + headerFields: [ + "Last-Modified": "Wed, 15 Jan 2024 10:30:00 GMT", + "X-Figma-Version": "123456789", + ] + ) + + let meta = try endpoint.content(from: response, with: Data()) + + XCTAssertEqual(meta.lastModified, "Wed, 15 Jan 2024 10:30:00 GMT") + XCTAssertEqual(meta.version, "123456789") + } + + func testContentThrowsOnNonHTTPResponse() { + let endpoint = GetFileMetaEndpoint(fileId: "abc123") + let response = URLResponse() + + XCTAssertThrowsError(try endpoint.content(from: response, with: Data())) + } +} diff --git a/Tests/FigmaAPITests/GetFileVersionsEndpointTests.swift b/Tests/FigmaAPITests/GetFileVersionsEndpointTests.swift new file mode 100644 index 0000000..7e83307 --- /dev/null +++ b/Tests/FigmaAPITests/GetFileVersionsEndpointTests.swift @@ -0,0 +1,51 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetFileVersionsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetFileVersionsEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/versions" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("FileVersionsResponse") + + let endpoint = GetFileVersionsEndpoint(fileId: "test") + let versions = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(versions.count, 2) + + let first = versions[0] + XCTAssertEqual(first.id, "ver123") + XCTAssertEqual(first.createdAt, "2024-01-15T10:30:00Z") + XCTAssertEqual(first.label, "v1.0") + XCTAssertEqual(first.description, "Initial release") + XCTAssertEqual(first.user.handle, "Designer") + } + + func testContentParsesVersionWithNullFields() throws { + let data = try FixtureLoader.loadData("FileVersionsResponse") + + let endpoint = GetFileVersionsEndpoint(fileId: "test") + let versions = try endpoint.content(from: nil, with: data) + + let second = versions[1] + XCTAssertEqual(second.id, "ver456") + XCTAssertNil(second.label) + XCTAssertNil(second.description) + XCTAssertNil(second.user.email) + XCTAssertNil(second.user.imgUrl) + } +} diff --git a/Tests/FigmaAPITests/GetImageFillsEndpointTests.swift b/Tests/FigmaAPITests/GetImageFillsEndpointTests.swift new file mode 100644 index 0000000..e455e55 --- /dev/null +++ b/Tests/FigmaAPITests/GetImageFillsEndpointTests.swift @@ -0,0 +1,32 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetImageFillsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetImageFillsEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/images" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("ImageFillsResponse") + + let endpoint = GetImageFillsEndpoint(fileId: "test") + let images = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(images.count, 2) + XCTAssertEqual(images["0:1"], "https://s3-alpha.figma.com/img/abc/123/image1.png") + XCTAssertEqual(images["0:2"], "https://s3-alpha.figma.com/img/def/456/image2.png") + } +} diff --git a/Tests/FigmaAPITests/GetMeEndpointTests.swift b/Tests/FigmaAPITests/GetMeEndpointTests.swift new file mode 100644 index 0000000..9613828 --- /dev/null +++ b/Tests/FigmaAPITests/GetMeEndpointTests.swift @@ -0,0 +1,33 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetMeEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetMeEndpoint() + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/me" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("GetMeResponse") + + let endpoint = GetMeEndpoint() + let user = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(user.id, "user123") + XCTAssertEqual(user.handle, "John Doe") + XCTAssertEqual(user.email, "john@example.com") + XCTAssertEqual(user.imgUrl, "https://example.com/avatar.png") + } +} diff --git a/Tests/FigmaAPITests/GetPaymentsEndpointTests.swift b/Tests/FigmaAPITests/GetPaymentsEndpointTests.swift new file mode 100644 index 0000000..95b27da --- /dev/null +++ b/Tests/FigmaAPITests/GetPaymentsEndpointTests.swift @@ -0,0 +1,44 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetPaymentsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetPaymentsEndpoint() + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/payments" + ) + } + + // MARK: - Response Parsing + + func testContentParsesPaymentsResponse() throws { + let data = try FixtureLoader.loadData("PaymentsResponse") + + let endpoint = GetPaymentsEndpoint() + let paymentInfo = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(paymentInfo.status, 200) + XCTAssertEqual(paymentInfo.users?.count, 2) + XCTAssertEqual(paymentInfo.users?[0].userId, "user1") + XCTAssertEqual(paymentInfo.users?[0].status, "active") + XCTAssertEqual(paymentInfo.users?[1].userId, "user2") + XCTAssertEqual(paymentInfo.users?[1].status, "inactive") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetPaymentsEndpoint() + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetProjectFilesEndpointTests.swift b/Tests/FigmaAPITests/GetProjectFilesEndpointTests.swift new file mode 100644 index 0000000..d604884 --- /dev/null +++ b/Tests/FigmaAPITests/GetProjectFilesEndpointTests.swift @@ -0,0 +1,54 @@ +@testable import FigmaAPI +import XCTest + +final class GetProjectFilesEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetProjectFilesEndpoint(projectId: "proj123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/projects/proj123/files" + ) + } + + // MARK: - Response Parsing + + func testContentParsesProjectFilesResponse() throws { + let response: ProjectFilesResponse = try FixtureLoader.load("ProjectFilesResponse") + + let endpoint = GetProjectFilesEndpoint(projectId: "proj123") + let files = endpoint.content(from: response) + + XCTAssertEqual(files.count, 2) + XCTAssertEqual(files[0].key, "file1") + XCTAssertEqual(files[0].name, "Components") + XCTAssertEqual(files[0].thumbnailUrl, "https://example.com/thumb1.png") + XCTAssertEqual(files[0].lastModified, "2024-01-15T10:30:00Z") + XCTAssertEqual(files[1].key, "file2") + XCTAssertNil(files[1].thumbnailUrl) + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("ProjectFilesResponse") + + let endpoint = GetProjectFilesEndpoint(projectId: "proj123") + let files = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(files.count, 2) + XCTAssertEqual(files[0].key, "file1") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetProjectFilesEndpoint(projectId: "proj123") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetPublishedVariablesEndpointTests.swift b/Tests/FigmaAPITests/GetPublishedVariablesEndpointTests.swift new file mode 100644 index 0000000..0546e4b --- /dev/null +++ b/Tests/FigmaAPITests/GetPublishedVariablesEndpointTests.swift @@ -0,0 +1,38 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetPublishedVariablesEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetPublishedVariablesEndpoint(fileId: "abc123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/variables/published" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("PublishedVariablesResponse") + + let endpoint = GetPublishedVariablesEndpoint(fileId: "test") + let meta = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(meta.variableCollections.count, 1) + XCTAssertEqual(meta.variables.count, 1) + + let collection = meta.variableCollections["VariableCollectionId:2:1"] + XCTAssertEqual(collection?.name, "Published Colors") + + let variable = meta.variables["VariableID:2:2"] + XCTAssertEqual(variable?.name, "brand/primary") + XCTAssertEqual(variable?.description, "Primary brand color") + } +} diff --git a/Tests/FigmaAPITests/GetReactionsEndpointTests.swift b/Tests/FigmaAPITests/GetReactionsEndpointTests.swift new file mode 100644 index 0000000..73e746f --- /dev/null +++ b/Tests/FigmaAPITests/GetReactionsEndpointTests.swift @@ -0,0 +1,54 @@ +@testable import FigmaAPI +import XCTest + +final class GetReactionsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetReactionsEndpoint(fileId: "abc123", commentId: "comment1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/comments/comment1/reactions" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let response: ReactionsResponse = try FixtureLoader.load("ReactionsResponse") + + let endpoint = GetReactionsEndpoint(fileId: "test", commentId: "c1") + let reactions = endpoint.content(from: response) + + XCTAssertEqual(reactions.count, 2) + } + + func testContentParsesFirstReaction() throws { + let response: ReactionsResponse = try FixtureLoader.load("ReactionsResponse") + + let endpoint = GetReactionsEndpoint(fileId: "test", commentId: "c1") + let reactions = endpoint.content(from: response) + + let first = reactions[0] + XCTAssertEqual(first.emoji, "\u{1F44D}") + XCTAssertEqual(first.user.id, "user1") + XCTAssertEqual(first.user.handle, "Designer") + XCTAssertEqual(first.createdAt, "2024-01-15T10:30:00Z") + } + + func testContentParsesReactionWithNullUserFields() throws { + let response: ReactionsResponse = try FixtureLoader.load("ReactionsResponse") + + let endpoint = GetReactionsEndpoint(fileId: "test", commentId: "c1") + let reactions = endpoint.content(from: response) + + let second = reactions[1] + XCTAssertEqual(second.emoji, "\u{1F389}") + XCTAssertNil(second.user.email) + XCTAssertNil(second.user.imgUrl) + } +} diff --git a/Tests/FigmaAPITests/GetStyleActionsEndpointTests.swift b/Tests/FigmaAPITests/GetStyleActionsEndpointTests.swift new file mode 100644 index 0000000..7e95fee --- /dev/null +++ b/Tests/FigmaAPITests/GetStyleActionsEndpointTests.swift @@ -0,0 +1,50 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetStyleActionsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetStyleActionsEndpoint(fileKey: "file123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/analytics/libraries/file123/style/actions" + ) + } + + // MARK: - Response Parsing + + func testContentParsesLibraryActionsResponse() throws { + let response: LibraryActionsResponse = try FixtureLoader.load("LibraryActionsResponse") + + let endpoint = GetStyleActionsEndpoint(fileKey: "test") + let actions = endpoint.content(from: response) + + XCTAssertEqual(actions.count, 1) + XCTAssertEqual(actions[0].actionType, "INSERT") + XCTAssertEqual(actions[0].actionCount, 42) + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("LibraryActionsResponse") + + let endpoint = GetStyleActionsEndpoint(fileKey: "test") + let actions = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(actions.count, 1) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetStyleActionsEndpoint(fileKey: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetStyleEndpointTests.swift b/Tests/FigmaAPITests/GetStyleEndpointTests.swift new file mode 100644 index 0000000..b418a50 --- /dev/null +++ b/Tests/FigmaAPITests/GetStyleEndpointTests.swift @@ -0,0 +1,51 @@ +@testable import FigmaAPI +import XCTest + +final class GetStyleEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetStyleEndpoint(key: "style_key_789") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/styles/style_key_789" + ) + } + + // MARK: - Response Parsing + + func testContentParsesGetStyleResponse() throws { + let response: GetStyleResponse = try FixtureLoader.load("GetStyleResponse") + + let endpoint = GetStyleEndpoint(key: "test") + let style = endpoint.content(from: response) + + XCTAssertEqual(style.nodeId, "5:1") + XCTAssertEqual(style.name, "Colors/Primary") + XCTAssertEqual(style.styleType, .fill) + XCTAssertEqual(style.description, "Primary brand color") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("GetStyleResponse") + + let endpoint = GetStyleEndpoint(key: "test") + let style = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(style.name, "Colors/Primary") + XCTAssertEqual(style.styleType, .fill) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetStyleEndpoint(key: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetStyleUsagesEndpointTests.swift b/Tests/FigmaAPITests/GetStyleUsagesEndpointTests.swift new file mode 100644 index 0000000..4f2d682 --- /dev/null +++ b/Tests/FigmaAPITests/GetStyleUsagesEndpointTests.swift @@ -0,0 +1,49 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetStyleUsagesEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetStyleUsagesEndpoint(fileKey: "file123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/analytics/libraries/file123/style/usages" + ) + } + + // MARK: - Response Parsing + + func testContentParsesLibraryUsagesResponse() throws { + let response: LibraryUsagesResponse = try FixtureLoader.load("LibraryUsagesResponse") + + let endpoint = GetStyleUsagesEndpoint(fileKey: "test") + let usages = endpoint.content(from: response) + + XCTAssertEqual(usages.count, 1) + XCTAssertEqual(usages[0].usageCount, 150) + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("LibraryUsagesResponse") + + let endpoint = GetStyleUsagesEndpoint(fileKey: "test") + let usages = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(usages.count, 1) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetStyleUsagesEndpoint(fileKey: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetTeamComponentSetsEndpointTests.swift b/Tests/FigmaAPITests/GetTeamComponentSetsEndpointTests.swift new file mode 100644 index 0000000..6adaa5b --- /dev/null +++ b/Tests/FigmaAPITests/GetTeamComponentSetsEndpointTests.swift @@ -0,0 +1,70 @@ +@testable import FigmaAPI +import XCTest + +final class GetTeamComponentSetsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetTeamComponentSetsEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/teams/team123/component_sets" + ) + } + + func testMakeRequestWithPagination() throws { + let pagination = PaginationParams(pageSize: 25, cursor: "next_page") + let endpoint = GetTeamComponentSetsEndpoint(teamId: "team123", pagination: pagination) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + let url = request.url?.absoluteString ?? "" + + XCTAssertTrue(url.contains("teams/team123/component_sets")) + XCTAssertTrue(url.contains("page_size=25")) + XCTAssertTrue(url.contains("cursor=next_page")) + } + + func testMakeRequestWithoutPaginationHasNoQueryItems() throws { + let endpoint = GetTeamComponentSetsEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertNil(request.url?.query) + } + + // MARK: - Response Parsing + + func testContentParsesFileComponentSetsResponse() throws { + let response: FileComponentSetsResponse = try FixtureLoader.load("FileComponentSetsResponse") + + let endpoint = GetTeamComponentSetsEndpoint(teamId: "team123") + let componentSets = endpoint.content(from: response) + + XCTAssertEqual(componentSets.count, 1) + XCTAssertEqual(componentSets[0].key, "cs1") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("FileComponentSetsResponse") + + let endpoint = GetTeamComponentSetsEndpoint(teamId: "team123") + let componentSets = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(componentSets.count, 1) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetTeamComponentSetsEndpoint(teamId: "team123") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetTeamComponentsEndpointTests.swift b/Tests/FigmaAPITests/GetTeamComponentsEndpointTests.swift new file mode 100644 index 0000000..b837e7c --- /dev/null +++ b/Tests/FigmaAPITests/GetTeamComponentsEndpointTests.swift @@ -0,0 +1,70 @@ +@testable import FigmaAPI +import XCTest + +final class GetTeamComponentsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetTeamComponentsEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/teams/team123/components" + ) + } + + func testMakeRequestWithPagination() throws { + let pagination = PaginationParams(pageSize: 10, cursor: "abc") + let endpoint = GetTeamComponentsEndpoint(teamId: "team123", pagination: pagination) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + let url = request.url?.absoluteString ?? "" + + XCTAssertTrue(url.contains("teams/team123/components")) + XCTAssertTrue(url.contains("page_size=10")) + XCTAssertTrue(url.contains("cursor=abc")) + } + + func testMakeRequestWithoutPaginationHasNoQueryItems() throws { + let endpoint = GetTeamComponentsEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertNil(request.url?.query) + } + + // MARK: - Response Parsing + + func testContentParsesComponentsResponse() throws { + let response: ComponentsResponse = try FixtureLoader.load("ComponentsResponse") + + let endpoint = GetTeamComponentsEndpoint(teamId: "team123") + let components = endpoint.content(from: response) + + XCTAssertEqual(components.count, 4) + XCTAssertEqual(components[0].name, "Icons/24/arrow_right") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("ComponentsResponse") + + let endpoint = GetTeamComponentsEndpoint(teamId: "team123") + let components = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(components.count, 4) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetTeamComponentsEndpoint(teamId: "team123") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetTeamProjectsEndpointTests.swift b/Tests/FigmaAPITests/GetTeamProjectsEndpointTests.swift new file mode 100644 index 0000000..fd4559c --- /dev/null +++ b/Tests/FigmaAPITests/GetTeamProjectsEndpointTests.swift @@ -0,0 +1,52 @@ +@testable import FigmaAPI +import XCTest + +final class GetTeamProjectsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetTeamProjectsEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/teams/team123/projects" + ) + } + + // MARK: - Response Parsing + + func testContentParsesTeamProjectsResponse() throws { + let response: TeamProjectsResponse = try FixtureLoader.load("TeamProjectsResponse") + + let endpoint = GetTeamProjectsEndpoint(teamId: "team123") + let projects = endpoint.content(from: response) + + XCTAssertEqual(projects.count, 2) + XCTAssertEqual(projects[0].id, "proj1") + XCTAssertEqual(projects[0].name, "Design System") + XCTAssertEqual(projects[1].id, "proj2") + XCTAssertEqual(projects[1].name, "Marketing") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("TeamProjectsResponse") + + let endpoint = GetTeamProjectsEndpoint(teamId: "team123") + let projects = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(projects.count, 2) + XCTAssertEqual(projects[0].id, "proj1") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetTeamProjectsEndpoint(teamId: "team123") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetTeamStylesEndpointTests.swift b/Tests/FigmaAPITests/GetTeamStylesEndpointTests.swift new file mode 100644 index 0000000..9d8e9ed --- /dev/null +++ b/Tests/FigmaAPITests/GetTeamStylesEndpointTests.swift @@ -0,0 +1,70 @@ +@testable import FigmaAPI +import XCTest + +final class GetTeamStylesEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetTeamStylesEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/teams/team123/styles" + ) + } + + func testMakeRequestWithPagination() throws { + let pagination = PaginationParams(pageSize: 50, cursor: "cursor_token") + let endpoint = GetTeamStylesEndpoint(teamId: "team123", pagination: pagination) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + let url = request.url?.absoluteString ?? "" + + XCTAssertTrue(url.contains("teams/team123/styles")) + XCTAssertTrue(url.contains("page_size=50")) + XCTAssertTrue(url.contains("cursor=cursor_token")) + } + + func testMakeRequestWithoutPaginationHasNoQueryItems() throws { + let endpoint = GetTeamStylesEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertNil(request.url?.query) + } + + // MARK: - Response Parsing + + func testContentParsesStylesResponse() throws { + let response: StylesResponse = try FixtureLoader.load("StylesResponse") + + let endpoint = GetTeamStylesEndpoint(teamId: "team123") + let styles = endpoint.content(from: response) + + XCTAssertEqual(styles.count, 5) + XCTAssertEqual(styles[0].name, "primary/background") + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("StylesResponse") + + let endpoint = GetTeamStylesEndpoint(teamId: "team123") + let styles = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(styles.count, 5) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetTeamStylesEndpoint(teamId: "team123") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetTeamWebhooksEndpointTests.swift b/Tests/FigmaAPITests/GetTeamWebhooksEndpointTests.swift new file mode 100644 index 0000000..33b56d3 --- /dev/null +++ b/Tests/FigmaAPITests/GetTeamWebhooksEndpointTests.swift @@ -0,0 +1,31 @@ +@testable import FigmaAPI +import XCTest + +final class GetTeamWebhooksEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetTeamWebhooksEndpoint(teamId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v2/teams/team123/webhooks" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let response: WebhooksResponse = try FixtureLoader.load("WebhooksResponse") + + let endpoint = GetTeamWebhooksEndpoint(teamId: "team123") + let webhooks = endpoint.content(from: response) + + XCTAssertEqual(webhooks.count, 2) + XCTAssertEqual(webhooks[0].id, "wh1") + XCTAssertEqual(webhooks[1].id, "wh2") + } +} diff --git a/Tests/FigmaAPITests/GetVariableActionsEndpointTests.swift b/Tests/FigmaAPITests/GetVariableActionsEndpointTests.swift new file mode 100644 index 0000000..3ed9da7 --- /dev/null +++ b/Tests/FigmaAPITests/GetVariableActionsEndpointTests.swift @@ -0,0 +1,50 @@ +import CustomDump +@testable import FigmaAPI +import XCTest + +final class GetVariableActionsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetVariableActionsEndpoint(fileKey: "file123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/analytics/libraries/file123/variable/actions" + ) + } + + // MARK: - Response Parsing + + func testContentParsesLibraryActionsResponse() throws { + let response: LibraryActionsResponse = try FixtureLoader.load("LibraryActionsResponse") + + let endpoint = GetVariableActionsEndpoint(fileKey: "test") + let actions = endpoint.content(from: response) + + XCTAssertEqual(actions.count, 1) + XCTAssertEqual(actions[0].actionType, "INSERT") + XCTAssertEqual(actions[0].actionCount, 42) + } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("LibraryActionsResponse") + + let endpoint = GetVariableActionsEndpoint(fileKey: "test") + let actions = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(actions.count, 1) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetVariableActionsEndpoint(fileKey: "test") + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } +} diff --git a/Tests/FigmaAPITests/GetWebhookEndpointTests.swift b/Tests/FigmaAPITests/GetWebhookEndpointTests.swift new file mode 100644 index 0000000..530fc26 --- /dev/null +++ b/Tests/FigmaAPITests/GetWebhookEndpointTests.swift @@ -0,0 +1,37 @@ +@testable import FigmaAPI +import XCTest + +final class GetWebhookEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetWebhookEndpoint(webhookId: "wh1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v2/webhooks/wh1" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("WebhookResponse") + + let endpoint = GetWebhookEndpoint(webhookId: "wh1") + let webhook = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(webhook.id, "wh1") + XCTAssertEqual(webhook.teamId, "team123") + XCTAssertEqual(webhook.eventType, "FILE_UPDATE") + XCTAssertNil(webhook.clientId) + XCTAssertEqual(webhook.endpoint, "https://example.com/webhook") + XCTAssertEqual(webhook.passcode, "secret123") + XCTAssertEqual(webhook.status, "ACTIVE") + XCTAssertEqual(webhook.description, "File update webhook") + XCTAssertEqual(webhook.protocolVersion, "2") + } +} diff --git a/Tests/FigmaAPITests/GetWebhookRequestsEndpointTests.swift b/Tests/FigmaAPITests/GetWebhookRequestsEndpointTests.swift new file mode 100644 index 0000000..3b3c629 --- /dev/null +++ b/Tests/FigmaAPITests/GetWebhookRequestsEndpointTests.swift @@ -0,0 +1,56 @@ +@testable import FigmaAPI +import XCTest + +final class GetWebhookRequestsEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetWebhookRequestsEndpoint(webhookId: "wh1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v2/webhooks/wh1/requests" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let response: WebhookRequestsResponse = try FixtureLoader.load("WebhookRequestsResponse") + + let endpoint = GetWebhookRequestsEndpoint(webhookId: "wh1") + let requests = endpoint.content(from: response) + + XCTAssertEqual(requests.count, 2) + } + + func testContentParsesRequestWithPayload() throws { + let response: WebhookRequestsResponse = try FixtureLoader.load("WebhookRequestsResponse") + + let endpoint = GetWebhookRequestsEndpoint(webhookId: "wh1") + let requests = endpoint.content(from: response) + + let first = requests[0] + XCTAssertEqual(first.id, "req1") + XCTAssertEqual(first.endpoint, "https://example.com/webhook") + XCTAssertEqual(first.payload?.eventType, "FILE_UPDATE") + XCTAssertEqual(first.payload?.timestamp, "2024-01-15T10:30:00Z") + XCTAssertNil(first.error) + XCTAssertEqual(first.createdAt, "2024-01-15T10:30:01Z") + } + + func testContentParsesRequestWithError() throws { + let response: WebhookRequestsResponse = try FixtureLoader.load("WebhookRequestsResponse") + + let endpoint = GetWebhookRequestsEndpoint(webhookId: "wh1") + let requests = endpoint.content(from: response) + + let second = requests[1] + XCTAssertEqual(second.id, "req2") + XCTAssertNil(second.payload) + XCTAssertEqual(second.error, "Connection timeout") + } +} diff --git a/Tests/FigmaAPITests/GetWebhooksEndpointTests.swift b/Tests/FigmaAPITests/GetWebhooksEndpointTests.swift new file mode 100644 index 0000000..d9afbf8 --- /dev/null +++ b/Tests/FigmaAPITests/GetWebhooksEndpointTests.swift @@ -0,0 +1,58 @@ +@testable import FigmaAPI +import XCTest + +final class GetWebhooksEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let endpoint = GetWebhooksEndpoint() + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v2/webhooks" + ) + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let response: WebhooksResponse = try FixtureLoader.load("WebhooksResponse") + + let endpoint = GetWebhooksEndpoint() + let webhooks = endpoint.content(from: response) + + XCTAssertEqual(webhooks.count, 2) + } + + func testContentParsesFirstWebhook() throws { + let response: WebhooksResponse = try FixtureLoader.load("WebhooksResponse") + + let endpoint = GetWebhooksEndpoint() + let webhooks = endpoint.content(from: response) + + let first = webhooks[0] + XCTAssertEqual(first.id, "wh1") + XCTAssertEqual(first.teamId, "team123") + XCTAssertEqual(first.eventType, "FILE_UPDATE") + XCTAssertNil(first.clientId) + XCTAssertEqual(first.status, "ACTIVE") + } + + func testContentParsesSecondWebhook() throws { + let response: WebhooksResponse = try FixtureLoader.load("WebhooksResponse") + + let endpoint = GetWebhooksEndpoint() + let webhooks = endpoint.content(from: response) + + let second = webhooks[1] + XCTAssertEqual(second.id, "wh2") + XCTAssertEqual(second.eventType, "FILE_DELETE") + XCTAssertEqual(second.clientId, "client456") + XCTAssertNil(second.passcode) + XCTAssertEqual(second.status, "PAUSED") + XCTAssertNil(second.description) + } +} diff --git a/Tests/FigmaAPITests/ImageEndpointTests.swift b/Tests/FigmaAPITests/ImageEndpointTests.swift index 569f603..0a9a598 100644 --- a/Tests/FigmaAPITests/ImageEndpointTests.swift +++ b/Tests/FigmaAPITests/ImageEndpointTests.swift @@ -8,7 +8,7 @@ final class ImageEndpointTests: XCTestCase { func testMakeRequestWithPNGParams() throws { let params = PNGParams(scale: 2.0) let endpoint = ImageEndpoint(fileId: "abc123", nodeIds: ["1:2", "1:3"], params: params) - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) let url = request.url?.absoluteString ?? "" @@ -25,7 +25,7 @@ final class ImageEndpointTests: XCTestCase { params.svgSimplifyStroke = true let endpoint = ImageEndpoint(fileId: "file123", nodeIds: ["10:1"], params: params) - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) let url = request.url?.absoluteString ?? "" @@ -38,7 +38,7 @@ final class ImageEndpointTests: XCTestCase { func testMakeRequestWithPDFParams() throws { let params = PDFParams() let endpoint = ImageEndpoint(fileId: "file123", nodeIds: ["5:1"], params: params) - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) let url = request.url?.absoluteString ?? "" @@ -50,7 +50,7 @@ final class ImageEndpointTests: XCTestCase { func testMakeRequestIncludesUseAbsoluteBounds() throws { let params = PNGParams(scale: 1.0) let endpoint = ImageEndpoint(fileId: "file123", nodeIds: ["1:1"], params: params) - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) let url = request.url?.absoluteString ?? "" diff --git a/Tests/FigmaAPITests/Mocks/MockClient.swift b/Tests/FigmaAPITests/Mocks/MockClient.swift index cde71f8..b696efd 100644 --- a/Tests/FigmaAPITests/Mocks/MockClient.swift +++ b/Tests/FigmaAPITests/Mocks/MockClient.swift @@ -58,7 +58,7 @@ public final class MockClient: Client, @unchecked Sendable { public func request(_ endpoint: T) async throws -> T.Content { let key = String(describing: type(of: endpoint)) // swiftlint:disable:next force_unwrapping - let baseURL = URL(string: "https://api.figma.com/v1/")! + let baseURL = URL(string: "https://api.figma.com/")! let request = try endpoint.makeRequest(baseURL: baseURL) // Record timestamp and get delay (thread-safe) diff --git a/Tests/FigmaAPITests/NodesEndpointTests.swift b/Tests/FigmaAPITests/NodesEndpointTests.swift index 1aa3c42..8faa9e8 100644 --- a/Tests/FigmaAPITests/NodesEndpointTests.swift +++ b/Tests/FigmaAPITests/NodesEndpointTests.swift @@ -7,7 +7,7 @@ final class NodesEndpointTests: XCTestCase { func testMakeRequestConstructsCorrectURL() throws { let endpoint = NodesEndpoint(fileId: "abc123", nodeIds: ["1:2", "1:3"]) - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -17,7 +17,7 @@ final class NodesEndpointTests: XCTestCase { func testMakeRequestWithSingleNodeId() throws { let endpoint = NodesEndpoint(fileId: "file123", nodeIds: ["10:5"]) - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -27,7 +27,7 @@ final class NodesEndpointTests: XCTestCase { func testMakeRequestWithManyNodeIds() throws { let nodeIds = (1 ... 100).map { "1:\($0)" } let endpoint = NodesEndpoint(fileId: "file123", nodeIds: nodeIds) - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) diff --git a/Tests/FigmaAPITests/PostCommentEndpointTests.swift b/Tests/FigmaAPITests/PostCommentEndpointTests.swift new file mode 100644 index 0000000..46976a8 --- /dev/null +++ b/Tests/FigmaAPITests/PostCommentEndpointTests.swift @@ -0,0 +1,66 @@ +@testable import FigmaAPI +import XCTest + +final class PostCommentEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let body = PostCommentBody(message: "Hello") + let endpoint = PostCommentEndpoint(fileId: "abc123", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/comments" + ) + } + + func testMakeRequestUsesPOSTMethod() throws { + let body = PostCommentBody(message: "Hello") + let endpoint = PostCommentEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "POST") + } + + func testMakeRequestSetsContentTypeHeader() throws { + let body = PostCommentBody(message: "Hello") + let endpoint = PostCommentEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") + } + + func testMakeRequestIncludesBody() throws { + let body = PostCommentBody(message: "Test comment", commentId: "parent1") + let endpoint = PostCommentEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let httpBody = try XCTUnwrap(request.httpBody) + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) + XCTAssertEqual(json["message"] as? String, "Test comment") + XCTAssertEqual(json["comment_id"] as? String, "parent1") + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("PostCommentResponse") + + let body = PostCommentBody(message: "New comment") + let endpoint = PostCommentEndpoint(fileId: "test", body: body) + let comment = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(comment.id, "comment3") + XCTAssertEqual(comment.message, "New comment") + XCTAssertEqual(comment.clientMeta?.nodeId, "5:1") + } +} diff --git a/Tests/FigmaAPITests/PostDevResourceEndpointTests.swift b/Tests/FigmaAPITests/PostDevResourceEndpointTests.swift new file mode 100644 index 0000000..56a2d2e --- /dev/null +++ b/Tests/FigmaAPITests/PostDevResourceEndpointTests.swift @@ -0,0 +1,68 @@ +@testable import FigmaAPI +import XCTest + +final class PostDevResourceEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let body = PostDevResourceBody(name: "Test", url: "https://example.com", nodeId: "10:1") + let endpoint = PostDevResourceEndpoint(fileId: "abc123", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/dev_resources" + ) + } + + func testMakeRequestUsesPOSTMethod() throws { + let body = PostDevResourceBody(name: "Test", url: "https://example.com", nodeId: "10:1") + let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "POST") + } + + func testMakeRequestSetsContentTypeHeader() throws { + let body = PostDevResourceBody(name: "Test", url: "https://example.com", nodeId: "10:1") + let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") + } + + func testMakeRequestIncludesBody() throws { + let body = PostDevResourceBody(name: "Storybook", url: "https://storybook.example.com", nodeId: "10:1", devStatus: "ready_for_dev") + let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let httpBody = try XCTUnwrap(request.httpBody) + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) + XCTAssertEqual(json["name"] as? String, "Storybook") + XCTAssertEqual(json["url"] as? String, "https://storybook.example.com") + XCTAssertEqual(json["node_id"] as? String, "10:1") + XCTAssertEqual(json["dev_status"] as? String, "ready_for_dev") + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("PostDevResourceResponse") + + let body = PostDevResourceBody(name: "New Resource", url: "https://example.com/resource", nodeId: "15:1") + let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) + let resource = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(resource.id, "res3") + XCTAssertEqual(resource.name, "New Resource") + XCTAssertEqual(resource.nodeId, "15:1") + } +} diff --git a/Tests/FigmaAPITests/PostReactionEndpointTests.swift b/Tests/FigmaAPITests/PostReactionEndpointTests.swift new file mode 100644 index 0000000..14fe601 --- /dev/null +++ b/Tests/FigmaAPITests/PostReactionEndpointTests.swift @@ -0,0 +1,62 @@ +@testable import FigmaAPI +import XCTest + +final class PostReactionEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let body = PostReactionBody(emoji: "👍") + let endpoint = PostReactionEndpoint(fileId: "abc123", commentId: "comment1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/comments/comment1/reactions" + ) + } + + func testMakeRequestUsesPOSTMethod() throws { + let body = PostReactionBody(emoji: "👍") + let endpoint = PostReactionEndpoint(fileId: "test", commentId: "c1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "POST") + } + + func testMakeRequestSetsContentTypeHeader() throws { + let body = PostReactionBody(emoji: "👍") + let endpoint = PostReactionEndpoint(fileId: "test", commentId: "c1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") + } + + func testMakeRequestIncludesBody() throws { + let body = PostReactionBody(emoji: "🎉") + let endpoint = PostReactionEndpoint(fileId: "test", commentId: "c1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let httpBody = try XCTUnwrap(request.httpBody) + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) + XCTAssertEqual(json["emoji"] as? String, "\u{1F389}") + } + + // MARK: - Response Parsing + + func testContentParsesEmptyResponse() throws { + let data = Data("{}".utf8) + let body = PostReactionBody(emoji: "👍") + let endpoint = PostReactionEndpoint(fileId: "test", commentId: "c1", body: body) + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } +} diff --git a/Tests/FigmaAPITests/PostWebhookEndpointTests.swift b/Tests/FigmaAPITests/PostWebhookEndpointTests.swift new file mode 100644 index 0000000..f72fe2c --- /dev/null +++ b/Tests/FigmaAPITests/PostWebhookEndpointTests.swift @@ -0,0 +1,55 @@ +@testable import FigmaAPI +import XCTest + +final class PostWebhookEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let body = PostWebhookBody(eventType: "FILE_UPDATE", teamId: "team123", endpoint: "https://example.com/webhook", passcode: "secret") + let endpoint = PostWebhookEndpoint(body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v2/webhooks" + ) + } + + func testMakeRequestUsesPOSTMethod() throws { + let body = PostWebhookBody(eventType: "FILE_UPDATE", teamId: "team123", endpoint: "https://example.com/webhook", passcode: "secret") + let endpoint = PostWebhookEndpoint(body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "POST") + } + + func testMakeRequestSetsContentTypeHeader() throws { + let body = PostWebhookBody(eventType: "FILE_UPDATE", teamId: "team123", endpoint: "https://example.com/webhook", passcode: "secret") + let endpoint = PostWebhookEndpoint(body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") + } + + func testMakeRequestIncludesBody() throws { + let body = PostWebhookBody(eventType: "FILE_UPDATE", teamId: "team123", endpoint: "https://example.com/webhook", passcode: "secret", description: "My webhook") + let endpoint = PostWebhookEndpoint(body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let httpBody = try XCTUnwrap(request.httpBody) + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) + XCTAssertEqual(json["event_type"] as? String, "FILE_UPDATE") + XCTAssertEqual(json["team_id"] as? String, "team123") + XCTAssertEqual(json["endpoint"] as? String, "https://example.com/webhook") + XCTAssertEqual(json["passcode"] as? String, "secret") + XCTAssertEqual(json["description"] as? String, "My webhook") + } +} diff --git a/Tests/FigmaAPITests/PutDevResourceEndpointTests.swift b/Tests/FigmaAPITests/PutDevResourceEndpointTests.swift new file mode 100644 index 0000000..021f34f --- /dev/null +++ b/Tests/FigmaAPITests/PutDevResourceEndpointTests.swift @@ -0,0 +1,67 @@ +@testable import FigmaAPI +import XCTest + +final class PutDevResourceEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let body = PutDevResourceBody(id: "res1", name: "Updated") + let endpoint = PutDevResourceEndpoint(fileId: "abc123", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v1/files/abc123/dev_resources" + ) + } + + func testMakeRequestUsesPUTMethod() throws { + let body = PutDevResourceBody(id: "res1", name: "Updated") + let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "PUT") + } + + func testMakeRequestSetsContentTypeHeader() throws { + let body = PutDevResourceBody(id: "res1", name: "Updated") + let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") + } + + func testMakeRequestIncludesBody() throws { + let body = PutDevResourceBody(id: "res1", name: "Updated Name", url: "https://new.example.com", devStatus: "completed") + let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let httpBody = try XCTUnwrap(request.httpBody) + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) + XCTAssertEqual(json["id"] as? String, "res1") + XCTAssertEqual(json["name"] as? String, "Updated Name") + XCTAssertEqual(json["url"] as? String, "https://new.example.com") + XCTAssertEqual(json["dev_status"] as? String, "completed") + } + + // MARK: - Response Parsing + + func testContentParsesResponse() throws { + let data = try FixtureLoader.loadData("PostDevResourceResponse") + + let body = PutDevResourceBody(id: "res3", name: "New Resource") + let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) + let resource = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(resource.id, "res3") + XCTAssertEqual(resource.name, "New Resource") + } +} diff --git a/Tests/FigmaAPITests/PutWebhookEndpointTests.swift b/Tests/FigmaAPITests/PutWebhookEndpointTests.swift new file mode 100644 index 0000000..c5d44db --- /dev/null +++ b/Tests/FigmaAPITests/PutWebhookEndpointTests.swift @@ -0,0 +1,52 @@ +@testable import FigmaAPI +import XCTest + +final class PutWebhookEndpointTests: XCTestCase { + // MARK: - URL Construction + + func testMakeRequestConstructsCorrectURL() throws { + let body = PutWebhookBody(eventType: "FILE_DELETE") + let endpoint = PutWebhookEndpoint(webhookId: "wh1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual( + request.url?.absoluteString, + "https://api.figma.com/v2/webhooks/wh1" + ) + } + + func testMakeRequestUsesPUTMethod() throws { + let body = PutWebhookBody(endpoint: "https://example.com/new") + let endpoint = PutWebhookEndpoint(webhookId: "wh1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.httpMethod, "PUT") + } + + func testMakeRequestSetsContentTypeHeader() throws { + let body = PutWebhookBody(passcode: "newpass") + let endpoint = PutWebhookEndpoint(webhookId: "wh1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") + } + + func testMakeRequestIncludesBody() throws { + let body = PutWebhookBody(eventType: "FILE_DELETE", description: "Updated webhook") + let endpoint = PutWebhookEndpoint(webhookId: "wh1", body: body) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let httpBody = try XCTUnwrap(request.httpBody) + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) + XCTAssertEqual(json["event_type"] as? String, "FILE_DELETE") + XCTAssertEqual(json["description"] as? String, "Updated webhook") + } +} diff --git a/Tests/FigmaAPITests/StylesEndpointTests.swift b/Tests/FigmaAPITests/StylesEndpointTests.swift index 2e3aa96..43a08df 100644 --- a/Tests/FigmaAPITests/StylesEndpointTests.swift +++ b/Tests/FigmaAPITests/StylesEndpointTests.swift @@ -7,7 +7,7 @@ final class StylesEndpointTests: XCTestCase { func testMakeRequestConstructsCorrectURL() throws { let endpoint = StylesEndpoint(fileId: "abc123") - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = endpoint.makeRequest(baseURL: baseURL) @@ -19,7 +19,7 @@ final class StylesEndpointTests: XCTestCase { func testMakeRequestWithSpecialCharactersInFileId() throws { let endpoint = StylesEndpoint(fileId: "file-with-dashes") - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = endpoint.makeRequest(baseURL: baseURL) diff --git a/Tests/FigmaAPITests/UpdateVariablesEndpointTests.swift b/Tests/FigmaAPITests/UpdateVariablesEndpointTests.swift index b630d8b..ae0318e 100644 --- a/Tests/FigmaAPITests/UpdateVariablesEndpointTests.swift +++ b/Tests/FigmaAPITests/UpdateVariablesEndpointTests.swift @@ -8,7 +8,7 @@ final class UpdateVariablesEndpointTests: XCTestCase { let body = VariablesUpdateRequest(variables: []) let endpoint = UpdateVariablesEndpoint(fileId: "abc123", body: body) // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -22,7 +22,7 @@ final class UpdateVariablesEndpointTests: XCTestCase { let body = VariablesUpdateRequest(variables: []) let endpoint = UpdateVariablesEndpoint(fileId: "test", body: body) // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -33,7 +33,7 @@ final class UpdateVariablesEndpointTests: XCTestCase { let body = VariablesUpdateRequest(variables: []) let endpoint = UpdateVariablesEndpoint(fileId: "test", body: body) // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -48,7 +48,7 @@ final class UpdateVariablesEndpointTests: XCTestCase { let body = VariablesUpdateRequest(variables: [update]) let endpoint = UpdateVariablesEndpoint(fileId: "test", body: body) // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) diff --git a/Tests/FigmaAPITests/VariablesEndpointTests.swift b/Tests/FigmaAPITests/VariablesEndpointTests.swift index 320efb7..3f2b49b 100644 --- a/Tests/FigmaAPITests/VariablesEndpointTests.swift +++ b/Tests/FigmaAPITests/VariablesEndpointTests.swift @@ -8,7 +8,7 @@ final class VariablesEndpointTests: XCTestCase { func testMakeRequestConstructsCorrectURL() throws { let endpoint = VariablesEndpoint(fileId: "abc123") - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/v1/")) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = endpoint.makeRequest(baseURL: baseURL) From c93a0ecc861aa20f685519abc3f10643f0e43fc3 Mon Sep 17 00:00:00 2001 From: alexey1312 Date: Mon, 16 Mar 2026 21:23:34 +0500 Subject: [PATCH 2/3] fix: align endpoints with Figma REST API spec and fix critical bugs - Fix DELETE endpoints crashing on empty 204 No Content responses by adding explicit content(from:with:) that handles empty body - Fix POST/PUT dev_resources to use correct path /v1/dev_resources with batch request/response wrapping (dev_resources/links_created/links_updated) - Fix PostDevResourceBody to include required file_key field per spec - Fix PutDevResourceBody to match spec (remove dev_status, keep id/name/url) - Fix GetPaymentsEndpoint to use query params per spec instead of path params - Fix GetWebhooksEndpoint to use query params (context, context_id) per spec - Fix GetActivityLogsEndpoint to use correct query filters per spec - Fix GetFileMetaEndpointTests for Linux FoundationNetworking compatibility - Remove incorrect @available deprecation from GetTeamWebhooksEndpoint - Add pageSize validation precondition to PaginationParams - Narrow Codable to Decodable for read-only models (Webhook, DevResource, FileVersion, ComponentSet, User) --- Sources/FigmaAPI/Endpoint/BaseEndpoint.swift | 1 + .../Endpoint/DeleteCommentEndpoint.swift | 6 +++ .../Endpoint/DeleteDevResourceEndpoint.swift | 6 +++ .../Endpoint/DeleteReactionEndpoint.swift | 6 +++ .../Endpoint/DeleteWebhookEndpoint.swift | 6 +++ .../Endpoint/GetActivityLogsEndpoint.swift | 37 +++++++++++-- .../Endpoint/GetPaymentsEndpoint.swift | 45 ++++++++++++++-- .../Endpoint/GetTeamWebhooksEndpoint.swift | 1 - .../Endpoint/GetWebhooksEndpoint.swift | 22 ++++++-- .../FigmaAPI/Endpoint/PaginationParams.swift | 3 ++ .../Endpoint/PostDevResourceEndpoint.swift | 30 ++++++++--- .../Endpoint/PutDevResourceEndpoint.swift | 30 ++++++++--- Sources/FigmaAPI/Model/ComponentSet.swift | 2 +- Sources/FigmaAPI/Model/DevResource.swift | 2 +- Sources/FigmaAPI/Model/FileVersion.swift | 2 +- .../FigmaAPI/Model/PostDevResourceBody.swift | 5 +- .../FigmaAPI/Model/PutDevResourceBody.swift | 9 +--- Sources/FigmaAPI/Model/User.swift | 2 +- Sources/FigmaAPI/Model/Webhook.swift | 2 +- .../DeleteCommentEndpointTests.swift | 11 +++- .../DeleteDevResourceEndpointTests.swift | 10 +++- .../DeleteReactionEndpointTests.swift | 10 +++- .../DeleteWebhookEndpointTests.swift | 10 +++- .../Fixtures/PaymentsResponse.json | 24 +++++---- .../Fixtures/PostDevResourceResponse.json | 14 +++-- .../Fixtures/PostReactionResponse.json | 10 ++++ .../Fixtures/PutDevResourceResponse.json | 11 ++++ .../GetActivityLogsEndpointTests.swift | 23 +++++++- .../GetFileMetaEndpointTests.swift | 6 +++ .../GetPaymentsEndpointTests.swift | 36 ++++++++++--- .../GetWebhooksEndpointTests.swift | 32 ++++++++++- .../PostDevResourceEndpointTests.swift | 54 ++++++++++++------- .../PostReactionEndpointTests.swift | 12 ++++- .../PutDevResourceEndpointTests.swift | 46 ++++++++++------ 34 files changed, 420 insertions(+), 106 deletions(-) create mode 100644 Tests/FigmaAPITests/Fixtures/PostReactionResponse.json create mode 100644 Tests/FigmaAPITests/Fixtures/PutDevResourceResponse.json diff --git a/Sources/FigmaAPI/Endpoint/BaseEndpoint.swift b/Sources/FigmaAPI/Endpoint/BaseEndpoint.swift index 25889f8..1fffadd 100644 --- a/Sources/FigmaAPI/Endpoint/BaseEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/BaseEndpoint.swift @@ -37,3 +37,4 @@ extension BaseEndpoint { } } } + diff --git a/Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift index 14469da..52815eb 100644 --- a/Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/DeleteCommentEndpoint.swift @@ -1,4 +1,5 @@ import Foundation +import YYJSON #if canImport(FoundationNetworking) import FoundationNetworking #endif @@ -25,4 +26,9 @@ public struct DeleteCommentEndpoint: BaseEndpoint { request.httpMethod = "DELETE" return request } + + public func content(from _: URLResponse?, with body: Data) throws -> EmptyResponse { + if body.isEmpty { return EmptyResponse() } + return try YYJSONDecoder().decode(EmptyResponse.self, from: body) + } } diff --git a/Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift index 3c65456..476cc80 100644 --- a/Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/DeleteDevResourceEndpoint.swift @@ -1,4 +1,5 @@ import Foundation +import YYJSON #if canImport(FoundationNetworking) import FoundationNetworking #endif @@ -25,4 +26,9 @@ public struct DeleteDevResourceEndpoint: BaseEndpoint { request.httpMethod = "DELETE" return request } + + public func content(from _: URLResponse?, with body: Data) throws -> EmptyResponse { + if body.isEmpty { return EmptyResponse() } + return try YYJSONDecoder().decode(EmptyResponse.self, from: body) + } } diff --git a/Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift index 1be124f..a9bb920 100644 --- a/Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/DeleteReactionEndpoint.swift @@ -1,4 +1,5 @@ import Foundation +import YYJSON #if canImport(FoundationNetworking) import FoundationNetworking #endif @@ -35,4 +36,9 @@ public struct DeleteReactionEndpoint: BaseEndpoint { request.httpMethod = "DELETE" return request } + + public func content(from _: URLResponse?, with body: Data) throws -> EmptyResponse { + if body.isEmpty { return EmptyResponse() } + return try YYJSONDecoder().decode(EmptyResponse.self, from: body) + } } diff --git a/Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift b/Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift index 64380d4..8f000b7 100644 --- a/Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/DeleteWebhookEndpoint.swift @@ -1,4 +1,5 @@ import Foundation +import YYJSON #if canImport(FoundationNetworking) import FoundationNetworking #endif @@ -21,4 +22,9 @@ public struct DeleteWebhookEndpoint: BaseEndpoint { request.httpMethod = "DELETE" return request } + + public func content(from _: URLResponse?, with body: Data) throws -> EmptyResponse { + if body.isEmpty { return EmptyResponse() } + return try YYJSONDecoder().decode(EmptyResponse.self, from: body) + } } diff --git a/Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift index 6139b39..6ef63e9 100644 --- a/Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/GetActivityLogsEndpoint.swift @@ -6,17 +6,48 @@ import Foundation public struct GetActivityLogsEndpoint: BaseEndpoint { public typealias Content = [ActivityLog] - public init() {} + private let events: String? + private let startTime: Double? + private let endTime: Double? + private let limit: Int? + private let order: String? + + public init( + events: String? = nil, + startTime: Double? = nil, + endTime: Double? = nil, + limit: Int? = nil, + order: String? = nil + ) { + self.events = events + self.startTime = startTime + self.endTime = endTime + self.limit = limit + self.order = order + } func content(from root: ActivityLogsResponse) -> [ActivityLog] { root.activityLogs } - public func makeRequest(baseURL: URL) -> URLRequest { + public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL .appendingPathComponent("v1") .appendingPathComponent("activity_logs") - return URLRequest(url: url) + guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + throw URLError(.badURL) + } + var items: [URLQueryItem] = [] + if let events { items.append(URLQueryItem(name: "events", value: events)) } + if let startTime { items.append(URLQueryItem(name: "start_time", value: "\(startTime)")) } + if let endTime { items.append(URLQueryItem(name: "end_time", value: "\(endTime)")) } + if let limit { items.append(URLQueryItem(name: "limit", value: "\(limit)")) } + if let order { items.append(URLQueryItem(name: "order", value: order)) } + if !items.isEmpty { comps.queryItems = items } + guard let finalURL = comps.url else { + throw URLError(.badURL) + } + return URLRequest(url: finalURL) } } diff --git a/Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift index 40d1b9f..18edeb8 100644 --- a/Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/GetPaymentsEndpoint.swift @@ -6,12 +6,51 @@ import Foundation public struct GetPaymentsEndpoint: BaseEndpoint { public typealias Content = PaymentInfo - public init() {} + private let pluginPaymentToken: String? + private let userId: String? + private let pluginId: String? + private let widgetId: String? + private let communityFileId: String? - public func makeRequest(baseURL: URL) -> URLRequest { + public init( + pluginPaymentToken: String? = nil, + userId: String? = nil, + pluginId: String? = nil, + widgetId: String? = nil, + communityFileId: String? = nil + ) { + self.pluginPaymentToken = pluginPaymentToken + self.userId = userId + self.pluginId = pluginId + self.widgetId = widgetId + self.communityFileId = communityFileId + } + + func content(from root: PaymentsResponse) -> PaymentInfo { + root.meta + } + + public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL .appendingPathComponent("v1") .appendingPathComponent("payments") - return URLRequest(url: url) + guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + throw URLError(.badURL) + } + var items: [URLQueryItem] = [] + if let pluginPaymentToken { items.append(URLQueryItem(name: "plugin_payment_token", value: pluginPaymentToken)) } + if let userId { items.append(URLQueryItem(name: "user_id", value: userId)) } + if let pluginId { items.append(URLQueryItem(name: "plugin_id", value: pluginId)) } + if let widgetId { items.append(URLQueryItem(name: "widget_id", value: widgetId)) } + if let communityFileId { items.append(URLQueryItem(name: "community_file_id", value: communityFileId)) } + if !items.isEmpty { comps.queryItems = items } + guard let finalURL = comps.url else { + throw URLError(.badURL) + } + return URLRequest(url: finalURL) } } + +struct PaymentsResponse: Decodable { + let meta: PaymentInfo +} diff --git a/Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift index 57d5c0b..859ef30 100644 --- a/Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/GetTeamWebhooksEndpoint.swift @@ -3,7 +3,6 @@ import Foundation import FoundationNetworking #endif -@available(*, deprecated, message: "Use GetWebhooksEndpoint instead") public struct GetTeamWebhooksEndpoint: BaseEndpoint { public typealias Content = [Webhook] diff --git a/Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift b/Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift index 42f3673..ca3f59f 100644 --- a/Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/GetWebhooksEndpoint.swift @@ -6,17 +6,33 @@ import Foundation public struct GetWebhooksEndpoint: BaseEndpoint { public typealias Content = [Webhook] - public init() {} + private let context: String? + private let contextId: String? + + public init(context: String? = nil, contextId: String? = nil) { + self.context = context + self.contextId = contextId + } func content(from root: WebhooksResponse) -> [Webhook] { root.webhooks } - public func makeRequest(baseURL: URL) -> URLRequest { + public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL .appendingPathComponent("v2") .appendingPathComponent("webhooks") - return URLRequest(url: url) + guard var comps = URLComponents(url: url, resolvingAgainstBaseURL: false) else { + throw URLError(.badURL) + } + var items: [URLQueryItem] = [] + if let context { items.append(URLQueryItem(name: "context", value: context)) } + if let contextId { items.append(URLQueryItem(name: "context_id", value: contextId)) } + if !items.isEmpty { comps.queryItems = items } + guard let finalURL = comps.url else { + throw URLError(.badURL) + } + return URLRequest(url: finalURL) } } diff --git a/Sources/FigmaAPI/Endpoint/PaginationParams.swift b/Sources/FigmaAPI/Endpoint/PaginationParams.swift index 4ecad4d..741b9c8 100644 --- a/Sources/FigmaAPI/Endpoint/PaginationParams.swift +++ b/Sources/FigmaAPI/Endpoint/PaginationParams.swift @@ -5,6 +5,9 @@ public struct PaginationParams: Sendable { public let cursor: String? public init(pageSize: Int? = nil, cursor: String? = nil) { + if let pageSize { + precondition(pageSize > 0, "pageSize must be positive") + } self.pageSize = pageSize self.cursor = cursor } diff --git a/Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift b/Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift index 2bc2f7c..82766ca 100644 --- a/Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/PostDevResourceEndpoint.swift @@ -6,28 +6,44 @@ import YYJSON #endif public struct PostDevResourceEndpoint: BaseEndpoint { - public typealias Content = DevResource + public typealias Content = [DevResource] - private let fileId: String private let body: PostDevResourceBody - public init(fileId: String, body: PostDevResourceBody) { - self.fileId = fileId + public init(body: PostDevResourceBody) { self.body = body } + func content(from root: LinksCreatedResponse) -> [DevResource] { + root.linksCreated + } + public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL .appendingPathComponent("v1") - .appendingPathComponent("files") - .appendingPathComponent(fileId) .appendingPathComponent("dev_resources") var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = try YYJSONEncoder().encode(body) + request.httpBody = try YYJSONEncoder().encode(DevResourcesWrapper(devResources: [body])) return request } } + +struct DevResourcesWrapper: Encodable { + let devResources: [PostDevResourceBody] + + private enum CodingKeys: String, CodingKey { + case devResources = "dev_resources" + } +} + +struct LinksCreatedResponse: Decodable { + let linksCreated: [DevResource] + + private enum CodingKeys: String, CodingKey { + case linksCreated = "links_created" + } +} diff --git a/Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift b/Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift index 75e2106..64a7092 100644 --- a/Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift +++ b/Sources/FigmaAPI/Endpoint/PutDevResourceEndpoint.swift @@ -6,28 +6,44 @@ import YYJSON #endif public struct PutDevResourceEndpoint: BaseEndpoint { - public typealias Content = DevResource + public typealias Content = [DevResource] - private let fileId: String private let body: PutDevResourceBody - public init(fileId: String, body: PutDevResourceBody) { - self.fileId = fileId + public init(body: PutDevResourceBody) { self.body = body } + func content(from root: LinksUpdatedResponse) -> [DevResource] { + root.linksUpdated + } + public func makeRequest(baseURL: URL) throws -> URLRequest { let url = baseURL .appendingPathComponent("v1") - .appendingPathComponent("files") - .appendingPathComponent(fileId) .appendingPathComponent("dev_resources") var request = URLRequest(url: url) request.httpMethod = "PUT" request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = try YYJSONEncoder().encode(body) + request.httpBody = try YYJSONEncoder().encode(PutDevResourcesWrapper(devResources: [body])) return request } } + +struct PutDevResourcesWrapper: Encodable { + let devResources: [PutDevResourceBody] + + private enum CodingKeys: String, CodingKey { + case devResources = "dev_resources" + } +} + +struct LinksUpdatedResponse: Decodable { + let linksUpdated: [DevResource] + + private enum CodingKeys: String, CodingKey { + case linksUpdated = "links_updated" + } +} diff --git a/Sources/FigmaAPI/Model/ComponentSet.swift b/Sources/FigmaAPI/Model/ComponentSet.swift index 58b0b0d..4cef3f6 100644 --- a/Sources/FigmaAPI/Model/ComponentSet.swift +++ b/Sources/FigmaAPI/Model/ComponentSet.swift @@ -1,4 +1,4 @@ -public struct ComponentSet: Codable, Sendable { +public struct ComponentSet: Decodable, Sendable { public let key: String public let nodeId: String public let name: String diff --git a/Sources/FigmaAPI/Model/DevResource.swift b/Sources/FigmaAPI/Model/DevResource.swift index 2bfccc4..d7bd51a 100644 --- a/Sources/FigmaAPI/Model/DevResource.swift +++ b/Sources/FigmaAPI/Model/DevResource.swift @@ -1,4 +1,4 @@ -public struct DevResource: Codable, Sendable { +public struct DevResource: Decodable, Sendable { public let id: String public let name: String public let url: String diff --git a/Sources/FigmaAPI/Model/FileVersion.swift b/Sources/FigmaAPI/Model/FileVersion.swift index c18a479..af5b63e 100644 --- a/Sources/FigmaAPI/Model/FileVersion.swift +++ b/Sources/FigmaAPI/Model/FileVersion.swift @@ -1,4 +1,4 @@ -public struct FileVersion: Codable, Sendable { +public struct FileVersion: Decodable, Sendable { public let id: String public let createdAt: String public let label: String? diff --git a/Sources/FigmaAPI/Model/PostDevResourceBody.swift b/Sources/FigmaAPI/Model/PostDevResourceBody.swift index 6303268..5c68fe7 100644 --- a/Sources/FigmaAPI/Model/PostDevResourceBody.swift +++ b/Sources/FigmaAPI/Model/PostDevResourceBody.swift @@ -1,18 +1,21 @@ public struct PostDevResourceBody: Encodable, Sendable { public let name: String public let url: String + public let fileKey: String public let nodeId: String public let devStatus: String? - public init(name: String, url: String, nodeId: String, devStatus: String? = nil) { + public init(name: String, url: String, fileKey: String, nodeId: String, devStatus: String? = nil) { self.name = name self.url = url + self.fileKey = fileKey self.nodeId = nodeId self.devStatus = devStatus } private enum CodingKeys: String, CodingKey { case name, url + case fileKey = "file_key" case nodeId = "node_id" case devStatus = "dev_status" } diff --git a/Sources/FigmaAPI/Model/PutDevResourceBody.swift b/Sources/FigmaAPI/Model/PutDevResourceBody.swift index c353252..d152f7a 100644 --- a/Sources/FigmaAPI/Model/PutDevResourceBody.swift +++ b/Sources/FigmaAPI/Model/PutDevResourceBody.swift @@ -2,17 +2,10 @@ public struct PutDevResourceBody: Encodable, Sendable { public let id: String public let name: String? public let url: String? - public let devStatus: String? - public init(id: String, name: String? = nil, url: String? = nil, devStatus: String? = nil) { + public init(id: String, name: String? = nil, url: String? = nil) { self.id = id self.name = name self.url = url - self.devStatus = devStatus - } - - private enum CodingKeys: String, CodingKey { - case id, name, url - case devStatus = "dev_status" } } diff --git a/Sources/FigmaAPI/Model/User.swift b/Sources/FigmaAPI/Model/User.swift index 70d5329..7113d84 100644 --- a/Sources/FigmaAPI/Model/User.swift +++ b/Sources/FigmaAPI/Model/User.swift @@ -1,4 +1,4 @@ -public struct User: Codable, Sendable { +public struct User: Decodable, Sendable { public let id: String public let handle: String public let email: String? diff --git a/Sources/FigmaAPI/Model/Webhook.swift b/Sources/FigmaAPI/Model/Webhook.swift index fa5d8f4..ecbc57b 100644 --- a/Sources/FigmaAPI/Model/Webhook.swift +++ b/Sources/FigmaAPI/Model/Webhook.swift @@ -1,4 +1,4 @@ -public struct Webhook: Codable, Sendable { +public struct Webhook: Decodable, Sendable { public let id: String public let teamId: String public let eventType: String diff --git a/Tests/FigmaAPITests/DeleteCommentEndpointTests.swift b/Tests/FigmaAPITests/DeleteCommentEndpointTests.swift index 45c7fcf..c0e0625 100644 --- a/Tests/FigmaAPITests/DeleteCommentEndpointTests.swift +++ b/Tests/FigmaAPITests/DeleteCommentEndpointTests.swift @@ -27,12 +27,19 @@ final class DeleteCommentEndpointTests: XCTestCase { // MARK: - Response Parsing - func testContentParsesEmptyResponse() throws { + func testContentParsesEmptyBody() throws { + let data = Data() + let endpoint = DeleteCommentEndpoint(fileId: "test", commentId: "c1") + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } + + func testContentParsesEmptyJSONBody() throws { let data = Data("{}".utf8) let endpoint = DeleteCommentEndpoint(fileId: "test", commentId: "c1") let response = try endpoint.content(from: nil, with: data) - // EmptyResponse just needs to decode without error XCTAssertNotNil(response) } } diff --git a/Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift b/Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift index 6f2b253..154a54f 100644 --- a/Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift +++ b/Tests/FigmaAPITests/DeleteDevResourceEndpointTests.swift @@ -27,7 +27,15 @@ final class DeleteDevResourceEndpointTests: XCTestCase { // MARK: - Response Parsing - func testContentParsesEmptyResponse() throws { + func testContentParsesEmptyBody() throws { + let data = Data() + let endpoint = DeleteDevResourceEndpoint(fileId: "test", resourceId: "res1") + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } + + func testContentParsesEmptyJSONBody() throws { let data = Data("{}".utf8) let endpoint = DeleteDevResourceEndpoint(fileId: "test", resourceId: "res1") let response = try endpoint.content(from: nil, with: data) diff --git a/Tests/FigmaAPITests/DeleteReactionEndpointTests.swift b/Tests/FigmaAPITests/DeleteReactionEndpointTests.swift index ea56015..1623df8 100644 --- a/Tests/FigmaAPITests/DeleteReactionEndpointTests.swift +++ b/Tests/FigmaAPITests/DeleteReactionEndpointTests.swift @@ -38,7 +38,15 @@ final class DeleteReactionEndpointTests: XCTestCase { // MARK: - Response Parsing - func testContentParsesEmptyResponse() throws { + func testContentParsesEmptyBody() throws { + let data = Data() + let endpoint = DeleteReactionEndpoint(fileId: "test", commentId: "c1", emoji: "👍") + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } + + func testContentParsesEmptyJSONBody() throws { let data = Data("{}".utf8) let endpoint = DeleteReactionEndpoint(fileId: "test", commentId: "c1", emoji: "👍") let response = try endpoint.content(from: nil, with: data) diff --git a/Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift b/Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift index 69b211c..09ab2b0 100644 --- a/Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift +++ b/Tests/FigmaAPITests/DeleteWebhookEndpointTests.swift @@ -27,7 +27,15 @@ final class DeleteWebhookEndpointTests: XCTestCase { // MARK: - Response Parsing - func testContentParsesEmptyResponse() throws { + func testContentParsesEmptyBody() throws { + let data = Data() + let endpoint = DeleteWebhookEndpoint(webhookId: "wh1") + let response = try endpoint.content(from: nil, with: data) + + XCTAssertNotNil(response) + } + + func testContentParsesEmptyJSONBody() throws { let data = Data("{}".utf8) let endpoint = DeleteWebhookEndpoint(webhookId: "wh1") let response = try endpoint.content(from: nil, with: data) diff --git a/Tests/FigmaAPITests/Fixtures/PaymentsResponse.json b/Tests/FigmaAPITests/Fixtures/PaymentsResponse.json index 91579e3..bb1f54a 100644 --- a/Tests/FigmaAPITests/Fixtures/PaymentsResponse.json +++ b/Tests/FigmaAPITests/Fixtures/PaymentsResponse.json @@ -1,13 +1,17 @@ { "status": 200, - "users": [ - { - "user_id": "user1", - "status": "active" - }, - { - "user_id": "user2", - "status": "inactive" - } - ] + "error": false, + "meta": { + "status": 200, + "users": [ + { + "user_id": "user1", + "status": "active" + }, + { + "user_id": "user2", + "status": "inactive" + } + ] + } } diff --git a/Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json b/Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json index 71ad8bd..dc5f2de 100644 --- a/Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json +++ b/Tests/FigmaAPITests/Fixtures/PostDevResourceResponse.json @@ -1,7 +1,11 @@ { - "id": "res3", - "name": "New Resource", - "url": "https://example.com/resource", - "node_id": "15:1", - "dev_status": "in_progress" + "links_created": [ + { + "id": "res3", + "name": "New Resource", + "url": "https://example.com/resource", + "node_id": "15:1", + "dev_status": "in_progress" + } + ] } diff --git a/Tests/FigmaAPITests/Fixtures/PostReactionResponse.json b/Tests/FigmaAPITests/Fixtures/PostReactionResponse.json new file mode 100644 index 0000000..e732d22 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/PostReactionResponse.json @@ -0,0 +1,10 @@ +{ + "emoji": "👍", + "user": { + "id": "user1", + "handle": "Designer", + "email": "designer@example.com", + "img_url": "https://example.com/avatar1.png" + }, + "created_at": "2024-01-15T12:00:00Z" +} diff --git a/Tests/FigmaAPITests/Fixtures/PutDevResourceResponse.json b/Tests/FigmaAPITests/Fixtures/PutDevResourceResponse.json new file mode 100644 index 0000000..c929806 --- /dev/null +++ b/Tests/FigmaAPITests/Fixtures/PutDevResourceResponse.json @@ -0,0 +1,11 @@ +{ + "links_updated": [ + { + "id": "res1", + "name": "Updated Name", + "url": "https://new.example.com", + "node_id": "10:1", + "dev_status": "completed" + } + ] +} diff --git a/Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift b/Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift index 2c127fc..f19da7b 100644 --- a/Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift +++ b/Tests/FigmaAPITests/GetActivityLogsEndpointTests.swift @@ -9,7 +9,7 @@ final class GetActivityLogsEndpointTests: XCTestCase { let endpoint = GetActivityLogsEndpoint() let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) - let request = endpoint.makeRequest(baseURL: baseURL) + let request = try endpoint.makeRequest(baseURL: baseURL) XCTAssertEqual( request.url?.absoluteString, @@ -17,6 +17,27 @@ final class GetActivityLogsEndpointTests: XCTestCase { ) } + func testMakeRequestWithFilters() throws { + let endpoint = GetActivityLogsEndpoint( + events: "FILE_OPEN,FILE_DELETE", + startTime: 1704067200.0, + endTime: 1706745599.0, + limit: 50, + order: "desc" + ) + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let components = try XCTUnwrap(URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)) + let queryItems = try XCTUnwrap(components.queryItems) + XCTAssertEqual(queryItems.first(where: { $0.name == "events" })?.value, "FILE_OPEN,FILE_DELETE") + XCTAssertNotNil(queryItems.first(where: { $0.name == "start_time" })) + XCTAssertNotNil(queryItems.first(where: { $0.name == "end_time" })) + XCTAssertEqual(queryItems.first(where: { $0.name == "limit" })?.value, "50") + XCTAssertEqual(queryItems.first(where: { $0.name == "order" })?.value, "desc") + } + // MARK: - Response Parsing func testContentParsesActivityLogsResponse() throws { diff --git a/Tests/FigmaAPITests/GetFileMetaEndpointTests.swift b/Tests/FigmaAPITests/GetFileMetaEndpointTests.swift index aa7cf92..024730e 100644 --- a/Tests/FigmaAPITests/GetFileMetaEndpointTests.swift +++ b/Tests/FigmaAPITests/GetFileMetaEndpointTests.swift @@ -2,6 +2,10 @@ import CustomDump @testable import FigmaAPI import XCTest +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + final class GetFileMetaEndpointTests: XCTestCase { // MARK: - URL Construction @@ -28,6 +32,7 @@ final class GetFileMetaEndpointTests: XCTestCase { // MARK: - Response Parsing + #if !canImport(FoundationNetworking) func testContentExtractsHeaderValues() throws { let endpoint = GetFileMetaEndpoint(fileId: "abc123") let url = try XCTUnwrap(URL(string: "https://api.figma.com/v1/files/abc123")) @@ -53,4 +58,5 @@ final class GetFileMetaEndpointTests: XCTestCase { XCTAssertThrowsError(try endpoint.content(from: response, with: Data())) } + #endif } diff --git a/Tests/FigmaAPITests/GetPaymentsEndpointTests.swift b/Tests/FigmaAPITests/GetPaymentsEndpointTests.swift index 95b27da..7f57c8a 100644 --- a/Tests/FigmaAPITests/GetPaymentsEndpointTests.swift +++ b/Tests/FigmaAPITests/GetPaymentsEndpointTests.swift @@ -6,15 +6,35 @@ final class GetPaymentsEndpointTests: XCTestCase { // MARK: - URL Construction func testMakeRequestConstructsCorrectURL() throws { - let endpoint = GetPaymentsEndpoint() + let endpoint = GetPaymentsEndpoint(pluginId: "plugin123") let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) - let request = endpoint.makeRequest(baseURL: baseURL) + let request = try endpoint.makeRequest(baseURL: baseURL) - XCTAssertEqual( - request.url?.absoluteString, - "https://api.figma.com/v1/payments" - ) + let components = try XCTUnwrap(URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)) + XCTAssertEqual(components.path, "/v1/payments") + XCTAssertEqual(components.queryItems?.first(where: { $0.name == "plugin_id" })?.value, "plugin123") + } + + func testMakeRequestWithTokenAuth() throws { + let endpoint = GetPaymentsEndpoint(pluginPaymentToken: "token123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let components = try XCTUnwrap(URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)) + XCTAssertEqual(components.queryItems?.first(where: { $0.name == "plugin_payment_token" })?.value, "token123") + } + + func testMakeRequestWithUserAndWidget() throws { + let endpoint = GetPaymentsEndpoint(userId: "user1", widgetId: "widget1") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let components = try XCTUnwrap(URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)) + XCTAssertEqual(components.queryItems?.first(where: { $0.name == "user_id" })?.value, "user1") + XCTAssertEqual(components.queryItems?.first(where: { $0.name == "widget_id" })?.value, "widget1") } // MARK: - Response Parsing @@ -22,7 +42,7 @@ final class GetPaymentsEndpointTests: XCTestCase { func testContentParsesPaymentsResponse() throws { let data = try FixtureLoader.loadData("PaymentsResponse") - let endpoint = GetPaymentsEndpoint() + let endpoint = GetPaymentsEndpoint(pluginId: "plugin123") let paymentInfo = try endpoint.content(from: nil, with: data) XCTAssertEqual(paymentInfo.status, 200) @@ -37,7 +57,7 @@ final class GetPaymentsEndpointTests: XCTestCase { func testContentThrowsOnInvalidJSON() { let invalidData = Data("invalid".utf8) - let endpoint = GetPaymentsEndpoint() + let endpoint = GetPaymentsEndpoint(pluginId: "plugin123") XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) } diff --git a/Tests/FigmaAPITests/GetWebhooksEndpointTests.swift b/Tests/FigmaAPITests/GetWebhooksEndpointTests.swift index d9afbf8..d89e7b3 100644 --- a/Tests/FigmaAPITests/GetWebhooksEndpointTests.swift +++ b/Tests/FigmaAPITests/GetWebhooksEndpointTests.swift @@ -8,7 +8,7 @@ final class GetWebhooksEndpointTests: XCTestCase { let endpoint = GetWebhooksEndpoint() let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) - let request = endpoint.makeRequest(baseURL: baseURL) + let request = try endpoint.makeRequest(baseURL: baseURL) XCTAssertEqual( request.url?.absoluteString, @@ -16,6 +16,18 @@ final class GetWebhooksEndpointTests: XCTestCase { ) } + func testMakeRequestWithContextFilter() throws { + let endpoint = GetWebhooksEndpoint(context: "team", contextId: "team123") + let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + + let request = try endpoint.makeRequest(baseURL: baseURL) + + let components = try XCTUnwrap(URLComponents(url: try XCTUnwrap(request.url), resolvingAgainstBaseURL: false)) + XCTAssertEqual(components.path, "/v2/webhooks") + XCTAssertEqual(components.queryItems?.first(where: { $0.name == "context" })?.value, "team") + XCTAssertEqual(components.queryItems?.first(where: { $0.name == "context_id" })?.value, "team123") + } + // MARK: - Response Parsing func testContentParsesResponse() throws { @@ -55,4 +67,22 @@ final class GetWebhooksEndpointTests: XCTestCase { XCTAssertEqual(second.status, "PAUSED") XCTAssertNil(second.description) } + + func testContentFromResponseWithBody() throws { + let data = try FixtureLoader.loadData("WebhooksResponse") + + let endpoint = GetWebhooksEndpoint() + let webhooks = try endpoint.content(from: nil, with: data) + + XCTAssertEqual(webhooks.count, 2) + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let endpoint = GetWebhooksEndpoint() + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } } diff --git a/Tests/FigmaAPITests/PostDevResourceEndpointTests.swift b/Tests/FigmaAPITests/PostDevResourceEndpointTests.swift index 56a2d2e..706660f 100644 --- a/Tests/FigmaAPITests/PostDevResourceEndpointTests.swift +++ b/Tests/FigmaAPITests/PostDevResourceEndpointTests.swift @@ -5,21 +5,21 @@ final class PostDevResourceEndpointTests: XCTestCase { // MARK: - URL Construction func testMakeRequestConstructsCorrectURL() throws { - let body = PostDevResourceBody(name: "Test", url: "https://example.com", nodeId: "10:1") - let endpoint = PostDevResourceEndpoint(fileId: "abc123", body: body) + let body = PostDevResourceBody(name: "Test", url: "https://example.com", fileKey: "abc123", nodeId: "10:1") + let endpoint = PostDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) XCTAssertEqual( request.url?.absoluteString, - "https://api.figma.com/v1/files/abc123/dev_resources" + "https://api.figma.com/v1/dev_resources" ) } func testMakeRequestUsesPOSTMethod() throws { - let body = PostDevResourceBody(name: "Test", url: "https://example.com", nodeId: "10:1") - let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) + let body = PostDevResourceBody(name: "Test", url: "https://example.com", fileKey: "abc123", nodeId: "10:1") + let endpoint = PostDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -28,8 +28,8 @@ final class PostDevResourceEndpointTests: XCTestCase { } func testMakeRequestSetsContentTypeHeader() throws { - let body = PostDevResourceBody(name: "Test", url: "https://example.com", nodeId: "10:1") - let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) + let body = PostDevResourceBody(name: "Test", url: "https://example.com", fileKey: "abc123", nodeId: "10:1") + let endpoint = PostDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -37,19 +37,22 @@ final class PostDevResourceEndpointTests: XCTestCase { XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") } - func testMakeRequestIncludesBody() throws { - let body = PostDevResourceBody(name: "Storybook", url: "https://storybook.example.com", nodeId: "10:1", devStatus: "ready_for_dev") - let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) + func testMakeRequestWrapsBodyInDevResourcesArray() throws { + let body = PostDevResourceBody(name: "Storybook", url: "https://storybook.example.com", fileKey: "file1", nodeId: "10:1", devStatus: "ready_for_dev") + let endpoint = PostDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) let httpBody = try XCTUnwrap(request.httpBody) let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) - XCTAssertEqual(json["name"] as? String, "Storybook") - XCTAssertEqual(json["url"] as? String, "https://storybook.example.com") - XCTAssertEqual(json["node_id"] as? String, "10:1") - XCTAssertEqual(json["dev_status"] as? String, "ready_for_dev") + let devResources = try XCTUnwrap(json["dev_resources"] as? [[String: Any]]) + XCTAssertEqual(devResources.count, 1) + XCTAssertEqual(devResources[0]["name"] as? String, "Storybook") + XCTAssertEqual(devResources[0]["url"] as? String, "https://storybook.example.com") + XCTAssertEqual(devResources[0]["file_key"] as? String, "file1") + XCTAssertEqual(devResources[0]["node_id"] as? String, "10:1") + XCTAssertEqual(devResources[0]["dev_status"] as? String, "ready_for_dev") } // MARK: - Response Parsing @@ -57,12 +60,23 @@ final class PostDevResourceEndpointTests: XCTestCase { func testContentParsesResponse() throws { let data = try FixtureLoader.loadData("PostDevResourceResponse") - let body = PostDevResourceBody(name: "New Resource", url: "https://example.com/resource", nodeId: "15:1") - let endpoint = PostDevResourceEndpoint(fileId: "test", body: body) - let resource = try endpoint.content(from: nil, with: data) + let body = PostDevResourceBody(name: "New Resource", url: "https://example.com/resource", fileKey: "file1", nodeId: "15:1") + let endpoint = PostDevResourceEndpoint(body: body) + let resources = try endpoint.content(from: nil, with: data) - XCTAssertEqual(resource.id, "res3") - XCTAssertEqual(resource.name, "New Resource") - XCTAssertEqual(resource.nodeId, "15:1") + XCTAssertEqual(resources.count, 1) + XCTAssertEqual(resources[0].id, "res3") + XCTAssertEqual(resources[0].name, "New Resource") + XCTAssertEqual(resources[0].nodeId, "15:1") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let body = PostDevResourceBody(name: "Test", url: "https://example.com", fileKey: "file1", nodeId: "10:1") + let endpoint = PostDevResourceEndpoint(body: body) + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) } } diff --git a/Tests/FigmaAPITests/PostReactionEndpointTests.swift b/Tests/FigmaAPITests/PostReactionEndpointTests.swift index 14fe601..99d0c85 100644 --- a/Tests/FigmaAPITests/PostReactionEndpointTests.swift +++ b/Tests/FigmaAPITests/PostReactionEndpointTests.swift @@ -52,11 +52,21 @@ final class PostReactionEndpointTests: XCTestCase { // MARK: - Response Parsing func testContentParsesEmptyResponse() throws { - let data = Data("{}".utf8) + let data = Data("{\"status\": 200, \"error\": false}".utf8) let body = PostReactionBody(emoji: "👍") let endpoint = PostReactionEndpoint(fileId: "test", commentId: "c1", body: body) let response = try endpoint.content(from: nil, with: data) XCTAssertNotNil(response) } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let body = PostReactionBody(emoji: "👍") + let endpoint = PostReactionEndpoint(fileId: "test", commentId: "c1", body: body) + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) + } } diff --git a/Tests/FigmaAPITests/PutDevResourceEndpointTests.swift b/Tests/FigmaAPITests/PutDevResourceEndpointTests.swift index 021f34f..c445c37 100644 --- a/Tests/FigmaAPITests/PutDevResourceEndpointTests.swift +++ b/Tests/FigmaAPITests/PutDevResourceEndpointTests.swift @@ -6,20 +6,20 @@ final class PutDevResourceEndpointTests: XCTestCase { func testMakeRequestConstructsCorrectURL() throws { let body = PutDevResourceBody(id: "res1", name: "Updated") - let endpoint = PutDevResourceEndpoint(fileId: "abc123", body: body) + let endpoint = PutDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) XCTAssertEqual( request.url?.absoluteString, - "https://api.figma.com/v1/files/abc123/dev_resources" + "https://api.figma.com/v1/dev_resources" ) } func testMakeRequestUsesPUTMethod() throws { let body = PutDevResourceBody(id: "res1", name: "Updated") - let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) + let endpoint = PutDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -29,7 +29,7 @@ final class PutDevResourceEndpointTests: XCTestCase { func testMakeRequestSetsContentTypeHeader() throws { let body = PutDevResourceBody(id: "res1", name: "Updated") - let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) + let endpoint = PutDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -37,31 +37,43 @@ final class PutDevResourceEndpointTests: XCTestCase { XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json") } - func testMakeRequestIncludesBody() throws { - let body = PutDevResourceBody(id: "res1", name: "Updated Name", url: "https://new.example.com", devStatus: "completed") - let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) + func testMakeRequestWrapsBodyInDevResourcesArray() throws { + let body = PutDevResourceBody(id: "res1", name: "Updated Name", url: "https://new.example.com") + let endpoint = PutDevResourceEndpoint(body: body) let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) let request = try endpoint.makeRequest(baseURL: baseURL) let httpBody = try XCTUnwrap(request.httpBody) let json = try XCTUnwrap(JSONSerialization.jsonObject(with: httpBody) as? [String: Any]) - XCTAssertEqual(json["id"] as? String, "res1") - XCTAssertEqual(json["name"] as? String, "Updated Name") - XCTAssertEqual(json["url"] as? String, "https://new.example.com") - XCTAssertEqual(json["dev_status"] as? String, "completed") + let devResources = try XCTUnwrap(json["dev_resources"] as? [[String: Any]]) + XCTAssertEqual(devResources.count, 1) + XCTAssertEqual(devResources[0]["id"] as? String, "res1") + XCTAssertEqual(devResources[0]["name"] as? String, "Updated Name") + XCTAssertEqual(devResources[0]["url"] as? String, "https://new.example.com") } // MARK: - Response Parsing func testContentParsesResponse() throws { - let data = try FixtureLoader.loadData("PostDevResourceResponse") + let data = try FixtureLoader.loadData("PutDevResourceResponse") - let body = PutDevResourceBody(id: "res3", name: "New Resource") - let endpoint = PutDevResourceEndpoint(fileId: "test", body: body) - let resource = try endpoint.content(from: nil, with: data) + let body = PutDevResourceBody(id: "res1", name: "Updated Name") + let endpoint = PutDevResourceEndpoint(body: body) + let resources = try endpoint.content(from: nil, with: data) - XCTAssertEqual(resource.id, "res3") - XCTAssertEqual(resource.name, "New Resource") + XCTAssertEqual(resources.count, 1) + XCTAssertEqual(resources[0].id, "res1") + XCTAssertEqual(resources[0].name, "Updated Name") + } + + // MARK: - Error Handling + + func testContentThrowsOnInvalidJSON() { + let invalidData = Data("invalid".utf8) + let body = PutDevResourceBody(id: "res1", name: "Test") + let endpoint = PutDevResourceEndpoint(body: body) + + XCTAssertThrowsError(try endpoint.content(from: nil, with: invalidData)) } } From e19332ab43537a8cc1bfd1e507cd2bf80978abf2 Mon Sep 17 00:00:00 2001 From: alexey1312 Date: Mon, 16 Mar 2026 21:38:19 +0500 Subject: [PATCH 3/3] fix: expose FigmaClient.baseURL as public constant Prevents downstream clients (e.g. ExFig) from hardcoding the base URL in their mock clients, which would silently break when the URL format changes (as it did when moving version prefix into individual endpoints). --- Sources/FigmaAPI/FigmaClient.swift | 4 ++-- Tests/FigmaAPITests/EndpointMakeRequestTests.swift | 9 +++------ Tests/FigmaAPITests/Mocks/MockClient.swift | 3 +-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Sources/FigmaAPI/FigmaClient.swift b/Sources/FigmaAPI/FigmaClient.swift index 4f813b2..d072b67 100644 --- a/Sources/FigmaAPI/FigmaClient.swift +++ b/Sources/FigmaAPI/FigmaClient.swift @@ -5,12 +5,12 @@ import Foundation public final class FigmaClient: BaseClient, @unchecked Sendable { // swiftlint:disable:next force_unwrapping - private static let figmaBaseURL = URL(string: "https://api.figma.com/")! + public static let baseURL = URL(string: "https://api.figma.com/")! public init(accessToken: String, timeout: TimeInterval?) { let config = URLSessionConfiguration.ephemeral config.httpAdditionalHeaders = ["X-Figma-Token": accessToken] config.timeoutIntervalForRequest = timeout ?? 30 - super.init(baseURL: Self.figmaBaseURL, config: config) + super.init(baseURL: Self.baseURL, config: config) } } diff --git a/Tests/FigmaAPITests/EndpointMakeRequestTests.swift b/Tests/FigmaAPITests/EndpointMakeRequestTests.swift index 2ce9058..d9c672f 100644 --- a/Tests/FigmaAPITests/EndpointMakeRequestTests.swift +++ b/Tests/FigmaAPITests/EndpointMakeRequestTests.swift @@ -11,8 +11,7 @@ final class EndpointMakeRequestTests: XCTestCase { // MARK: - Valid Base URL (happy path) func testImageEndpointMakeRequestSucceeds() throws { - // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + let baseURL = FigmaClient.baseURL let endpoint = ImageEndpoint(fileId: "abc123", nodeIds: ["1:2"], params: SVGParams()) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -22,8 +21,7 @@ final class EndpointMakeRequestTests: XCTestCase { } func testNodesEndpointMakeRequestSucceeds() throws { - // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + let baseURL = FigmaClient.baseURL let endpoint = NodesEndpoint(fileId: "abc123", nodeIds: ["1:2", "3:4"]) let request = try endpoint.makeRequest(baseURL: baseURL) @@ -33,8 +31,7 @@ final class EndpointMakeRequestTests: XCTestCase { } func testFileMetadataEndpointMakeRequestSucceeds() throws { - // swiftlint:disable:next force_unwrapping - let baseURL = try XCTUnwrap(URL(string: "https://api.figma.com/")) + let baseURL = FigmaClient.baseURL let endpoint = FileMetadataEndpoint(fileId: "abc123") let request = try endpoint.makeRequest(baseURL: baseURL) diff --git a/Tests/FigmaAPITests/Mocks/MockClient.swift b/Tests/FigmaAPITests/Mocks/MockClient.swift index b696efd..fb4d37f 100644 --- a/Tests/FigmaAPITests/Mocks/MockClient.swift +++ b/Tests/FigmaAPITests/Mocks/MockClient.swift @@ -57,8 +57,7 @@ public final class MockClient: Client, @unchecked Sendable { public func request(_ endpoint: T) async throws -> T.Content { let key = String(describing: type(of: endpoint)) - // swiftlint:disable:next force_unwrapping - let baseURL = URL(string: "https://api.figma.com/")! + let baseURL = FigmaClient.baseURL let request = try endpoint.makeRequest(baseURL: baseURL) // Record timestamp and get delay (thread-safe)