From 0cfbd7ed4e723cc4945f17028ad9cb0f779860ac Mon Sep 17 00:00:00 2001 From: Adam Young Date: Wed, 24 Jan 2024 21:50:00 +0000 Subject: [PATCH] FEATURE: Request authentication token (#146) * Add Token model * CreateRequetToken endpoint * Add requestToken to AuthenticationService * FEATURE: Request authentication token --- .../AuthenticationService.swift | 71 ---------- Sources/TMDb/Extensions/URL+TMDb.swift | 4 + .../TMDb/Helpers/AuthenticateURLBuilder.swift | 46 +++++++ Sources/TMDb/Models/Token.swift | 56 ++++++++ .../HTTPClient.swift | 0 .../APIClient => Services}/APIClient.swift | 0 .../AuthenticateURLBuilding.swift | 28 ++++ .../AuthenticationEndpoint.swift | 6 + .../AuthenticationService.swift | 116 +++++++++++++++++ .../Certifications/CertificationService.swift | 0 .../CertificationsEndpoint.swift | 0 .../Company}/CompanyEndpoint.swift | 0 .../Company/CompanyService.swift | 0 .../ConfigurationEndpoint.swift | 0 .../Configuration/ConfigurationService.swift | 0 .../Discover}/DiscoverEndpoint.swift | 0 .../Discover/DiscoverService.swift | 0 .../{ => Services}/Genres/GenreService.swift | 0 .../Genres}/GenresEndpoint.swift | 0 .../TMDb/{ => Services}/LocaleProviding.swift | 0 .../{ => Services}/Movies/MovieService.swift | 1 + .../Movies}/MoviesEndpoint.swift | 0 .../People}/PeopleEndpoint.swift | 0 .../{ => Services}/People/PersonService.swift | 0 .../Search}/SearchEndpoint.swift | 0 .../{ => Services}/Search/SearchService.swift | 0 .../TVEpisodes/TVEpisodeService.swift | 0 .../TVEpisodes}/TVEpisodesEndpoint.swift | 0 .../TVSeasons/TVSeasonService.swift | 0 .../TVSeasons}/TVSeasonsEndpoint.swift | 0 .../TVSeries}/TVSeriesEndpoint.swift | 0 .../TVSeries/TVSeriesService.swift | 0 .../Trending}/TrendingEndpoint.swift | 0 .../Trending/TrendingService.swift | 0 .../TrendingTimeWindowFilterType.swift | 0 .../WatchProviderEndpoint.swift | 0 .../WatchProviders/WatchProviderService.swift | 0 .../Extensions/AuthenticationService.md | 4 +- Sources/TMDb/TMDbFactory.swift | 34 ++++- .../AuthenticationIntegrationTests.swift | 11 +- .../AuthenticationServiceTests.swift | 66 ---------- .../Helpers/AuthenticateURLBuilderTests.swift | 64 +++++++++ .../AuthenticateURLMockBuilder.swift | 40 ++++++ .../{ => Helpers}/LocaleMockProvider.swift | 0 .../Mocks/Models/GuestSession+Mocks.swift | 2 +- .../TMDbTests/Mocks/Models/Token+Mocks.swift | 37 ++++++ .../APIClient}/MockAPIClient.swift | 0 .../HTTPClient}/HTTPMockClient.swift | 0 .../HTTPClient}/MockURLProtocol.swift | 0 .../Mocks/{ => Networking}/URL+Mocks.swift | 0 Tests/TMDbTests/Models/TokenTests.swift | 39 ++++++ .../Resources/json/request-token.json | 5 + .../AuthenticationServiceTests.swift | 122 ++++++++++++++++++ .../AuthenticationEndpointTests.swift | 8 ++ .../CertificationServiceTests.swift | 0 .../CertificationsEndpointTests.swift | 0 .../Company/CompanyServiceTests.swift | 0 .../Endpoints/CompanyEndpointTests.swift | 0 .../ConfigurationServiceTests.swift | 0 .../ConfigurationEndpointTests.swift | 0 .../Discover/DiscoverServiceTests.swift | 0 .../Endpoints/DiscoverEndpointTests.swift | 0 .../Discover/MovieSortTests.swift | 0 .../Discover/TVSeriesSortTests.swift | 0 .../Endpoints/GenresEndpointTests.swift | 0 .../Genres/GenreServiceTests.swift | 0 .../Endpoints/MoviesEndpointTests.swift | 0 .../Movies/MovieServiceTests.swift | 0 .../Endpoints/PeopleEndpointTests.swift | 0 .../People/PersonServiceTests.swift | 0 .../Endpoints/SearchEndpointTests.swift | 0 .../Search/SearchServiceTests.swift | 0 .../Endpoints}/TVEpisodesEndpointTests.swift | 0 .../TVEpisodes/TVEpisodeServiceTests.swift | 0 .../Endpoints/TVSeasonsEndpointTests.swift | 0 .../TVSeasons/TVSeasonServiceTests.swift | 0 .../Endpoints/TVSeriesEndpointTests.swift | 0 .../TVSeries/TVSeriesServiceTests.swift | 0 .../Endpoints/TrendingEndpointTests.swift | 0 .../Trending/TrendingServiceTests.swift | 0 .../TrendingTimeWindowFilterTypeTests.swift | 0 .../WatchProviderEndpointTests.swift | 0 .../WatchProviderServiceTests.swift | 0 .../Data+LoadFromFile.swift | 0 .../Date+RandomDate.swift | 0 .../Int+RandomID.swift | 0 .../JSONDecoder+DecodeFromFile.swift | 0 .../JSONEncoder+TMDb.swift | 0 .../String+RandomID.swift | 0 89 files changed, 614 insertions(+), 146 deletions(-) delete mode 100644 Sources/TMDb/Authentication/AuthenticationService.swift create mode 100644 Sources/TMDb/Helpers/AuthenticateURLBuilder.swift create mode 100644 Sources/TMDb/Models/Token.swift rename Sources/TMDb/Networking/{HTTPClient => APIClient}/HTTPClient.swift (100%) rename Sources/TMDb/{Networking/APIClient => Services}/APIClient.swift (100%) create mode 100644 Sources/TMDb/Services/Authentication/AuthenticateURLBuilding.swift rename Sources/TMDb/{Authentication/Endpoints => Services/Authentication}/AuthenticationEndpoint.swift (85%) create mode 100644 Sources/TMDb/Services/Authentication/AuthenticationService.swift rename Sources/TMDb/{ => Services}/Certifications/CertificationService.swift (100%) rename Sources/TMDb/{Certifications/Endpoints => Services/Certifications}/CertificationsEndpoint.swift (100%) rename Sources/TMDb/{Company/Endpoints => Services/Company}/CompanyEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/Company/CompanyService.swift (100%) rename Sources/TMDb/{Configuration/Endpoint => Services/Configuration}/ConfigurationEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/Configuration/ConfigurationService.swift (100%) rename Sources/TMDb/{Discover/Endpoints => Services/Discover}/DiscoverEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/Discover/DiscoverService.swift (100%) rename Sources/TMDb/{ => Services}/Genres/GenreService.swift (100%) rename Sources/TMDb/{Genres/Endpoints => Services/Genres}/GenresEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/LocaleProviding.swift (100%) rename Sources/TMDb/{ => Services}/Movies/MovieService.swift (99%) rename Sources/TMDb/{Movies/Endpoints => Services/Movies}/MoviesEndpoint.swift (100%) rename Sources/TMDb/{People/Endpoints => Services/People}/PeopleEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/People/PersonService.swift (100%) rename Sources/TMDb/{Search/Endpoints => Services/Search}/SearchEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/Search/SearchService.swift (100%) rename Sources/TMDb/{ => Services}/TVEpisodes/TVEpisodeService.swift (100%) rename Sources/TMDb/{TVEpisodes/Endpoints => Services/TVEpisodes}/TVEpisodesEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/TVSeasons/TVSeasonService.swift (100%) rename Sources/TMDb/{TVSeasons/Endpoints => Services/TVSeasons}/TVSeasonsEndpoint.swift (100%) rename Sources/TMDb/{TVSeries/Endpoints => Services/TVSeries}/TVSeriesEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/TVSeries/TVSeriesService.swift (100%) rename Sources/TMDb/{Trending/Endpoints => Services/Trending}/TrendingEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/Trending/TrendingService.swift (100%) rename Sources/TMDb/{ => Services}/Trending/TrendingTimeWindowFilterType.swift (100%) rename Sources/TMDb/{WatchProviders/Endpoints => Services/WatchProviders}/WatchProviderEndpoint.swift (100%) rename Sources/TMDb/{ => Services}/WatchProviders/WatchProviderService.swift (100%) delete mode 100644 Tests/TMDbTests/Authentication/AuthenticationServiceTests.swift create mode 100644 Tests/TMDbTests/Helpers/AuthenticateURLBuilderTests.swift create mode 100644 Tests/TMDbTests/Mocks/Authentication/AuthenticateURLMockBuilder.swift rename Tests/TMDbTests/Mocks/{ => Helpers}/LocaleMockProvider.swift (100%) create mode 100644 Tests/TMDbTests/Mocks/Models/Token+Mocks.swift rename Tests/TMDbTests/Mocks/{Client => Networking/APIClient}/MockAPIClient.swift (100%) rename Tests/TMDbTests/{Networking/Mocks => Mocks/Networking/HTTPClient}/HTTPMockClient.swift (100%) rename Tests/TMDbTests/{Networking/Mocks => Mocks/Networking/HTTPClient}/MockURLProtocol.swift (100%) rename Tests/TMDbTests/Mocks/{ => Networking}/URL+Mocks.swift (100%) create mode 100644 Tests/TMDbTests/Models/TokenTests.swift create mode 100644 Tests/TMDbTests/Resources/json/request-token.json create mode 100644 Tests/TMDbTests/Services/Authentication/AuthenticationServiceTests.swift rename Tests/TMDbTests/{ => Services}/Authentication/Endpoints/AuthenticationEndpointTests.swift (79%) rename Tests/TMDbTests/{ => Services}/Certifications/CertificationServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Certifications/Endpoints/CertificationsEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Company/CompanyServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Company/Endpoints/CompanyEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Configuration/ConfigurationServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Configuration/Endpoints/ConfigurationEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Discover/DiscoverServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Discover/Endpoints/DiscoverEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Discover/MovieSortTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Discover/TVSeriesSortTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Genres/Endpoints/GenresEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Genres/GenreServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Movies/Endpoints/MoviesEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Movies/MovieServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/People/Endpoints/PeopleEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/People/PersonServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Search/Endpoints/SearchEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Search/SearchServiceTests.swift (100%) rename Tests/TMDbTests/{TVEpisodes/ENdpoints => Services/TVEpisodes/Endpoints}/TVEpisodesEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/TVEpisodes/TVEpisodeServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/TVSeasons/Endpoints/TVSeasonsEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/TVSeasons/TVSeasonServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/TVSeries/Endpoints/TVSeriesEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/TVSeries/TVSeriesServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Trending/Endpoints/TrendingEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Trending/TrendingServiceTests.swift (100%) rename Tests/TMDbTests/{ => Services}/Trending/TrendingTimeWindowFilterTypeTests.swift (100%) rename Tests/TMDbTests/{ => Services}/WatchProviders/Endpoints/WatchProviderEndpointTests.swift (100%) rename Tests/TMDbTests/{ => Services}/WatchProviders/WatchProviderServiceTests.swift (100%) rename Tests/TMDbTests/{Utilities => TestUtils}/Data+LoadFromFile.swift (100%) rename Tests/TMDbTests/{Utilities => TestUtils}/Date+RandomDate.swift (100%) rename Tests/TMDbTests/{Utilities => TestUtils}/Int+RandomID.swift (100%) rename Tests/TMDbTests/{Utilities => TestUtils}/JSONDecoder+DecodeFromFile.swift (100%) rename Tests/TMDbTests/{Utilities => TestUtils}/JSONEncoder+TMDb.swift (100%) rename Tests/TMDbTests/{Utilities => TestUtils}/String+RandomID.swift (100%) diff --git a/Sources/TMDb/Authentication/AuthenticationService.swift b/Sources/TMDb/Authentication/AuthenticationService.swift deleted file mode 100644 index b5192f90..00000000 --- a/Sources/TMDb/Authentication/AuthenticationService.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// AuthenticationService.swift -// TMDb -// -// Copyright © 2024 Adam Young. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an AS IS BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -/// -/// Provides an interface for authenticating with TMDb. -/// -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public final class AuthenticationService { - - private let apiClient: any APIClient - - /// - /// Creates an authentication service object. - /// - public convenience init() { - self.init( - apiClient: TMDbFactory.authAPIClient - ) - } - - init(apiClient: some APIClient) { - self.apiClient = apiClient - } - - /// - /// Creates a guest session with TMDb. - /// - /// Guest sessions are a special kind of session that give you some of the - /// functionality of an account, but not all. For example, some of the - /// things you can do with a guest session are; maintain a rated list, a - /// watchlist and a favourite list. - /// - /// Guest sessions will automatically be deleted if they are not used - /// within 60 minutes of it being issued. - /// - /// [TMDb API - Authentication: Create Guest Session](https://developer.themoviedb.org/reference/certifications-tv-list) - /// - /// - Throws: TMDb error ``TMDbError``. - /// - /// - Returns: A guest session. - /// - public func createGuestSession() async throws -> GuestSession { - let session: GuestSession - do { - session = try await apiClient.get(endpoint: AuthenticationEndpoint.createGuestSession) - } catch let error { - throw TMDbError(error: error) - } - - return session - } - -} diff --git a/Sources/TMDb/Extensions/URL+TMDb.swift b/Sources/TMDb/Extensions/URL+TMDb.swift index ad2e94eb..21c7daa3 100644 --- a/Sources/TMDb/Extensions/URL+TMDb.swift +++ b/Sources/TMDb/Extensions/URL+TMDb.swift @@ -25,4 +25,8 @@ extension URL { URL(string: "https://api.themoviedb.org/3")! } + static var tmdbWebSiteURL: URL { + URL(string: "https://www.themoviedb.org")! + } + } diff --git a/Sources/TMDb/Helpers/AuthenticateURLBuilder.swift b/Sources/TMDb/Helpers/AuthenticateURLBuilder.swift new file mode 100644 index 00000000..72f153d2 --- /dev/null +++ b/Sources/TMDb/Helpers/AuthenticateURLBuilder.swift @@ -0,0 +1,46 @@ +// +// AuthenticateURLBuilder.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +final class AuthenticateURLBuilder: AuthenticateURLBuilding { + + private let baseURL: URL + + init(baseURL: URL) { + self.baseURL = baseURL + } + + func authenticateURL(with requestToken: String) -> URL { + authenticateURL(with: requestToken, redirectURL: nil) + } + + func authenticateURL(with requestToken: String, redirectURL: URL?) -> URL { + var url = baseURL + .appendingPathComponent("authenticate") + .appendingPathComponent(requestToken) + + if let redirectURL { + url = url.appendingQueryItem(name: "redirect_to", value: redirectURL.absoluteString) + } + + return url + } + +} diff --git a/Sources/TMDb/Models/Token.swift b/Sources/TMDb/Models/Token.swift new file mode 100644 index 00000000..01ce5c14 --- /dev/null +++ b/Sources/TMDb/Models/Token.swift @@ -0,0 +1,56 @@ +// +// Token.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// +/// A model representing an internediate request token. +/// +public struct Token: Codable, Equatable, Hashable { + + /// + /// Was token creation successful. + /// + public let success: Bool + + /// + /// An intermediate request token. + /// + public let requestToken: String + + /// + /// Date of token expiry. + /// + public let expiresAt: Date + + /// + /// Creates an internediate request token. + /// + /// - Parameters: + /// - success: Was token creation successful. + /// - requestToken: An intermediate request token. + /// - expiresAt: Date of token expiry. + /// + public init(success: Bool, requestToken: String, expiresAt: Date) { + self.success = success + self.requestToken = requestToken + self.expiresAt = expiresAt + } + +} diff --git a/Sources/TMDb/Networking/HTTPClient/HTTPClient.swift b/Sources/TMDb/Networking/APIClient/HTTPClient.swift similarity index 100% rename from Sources/TMDb/Networking/HTTPClient/HTTPClient.swift rename to Sources/TMDb/Networking/APIClient/HTTPClient.swift diff --git a/Sources/TMDb/Networking/APIClient/APIClient.swift b/Sources/TMDb/Services/APIClient.swift similarity index 100% rename from Sources/TMDb/Networking/APIClient/APIClient.swift rename to Sources/TMDb/Services/APIClient.swift diff --git a/Sources/TMDb/Services/Authentication/AuthenticateURLBuilding.swift b/Sources/TMDb/Services/Authentication/AuthenticateURLBuilding.swift new file mode 100644 index 00000000..a05c8d46 --- /dev/null +++ b/Sources/TMDb/Services/Authentication/AuthenticateURLBuilding.swift @@ -0,0 +1,28 @@ +// +// AuthenticateURLBuilding.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol AuthenticateURLBuilding { + + func authenticateURL(with requestToken: String) -> URL + + func authenticateURL(with requestToken: String, redirectURL: URL?) -> URL + +} diff --git a/Sources/TMDb/Authentication/Endpoints/AuthenticationEndpoint.swift b/Sources/TMDb/Services/Authentication/AuthenticationEndpoint.swift similarity index 85% rename from Sources/TMDb/Authentication/Endpoints/AuthenticationEndpoint.swift rename to Sources/TMDb/Services/Authentication/AuthenticationEndpoint.swift index 5c7a318e..23c2a4dc 100644 --- a/Sources/TMDb/Authentication/Endpoints/AuthenticationEndpoint.swift +++ b/Sources/TMDb/Services/Authentication/AuthenticationEndpoint.swift @@ -22,6 +22,7 @@ import Foundation enum AuthenticationEndpoint { case createGuestSession + case createRequestToken } @@ -35,6 +36,11 @@ extension AuthenticationEndpoint: Endpoint { Self.basePath .appendingPathComponent("guest_session") .appendingPathComponent("new") + + case .createRequestToken: + Self.basePath + .appendingPathComponent("token") + .appendingPathComponent("new") } } diff --git a/Sources/TMDb/Services/Authentication/AuthenticationService.swift b/Sources/TMDb/Services/Authentication/AuthenticationService.swift new file mode 100644 index 00000000..03308b18 --- /dev/null +++ b/Sources/TMDb/Services/Authentication/AuthenticationService.swift @@ -0,0 +1,116 @@ +// +// AuthenticationService.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +/// +/// Provides an interface for authenticating and generating session IDs with TMDb. +/// +/// Details of generating session IDs for TMDb can be found at +/// [TMDb API - How do I generate a session ID?](https://developer.themoviedb.org/reference/authentication-how-do-i-generate-a-session-id) +/// +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) +public final class AuthenticationService { + + private let apiClient: any APIClient + private let authenticateURLBuilder: any AuthenticateURLBuilding + + /// + /// Creates an authentication service object. + /// + public convenience init() { + self.init( + apiClient: TMDbFactory.authAPIClient, + authenticateURLBuilder: TMDbFactory.authenticateURLBuilder + ) + } + + init( + apiClient: some APIClient, + authenticateURLBuilder: some AuthenticateURLBuilding + ) { + self.apiClient = apiClient + self.authenticateURLBuilder = authenticateURLBuilder + } + + /// + /// Creates a guest session with TMDb. + /// + /// Guest sessions are a special kind of session that give you some of the functionality of an account, but not + /// all. For example, some of the things you can do with a guest session are; maintain a rated list, a watchlist + /// and a favourite list. + /// + /// Guest sessions will automatically be deleted if they are not used within 60 minutes of it being issued. + /// + /// [TMDb API - Authentication: Create Guest Session](https://developer.themoviedb.org/reference/authentication-create-guest-session) + /// + /// - Throws: TMDb error ``TMDbError``. + /// + /// - Returns: A guest session. + /// + public func guestSession() async throws -> GuestSession { + let session: GuestSession + do { + session = try await apiClient.get(endpoint: AuthenticationEndpoint.createGuestSession) + } catch let error { + throw TMDbError(error: error) + } + + return session + } + + /// + /// Creates an intermediate request token that can be used to validate a TMDb user login. + /// + /// This is a temporary token that is required to ask the user for permission to access their account. This token + /// will auto expire after 60 minutes if it's not used. + /// + /// [TMDb API - Authentication: Create Request Token](https://developer.themoviedb.org/reference/authentication-create-request-token) + /// + /// - Returns: An intermediate request token. + /// + public func requestToken() async throws -> Token { + let token: Token + do { + token = try await apiClient.get(endpoint: AuthenticationEndpoint.createRequestToken) + } catch let error { + throw TMDbError(error: error) + } + + return token + } + + /// + /// Builds the URL used for the user to authenticate with after requesting an intermediate request token. + /// + /// An internediate request token can be generated by calling ``requestToken()``. + /// + /// - Parameters: + /// - token: An intermediate request token. + /// - redirectURL: Optional URL to redirect to once the user has authenticated. + /// + /// - Returns: An authenticate URL. + /// + public func authenticateURL(for token: Token, redirectURL: URL? = nil) -> URL { + let url = authenticateURLBuilder.authenticateURL(with: token.requestToken, redirectURL: redirectURL) + + return url + } + +} diff --git a/Sources/TMDb/Certifications/CertificationService.swift b/Sources/TMDb/Services/Certifications/CertificationService.swift similarity index 100% rename from Sources/TMDb/Certifications/CertificationService.swift rename to Sources/TMDb/Services/Certifications/CertificationService.swift diff --git a/Sources/TMDb/Certifications/Endpoints/CertificationsEndpoint.swift b/Sources/TMDb/Services/Certifications/CertificationsEndpoint.swift similarity index 100% rename from Sources/TMDb/Certifications/Endpoints/CertificationsEndpoint.swift rename to Sources/TMDb/Services/Certifications/CertificationsEndpoint.swift diff --git a/Sources/TMDb/Company/Endpoints/CompanyEndpoint.swift b/Sources/TMDb/Services/Company/CompanyEndpoint.swift similarity index 100% rename from Sources/TMDb/Company/Endpoints/CompanyEndpoint.swift rename to Sources/TMDb/Services/Company/CompanyEndpoint.swift diff --git a/Sources/TMDb/Company/CompanyService.swift b/Sources/TMDb/Services/Company/CompanyService.swift similarity index 100% rename from Sources/TMDb/Company/CompanyService.swift rename to Sources/TMDb/Services/Company/CompanyService.swift diff --git a/Sources/TMDb/Configuration/Endpoint/ConfigurationEndpoint.swift b/Sources/TMDb/Services/Configuration/ConfigurationEndpoint.swift similarity index 100% rename from Sources/TMDb/Configuration/Endpoint/ConfigurationEndpoint.swift rename to Sources/TMDb/Services/Configuration/ConfigurationEndpoint.swift diff --git a/Sources/TMDb/Configuration/ConfigurationService.swift b/Sources/TMDb/Services/Configuration/ConfigurationService.swift similarity index 100% rename from Sources/TMDb/Configuration/ConfigurationService.swift rename to Sources/TMDb/Services/Configuration/ConfigurationService.swift diff --git a/Sources/TMDb/Discover/Endpoints/DiscoverEndpoint.swift b/Sources/TMDb/Services/Discover/DiscoverEndpoint.swift similarity index 100% rename from Sources/TMDb/Discover/Endpoints/DiscoverEndpoint.swift rename to Sources/TMDb/Services/Discover/DiscoverEndpoint.swift diff --git a/Sources/TMDb/Discover/DiscoverService.swift b/Sources/TMDb/Services/Discover/DiscoverService.swift similarity index 100% rename from Sources/TMDb/Discover/DiscoverService.swift rename to Sources/TMDb/Services/Discover/DiscoverService.swift diff --git a/Sources/TMDb/Genres/GenreService.swift b/Sources/TMDb/Services/Genres/GenreService.swift similarity index 100% rename from Sources/TMDb/Genres/GenreService.swift rename to Sources/TMDb/Services/Genres/GenreService.swift diff --git a/Sources/TMDb/Genres/Endpoints/GenresEndpoint.swift b/Sources/TMDb/Services/Genres/GenresEndpoint.swift similarity index 100% rename from Sources/TMDb/Genres/Endpoints/GenresEndpoint.swift rename to Sources/TMDb/Services/Genres/GenresEndpoint.swift diff --git a/Sources/TMDb/LocaleProviding.swift b/Sources/TMDb/Services/LocaleProviding.swift similarity index 100% rename from Sources/TMDb/LocaleProviding.swift rename to Sources/TMDb/Services/LocaleProviding.swift diff --git a/Sources/TMDb/Movies/MovieService.swift b/Sources/TMDb/Services/Movies/MovieService.swift similarity index 99% rename from Sources/TMDb/Movies/MovieService.swift rename to Sources/TMDb/Services/Movies/MovieService.swift index 962a7fb9..5dd8d032 100644 --- a/Sources/TMDb/Movies/MovieService.swift +++ b/Sources/TMDb/Services/Movies/MovieService.swift @@ -325,6 +325,7 @@ public final class MovieService { /// Returns watch providers for a movie /// /// [TMDb API - Movie: Watch providers](https://developer.themoviedb.org/reference/movie-watch-providers) + /// /// - Parameters: /// - id: The identifier of the movie. /// diff --git a/Sources/TMDb/Movies/Endpoints/MoviesEndpoint.swift b/Sources/TMDb/Services/Movies/MoviesEndpoint.swift similarity index 100% rename from Sources/TMDb/Movies/Endpoints/MoviesEndpoint.swift rename to Sources/TMDb/Services/Movies/MoviesEndpoint.swift diff --git a/Sources/TMDb/People/Endpoints/PeopleEndpoint.swift b/Sources/TMDb/Services/People/PeopleEndpoint.swift similarity index 100% rename from Sources/TMDb/People/Endpoints/PeopleEndpoint.swift rename to Sources/TMDb/Services/People/PeopleEndpoint.swift diff --git a/Sources/TMDb/People/PersonService.swift b/Sources/TMDb/Services/People/PersonService.swift similarity index 100% rename from Sources/TMDb/People/PersonService.swift rename to Sources/TMDb/Services/People/PersonService.swift diff --git a/Sources/TMDb/Search/Endpoints/SearchEndpoint.swift b/Sources/TMDb/Services/Search/SearchEndpoint.swift similarity index 100% rename from Sources/TMDb/Search/Endpoints/SearchEndpoint.swift rename to Sources/TMDb/Services/Search/SearchEndpoint.swift diff --git a/Sources/TMDb/Search/SearchService.swift b/Sources/TMDb/Services/Search/SearchService.swift similarity index 100% rename from Sources/TMDb/Search/SearchService.swift rename to Sources/TMDb/Services/Search/SearchService.swift diff --git a/Sources/TMDb/TVEpisodes/TVEpisodeService.swift b/Sources/TMDb/Services/TVEpisodes/TVEpisodeService.swift similarity index 100% rename from Sources/TMDb/TVEpisodes/TVEpisodeService.swift rename to Sources/TMDb/Services/TVEpisodes/TVEpisodeService.swift diff --git a/Sources/TMDb/TVEpisodes/Endpoints/TVEpisodesEndpoint.swift b/Sources/TMDb/Services/TVEpisodes/TVEpisodesEndpoint.swift similarity index 100% rename from Sources/TMDb/TVEpisodes/Endpoints/TVEpisodesEndpoint.swift rename to Sources/TMDb/Services/TVEpisodes/TVEpisodesEndpoint.swift diff --git a/Sources/TMDb/TVSeasons/TVSeasonService.swift b/Sources/TMDb/Services/TVSeasons/TVSeasonService.swift similarity index 100% rename from Sources/TMDb/TVSeasons/TVSeasonService.swift rename to Sources/TMDb/Services/TVSeasons/TVSeasonService.swift diff --git a/Sources/TMDb/TVSeasons/Endpoints/TVSeasonsEndpoint.swift b/Sources/TMDb/Services/TVSeasons/TVSeasonsEndpoint.swift similarity index 100% rename from Sources/TMDb/TVSeasons/Endpoints/TVSeasonsEndpoint.swift rename to Sources/TMDb/Services/TVSeasons/TVSeasonsEndpoint.swift diff --git a/Sources/TMDb/TVSeries/Endpoints/TVSeriesEndpoint.swift b/Sources/TMDb/Services/TVSeries/TVSeriesEndpoint.swift similarity index 100% rename from Sources/TMDb/TVSeries/Endpoints/TVSeriesEndpoint.swift rename to Sources/TMDb/Services/TVSeries/TVSeriesEndpoint.swift diff --git a/Sources/TMDb/TVSeries/TVSeriesService.swift b/Sources/TMDb/Services/TVSeries/TVSeriesService.swift similarity index 100% rename from Sources/TMDb/TVSeries/TVSeriesService.swift rename to Sources/TMDb/Services/TVSeries/TVSeriesService.swift diff --git a/Sources/TMDb/Trending/Endpoints/TrendingEndpoint.swift b/Sources/TMDb/Services/Trending/TrendingEndpoint.swift similarity index 100% rename from Sources/TMDb/Trending/Endpoints/TrendingEndpoint.swift rename to Sources/TMDb/Services/Trending/TrendingEndpoint.swift diff --git a/Sources/TMDb/Trending/TrendingService.swift b/Sources/TMDb/Services/Trending/TrendingService.swift similarity index 100% rename from Sources/TMDb/Trending/TrendingService.swift rename to Sources/TMDb/Services/Trending/TrendingService.swift diff --git a/Sources/TMDb/Trending/TrendingTimeWindowFilterType.swift b/Sources/TMDb/Services/Trending/TrendingTimeWindowFilterType.swift similarity index 100% rename from Sources/TMDb/Trending/TrendingTimeWindowFilterType.swift rename to Sources/TMDb/Services/Trending/TrendingTimeWindowFilterType.swift diff --git a/Sources/TMDb/WatchProviders/Endpoints/WatchProviderEndpoint.swift b/Sources/TMDb/Services/WatchProviders/WatchProviderEndpoint.swift similarity index 100% rename from Sources/TMDb/WatchProviders/Endpoints/WatchProviderEndpoint.swift rename to Sources/TMDb/Services/WatchProviders/WatchProviderEndpoint.swift diff --git a/Sources/TMDb/WatchProviders/WatchProviderService.swift b/Sources/TMDb/Services/WatchProviders/WatchProviderService.swift similarity index 100% rename from Sources/TMDb/WatchProviders/WatchProviderService.swift rename to Sources/TMDb/Services/WatchProviders/WatchProviderService.swift diff --git a/Sources/TMDb/TMDb.docc/Extensions/AuthenticationService.md b/Sources/TMDb/TMDb.docc/Extensions/AuthenticationService.md index 7596d4c4..c8f9d72f 100644 --- a/Sources/TMDb/TMDb.docc/Extensions/AuthenticationService.md +++ b/Sources/TMDb/TMDb.docc/Extensions/AuthenticationService.md @@ -8,4 +8,6 @@ ### Creating Sessions -- ``createGuestSession()`` +- ``guestSession()`` +- ``requestToken()`` +- ``authenticateURL(for:redirectURL:)`` diff --git a/Sources/TMDb/TMDbFactory.swift b/Sources/TMDb/TMDbFactory.swift index 567eabdc..f4f497e1 100644 --- a/Sources/TMDb/TMDbFactory.swift +++ b/Sources/TMDb/TMDbFactory.swift @@ -32,9 +32,9 @@ extension TMDbFactory { static var apiClient: some APIClient { TMDbAPIClient( - apiKey: TMDb.configuration.apiKey(), - baseURL: .tmdbAPIBaseURL, - httpClient: TMDb.configuration.httpClient(), + apiKey: apiKey, + baseURL: tmdbAPIBaseURL, + httpClient: httpClient, serialiser: serialiser, localeProvider: localeProvider() ) @@ -42,14 +42,18 @@ extension TMDbFactory { static var authAPIClient: some APIClient { TMDbAPIClient( - apiKey: TMDb.configuration.apiKey(), + apiKey: apiKey, baseURL: .tmdbAPIBaseURL, - httpClient: TMDb.configuration.httpClient(), + httpClient: httpClient, serialiser: authSerialiser, localeProvider: localeProvider() ) } + static var authenticateURLBuilder: some AuthenticateURLBuilding { + AuthenticateURLBuilder(baseURL: tmdbWebSiteURL) + } + static func localeProvider() -> some LocaleProviding { LocaleProvider(locale: .current) } @@ -96,3 +100,23 @@ extension TMDbFactory { } } + +extension TMDbFactory { + + private static var tmdbAPIBaseURL: URL { + URL.tmdbAPIBaseURL + } + + private static var tmdbWebSiteURL: URL { + URL.tmdbWebSiteURL + } + + private static var apiKey: String { + TMDb.configuration.apiKey() + } + + private static var httpClient: any HTTPClient { + TMDb.configuration.httpClient() + } + +} diff --git a/Tests/TMDbIntegrationTests/AuthenticationIntegrationTests.swift b/Tests/TMDbIntegrationTests/AuthenticationIntegrationTests.swift index 6b6a105d..c145cc14 100644 --- a/Tests/TMDbIntegrationTests/AuthenticationIntegrationTests.swift +++ b/Tests/TMDbIntegrationTests/AuthenticationIntegrationTests.swift @@ -35,11 +35,18 @@ final class AuthenticationIntegrationTests: XCTestCase { super.tearDown() } - func testCreateGuestSession() async throws { - let session = try await authenticationService.createGuestSession() + func testGuestSession() async throws { + let session = try await authenticationService.guestSession() XCTAssertTrue(session.success) XCTAssertNotEqual(session.guestSessionID, "") } + func testRequestToken() async throws { + let token = try await authenticationService.requestToken() + + XCTAssertTrue(token.success) + XCTAssertNotEqual(token.requestToken, "") + } + } diff --git a/Tests/TMDbTests/Authentication/AuthenticationServiceTests.swift b/Tests/TMDbTests/Authentication/AuthenticationServiceTests.swift deleted file mode 100644 index 3fd25bb6..00000000 --- a/Tests/TMDbTests/Authentication/AuthenticationServiceTests.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// AuthenticationServiceTests.swift -// TMDb -// -// Copyright © 2024 Adam Young. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an AS IS BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -@testable import TMDb -import XCTest - -final class AuthenticationServiceTests: XCTestCase { - - var service: AuthenticationService! - var apiClient: MockAPIClient! - - override func setUp() { - super.setUp() - apiClient = MockAPIClient() - service = AuthenticationService(apiClient: apiClient) - } - - override func tearDown() { - apiClient = nil - service = nil - super.tearDown() - } - - func testCreateGuestSessionReturnsGuestSession() async throws { - let expectedResult = GuestSession.mock(expiresAt: Date(timeIntervalSince1970: 1_705_956_596)) - - apiClient.result = .success(expectedResult) - - let result = try await service.createGuestSession() - - XCTAssertEqual(result, expectedResult) - XCTAssertEqual(apiClient.lastPath, AuthenticationEndpoint.createGuestSession.path) - } - - func testCreateGuestSessionWhenErrorsThrowsError() async throws { - apiClient.result = .failure(.unknown) - - var error: Error? - do { - _ = try await service.createGuestSession() - } catch let err { - error = err - } - - let tmdbAPIError = try XCTUnwrap(error as? TMDbError) - - XCTAssertEqual(tmdbAPIError, .unknown) - } - -} diff --git a/Tests/TMDbTests/Helpers/AuthenticateURLBuilderTests.swift b/Tests/TMDbTests/Helpers/AuthenticateURLBuilderTests.swift new file mode 100644 index 00000000..1f88657e --- /dev/null +++ b/Tests/TMDbTests/Helpers/AuthenticateURLBuilderTests.swift @@ -0,0 +1,64 @@ +// +// AuthenticateURLBuilderTests.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@testable import TMDb +import XCTest + +final class AuthenticateURLBuilderTests: XCTestCase { + + var builder: AuthenticateURLBuilder! + var baseURL: URL! + + override func setUp() { + super.setUp() + baseURL = URL(string: "https://some.domain.com") + builder = AuthenticateURLBuilder(baseURL: baseURL) + } + + override func tearDown() { + builder = nil + baseURL = nil + super.tearDown() + } + + func testAuthenticateURLReturnsURL() { + let requestToken = "qwertyuiop" + let expectedURL = baseURL + .appendingPathComponent("authenticate") + .appendingPathComponent(requestToken) + + let url = builder.authenticateURL(with: requestToken) + + XCTAssertEqual(url, expectedURL) + } + + func testAuthenticateURLWithRedirectURLReturnsURL() throws { + let requestToken = "qwertyuiop" + let redirectURL = try XCTUnwrap(URL(string: "https://my.domain.com/auth/callback")) + let expectedURL = baseURL + .appendingPathComponent("authenticate") + .appendingPathComponent(requestToken) + .appendingQueryItem(name: "redirect_to", value: redirectURL.absoluteString) + + let url = builder.authenticateURL(with: requestToken, redirectURL: redirectURL) + + XCTAssertEqual(url, expectedURL) + } + +} diff --git a/Tests/TMDbTests/Mocks/Authentication/AuthenticateURLMockBuilder.swift b/Tests/TMDbTests/Mocks/Authentication/AuthenticateURLMockBuilder.swift new file mode 100644 index 00000000..de8be9ff --- /dev/null +++ b/Tests/TMDbTests/Mocks/Authentication/AuthenticateURLMockBuilder.swift @@ -0,0 +1,40 @@ +// +// AuthenticateURLMockBuilder.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +@testable import TMDb + +final class AuthenticateURLMockBuilder: AuthenticateURLBuilding { + + var authenticateURLResult: URL = .init(string: "https://some.domain.com/authenticate")! + private(set) var lastRequestToken: String? + private(set) var lastRedirectURL: URL? + + func authenticateURL(with requestToken: String) -> URL { + authenticateURL(with: requestToken, redirectURL: nil) + } + + func authenticateURL(with requestToken: String, redirectURL: URL?) -> URL { + lastRequestToken = requestToken + lastRedirectURL = redirectURL + + return authenticateURLResult + } + +} diff --git a/Tests/TMDbTests/Mocks/LocaleMockProvider.swift b/Tests/TMDbTests/Mocks/Helpers/LocaleMockProvider.swift similarity index 100% rename from Tests/TMDbTests/Mocks/LocaleMockProvider.swift rename to Tests/TMDbTests/Mocks/Helpers/LocaleMockProvider.swift diff --git a/Tests/TMDbTests/Mocks/Models/GuestSession+Mocks.swift b/Tests/TMDbTests/Mocks/Models/GuestSession+Mocks.swift index 4c8de781..59729aae 100644 --- a/Tests/TMDbTests/Mocks/Models/GuestSession+Mocks.swift +++ b/Tests/TMDbTests/Mocks/Models/GuestSession+Mocks.swift @@ -25,7 +25,7 @@ extension GuestSession { static func mock( success: Bool = true, guestSessionID: String = "jdbqej40d9b562zk42ma8u4tp1saup5q", - expiresAt: Date + expiresAt: Date = Date(timeIntervalSince1970: 1_705_956_596) ) -> GuestSession { GuestSession( success: success, diff --git a/Tests/TMDbTests/Mocks/Models/Token+Mocks.swift b/Tests/TMDbTests/Mocks/Models/Token+Mocks.swift new file mode 100644 index 00000000..a0211142 --- /dev/null +++ b/Tests/TMDbTests/Mocks/Models/Token+Mocks.swift @@ -0,0 +1,37 @@ +// +// Token+Mocks.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +@testable import TMDb + +extension Token { + + static func mock( + success: Bool = true, + requestToken: String = "10530f2246e244555d122016db7c65599c8d6f4d", + expiresAt: Date = Date(timeIntervalSince1970: 1_705_956_596) + ) -> Token { + Token( + success: success, + requestToken: requestToken, + expiresAt: expiresAt + ) + } + +} diff --git a/Tests/TMDbTests/Mocks/Client/MockAPIClient.swift b/Tests/TMDbTests/Mocks/Networking/APIClient/MockAPIClient.swift similarity index 100% rename from Tests/TMDbTests/Mocks/Client/MockAPIClient.swift rename to Tests/TMDbTests/Mocks/Networking/APIClient/MockAPIClient.swift diff --git a/Tests/TMDbTests/Networking/Mocks/HTTPMockClient.swift b/Tests/TMDbTests/Mocks/Networking/HTTPClient/HTTPMockClient.swift similarity index 100% rename from Tests/TMDbTests/Networking/Mocks/HTTPMockClient.swift rename to Tests/TMDbTests/Mocks/Networking/HTTPClient/HTTPMockClient.swift diff --git a/Tests/TMDbTests/Networking/Mocks/MockURLProtocol.swift b/Tests/TMDbTests/Mocks/Networking/HTTPClient/MockURLProtocol.swift similarity index 100% rename from Tests/TMDbTests/Networking/Mocks/MockURLProtocol.swift rename to Tests/TMDbTests/Mocks/Networking/HTTPClient/MockURLProtocol.swift diff --git a/Tests/TMDbTests/Mocks/URL+Mocks.swift b/Tests/TMDbTests/Mocks/Networking/URL+Mocks.swift similarity index 100% rename from Tests/TMDbTests/Mocks/URL+Mocks.swift rename to Tests/TMDbTests/Mocks/Networking/URL+Mocks.swift diff --git a/Tests/TMDbTests/Models/TokenTests.swift b/Tests/TMDbTests/Models/TokenTests.swift new file mode 100644 index 00000000..40c09894 --- /dev/null +++ b/Tests/TMDbTests/Models/TokenTests.swift @@ -0,0 +1,39 @@ +// +// TokenTests.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@testable import TMDb +import XCTest + +final class TokenTests: XCTestCase { + + func testDecodeReturnsToken() throws { + let expectedResult = Token( + success: true, + requestToken: "10530f2246e244555d122016db7c65599c8d6f4d", + expiresAt: Date(timeIntervalSince1970: 1_705_956_596) + ) + + let result = try JSONDecoder.theMovieDatabaseAuth.decode(Token.self, fromResource: "request-token") + + XCTAssertEqual(result.success, expectedResult.success) + XCTAssertEqual(result.requestToken, expectedResult.requestToken) + XCTAssertEqual(result.expiresAt, expectedResult.expiresAt) + } + +} diff --git a/Tests/TMDbTests/Resources/json/request-token.json b/Tests/TMDbTests/Resources/json/request-token.json new file mode 100644 index 00000000..e8ffd9ea --- /dev/null +++ b/Tests/TMDbTests/Resources/json/request-token.json @@ -0,0 +1,5 @@ +{ + "success": true, + "expires_at": "2024-01-22 20:49:56 UTC", + "request_token": "10530f2246e244555d122016db7c65599c8d6f4d" +} diff --git a/Tests/TMDbTests/Services/Authentication/AuthenticationServiceTests.swift b/Tests/TMDbTests/Services/Authentication/AuthenticationServiceTests.swift new file mode 100644 index 00000000..d4d06084 --- /dev/null +++ b/Tests/TMDbTests/Services/Authentication/AuthenticationServiceTests.swift @@ -0,0 +1,122 @@ +// +// AuthenticationServiceTests.swift +// TMDb +// +// Copyright © 2024 Adam Young. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an AS IS BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@testable import TMDb +import XCTest + +final class AuthenticationServiceTests: XCTestCase { + + var service: AuthenticationService! + var apiClient: MockAPIClient! + var authenticateURLBuilder: AuthenticateURLMockBuilder! + + override func setUp() { + super.setUp() + apiClient = MockAPIClient() + authenticateURLBuilder = AuthenticateURLMockBuilder() + service = AuthenticationService(apiClient: apiClient, authenticateURLBuilder: authenticateURLBuilder) + } + + override func tearDown() { + service = nil + authenticateURLBuilder = nil + apiClient = nil + super.tearDown() + } + + func testGuestSessionReturnsGuestSession() async throws { + let expectedResult = GuestSession.mock() + + apiClient.result = .success(expectedResult) + + let result = try await service.guestSession() + + XCTAssertEqual(result, expectedResult) + XCTAssertEqual(apiClient.lastPath, AuthenticationEndpoint.createGuestSession.path) + } + + func testGuestSessionWhenErrorsThrowsError() async throws { + apiClient.result = .failure(.unknown) + + var error: Error? + do { + _ = try await service.guestSession() + } catch let err { + error = err + } + + let tmdbAPIError = try XCTUnwrap(error as? TMDbError) + + XCTAssertEqual(tmdbAPIError, .unknown) + } + + func testRequestTokenReturnsToken() async throws { + let expectedResult = Token.mock() + + apiClient.result = .success(expectedResult) + + let result = try await service.requestToken() + + XCTAssertEqual(result, expectedResult) + XCTAssertEqual(apiClient.lastPath, AuthenticationEndpoint.createRequestToken.path) + } + + func testRequestTokenWhenErrorsThrowsError() async throws { + apiClient.result = .failure(.unknown) + + var error: Error? + do { + _ = try await service.requestToken() + } catch let err { + error = err + } + + let tmdbAPIError = try XCTUnwrap(error as? TMDbError) + + XCTAssertEqual(tmdbAPIError, .unknown) + } + + func testAuthenticateURLReturnsURL() throws { + let expiresAt = Date(timeIntervalSince1970: 1_705_956_596) + let token = Token(success: true, requestToken: "abc123", expiresAt: expiresAt) + let expectedURL = try XCTUnwrap(URL(string: "https://some.domain.com/authenticate/abc123")) + authenticateURLBuilder.authenticateURLResult = expectedURL + + let url = service.authenticateURL(for: token) + + XCTAssertEqual(url, expectedURL) + XCTAssertEqual(authenticateURLBuilder.lastRequestToken, token.requestToken) + XCTAssertNil(authenticateURLBuilder.lastRedirectURL) + } + + func testAuthenticateURLWithRedirectURLReturnsURL() throws { + let expiresAt = Date(timeIntervalSince1970: 1_705_956_596) + let token = Token(success: true, requestToken: "abc123", expiresAt: expiresAt) + let redirectURL = try XCTUnwrap(URL(string: "https://some.domain.com/auth/callback")) + let expectedURL = try XCTUnwrap(URL(string: "https://some.domain.com/authenticate/abc123")) + authenticateURLBuilder.authenticateURLResult = expectedURL + + let url = service.authenticateURL(for: token, redirectURL: redirectURL) + + XCTAssertEqual(url, expectedURL) + XCTAssertEqual(authenticateURLBuilder.lastRequestToken, token.requestToken) + XCTAssertEqual(authenticateURLBuilder.lastRedirectURL, redirectURL) + } + +} diff --git a/Tests/TMDbTests/Authentication/Endpoints/AuthenticationEndpointTests.swift b/Tests/TMDbTests/Services/Authentication/Endpoints/AuthenticationEndpointTests.swift similarity index 79% rename from Tests/TMDbTests/Authentication/Endpoints/AuthenticationEndpointTests.swift rename to Tests/TMDbTests/Services/Authentication/Endpoints/AuthenticationEndpointTests.swift index 6585e43b..9e91e693 100644 --- a/Tests/TMDbTests/Authentication/Endpoints/AuthenticationEndpointTests.swift +++ b/Tests/TMDbTests/Services/Authentication/Endpoints/AuthenticationEndpointTests.swift @@ -30,4 +30,12 @@ final class AuthenticationEndpointTests: XCTestCase { XCTAssertEqual(url, expectedURL) } + func testCreateRequetTokenEndpointReturnsURL() throws { + let expectedURL = try XCTUnwrap(URL(string: "/authentication/token/new")) + + let url = AuthenticationEndpoint.createRequestToken.path + + XCTAssertEqual(url, expectedURL) + } + } diff --git a/Tests/TMDbTests/Certifications/CertificationServiceTests.swift b/Tests/TMDbTests/Services/Certifications/CertificationServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Certifications/CertificationServiceTests.swift rename to Tests/TMDbTests/Services/Certifications/CertificationServiceTests.swift diff --git a/Tests/TMDbTests/Certifications/Endpoints/CertificationsEndpointTests.swift b/Tests/TMDbTests/Services/Certifications/Endpoints/CertificationsEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Certifications/Endpoints/CertificationsEndpointTests.swift rename to Tests/TMDbTests/Services/Certifications/Endpoints/CertificationsEndpointTests.swift diff --git a/Tests/TMDbTests/Company/CompanyServiceTests.swift b/Tests/TMDbTests/Services/Company/CompanyServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Company/CompanyServiceTests.swift rename to Tests/TMDbTests/Services/Company/CompanyServiceTests.swift diff --git a/Tests/TMDbTests/Company/Endpoints/CompanyEndpointTests.swift b/Tests/TMDbTests/Services/Company/Endpoints/CompanyEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Company/Endpoints/CompanyEndpointTests.swift rename to Tests/TMDbTests/Services/Company/Endpoints/CompanyEndpointTests.swift diff --git a/Tests/TMDbTests/Configuration/ConfigurationServiceTests.swift b/Tests/TMDbTests/Services/Configuration/ConfigurationServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Configuration/ConfigurationServiceTests.swift rename to Tests/TMDbTests/Services/Configuration/ConfigurationServiceTests.swift diff --git a/Tests/TMDbTests/Configuration/Endpoints/ConfigurationEndpointTests.swift b/Tests/TMDbTests/Services/Configuration/Endpoints/ConfigurationEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Configuration/Endpoints/ConfigurationEndpointTests.swift rename to Tests/TMDbTests/Services/Configuration/Endpoints/ConfigurationEndpointTests.swift diff --git a/Tests/TMDbTests/Discover/DiscoverServiceTests.swift b/Tests/TMDbTests/Services/Discover/DiscoverServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Discover/DiscoverServiceTests.swift rename to Tests/TMDbTests/Services/Discover/DiscoverServiceTests.swift diff --git a/Tests/TMDbTests/Discover/Endpoints/DiscoverEndpointTests.swift b/Tests/TMDbTests/Services/Discover/Endpoints/DiscoverEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Discover/Endpoints/DiscoverEndpointTests.swift rename to Tests/TMDbTests/Services/Discover/Endpoints/DiscoverEndpointTests.swift diff --git a/Tests/TMDbTests/Discover/MovieSortTests.swift b/Tests/TMDbTests/Services/Discover/MovieSortTests.swift similarity index 100% rename from Tests/TMDbTests/Discover/MovieSortTests.swift rename to Tests/TMDbTests/Services/Discover/MovieSortTests.swift diff --git a/Tests/TMDbTests/Discover/TVSeriesSortTests.swift b/Tests/TMDbTests/Services/Discover/TVSeriesSortTests.swift similarity index 100% rename from Tests/TMDbTests/Discover/TVSeriesSortTests.swift rename to Tests/TMDbTests/Services/Discover/TVSeriesSortTests.swift diff --git a/Tests/TMDbTests/Genres/Endpoints/GenresEndpointTests.swift b/Tests/TMDbTests/Services/Genres/Endpoints/GenresEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Genres/Endpoints/GenresEndpointTests.swift rename to Tests/TMDbTests/Services/Genres/Endpoints/GenresEndpointTests.swift diff --git a/Tests/TMDbTests/Genres/GenreServiceTests.swift b/Tests/TMDbTests/Services/Genres/GenreServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Genres/GenreServiceTests.swift rename to Tests/TMDbTests/Services/Genres/GenreServiceTests.swift diff --git a/Tests/TMDbTests/Movies/Endpoints/MoviesEndpointTests.swift b/Tests/TMDbTests/Services/Movies/Endpoints/MoviesEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Movies/Endpoints/MoviesEndpointTests.swift rename to Tests/TMDbTests/Services/Movies/Endpoints/MoviesEndpointTests.swift diff --git a/Tests/TMDbTests/Movies/MovieServiceTests.swift b/Tests/TMDbTests/Services/Movies/MovieServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Movies/MovieServiceTests.swift rename to Tests/TMDbTests/Services/Movies/MovieServiceTests.swift diff --git a/Tests/TMDbTests/People/Endpoints/PeopleEndpointTests.swift b/Tests/TMDbTests/Services/People/Endpoints/PeopleEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/People/Endpoints/PeopleEndpointTests.swift rename to Tests/TMDbTests/Services/People/Endpoints/PeopleEndpointTests.swift diff --git a/Tests/TMDbTests/People/PersonServiceTests.swift b/Tests/TMDbTests/Services/People/PersonServiceTests.swift similarity index 100% rename from Tests/TMDbTests/People/PersonServiceTests.swift rename to Tests/TMDbTests/Services/People/PersonServiceTests.swift diff --git a/Tests/TMDbTests/Search/Endpoints/SearchEndpointTests.swift b/Tests/TMDbTests/Services/Search/Endpoints/SearchEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Search/Endpoints/SearchEndpointTests.swift rename to Tests/TMDbTests/Services/Search/Endpoints/SearchEndpointTests.swift diff --git a/Tests/TMDbTests/Search/SearchServiceTests.swift b/Tests/TMDbTests/Services/Search/SearchServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Search/SearchServiceTests.swift rename to Tests/TMDbTests/Services/Search/SearchServiceTests.swift diff --git a/Tests/TMDbTests/TVEpisodes/ENdpoints/TVEpisodesEndpointTests.swift b/Tests/TMDbTests/Services/TVEpisodes/Endpoints/TVEpisodesEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/TVEpisodes/ENdpoints/TVEpisodesEndpointTests.swift rename to Tests/TMDbTests/Services/TVEpisodes/Endpoints/TVEpisodesEndpointTests.swift diff --git a/Tests/TMDbTests/TVEpisodes/TVEpisodeServiceTests.swift b/Tests/TMDbTests/Services/TVEpisodes/TVEpisodeServiceTests.swift similarity index 100% rename from Tests/TMDbTests/TVEpisodes/TVEpisodeServiceTests.swift rename to Tests/TMDbTests/Services/TVEpisodes/TVEpisodeServiceTests.swift diff --git a/Tests/TMDbTests/TVSeasons/Endpoints/TVSeasonsEndpointTests.swift b/Tests/TMDbTests/Services/TVSeasons/Endpoints/TVSeasonsEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/TVSeasons/Endpoints/TVSeasonsEndpointTests.swift rename to Tests/TMDbTests/Services/TVSeasons/Endpoints/TVSeasonsEndpointTests.swift diff --git a/Tests/TMDbTests/TVSeasons/TVSeasonServiceTests.swift b/Tests/TMDbTests/Services/TVSeasons/TVSeasonServiceTests.swift similarity index 100% rename from Tests/TMDbTests/TVSeasons/TVSeasonServiceTests.swift rename to Tests/TMDbTests/Services/TVSeasons/TVSeasonServiceTests.swift diff --git a/Tests/TMDbTests/TVSeries/Endpoints/TVSeriesEndpointTests.swift b/Tests/TMDbTests/Services/TVSeries/Endpoints/TVSeriesEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/TVSeries/Endpoints/TVSeriesEndpointTests.swift rename to Tests/TMDbTests/Services/TVSeries/Endpoints/TVSeriesEndpointTests.swift diff --git a/Tests/TMDbTests/TVSeries/TVSeriesServiceTests.swift b/Tests/TMDbTests/Services/TVSeries/TVSeriesServiceTests.swift similarity index 100% rename from Tests/TMDbTests/TVSeries/TVSeriesServiceTests.swift rename to Tests/TMDbTests/Services/TVSeries/TVSeriesServiceTests.swift diff --git a/Tests/TMDbTests/Trending/Endpoints/TrendingEndpointTests.swift b/Tests/TMDbTests/Services/Trending/Endpoints/TrendingEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/Trending/Endpoints/TrendingEndpointTests.swift rename to Tests/TMDbTests/Services/Trending/Endpoints/TrendingEndpointTests.swift diff --git a/Tests/TMDbTests/Trending/TrendingServiceTests.swift b/Tests/TMDbTests/Services/Trending/TrendingServiceTests.swift similarity index 100% rename from Tests/TMDbTests/Trending/TrendingServiceTests.swift rename to Tests/TMDbTests/Services/Trending/TrendingServiceTests.swift diff --git a/Tests/TMDbTests/Trending/TrendingTimeWindowFilterTypeTests.swift b/Tests/TMDbTests/Services/Trending/TrendingTimeWindowFilterTypeTests.swift similarity index 100% rename from Tests/TMDbTests/Trending/TrendingTimeWindowFilterTypeTests.swift rename to Tests/TMDbTests/Services/Trending/TrendingTimeWindowFilterTypeTests.swift diff --git a/Tests/TMDbTests/WatchProviders/Endpoints/WatchProviderEndpointTests.swift b/Tests/TMDbTests/Services/WatchProviders/Endpoints/WatchProviderEndpointTests.swift similarity index 100% rename from Tests/TMDbTests/WatchProviders/Endpoints/WatchProviderEndpointTests.swift rename to Tests/TMDbTests/Services/WatchProviders/Endpoints/WatchProviderEndpointTests.swift diff --git a/Tests/TMDbTests/WatchProviders/WatchProviderServiceTests.swift b/Tests/TMDbTests/Services/WatchProviders/WatchProviderServiceTests.swift similarity index 100% rename from Tests/TMDbTests/WatchProviders/WatchProviderServiceTests.swift rename to Tests/TMDbTests/Services/WatchProviders/WatchProviderServiceTests.swift diff --git a/Tests/TMDbTests/Utilities/Data+LoadFromFile.swift b/Tests/TMDbTests/TestUtils/Data+LoadFromFile.swift similarity index 100% rename from Tests/TMDbTests/Utilities/Data+LoadFromFile.swift rename to Tests/TMDbTests/TestUtils/Data+LoadFromFile.swift diff --git a/Tests/TMDbTests/Utilities/Date+RandomDate.swift b/Tests/TMDbTests/TestUtils/Date+RandomDate.swift similarity index 100% rename from Tests/TMDbTests/Utilities/Date+RandomDate.swift rename to Tests/TMDbTests/TestUtils/Date+RandomDate.swift diff --git a/Tests/TMDbTests/Utilities/Int+RandomID.swift b/Tests/TMDbTests/TestUtils/Int+RandomID.swift similarity index 100% rename from Tests/TMDbTests/Utilities/Int+RandomID.swift rename to Tests/TMDbTests/TestUtils/Int+RandomID.swift diff --git a/Tests/TMDbTests/Utilities/JSONDecoder+DecodeFromFile.swift b/Tests/TMDbTests/TestUtils/JSONDecoder+DecodeFromFile.swift similarity index 100% rename from Tests/TMDbTests/Utilities/JSONDecoder+DecodeFromFile.swift rename to Tests/TMDbTests/TestUtils/JSONDecoder+DecodeFromFile.swift diff --git a/Tests/TMDbTests/Utilities/JSONEncoder+TMDb.swift b/Tests/TMDbTests/TestUtils/JSONEncoder+TMDb.swift similarity index 100% rename from Tests/TMDbTests/Utilities/JSONEncoder+TMDb.swift rename to Tests/TMDbTests/TestUtils/JSONEncoder+TMDb.swift diff --git a/Tests/TMDbTests/Utilities/String+RandomID.swift b/Tests/TMDbTests/TestUtils/String+RandomID.swift similarity index 100% rename from Tests/TMDbTests/Utilities/String+RandomID.swift rename to Tests/TMDbTests/TestUtils/String+RandomID.swift