Implemented the LocationsClient client.
This commit is contained in:
parent
c1a2acb248
commit
c3e0d86870
@ -36,6 +36,7 @@ let package = Package(
|
|||||||
.testTarget(
|
.testTarget(
|
||||||
name: "LocationsTests",
|
name: "LocationsTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"APICore",
|
||||||
"Locations"
|
"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.
|
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
public struct Location {
|
public struct Location: Equatable {
|
||||||
|
|
||||||
|
// MARK: Properties
|
||||||
|
|
||||||
public let name: String?
|
public let name: String?
|
||||||
public let latitude: Float
|
public let latitude: Float
|
||||||
public let longitude: 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
|
// 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