[Feature] Service #4

Merged
javier merged 13 commits from feature/service into main 2023-04-21 18:16:38 +00:00
26 changed files with 1001 additions and 69 deletions

View File

@ -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] = [:]

View 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"
}
}

View 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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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
View 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"
}
}

View 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"
}
}

View 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)]
}
}
}

View 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"
}
}

View File

@ -0,0 +1,9 @@
import Communications
protocol Filter {
// MARK: Functions
func makeParameters() -> Endpoint.Parameters
}

View 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
}

View 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
}
}

View File

@ -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

View File

@ -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)
}
}

View 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)
}
}

View File

@ -0,0 +1,11 @@
@testable import AmiiboService
extension GetAmiiboEndpoint {
// MARK: Initialisers
init() {
self.init(parameters: .init())
}
}

View File

@ -0,0 +1,11 @@
@testable import AmiiboService
extension GetCharacterEndpoint {
// MARK: Initialisers
init() {
self.init(parameters: .init())
}
}

View File

@ -0,0 +1,11 @@
@testable import AmiiboService
extension GetGameSeriesEndpoint {
// MARK: Initialisers
init() {
self.init(parameters: .init())
}
}

View File

@ -0,0 +1,11 @@
@testable import AmiiboService
extension GetSeriesEndpoint {
// MARK: Initialisers
init() {
self.init(parameters: .init())
}
}

View File

@ -0,0 +1,11 @@
@testable import AmiiboService
extension GetTypeEndpoint {
// MARK: Initialisers
init() {
self.init(parameters: .init())
}
}

File diff suppressed because one or more lines are too long

View 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)
}
}
}