Implemented the LocationsClient client.
This commit is contained in:
parent
c1a2acb248
commit
c3e0d86870
@ -36,6 +36,7 @@ let package = Package(
|
||||
.testTarget(
|
||||
name: "LocationsTests",
|
||||
dependencies: [
|
||||
"APICore",
|
||||
"Locations"
|
||||
]
|
||||
),
|
||||
|
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Client.swift
|
||||
// APICore
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
/// This protocol defines a client that will be making the API calls.
|
||||
public protocol Client {
|
||||
func request<Model: Decodable>(
|
||||
endpoint: some Endpoint,
|
||||
for model: Model.Type
|
||||
) async throws -> Model
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
//
|
||||
// LocationsClient.swift
|
||||
// Locations
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import APICore
|
||||
import Foundation
|
||||
|
||||
struct LocationsClient {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let session: URLSession
|
||||
private let decoder: JSONDecoder = .init()
|
||||
private let makeURLRequest: MakeURLRequestUseCase = .init()
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init(configuration: URLSessionConfiguration = .default) {
|
||||
self.session = .init(configuration: configuration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Client
|
||||
|
||||
extension LocationsClient: Client {
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
func request<Model: Decodable>(
|
||||
endpoint: some Endpoint,
|
||||
for model: Model.Type
|
||||
) async throws -> Model {
|
||||
let urlRequest = try makeURLRequest(endpoint: endpoint)
|
||||
let (data, response) = try await session.data(for: urlRequest)
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
throw LocationsClientError.responseNotReturned
|
||||
}
|
||||
|
||||
switch httpResponse.statusCode {
|
||||
case 200:
|
||||
return try decoder.decode(model, from: data)
|
||||
case 400...499:
|
||||
throw LocationsClientError.statusErrorClient
|
||||
case 500...599:
|
||||
throw LocationsClientError.statusErrorServer
|
||||
default:
|
||||
throw LocationsClientError.statusErrorUnexpected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Errors
|
||||
|
||||
public enum LocationsClientError: Error {
|
||||
case responseNotReturned
|
||||
case statusErrorClient
|
||||
case statusErrorServer
|
||||
case statusErrorUnexpected
|
||||
}
|
@ -6,10 +6,26 @@
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
public struct Location {
|
||||
public struct Location: Equatable {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
public let name: String?
|
||||
public let latitude: Float
|
||||
public let longitude: Float
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init(
|
||||
name: String? = nil,
|
||||
latitude: Float,
|
||||
longitude: Float
|
||||
) {
|
||||
self.name = name
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
|
@ -0,0 +1,164 @@
|
||||
//
|
||||
// LocationsClientTests.swift
|
||||
// LocationsTests
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import APICore
|
||||
import XCTest
|
||||
|
||||
@testable import Locations
|
||||
|
||||
final class LocationsClientTests: XCTestCase {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let makeURLRequest = MakeURLRequestUseCase()
|
||||
private let sessionConfiguration = {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
|
||||
configuration.protocolClasses = [MockURLProtocol.self]
|
||||
|
||||
return configuration
|
||||
}()
|
||||
|
||||
private var client: LocationsClient!
|
||||
private var url: URL!
|
||||
private var data: Data!
|
||||
|
||||
// MARK: Setup
|
||||
|
||||
override func setUp() async throws {
|
||||
client = .init(configuration: sessionConfiguration)
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
client = nil
|
||||
}
|
||||
|
||||
// MARK: Tests
|
||||
|
||||
func test_request_withGetLocationsEndpoint_forLocations_whenResponseOK() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 200,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await client.request(endpoint: endpoint, for: Locations.self)
|
||||
|
||||
// THEN
|
||||
XCTAssertEqual(result, Locations(locations: [
|
||||
.init(
|
||||
name: "Amsterdam",
|
||||
latitude: 52.3547498,
|
||||
longitude: 4.8339215
|
||||
),
|
||||
.init(
|
||||
name: "Mumbai",
|
||||
latitude: 19.0823998,
|
||||
longitude: 72.8111468
|
||||
),
|
||||
.init(
|
||||
name: "Copenhagen",
|
||||
latitude: 55.6713442,
|
||||
longitude: 12.523785
|
||||
),
|
||||
.init(
|
||||
latitude: 40.4380638,
|
||||
longitude: -3.7495758
|
||||
)
|
||||
]))
|
||||
}
|
||||
|
||||
func test_request_withGetLocationsEndpoint_forLocations_whenResponseClientError() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 404,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
_ = try await client.request(endpoint: endpoint, for: Locations.self)
|
||||
} catch LocationsClientError.statusErrorClient {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_request_withGetLocationsEndpoint_forLocations_whenResponseServerError() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 500,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
_ = try await client.request(endpoint: endpoint, for: Locations.self)
|
||||
} catch LocationsClientError.statusErrorServer {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_request_withGetLocationsEndpoint_forLocations_whenResponseUnexpectedError() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 302,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
_ = try await client.request(endpoint: endpoint, for: Locations.self)
|
||||
} catch LocationsClientError.statusErrorUnexpected {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Models
|
||||
|
||||
private struct Locations: Decodable, Equatable {
|
||||
public let locations: [Location]
|
||||
}
|
||||
|
||||
// MARK: - String+Constants
|
||||
|
||||
private extension Data {
|
||||
static let locations = "{\"locations\":[{\"name\":\"Amsterdam\",\"lat\":52.3547498,\"long\":4.8339215},{\"name\":\"Mumbai\",\"lat\":19.0823998,\"long\":72.8111468},{\"name\":\"Copenhagen\",\"lat\":55.6713442,\"long\":12.523785},{\"lat\":40.4380638,\"long\":-3.7495758}]}".data(using: .utf8)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user