From 3cff7fc19e47d9366635a4852ee298aaadb73af3 Mon Sep 17 00:00:00 2001 From: Jota Uribe Date: Mon, 19 Sep 2022 11:15:24 -0500 Subject: [PATCH] Release 0.4.2 (#5) * Add LICENSE file * Fix wrong path being returned for music videos catalog requests, Improve custom error descriptions, Improve routers unit tests * Improve DataProvider unit tests * Modify methods encapsulation to public to make them available to library consumers * Fix router generated path for catalog resources --- LICENSE | 21 ++ Sources/AmuseKit/AmuseKit.swift | 9 +- .../AmuseKit/Networking/DataProvider.swift | 4 +- .../Networking/ResourceConvertible.swift | 20 +- Sources/AmuseKit/Networking/Router.swift | 17 +- .../DataModels/invalid_authentication.json | 11 + .../MockAPIService.swift | 1 + .../Helpers/MockDataProvider.swift | 21 ++ .../MockStorageService.swift | 2 +- .../Networking/DataProviderTests.swift | 320 +++++++++--------- .../Networking/RouterTests.swift | 127 ++++++- 11 files changed, 348 insertions(+), 205 deletions(-) create mode 100644 LICENSE create mode 100644 Tests/AmuseKitTests/DataModels/invalid_authentication.json rename Tests/AmuseKitTests/{Networking => Helpers}/MockAPIService.swift (99%) create mode 100644 Tests/AmuseKitTests/Helpers/MockDataProvider.swift rename Tests/AmuseKitTests/{Storage => Helpers}/MockStorageService.swift (81%) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..175101d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Jota Uribe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Sources/AmuseKit/AmuseKit.swift b/Sources/AmuseKit/AmuseKit.swift index fbef7ea..036be0c 100644 --- a/Sources/AmuseKit/AmuseKit.swift +++ b/Sources/AmuseKit/AmuseKit.swift @@ -11,7 +11,6 @@ protocol AmuseOption: RawRepresentable, Hashable, CaseIterable {} public class AmuseKit { enum AmuseError: Error { - case invalidRequest case missingDevToken case missingUserToken } @@ -20,10 +19,10 @@ public class AmuseKit { extension AmuseKit.AmuseError: LocalizedError { public var errorDescription: String? { switch self { - case .invalidRequest: - return NSLocalizedString("", comment: "") - default: - return nil + case .missingDevToken: + return "Developer token is missing" + case .missingUserToken: + return "UserToken token is missing" } } } diff --git a/Sources/AmuseKit/Networking/DataProvider.swift b/Sources/AmuseKit/Networking/DataProvider.swift index 1ca4857..34136fb 100644 --- a/Sources/AmuseKit/Networking/DataProvider.swift +++ b/Sources/AmuseKit/Networking/DataProvider.swift @@ -47,7 +47,7 @@ public extension AmuseKit { // MARK: - Catalog Methods - func catalog(_ resourceType: CatalogResourceConvertible, ids: [String]) throws -> AnyPublisher, Error> { + public func catalog(_ resourceType: CatalogResourceConvertible, ids: [String]) throws -> AnyPublisher, Error> { guard let developerToken = storage.developerToken else { throw AmuseKit.AmuseError.missingDevToken } @@ -66,7 +66,7 @@ public extension AmuseKit { // MARK: - Library Methods - func library(_ resourceType: LibraryResourceConvertible) throws -> AnyPublisher, Error> { + public func library(_ resourceType: LibraryResourceConvertible) throws -> AnyPublisher, Error> { guard let developerToken = storage.developerToken else { throw AmuseKit.AmuseError.missingDevToken } diff --git a/Sources/AmuseKit/Networking/ResourceConvertible.swift b/Sources/AmuseKit/Networking/ResourceConvertible.swift index 1417451..2c7054f 100644 --- a/Sources/AmuseKit/Networking/ResourceConvertible.swift +++ b/Sources/AmuseKit/Networking/ResourceConvertible.swift @@ -30,23 +30,23 @@ public extension AmuseKit { } } -extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryAlbum { +public extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryAlbum { static var albums = Self.init(rawValue: .albums) } -extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryArtist { +public extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryArtist { static var artists = Self.init(rawValue: .artists) } -extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryMusicVideo { +public extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryMusicVideo { static var musicVideos = Self.init(rawValue: .musicVideos) } -extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryPlaylist { +public extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibraryPlaylist { static var playlists = Self.init(rawValue: .playlists) } -extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibrarySong { +public extension AmuseKit.LibraryResourceConvertible where Model == AmuseKit.LibrarySong { static var songs = Self.init(rawValue: .songs) } @@ -71,23 +71,23 @@ public extension AmuseKit { } } -extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Album { +public extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Album { static var albums = Self.init(rawValue: .albums) } -extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Artist { +public extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Artist { static var artists = Self.init(rawValue: .artists) } -extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.MusicVideo { +public extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.MusicVideo { static var musicVideos = Self.init(rawValue: .musicVideos) } -extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Playlist { +public extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Playlist { static var playlists = Self.init(rawValue: .playlists) } -extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Song { +public extension AmuseKit.CatalogResourceConvertible where Model == AmuseKit.Song { static var songs = Self.init(rawValue: .songs) } diff --git a/Sources/AmuseKit/Networking/Router.swift b/Sources/AmuseKit/Networking/Router.swift index 7a325d0..40bee40 100644 --- a/Sources/AmuseKit/Networking/Router.swift +++ b/Sources/AmuseKit/Networking/Router.swift @@ -28,8 +28,8 @@ extension AmuseKit { extension AmuseKit.Router: URLConvertible, URLRequestConvertible { private var path: String { switch self { - case .catalog(let countryCode, let resourceType): - return "/v1/catalog/\(countryCode)/search/\(resourceType)" + case .catalog(let countryCode, let type): + return "/v1/catalog/\(countryCode)/\(type.lastPathComponent)" case .library(let type): return "/v1/me/\(type.lastPathComponent)" case .recommendations: @@ -52,12 +52,23 @@ extension AmuseKit.Router: URLConvertible, URLRequestConvertible { func asURLRequest(_ queryItems: [URLQueryItem]) throws -> URLRequest { guard let url = asURL(queryItems) else { - throw AmuseKit.AmuseError.invalidRequest + throw URLError(.badURL) } return URLRequest(url: url) } } +fileprivate extension AmuseKit.CatalogResourceType { + var lastPathComponent: String { + switch self { + case .musicVideos: + return "music-videos" + default: + return rawValue + } + } +} + fileprivate extension AmuseKit.LibraryResourceType { var lastPathComponent: String { switch self { diff --git a/Tests/AmuseKitTests/DataModels/invalid_authentication.json b/Tests/AmuseKitTests/DataModels/invalid_authentication.json new file mode 100644 index 0000000..a5ca7b7 --- /dev/null +++ b/Tests/AmuseKitTests/DataModels/invalid_authentication.json @@ -0,0 +1,11 @@ +{ + "errors": [ + { + "id": "KHFHBMG2ZFDUYJQUMALKZAZ6LI", + "title": "Forbidden", + "detail": "Invalid authentication", + "status": "403", + "code": "40300" + } + ] +} \ No newline at end of file diff --git a/Tests/AmuseKitTests/Networking/MockAPIService.swift b/Tests/AmuseKitTests/Helpers/MockAPIService.swift similarity index 99% rename from Tests/AmuseKitTests/Networking/MockAPIService.swift rename to Tests/AmuseKitTests/Helpers/MockAPIService.swift index 4cff0ef..5c2b5a1 100644 --- a/Tests/AmuseKitTests/Networking/MockAPIService.swift +++ b/Tests/AmuseKitTests/Helpers/MockAPIService.swift @@ -35,3 +35,4 @@ struct MockAPIService: APIService { return publisher } } + diff --git a/Tests/AmuseKitTests/Helpers/MockDataProvider.swift b/Tests/AmuseKitTests/Helpers/MockDataProvider.swift new file mode 100644 index 0000000..1320baf --- /dev/null +++ b/Tests/AmuseKitTests/Helpers/MockDataProvider.swift @@ -0,0 +1,21 @@ +// +// MockDataProvider.swift +// AmuseKit +// +// Created by Jota Uribe on 16/09/22. +// + +@testable import AmuseKit + + +extension AmuseKit.DataProvider { + static func mock(resourceName: String) -> AmuseKit.DataProvider { + var service: MockAPIService = MockAPIService() + service.resourceName = resourceName + var mock: AmuseKit.DataProvider + mock = .init(storage: MockStorageService(), + service: service) + mock.setDeveloperToken("A1D2E3V4T5O6K7E8N9") + return mock + } +} diff --git a/Tests/AmuseKitTests/Storage/MockStorageService.swift b/Tests/AmuseKitTests/Helpers/MockStorageService.swift similarity index 81% rename from Tests/AmuseKitTests/Storage/MockStorageService.swift rename to Tests/AmuseKitTests/Helpers/MockStorageService.swift index 15e0491..ac6337f 100644 --- a/Tests/AmuseKitTests/Storage/MockStorageService.swift +++ b/Tests/AmuseKitTests/Helpers/MockStorageService.swift @@ -8,7 +8,7 @@ import Foundation @testable import AmuseKit -struct MockStorageService: StorageService { +class MockStorageService: StorageService { var developerToken: String? var userToken: String? } diff --git a/Tests/AmuseKitTests/Networking/DataProviderTests.swift b/Tests/AmuseKitTests/Networking/DataProviderTests.swift index dcd7847..9c7d96d 100644 --- a/Tests/AmuseKitTests/Networking/DataProviderTests.swift +++ b/Tests/AmuseKitTests/Networking/DataProviderTests.swift @@ -18,94 +18,94 @@ final class DataProviderTests: XCTestCase { tasks = [] } - // MARK: - Catalog Methods + func test_developerTokenSavesOnStorage() { + let developerToken = "A1U2S3E4R5T6O7K8E9N0" + let mockStorage = MockStorageService() + let sut: AmuseKit.DataProvider = .init(storage: mockStorage, + service: MockAPIService()) + sut.setDeveloperToken(developerToken) + XCTAssertEqual(mockStorage.developerToken, developerToken) + } - func testCatalogAlbums() throws { - let completionExpectation = XCTestExpectation(description: "completion should be called") - let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "catalog_albums") - try mock.catalog(.albums, ids: ["123", "456", "789"]) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + func test_userTokenSavesOnStorage() { + let userToken = "A1U2S3E4R5T6O7K8E9N0" + let mockStorage = MockStorageService() + let sut: AmuseKit.DataProvider = .init(storage: mockStorage, + service: MockAPIService()) + sut.setUserToken(userToken) + XCTAssertEqual(mockStorage.userToken, userToken) } - func testCatalogArtists() throws { + func test_request_withErrorResponse_returnsErrors() throws { let completionExpectation = XCTestExpectation(description: "completion should be called") let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "catalog_artists") - try mock.catalog(.artists, ids: ["123", "456", "789"]) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + let sut: AmuseKit.DataProvider = .mock(resourceName: "invalid_authentication") + try sut.library(.albums).sink { error in + completionExpectation.fulfill() + } receiveValue: { response in + let error = response.errors?.first + XCTAssertEqual(error?.id, "KHFHBMG2ZFDUYJQUMALKZAZ6LI") + XCTAssertEqual(error?.code, "40300") + XCTAssertEqual(error?.detail, "Invalid authentication") + XCTAssertEqual(error?.status, "403") + valueExpectation.fulfill() + }.store(in: &tasks) + wait(for: [valueExpectation, completionExpectation], timeout: timeout) } - func testCatalogMusicVideos() throws { - let completionExpectation = XCTestExpectation(description: "completion should be called") - let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "catalog_music-videos") - try mock.catalog(.musicVideos, ids: ["123", "456", "789"]) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { response in - print(response) - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + // MARK: - Catalog Methods + + func test_catalogRequest_withMissingDeveloperToken_throwsError() throws { + let sut: AmuseKit.DataProvider = .init(storage: MockStorageService(), + service: MockAPIService()) + XCTAssertThrowsError(try sut.catalog(.albums, ids: [])) } - func testCatalogPlaylists() throws { - let completionExpectation = XCTestExpectation(description: "completion should be called") - let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "catalog_playlists") - try mock.catalog(.playlists, ids: ["123", "456", "789"]) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + func test_catalogAlbumsRequest_withValidResponse_returnsData() throws { + let ids = ["123", "456", "789"] + let sut: AmuseKit.DataProvider = .mock(resourceName: "catalog_albums") + try test_catalogRequest_withValidResponse_returnsData(provider: sut, + resourceType: .albums, + ids: ids) } - func testCatalogSongs() throws { - let completionExpectation = XCTestExpectation(description: "completion should be called") - let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "catalog_songs") - try mock.catalog(.songs, ids: ["123", "456", "789"]) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + func test_catalogArtistsRequest_withValidResponse_returnsData() throws { + let ids = ["123", "456", "789"] + let sut: AmuseKit.DataProvider = .mock(resourceName: "catalog_artists") + try test_catalogRequest_withValidResponse_returnsData(provider: sut, + resourceType: .artists, + ids: ids) } - - func testCatalogSearch() throws { + + func test_catalogMusicVideosRequest_withValidResponse_returnsData() throws { + let ids = ["123", "456", "789"] + let sut: AmuseKit.DataProvider = .mock(resourceName: "catalog_music-videos") + try test_catalogRequest_withValidResponse_returnsData(provider: sut, + resourceType: .musicVideos, + ids: ids) + } + + func test_catalogPlaylistsRequest_withValidResponse_returnsData() throws { + let ids = ["123", "456", "789"] + let sut: AmuseKit.DataProvider = .mock(resourceName: "catalog_playlists") + try test_catalogRequest_withValidResponse_returnsData(provider: sut, + resourceType: .playlists, + ids: ids) + } + + func test_catalogSongsRequest_withValidResponse_returnsData() throws { + let ids = ["123", "456", "789"] + let sut: AmuseKit.DataProvider = .mock(resourceName: "catalog_songs") + try test_catalogRequest_withValidResponse_returnsData(provider: sut, + resourceType: .songs, + ids: ids) + } + + func test_catalogSearchRequest_withValidResponse_returnsResults() throws { let completionExpectation = XCTestExpectation(description: "completion should be called") let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "catalog_search") - try mock.catalogSearch(searchTerm: "") + let sut: AmuseKit.DataProvider = .mock(resourceName: "catalog_search") + try sut.catalogSearch(searchTerm: "") .receive(on: DispatchQueue.main) .sink(receiveCompletion: { _ in completionExpectation.fulfill() @@ -117,83 +117,102 @@ final class DataProviderTests: XCTestCase { XCTAssertNotNil(value.results?.musicVideos) valueExpectation.fulfill() }).store(in: &tasks) - + wait(for: [completionExpectation, valueExpectation], timeout: timeout) } // MARK: Library Methods - func testLibraryAlbums() throws { - let completionExpectation = XCTestExpectation(description: "completion should be called") - let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "library_albums") - try mock.library(.albums) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + func test_libraryRequest_withMissingDeveloperToken_throwsError() throws { + let sut: AmuseKit.DataProvider = .init(storage: MockStorageService(), + service: MockAPIService()) + XCTAssertThrowsError(try sut.library(.albums)) } - func testLibraryArtists() throws { - let completionExpectation = XCTestExpectation(description: "completion should be called") - let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "library_artists") - try mock.library(.artists) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + func test_libraryAlbumsRequest_withValidResponse_returnsData() throws { + let sut: AmuseKit.DataProvider = .mock(resourceName: "library_albums") + try test_libraryRequest_withValidResponse_returnsData(provider: sut, + resourceType: .albums) } - func testLibraryPlaylists() throws { - let completionExpectation = XCTestExpectation(description: "completion should be called") - let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "library_playlists") - try mock.library(.playlists) - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - completionExpectation.fulfill() - }, receiveValue: { value in - XCTAssertNotNil(value.data) - valueExpectation.fulfill() - }).store(in: &tasks) - - wait(for: [completionExpectation, valueExpectation], timeout: timeout) + func test_libraryArtistsRequest_withValidResponse_returnsData() throws { + let sut: AmuseKit.DataProvider = .mock(resourceName: "library_artists") + try test_libraryRequest_withValidResponse_returnsData(provider: sut, + resourceType: .artists) + } + + func test_libraryMusicVideosRequest_withValidResponse_returnsData() throws { + let sut: AmuseKit.DataProvider = .mock(resourceName: "library_music-videos") + try test_libraryRequest_withValidResponse_returnsData(provider: sut, + resourceType: .musicVideos) } - func testLibraryMusicVideos() throws { + func test_libraryPlaylistsRequest_withValidResponse_returnsData() throws { + let sut: AmuseKit.DataProvider = .mock(resourceName: "library_playlists") + try test_libraryRequest_withValidResponse_returnsData(provider: sut, + resourceType: .playlists) + } + + func test_librarySongsRequest_withValidResponse_returnsData() throws { + let sut: AmuseKit.DataProvider = .mock(resourceName: "library_songs") + try test_libraryRequest_withValidResponse_returnsData(provider: sut, + resourceType: .songs) + } + + func test_LibrarySearchRequest_withValidResponse_returnsResults() throws { let completionExpectation = XCTestExpectation(description: "completion should be called") let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "library_music-videos") - try mock.library(.musicVideos) + let sut: AmuseKit.DataProvider = .mock(resourceName: "library_search") + try sut.librarySearch(searchTerm: "") .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { error in - print(error) + .sink(receiveCompletion: { _ in completionExpectation.fulfill() }, receiveValue: { value in - XCTAssertNotNil(value.data) + XCTAssertNotNil(value.results?.playlists) + XCTAssertNotNil(value.results?.albums) + XCTAssertNotNil(value.results?.artists) + XCTAssertNotNil(value.results?.songs) valueExpectation.fulfill() }).store(in: &tasks) - + wait(for: [completionExpectation, valueExpectation], timeout: timeout) } + + static var allTests = [ + ("test_developerTokenSavesOnStorage", test_developerTokenSavesOnStorage), + ("test_userTokenSavesOnStorage", test_userTokenSavesOnStorage), + ("test_request_withErrorResponse_returnsErrors", test_request_withErrorResponse_returnsErrors), + // Catalog + ("test_catalogRequest_withMissingDeveloperToken_throwsError", test_catalogRequest_withMissingDeveloperToken_throwsError), + ("test_catalogAlbumsRequest_withValidResponse_returnsData", test_catalogAlbumsRequest_withValidResponse_returnsData), + ("test_catalogArtistsRequest_withValidResponse_returnsData", test_catalogArtistsRequest_withValidResponse_returnsData), + ("test_catalogMusicVideosRequest_withValidResponse_returnsData", test_catalogMusicVideosRequest_withValidResponse_returnsData), + ("test_catalogPlaylistsRequest_withValidResponse_returnsData", test_catalogPlaylistsRequest_withValidResponse_returnsData), + ("test_catalogSongsRequest_withValidResponse_returnsData", test_catalogSongsRequest_withValidResponse_returnsData), + ("test_catalogSearchRequest_withValidResponse_returnsResults", test_catalogSearchRequest_withValidResponse_returnsResults), + // Library + ("test_libraryRequest_withMissingDeveloperToken_throwsError", test_libraryRequest_withMissingDeveloperToken_throwsError), + ("test_libraryAlbumsRequest_withValidResponse_returnsData", test_libraryAlbumsRequest_withValidResponse_returnsData), + ("test_libraryArtistsRequest_withValidResponse_returnsData", test_libraryArtistsRequest_withValidResponse_returnsData), + ("test_libraryMusicVideosRequest_withValidResponse_returnsData", test_libraryMusicVideosRequest_withValidResponse_returnsData), + ("test_libraryPlaylistsRequest_withValidResponse_returnsData", test_libraryPlaylistsRequest_withValidResponse_returnsData), + ("test_librarySongsRequest_withValidResponse_returnsData", test_librarySongsRequest_withValidResponse_returnsData), + ("test_LibrarySearchRequest_withValidResponse_returnsResults", test_LibrarySearchRequest_withValidResponse_returnsResults) + ] +} +// MARK: - Helper Methods - func testLibrarySongs() throws { +extension DataProviderTests { + + private func test_catalogRequest_withValidResponse_returnsData( + provider: AmuseKit.DataProvider, + resourceType: AmuseKit.CatalogResourceConvertible, + ids: [String] + ) throws { let completionExpectation = XCTestExpectation(description: "completion should be called") let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "library_songs") - try mock.library(.songs) + try provider.catalog(resourceType, ids: ids) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { _ in completionExpectation.fulfill() @@ -201,56 +220,25 @@ final class DataProviderTests: XCTestCase { XCTAssertNotNil(value.data) valueExpectation.fulfill() }).store(in: &tasks) - + wait(for: [completionExpectation, valueExpectation], timeout: timeout) } - func testLibrarySearch() throws { + private func test_libraryRequest_withValidResponse_returnsData( + provider: AmuseKit.DataProvider, + resourceType: AmuseKit.LibraryResourceConvertible + ) throws { let completionExpectation = XCTestExpectation(description: "completion should be called") let valueExpectation = XCTestExpectation(description: "value callback should be called") - let mock = mockDataProvider(resourceName: "library_search") - try mock.librarySearch(searchTerm: "") + try provider.library(resourceType) .receive(on: DispatchQueue.main) .sink(receiveCompletion: { _ in completionExpectation.fulfill() }, receiveValue: { value in - XCTAssertNotNil(value.results?.playlists) - XCTAssertNotNil(value.results?.albums) - XCTAssertNotNil(value.results?.artists) - XCTAssertNotNil(value.results?.songs) + XCTAssertNotNil(value.data) valueExpectation.fulfill() }).store(in: &tasks) - + wait(for: [completionExpectation, valueExpectation], timeout: timeout) } - - // MARK: - Helper Methods - - private func mockDataProvider(resourceName: String) -> AmuseKit.DataProvider { - var service: MockAPIService = MockAPIService() - service.resourceName = resourceName - var mock: AmuseKit.DataProvider - mock = .init(storage: MockStorageService(), - service: service) - mock.setDeveloperToken("A1D2E3V4T5O6K7E8N9") - return mock - } - - static var allTests = [ - // Catalog - ("testCatalogAlbums", testCatalogAlbums), - ("testCatalogArtists", testCatalogArtists), - ("testCatalogMusicVideos", testCatalogMusicVideos), - ("testCatalogPlaylists", testCatalogPlaylists), - ("testCatalogSongs", testCatalogSongs), - ("testCatalogSearch", testCatalogSearch), - // Library - ("testLibraryAlbums", testLibraryAlbums), - ("testLibraryArtists", testLibraryArtists), - ("testLibraryMusicVideos", testLibraryMusicVideos), - ("testLibraryPlaylists", testLibraryPlaylists), - ("testLibrarySongs", testLibrarySongs), - ("testLibrarySearch", testLibrarySearch) - ] } - diff --git a/Tests/AmuseKitTests/Networking/RouterTests.swift b/Tests/AmuseKitTests/Networking/RouterTests.swift index 750c9c6..22aa0ce 100644 --- a/Tests/AmuseKitTests/Networking/RouterTests.swift +++ b/Tests/AmuseKitTests/Networking/RouterTests.swift @@ -9,47 +9,138 @@ import XCTest @testable import AmuseKit class RouterTests: XCTestCase { + let countryCode: String = "us" - func testLibraryPlaylistsRouter() throws { - let router = AmuseKit.Router.library(.playlists) + // MARK: - Catalog Routers + + func test_catalogAlbumsRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.catalog(countryCode: countryCode, resourceType: .albums) + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "albums") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/catalog/us/albums?") + + } + + func test_catalogArtistsRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.catalog(countryCode: countryCode, resourceType: .artists) + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "artists") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/catalog/us/artists?") + } + + func test_catalogMusicVideosRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.catalog(countryCode: countryCode, resourceType: .musicVideos) + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "music-videos") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/catalog/us/music-videos?") + } + + func test_catalogPlaylistsRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.catalog(countryCode: countryCode, resourceType: .playlists) let url = router.asURL([]) XCTAssertEqual(url?.lastPathComponent, "playlists") - _ = try XCTUnwrap(router.asURLRequest([])) + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/catalog/us/playlists?") + } + + func test_catalogSongsRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.catalog(countryCode: countryCode, resourceType: .songs) + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "songs") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/catalog/us/songs?") } - func testLibraryAlbumsRouter() throws { + // MARK: - Library Routers + + func test_libraryAlbumsRouter_returnsValidURLRequest() throws { let router = AmuseKit.Router.library(.albums) let url = router.asURL([]) XCTAssertEqual(url?.lastPathComponent, "albums") - _ = try XCTUnwrap(router.asURLRequest([])) + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/me/library/albums?") } - func testLibraryArtistsRouter() throws { + func test_libraryArtistsRouter_returnsValidURLRequest() throws { let router = AmuseKit.Router.library(.artists) let url = router.asURL([]) XCTAssertEqual(url?.lastPathComponent, "artists") - _ = try XCTUnwrap(router.asURLRequest([])) + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/me/library/artists?") + } + + func test_libraryMusicVideosRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.library(.musicVideos) + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "music-videos") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/me/library/music-videos?") + } + + func test_libraryPlaylistsRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.library(.playlists) + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "playlists") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/me/library/playlists?") } - func testLibrarySongsRouter() throws { + func test_librarySongsRouter_returnsValidURLRequest() throws { let router = AmuseKit.Router.library(.songs) let url = router.asURL([]) XCTAssertEqual(url?.lastPathComponent, "songs") - _ = try XCTUnwrap(router.asURLRequest([])) + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/me/library/songs?") } - func testLibraryMusicVideosRouter() throws { - let router = AmuseKit.Router.library(.musicVideos) + // MARK: - Recommendations + + func test_recommendationsRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.recommendations let url = router.asURL([]) - XCTAssertEqual(url?.lastPathComponent, "music-videos") - _ = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(url?.lastPathComponent, "recommendations") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/me/recommendations?") + } + + // MARK: - Search + + func test_librarySearchRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.librarySearch + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "search") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/me/library/search?") + } + + func test_searchRouter_returnsValidURLRequest() throws { + let router = AmuseKit.Router.search(countryCode: countryCode) + let url = router.asURL([]) + XCTAssertEqual(url?.lastPathComponent, "search") + let request = try XCTUnwrap(router.asURLRequest([])) + XCTAssertEqual(request.url?.absoluteString, "https://api.music.apple.com/v1/catalog/us/search?") } static var allTests = [ - ("testLibraryPlaylistsRouter", testLibraryPlaylistsRouter), - ("testLibraryAlbumsRouter", testLibraryAlbumsRouter), - ("testLibraryArtistsRouter", testLibraryArtistsRouter), - ("testLibrarySongsRouter", testLibrarySongsRouter), - ("testLibraryMusicVideosRouter", testLibraryMusicVideosRouter) + // Catalog + ("test_catalogAlbumsRouter_returnsValidURLRequest", test_catalogAlbumsRouter_returnsValidURLRequest), + ("test_catalogArtistsRouter_returnsValidURLRequest", test_catalogArtistsRouter_returnsValidURLRequest), + ("test_catalogMusicVideosRouter_returnsValidURLRequest", test_catalogMusicVideosRouter_returnsValidURLRequest), + ("test_catalogPlaylistsRouter_returnsValidURLRequest", test_catalogPlaylistsRouter_returnsValidURLRequest), + ("test_catalogSongsRouter_returnsValidURLRequest", test_catalogSongsRouter_returnsValidURLRequest), + // Library + ("test_libraryAlbumsRouter_returnsValidURLRequest", test_libraryAlbumsRouter_returnsValidURLRequest), + ("test_libraryArtistsRouter_returnsValidURLRequest", test_libraryArtistsRouter_returnsValidURLRequest), + ("test_libraryMusicVideosRouter_returnsValidURLRequest", test_libraryMusicVideosRouter_returnsValidURLRequest), + ("test_libraryPlaylistsRouter_returnsValidURLRequest", test_libraryPlaylistsRouter_returnsValidURLRequest), + ("test_librarySongsRouter_returnsValidURLRequest", test_librarySongsRouter_returnsValidURLRequest), + // Recommendations + ("test_recommendationsRouter_returnsValidURLRequest", test_recommendationsRouter_returnsValidURLRequest), + // Search + ("test_librarySearchRouter_returnsValidURLRequest", test_librarySearchRouter_returnsValidURLRequest), + ("test_searchRouter_returnsValidURLRequest", test_searchRouter_returnsValidURLRequest) ] }