[Feature] Service #4
@ -8,7 +8,7 @@ struct GetAmiiboEndpoint: Endpoint {
|
||||
let scheme: String = .Scheme.https
|
||||
let host: String = .Host.amiiboApi
|
||||
let port: Int? = nil
|
||||
let path: String = .Path.type
|
||||
let path: String = .Path.amiibo
|
||||
let parameters: Parameters
|
||||
let method: HTTPRequestMethod = .get
|
||||
let headers: [String : String] = [:]
|
||||
|
119
Sources/Filters/AmiiboFilter.swift
Normal file
119
Sources/Filters/AmiiboFilter.swift
Normal file
@ -0,0 +1,119 @@
|
||||
public struct AmiiboFilter {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let id: String?
|
||||
private let head: String?
|
||||
private let tail: String?
|
||||
private let name: String?
|
||||
private let type: String?
|
||||
private let gameSeries: String?
|
||||
private let amiiboSeries: String?
|
||||
private let character: String?
|
||||
private let showExtras: ShowExtras
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init(
|
||||
id: String? = nil,
|
||||
head: String? = nil,
|
||||
tail: String? = nil,
|
||||
name: String? = nil,
|
||||
type: String? = nil,
|
||||
gameSeries: String? = nil,
|
||||
amiiboSeries: String? = nil,
|
||||
character: String? = nil,
|
||||
showExtras: ShowExtras = .none
|
||||
) {
|
||||
self.id = id
|
||||
self.head = head
|
||||
self.tail = tail
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.gameSeries = gameSeries
|
||||
self.amiiboSeries = amiiboSeries
|
||||
self.character = character
|
||||
self.showExtras = showExtras
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Filter
|
||||
|
||||
extension AmiiboFilter: Filter {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
func makeParameters() -> [String : String?] {
|
||||
var parameters: [String : String?] = [:]
|
||||
|
||||
if let id {
|
||||
parameters[.Key.id] = id
|
||||
}
|
||||
|
||||
if let head {
|
||||
parameters[.Key.head] = head
|
||||
}
|
||||
|
||||
if let tail {
|
||||
parameters[.Key.tail] = tail
|
||||
}
|
||||
|
||||
if let name {
|
||||
parameters[.Key.name] = name
|
||||
}
|
||||
|
||||
if let type {
|
||||
parameters[.Key.type] = type
|
||||
}
|
||||
|
||||
if let gameSeries {
|
||||
parameters[.Key.gameSeries] = gameSeries
|
||||
}
|
||||
|
||||
if let amiiboSeries {
|
||||
parameters[.Key.amiiboSeries] = amiiboSeries
|
||||
}
|
||||
|
||||
if let character {
|
||||
parameters[.Key.character] = character
|
||||
}
|
||||
|
||||
switch showExtras {
|
||||
case .games:
|
||||
parameters[.Key.showGames] = nil
|
||||
case .usage:
|
||||
parameters[.Key.showUsage] = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Enumerations
|
||||
|
||||
public enum ShowExtras {
|
||||
case none
|
||||
case games
|
||||
case usage
|
||||
}
|
||||
|
||||
// MARK: - String+Key
|
||||
|
||||
private extension String {
|
||||
enum Key {
|
||||
static let id = "id"
|
||||
static let head = "head"
|
||||
static let tail = "tail"
|
||||
static let name = "name"
|
||||
static let type = "type"
|
||||
static let gameSeries = "gameSeries"
|
||||
static let amiiboSeries = "amiiboSeries"
|
||||
static let character = "character"
|
||||
static let showGames = "showgames"
|
||||
static let showUsage = "showusage"
|
||||
}
|
||||
}
|
56
Sources/Filters/KeyNameFilter.swift
Normal file
56
Sources/Filters/KeyNameFilter.swift
Normal file
@ -0,0 +1,56 @@
|
||||
public struct KeyNameFilter {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let key: String?
|
||||
private let name: String?
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init(
|
||||
key: String? = nil,
|
||||
name: String? = nil
|
||||
) {
|
||||
self.key = key
|
||||
self.name = name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Filter
|
||||
|
||||
extension KeyNameFilter: Filter {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
func makeParameters() -> [String : String?] {
|
||||
var parameters: [String : String?] = [:]
|
||||
|
||||
if let key {
|
||||
parameters[.Key.key] = key
|
||||
}
|
||||
|
||||
if let name {
|
||||
parameters[.Key.name] = name
|
||||
}
|
||||
|
||||
return parameters
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Type aliases
|
||||
|
||||
public typealias AmiiboSeriesFilter = KeyNameFilter
|
||||
public typealias AmiiboTypeFilter = KeyNameFilter
|
||||
public typealias CharacterFilter = KeyNameFilter
|
||||
public typealias GameSeriesFilter = KeyNameFilter
|
||||
|
||||
// MARK: - String+Key
|
||||
|
||||
private extension String {
|
||||
enum Key {
|
||||
static let key = "key"
|
||||
static let name = "name"
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
extension Amiibo {
|
||||
public struct Game {
|
||||
public let identifiers: [String]
|
||||
public let name: String
|
||||
public let usage: [Usage]
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
||||
extension Amiibo.Game: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case identifiers = "gameID"
|
||||
case name = "gameName"
|
||||
case usage = "amiiboUsage"
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension Amiibo {
|
||||
public struct Release {
|
||||
public let australia: Date?
|
||||
public let europe: Date?
|
||||
public let japan: Date?
|
||||
public let america: Date?
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
||||
extension Amiibo.Release: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case australia = "au"
|
||||
case europe = "eu"
|
||||
case japan = "jp"
|
||||
case america = "na"
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
extension Amiibo {
|
||||
public struct Usage {
|
||||
public let explanation: String
|
||||
public let isWritable: Bool
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
||||
extension Amiibo.Usage: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case explanation = "Usage"
|
||||
case isWritable = "write"
|
||||
}
|
||||
}
|
15
Sources/Models/Game.swift
Normal file
15
Sources/Models/Game.swift
Normal file
@ -0,0 +1,15 @@
|
||||
public struct Game {
|
||||
public let ids: [String]
|
||||
public let name: String
|
||||
public let usage: [Usage]?
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
||||
extension Game: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case ids = "gameID"
|
||||
case name = "gameName"
|
||||
case usage = "amiiboUsage"
|
||||
}
|
||||
}
|
19
Sources/Models/Release.swift
Normal file
19
Sources/Models/Release.swift
Normal file
@ -0,0 +1,19 @@
|
||||
import Foundation
|
||||
|
||||
public struct Release {
|
||||
public let australia: Date?
|
||||
public let europe: Date?
|
||||
public let japan: Date?
|
||||
public let america: Date?
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
||||
extension Release: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case australia = "au"
|
||||
case europe = "eu"
|
||||
case japan = "jp"
|
||||
case america = "na"
|
||||
}
|
||||
}
|
27
Sources/Models/Result.swift
Normal file
27
Sources/Models/Result.swift
Normal file
@ -0,0 +1,27 @@
|
||||
struct Result<Model: Decodable> {
|
||||
let items: [Model]
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
||||
extension Result: Decodable {
|
||||
|
||||
// MARK: Enumerations
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case items = "amiibo"
|
||||
}
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
do {
|
||||
self.items = try container.decode([Model].self, forKey: .items)
|
||||
} catch {
|
||||
self.items = [try container.decode(Model.self, forKey: .items)]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
13
Sources/Models/Usage.swift
Normal file
13
Sources/Models/Usage.swift
Normal file
@ -0,0 +1,13 @@
|
||||
public struct Usage {
|
||||
public let explanation: String
|
||||
public let isWritable: Bool
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
||||
extension Usage: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case explanation = "Usage"
|
||||
case isWritable = "write"
|
||||
}
|
||||
}
|
9
Sources/Protocols/Filter.swift
Normal file
9
Sources/Protocols/Filter.swift
Normal file
@ -0,0 +1,9 @@
|
||||
import Communications
|
||||
|
||||
protocol Filter {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
func makeParameters() -> Endpoint.Parameters
|
||||
|
||||
}
|
14
Sources/Protocols/Service.swift
Normal file
14
Sources/Protocols/Service.swift
Normal file
@ -0,0 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
protocol Service {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
func amiibos(filter: AmiiboFilter) async throws -> [Amiibo]
|
||||
func amiiboSeries(filter: AmiiboSeriesFilter) async throws -> [AmiiboSeries]
|
||||
func amiiboTypes(filter: AmiiboTypeFilter) async throws -> [AmiiboType]
|
||||
func gameSeries(filter: GameSeriesFilter) async throws -> [GameSeries]
|
||||
func characters(filter: CharacterFilter) async throws -> [Character]
|
||||
func lastUpdated() async throws -> Date
|
||||
|
||||
}
|
87
Sources/Services/AmiiboService.swift
Normal file
87
Sources/Services/AmiiboService.swift
Normal file
@ -0,0 +1,87 @@
|
||||
import Foundation
|
||||
|
||||
public struct AmiiboService {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let client: AmiiboClient
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init(configuration: URLSessionConfiguration) {
|
||||
self.client = .init(configuration: configuration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Service
|
||||
|
||||
extension AmiiboService: Service {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
public func amiibos(
|
||||
filter: AmiiboFilter = .init()
|
||||
) async throws -> [Amiibo] {
|
||||
client.setDateDecodingStrategy(.formatted(.dateOnly))
|
||||
|
||||
return try await client.request(
|
||||
endpoint: GetAmiiboEndpoint(parameters: filter.makeParameters()),
|
||||
as: Result<Amiibo>.self
|
||||
).items
|
||||
}
|
||||
|
||||
public func amiiboSeries(
|
||||
filter: AmiiboSeriesFilter = .init()
|
||||
) async throws -> [AmiiboSeries] {
|
||||
client.setDateDecodingStrategy(.deferredToDate)
|
||||
|
||||
return try await client.request(
|
||||
endpoint: GetSeriesEndpoint(parameters: filter.makeParameters()),
|
||||
as: Result<AmiiboSeries>.self
|
||||
).items
|
||||
}
|
||||
|
||||
public func amiiboTypes(
|
||||
filter: AmiiboTypeFilter = .init()
|
||||
) async throws -> [AmiiboType] {
|
||||
client.setDateDecodingStrategy(.deferredToDate)
|
||||
|
||||
return try await client.request(
|
||||
endpoint: GetTypeEndpoint(parameters: filter.makeParameters()),
|
||||
as: Result<AmiiboType>.self
|
||||
).items
|
||||
}
|
||||
|
||||
public func characters(
|
||||
filter: CharacterFilter = .init()
|
||||
) async throws -> [Character] {
|
||||
client.setDateDecodingStrategy(.deferredToDate)
|
||||
|
||||
return try await client.request(
|
||||
endpoint: GetCharacterEndpoint(parameters: filter.makeParameters()),
|
||||
as: Result<Character>.self
|
||||
).items
|
||||
}
|
||||
|
||||
public func gameSeries(
|
||||
filter: GameSeriesFilter = .init()
|
||||
) async throws -> [GameSeries] {
|
||||
client.setDateDecodingStrategy(.deferredToDate)
|
||||
|
||||
return try await client.request(
|
||||
endpoint: GetGameSeriesEndpoint(parameters: filter.makeParameters()),
|
||||
as: Result<GameSeries>.self
|
||||
).items
|
||||
}
|
||||
|
||||
public func lastUpdated() async throws -> Date {
|
||||
client.setDateDecodingStrategy(.formatted(.dateAndTime))
|
||||
|
||||
return try await client.request(
|
||||
endpoint: GetLastUpdatedEndpoint(),
|
||||
as: LastUpdated.self
|
||||
).timestamp
|
||||
}
|
||||
|
||||
}
|
@ -38,7 +38,7 @@ final class AmiiboClientTests: XCTestCase {
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Seed.dataWithoutTimestamp
|
||||
data: .Client.Seed.dataWithoutTimestamp
|
||||
)
|
||||
|
||||
// WHEN
|
||||
@ -58,7 +58,7 @@ final class AmiiboClientTests: XCTestCase {
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Seed.dataWithDateAndTime
|
||||
data: .Client.Seed.dataWithDateAndTime
|
||||
)
|
||||
|
||||
client.setDateDecodingStrategy(.formatted(.dateAndTime))
|
||||
@ -80,7 +80,7 @@ final class AmiiboClientTests: XCTestCase {
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Seed.dataUnrelated
|
||||
data: .Client.Seed.dataUnrelated
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
@ -102,7 +102,7 @@ final class AmiiboClientTests: XCTestCase {
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Seed.dataWithDateAndTime
|
||||
data: .Client.Seed.dataWithDateAndTime
|
||||
)
|
||||
|
||||
client.setDateDecodingStrategy(.formatted(.dateOnly))
|
||||
@ -126,7 +126,7 @@ final class AmiiboClientTests: XCTestCase {
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .notFound,
|
||||
data: .Seed.dataWithoutTimestamp
|
||||
data: .Client.Seed.dataWithoutTimestamp
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
|
@ -1,10 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
enum Seed {
|
||||
static let dataUnrelated = "{\"something\":\"Something goes in here...\"}".data(using: .utf8)
|
||||
static let dataWithoutTimestamp = "{\"timestamp\":null}".data(using: .utf8)
|
||||
static let dataWithDateOnly = "{\"timestamp\":\"2023-03-23\"}".data(using: .utf8)
|
||||
static let dataWithDateAndTime = "{\"timestamp\":\"2023-03-23T13:11:20.382254\"}".data(using: .utf8)
|
||||
}
|
||||
}
|
31
Tests/Helpers/Extensions/Data+Seed.swift
Normal file
31
Tests/Helpers/Extensions/Data+Seed.swift
Normal file
@ -0,0 +1,31 @@
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
enum Client {
|
||||
enum Seed {
|
||||
static let dataUnrelated = "{\"something\":\"Something goes in here...\"}".data(using: .utf8)
|
||||
static let dataWithoutTimestamp = "{\"timestamp\":null}".data(using: .utf8)
|
||||
static let dataWithDateOnly = "{\"timestamp\":\"2023-03-23\"}".data(using: .utf8)
|
||||
static let dataWithDateAndTime = "{\"timestamp\":\"2023-03-23T13:11:20.382254\"}".data(using: .utf8)
|
||||
}
|
||||
}
|
||||
|
||||
enum Service {
|
||||
static let amiibos = String.Amiibo.withoutGameOrUsage.data(using: .utf8)
|
||||
static let amiibosWithGames = String.Amiibo.withGames.data(using: .utf8)
|
||||
static let amiibosWithUsage = String.Amiibo.withUsage.data(using: .utf8)
|
||||
static let amiiboWithBadFormattedDates = String.Amiibo.withBadFormattedReleaseDates.data(using: .utf8)
|
||||
static let amiiboWithMissingFields = String.Amiibo.withMissingFields.data(using: .utf8)
|
||||
static let amiiboSeries = String.AmiiboSeries.all.data(using: .utf8)
|
||||
static let amiiboSeriesWithMissingFields = String.AmiiboSeries.withMissingFields.data(using: .utf8)
|
||||
static let amiiboTypes = String.AmiiboType.all.data(using: .utf8)
|
||||
static let amiiboTypesWithMissingFields = String.AmiiboType.withMissingFields.data(using: .utf8)
|
||||
static let characters = String.Character.all.data(using: .utf8)
|
||||
static let charactersWithMissingFields = String.Character.withMissingFields.data(using: .utf8)
|
||||
static let gameSeries = String.GameSeries.all.data(using: .utf8)
|
||||
static let gameSeriesWithMissingFields = String.GameSeries.withMissingFields.data(using: .utf8)
|
||||
static let lastUpdated = String.LastUpdated.all.data(using: .utf8)
|
||||
static let lastUpdatedWithBadFormattedDate = String.LastUpdated.withBadFormattedDate.data(using: .utf8)
|
||||
static let lastUpdatedWithBadInfo = String.LastUpdated.withBadInfo.data(using: .utf8)
|
||||
}
|
||||
}
|
11
Tests/Helpers/Extensions/GetAmiiboEndpoint+Init.swift
Normal file
11
Tests/Helpers/Extensions/GetAmiiboEndpoint+Init.swift
Normal file
@ -0,0 +1,11 @@
|
||||
@testable import AmiiboService
|
||||
|
||||
extension GetAmiiboEndpoint {
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init() {
|
||||
self.init(parameters: .init())
|
||||
}
|
||||
|
||||
}
|
11
Tests/Helpers/Extensions/GetCharacterEndpoint+Init.swift
Normal file
11
Tests/Helpers/Extensions/GetCharacterEndpoint+Init.swift
Normal file
@ -0,0 +1,11 @@
|
||||
@testable import AmiiboService
|
||||
|
||||
extension GetCharacterEndpoint {
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init() {
|
||||
self.init(parameters: .init())
|
||||
}
|
||||
|
||||
}
|
11
Tests/Helpers/Extensions/GetGameSeriesEndpoint+Init.swift
Normal file
11
Tests/Helpers/Extensions/GetGameSeriesEndpoint+Init.swift
Normal file
@ -0,0 +1,11 @@
|
||||
@testable import AmiiboService
|
||||
|
||||
extension GetGameSeriesEndpoint {
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init() {
|
||||
self.init(parameters: .init())
|
||||
}
|
||||
|
||||
}
|
11
Tests/Helpers/Extensions/GetSeriesEndpoint+Init.swift
Normal file
11
Tests/Helpers/Extensions/GetSeriesEndpoint+Init.swift
Normal file
@ -0,0 +1,11 @@
|
||||
@testable import AmiiboService
|
||||
|
||||
extension GetSeriesEndpoint {
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init() {
|
||||
self.init(parameters: .init())
|
||||
}
|
||||
|
||||
}
|
11
Tests/Helpers/Extensions/GetTypeEndpoint+Init.swift
Normal file
11
Tests/Helpers/Extensions/GetTypeEndpoint+Init.swift
Normal file
@ -0,0 +1,11 @@
|
||||
@testable import AmiiboService
|
||||
|
||||
extension GetTypeEndpoint {
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init() {
|
||||
self.init(parameters: .init())
|
||||
}
|
||||
|
||||
}
|
35
Tests/Helpers/Extensions/String+Seed.swift
Normal file
35
Tests/Helpers/Extensions/String+Seed.swift
Normal file
File diff suppressed because one or more lines are too long
515
Tests/Services/AmiiboServiceTests.swift
Normal file
515
Tests/Services/AmiiboServiceTests.swift
Normal file
@ -0,0 +1,515 @@
|
||||
import Communications
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import AmiiboService
|
||||
|
||||
final class AmiiboServiceTests: XCTestCase {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let makeURLRequest = MakeURLRequestUseCase()
|
||||
private let configuration = {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
|
||||
configuration.protocolClasses = [MockURLProtocol.self]
|
||||
|
||||
return configuration
|
||||
}()
|
||||
|
||||
private var service: AmiiboService!
|
||||
|
||||
// MARK: Setup
|
||||
|
||||
override func setUp() async throws {
|
||||
service = .init(configuration: configuration)
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
service = nil
|
||||
}
|
||||
|
||||
// MARK: Amiibos tests
|
||||
|
||||
func test_amiibos() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetAmiiboEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiibos
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.amiibos()
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(result.isEmpty)
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertNil(result.first?.games3DS)
|
||||
XCTAssertNil(result.first?.gamesWiiU)
|
||||
XCTAssertNil(result.first?.gamesSwitch)
|
||||
XCTAssertNil(result.last?.games3DS)
|
||||
XCTAssertNil(result.last?.gamesWiiU)
|
||||
XCTAssertNil(result.last?.gamesSwitch)
|
||||
}
|
||||
|
||||
func test_amiibos_withGameData() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetAmiiboEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiibosWithGames
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.amiibos()
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(result.isEmpty)
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertNotNil(result.first?.games3DS)
|
||||
XCTAssertEqual(result.first?.games3DS?.count, 21)
|
||||
XCTAssertNil(result.first?.games3DS?.first?.usage)
|
||||
XCTAssertNotNil(result.first?.gamesWiiU)
|
||||
XCTAssertEqual(result.first?.gamesWiiU?.count, 8)
|
||||
XCTAssertNil(result.first?.gamesWiiU?.first?.usage)
|
||||
XCTAssertNotNil(result.first?.gamesSwitch)
|
||||
XCTAssertEqual(result.first?.gamesSwitch?.count, 8)
|
||||
XCTAssertNil(result.first?.gamesSwitch?.first?.usage)
|
||||
XCTAssertNotNil(result.last?.games3DS)
|
||||
XCTAssertEqual(result.last?.games3DS?.count, 22)
|
||||
XCTAssertNil(result.last?.games3DS?.first?.usage)
|
||||
XCTAssertNotNil(result.last?.gamesWiiU)
|
||||
XCTAssertEqual(result.last?.gamesWiiU?.count, 8)
|
||||
XCTAssertNil(result.last?.gamesWiiU?.first?.usage)
|
||||
XCTAssertNotNil(result.last?.gamesSwitch)
|
||||
XCTAssertEqual(result.last?.gamesSwitch?.count, 9)
|
||||
XCTAssertNil(result.last?.gamesSwitch?.first?.usage)
|
||||
}
|
||||
|
||||
func test_amiibos_withUsageData() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetAmiiboEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiibosWithUsage
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.amiibos()
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(result.isEmpty)
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertNotNil(result.first?.games3DS)
|
||||
XCTAssertEqual(result.first?.games3DS?.count, 21)
|
||||
XCTAssertNotNil(result.first?.games3DS?.first?.usage)
|
||||
XCTAssertEqual(result.first?.games3DS?.first?.usage?.count, 1)
|
||||
XCTAssertNotNil(result.first?.gamesWiiU)
|
||||
XCTAssertEqual(result.first?.gamesWiiU?.count, 8)
|
||||
XCTAssertNotNil(result.first?.gamesWiiU?.first?.usage)
|
||||
XCTAssertEqual(result.first?.gamesWiiU?.first?.usage?.count, 1)
|
||||
XCTAssertNotNil(result.first?.gamesSwitch)
|
||||
XCTAssertEqual(result.first?.gamesSwitch?.count, 8)
|
||||
XCTAssertNotNil(result.first?.gamesSwitch?.first?.usage)
|
||||
XCTAssertEqual(result.first?.gamesSwitch?.first?.usage?.count, 1)
|
||||
XCTAssertNotNil(result.last?.games3DS)
|
||||
XCTAssertEqual(result.last?.games3DS?.count, 22)
|
||||
XCTAssertNotNil(result.last?.games3DS?.first?.usage)
|
||||
XCTAssertEqual(result.last?.games3DS?.first?.usage?.count, 1)
|
||||
XCTAssertNotNil(result.last?.gamesWiiU)
|
||||
XCTAssertEqual(result.last?.gamesWiiU?.count, 8)
|
||||
XCTAssertNotNil(result.last?.gamesWiiU?.first?.usage)
|
||||
XCTAssertEqual(result.last?.gamesWiiU?.first?.usage?.count, 1)
|
||||
XCTAssertNotNil(result.last?.gamesSwitch)
|
||||
XCTAssertEqual(result.last?.gamesSwitch?.count, 9)
|
||||
XCTAssertNotNil(result.last?.gamesSwitch?.first?.usage)
|
||||
XCTAssertEqual(result.last?.gamesSwitch?.first?.usage?.count, 1)
|
||||
}
|
||||
|
||||
func test_amiibos_whenDateDecodingStrategyMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetAmiiboEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiiboWithBadFormattedDates
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.amiibos()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_amiibos_whenDataMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetAmiiboEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiiboWithMissingFields
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.amiibos()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_amiibos_whenResponseNotOk() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetAmiiboEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .notFound,
|
||||
data: .Service.amiibos
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.amiibos()
|
||||
} catch is AmiiboClientError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Amiibo series tests
|
||||
|
||||
func test_amiiboSeries() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetSeriesEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiiboSeries
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.amiiboSeries()
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(result.isEmpty)
|
||||
XCTAssertEqual(result.count, 25)
|
||||
}
|
||||
|
||||
func test_amiiboSeries_whenDataMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetSeriesEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiiboSeriesWithMissingFields
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.amiiboSeries()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_amiiboSeries_whenResponseNotOk() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetSeriesEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .internalServerError,
|
||||
data: .Service.amiiboSeries
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.amiiboSeries()
|
||||
} catch is AmiiboClientError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Amiibo types tests
|
||||
|
||||
func test_amiiboTypes() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetTypeEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiiboTypes
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.amiiboTypes()
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(result.isEmpty)
|
||||
XCTAssertEqual(result.count, 4)
|
||||
}
|
||||
|
||||
func test_amiiboTypes_whenDataMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetTypeEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.amiiboTypesWithMissingFields
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.amiiboTypes()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_amiiboTypes_whenResponseNotOk() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetTypeEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .forbidden,
|
||||
data: .Service.amiiboTypes
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.amiiboTypes()
|
||||
} catch is AmiiboClientError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Character tests
|
||||
|
||||
func test_character() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetCharacterEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.characters
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.characters()
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(result.isEmpty)
|
||||
XCTAssertEqual(result.count, 636)
|
||||
}
|
||||
|
||||
func test_character_whenDataMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetCharacterEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.charactersWithMissingFields
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.characters()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_character_whenResponseNotOk() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetCharacterEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .badGateway,
|
||||
data: .Service.characters
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.characters()
|
||||
} catch is AmiiboClientError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Game series tests
|
||||
|
||||
func test_gameSeries() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetGameSeriesEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.gameSeries
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.gameSeries()
|
||||
|
||||
// THEN
|
||||
XCTAssertFalse(result.isEmpty)
|
||||
XCTAssertEqual(result.count, 115)
|
||||
}
|
||||
|
||||
func test_gameSeries_whenDataMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetGameSeriesEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.gameSeriesWithMissingFields
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.gameSeries()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_gameSeries_whenResponseNotOk() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetGameSeriesEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .badRequest,
|
||||
data: .Service.gameSeries
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.gameSeries()
|
||||
} catch is AmiiboClientError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Last updated tests
|
||||
|
||||
func test_lastUpdated() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLastUpdatedEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.lastUpdated
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.lastUpdated()
|
||||
|
||||
// THEN
|
||||
XCTAssertNotNil(result)
|
||||
}
|
||||
|
||||
func test_lastUpdated_whenDateDecodingStrategyMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLastUpdatedEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.lastUpdatedWithBadFormattedDate
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.lastUpdated()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_lastUpdated_whenDataMismatch() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLastUpdatedEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .ok,
|
||||
data: .Service.lastUpdatedWithBadInfo
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.lastUpdated()
|
||||
} catch is DecodingError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_lastUpdated_whenResponseNotOk() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLastUpdatedEndpoint()
|
||||
let url: URL! = try makeURLRequest(endpoint: endpoint).url
|
||||
|
||||
MockURLProtocol.mockData[.init(url: url)] = .init(
|
||||
status: .unauthorized,
|
||||
data: .Service.lastUpdated
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
let _ = try await service.lastUpdated()
|
||||
} catch is AmiiboClientError {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user