Compare commits
11 Commits
main
...
libraries/
Author | SHA1 | Date | |
---|---|---|---|
2508275714 | |||
c3e0d86870 | |||
c1a2acb248 | |||
35c2340a9b | |||
324a82a8e7 | |||
4f25f19833 | |||
128d11a3cf | |||
e1e5a9a36d | |||
30b84f735b | |||
e925048aa8 | |||
74b30733df |
@ -1,28 +1,44 @@
|
||||
// swift-tools-version: 5.8
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Libraries",
|
||||
platforms: [
|
||||
.iOS(.v16)
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "Libraries",
|
||||
targets: ["Libraries"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
// .package(url: /* package url */, from: "1.0.0"),
|
||||
targets: [
|
||||
"Locations"
|
||||
]
|
||||
),
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "Libraries",
|
||||
dependencies: []),
|
||||
name: "APICore",
|
||||
dependencies: []
|
||||
),
|
||||
.target(
|
||||
name: "Locations",
|
||||
dependencies: [
|
||||
"APICore"
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "LibrariesTests",
|
||||
dependencies: ["Libraries"]),
|
||||
name: "APICoreTests",
|
||||
dependencies: [
|
||||
"APICore"
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "LocationsTests",
|
||||
dependencies: [
|
||||
"APICore",
|
||||
"Locations"
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -0,0 +1,63 @@
|
||||
//
|
||||
// MockURLProtocol.swift
|
||||
// APICore
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This class overrides the `URLProtocol` protocol used by the `URLSession` to handle the loading of protocol-specific URL data so it is possible to mock URL response for testing purposes.
|
||||
public class MockURLProtocol: URLProtocol {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
public static var mockData: [URL: MockURLResponse] = [:]
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
public override class func canInit(with task: URLSessionTask) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
public override class func canInit(with request: URLRequest) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
public override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
||||
request
|
||||
}
|
||||
|
||||
public override func startLoading() {
|
||||
guard
|
||||
let url = request.url,
|
||||
let response = Self.mockData[url]
|
||||
else {
|
||||
client?.urlProtocolDidFinishLoading(self)
|
||||
return
|
||||
}
|
||||
|
||||
if let data = response.data {
|
||||
client?.urlProtocol(self, didLoad: data)
|
||||
}
|
||||
|
||||
if let httpResponse = HTTPURLResponse(
|
||||
url: url,
|
||||
statusCode: response.status,
|
||||
httpVersion: nil,
|
||||
headerFields: response.headers
|
||||
) {
|
||||
client?.urlProtocol(
|
||||
self,
|
||||
didReceive: httpResponse,
|
||||
cacheStoragePolicy: .allowedInMemoryOnly
|
||||
)
|
||||
}
|
||||
|
||||
client?.urlProtocolDidFinishLoading(self)
|
||||
}
|
||||
|
||||
public override func stopLoading() {}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
//
|
||||
// HTTPRequestMethod.swift
|
||||
// APICore
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
/// Enumeration that represents the available HTTP request methods to use in this library.
|
||||
public enum HTTPRequestMethod: String {
|
||||
case get = "GET"
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
//
|
||||
// MockURLResponse.swift
|
||||
// APICore
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This model includes the data to be injected into an specific URL at the time of mocking its response.
|
||||
public struct MockURLResponse {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
public let status: Int
|
||||
public let headers: [String: String]
|
||||
public let data: Data?
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init(
|
||||
status: Int,
|
||||
headers: [String : String],
|
||||
data: Data? = nil
|
||||
) {
|
||||
self.status = status
|
||||
self.headers = headers
|
||||
self.data = data
|
||||
}
|
||||
|
||||
}
|
@ -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,20 @@
|
||||
//
|
||||
// Endpoint.swift
|
||||
// APICore
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This protocol defines an endpoint to be used in an API call.
|
||||
public protocol Endpoint {
|
||||
var scheme: String { get }
|
||||
var host: String { get }
|
||||
var port: Int? { get }
|
||||
var path: String { get }
|
||||
var method: HTTPRequestMethod { get }
|
||||
var headers: [String: String] { get }
|
||||
var body: Data? { get }
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
//
|
||||
// MakeURLRequestUseCase.swift
|
||||
// APICore
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This use case generate a url request out of a given endpoint.
|
||||
public struct MakeURLRequestUseCase {
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init() {}
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
/// Generate a `URLRequest` instance out of a given endpoint that conforms to the `Endpoint` protocol.
|
||||
/// - Parameter endpoint: An endpoint which is used to generate a `URLRequest` instance from.
|
||||
/// - Returns: A `URLRequest` instance filled with data provided by the given endpoint.
|
||||
public func callAsFunction(endpoint: some Endpoint) throws -> URLRequest {
|
||||
var urlComponents = URLComponents()
|
||||
|
||||
urlComponents.scheme = endpoint.scheme
|
||||
urlComponents.host = endpoint.host
|
||||
urlComponents.path = endpoint.path
|
||||
|
||||
if let port = endpoint.port {
|
||||
urlComponents.port = port
|
||||
}
|
||||
|
||||
guard let url = urlComponents.url else {
|
||||
throw MakeURLRequestError.urlNotCreated
|
||||
}
|
||||
|
||||
var urlRequest = URLRequest(url: url)
|
||||
|
||||
urlRequest.httpMethod = endpoint.method.rawValue
|
||||
urlRequest.httpBody = endpoint.body
|
||||
urlRequest.allHTTPHeaderFields = endpoint.headers
|
||||
|
||||
return urlRequest
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Errors
|
||||
|
||||
enum MakeURLRequestError: Error {
|
||||
case urlNotCreated
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
public struct Libraries {
|
||||
public private(set) var text = "Hello, World!"
|
||||
|
||||
public init() {
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
//
|
||||
// GetLocationsEndpoint.swift
|
||||
// Locations
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import APICore
|
||||
import Foundation
|
||||
|
||||
struct GetLocationsEndpoint: Endpoint {
|
||||
let scheme: String = .Scheme.https
|
||||
let host: String = .Hosts.default
|
||||
let port: Int? = nil
|
||||
let path: String = .Paths.getLocations
|
||||
let method: HTTPRequestMethod = .get
|
||||
let headers: [String: String] = [:]
|
||||
let body: Data? = nil
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
//
|
||||
// String+Constants.swift
|
||||
// Locations
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
extension String {
|
||||
enum Scheme {
|
||||
static let https = "https"
|
||||
}
|
||||
|
||||
enum Hosts {
|
||||
static let `default` = "raw.githubusercontent.com"
|
||||
}
|
||||
|
||||
enum Paths {
|
||||
static let getLocations = "/abnamrocoesd/assignment-ios/main/locations.json"
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
//
|
||||
// Location.swift
|
||||
// Locations (Library)
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
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
|
||||
|
||||
extension Location: Decodable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case latitude = "lat"
|
||||
case longitude = "long"
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
//
|
||||
// LocationsService.swift
|
||||
// Locations
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import APICore
|
||||
import Foundation
|
||||
|
||||
public struct LocationsService {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let client: Client
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
public init(configuration: URLSessionConfiguration = .default) {
|
||||
self.client = LocationsClient(configuration: configuration)
|
||||
}
|
||||
|
||||
// MARK: Functions
|
||||
|
||||
public func getLocations() async throws -> [Location] {
|
||||
try await client.request(
|
||||
endpoint: GetLocationsEndpoint(),
|
||||
for: Locations.self
|
||||
).locations
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Models
|
||||
|
||||
struct Locations: Decodable, Equatable {
|
||||
public let locations: [Location]
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
//
|
||||
// MakeURLRequestUseCaseTests.swift
|
||||
// APICoreTests
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import APICore
|
||||
|
||||
final class MakeURLRequestUseCaseTests: XCTestCase {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let makeURLRequest = MakeURLRequestUseCase()
|
||||
|
||||
// MARK: Test cases
|
||||
|
||||
func test_withEndpoint_initialisedByDefault() throws {
|
||||
// GIVEN
|
||||
let endpoint = TestEndpoint()
|
||||
|
||||
// WHEN
|
||||
let result = try makeURLRequest(endpoint: endpoint)
|
||||
|
||||
// THEN
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result.url?.absoluteString, "http://www.something.com/path/to/endpoint")
|
||||
XCTAssertEqual(result.httpMethod, HTTPRequestMethod.get.rawValue)
|
||||
XCTAssertEqual(result.allHTTPHeaderFields, [:])
|
||||
XCTAssertNil(result.httpBody)
|
||||
}
|
||||
|
||||
func test_withEndpoint_initialisedWithPort() throws {
|
||||
// GIVEN
|
||||
let endpoint = TestEndpoint(port: 8080)
|
||||
|
||||
// WHEN
|
||||
let result = try makeURLRequest(endpoint: endpoint)
|
||||
|
||||
// THEN
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result.url?.absoluteString, "http://www.something.com:8080/path/to/endpoint")
|
||||
XCTAssertEqual(result.httpMethod, HTTPRequestMethod.get.rawValue)
|
||||
XCTAssertEqual(result.allHTTPHeaderFields, [:])
|
||||
XCTAssertNil(result.httpBody)
|
||||
}
|
||||
|
||||
func test_withEndpoint_initialisedWithHeaders() throws {
|
||||
// GIVEN
|
||||
let endpoint = TestEndpoint(headers: [
|
||||
"aHeader": "aValueForHead",
|
||||
"someOtherHeader": "someValueForOtherHeader"
|
||||
])
|
||||
|
||||
// WHEN
|
||||
let result = try makeURLRequest(endpoint: endpoint)
|
||||
|
||||
// THEN
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result.url?.absoluteString, "http://www.something.com/path/to/endpoint")
|
||||
XCTAssertEqual(result.httpMethod, HTTPRequestMethod.get.rawValue)
|
||||
XCTAssertEqual(result.allHTTPHeaderFields, [
|
||||
"aHeader": "aValueForHead",
|
||||
"someOtherHeader": "someValueForOtherHeader"
|
||||
])
|
||||
XCTAssertNil(result.httpBody)
|
||||
}
|
||||
|
||||
func test_withEndpoint_initialisedWithBody() throws {
|
||||
// GIVEN
|
||||
let data = "This is some data for a body of a request".data(using: .utf8)
|
||||
let endpoint = TestEndpoint(body: data)
|
||||
|
||||
// WHEN
|
||||
let result = try makeURLRequest(endpoint: endpoint)
|
||||
|
||||
// THEN
|
||||
XCTAssertNotNil(result)
|
||||
XCTAssertEqual(result.url?.absoluteString, "http://www.something.com/path/to/endpoint")
|
||||
XCTAssertEqual(result.httpMethod, HTTPRequestMethod.get.rawValue)
|
||||
XCTAssertEqual(result.allHTTPHeaderFields, [:])
|
||||
XCTAssertEqual(result.httpBody, data)
|
||||
XCTAssertNotNil(data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - TestEndpoint
|
||||
|
||||
private struct TestEndpoint: Endpoint {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
let scheme: String = "http"
|
||||
let host: String = "www.something.com"
|
||||
let path: String = "/path/to/endpoint"
|
||||
let method: HTTPRequestMethod = .get
|
||||
|
||||
var port: Int?
|
||||
var headers: [String : String]
|
||||
var body: Data?
|
||||
|
||||
// MARK: Initialisers
|
||||
|
||||
init(
|
||||
port: Int? = nil,
|
||||
headers: [String : String] = [:],
|
||||
body: Data? = nil
|
||||
) {
|
||||
self.port = port
|
||||
self.body = body
|
||||
self.headers = headers
|
||||
}
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import XCTest
|
||||
@testable import Libraries
|
||||
|
||||
final class LibrariesTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(Libraries().text, "Hello, World!")
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
//
|
||||
// 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 = .Responses.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 = .Responses.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 = .Responses.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 = .Responses.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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
//
|
||||
// GetLocationsEndpointTests.swift
|
||||
// LocationsTests
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import Locations
|
||||
|
||||
final class GetLocationsEndpointTests: XCTestCase {
|
||||
|
||||
// MARK: Tests
|
||||
|
||||
func test_init() {
|
||||
// GIVEN
|
||||
// WHEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
// THEN
|
||||
XCTAssertNotNil(endpoint)
|
||||
XCTAssertEqual(endpoint.scheme, .Scheme.https)
|
||||
XCTAssertEqual(endpoint.host, .Hosts.default)
|
||||
XCTAssertNil(endpoint.port)
|
||||
XCTAssertEqual(endpoint.path, .Paths.getLocations)
|
||||
XCTAssertTrue(endpoint.headers.isEmpty)
|
||||
XCTAssertNil(endpoint.body)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Data+Constants.swift
|
||||
// LocationsTests
|
||||
//
|
||||
// Created by Javier Cicchelli on 10/04/2023.
|
||||
// Copyright © 2023 Röck+Cöde. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
enum Responses {
|
||||
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)
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
//
|
||||
// LocationsServiceTests.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 LocationsServiceTests: XCTestCase {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
private let makeURLRequest = MakeURLRequestUseCase()
|
||||
private let sessionConfiguration = {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
|
||||
configuration.protocolClasses = [MockURLProtocol.self]
|
||||
|
||||
return configuration
|
||||
}()
|
||||
|
||||
private var service: LocationsService!
|
||||
private var url: URL!
|
||||
private var data: Data!
|
||||
|
||||
// MARK: Setup
|
||||
|
||||
override func setUp() async throws {
|
||||
service = .init(configuration: sessionConfiguration)
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
service = nil
|
||||
}
|
||||
|
||||
// MARK: Tests
|
||||
|
||||
func test_getLocations_whenResponseOK() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .Responses.locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 200,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN
|
||||
let result = try await service.getLocations()
|
||||
|
||||
// THEN
|
||||
XCTAssertEqual(result, [
|
||||
.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_getLocations_whenResponseClientError() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .Responses.locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 404,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
_ = try await service.getLocations()
|
||||
} catch LocationsClientError.statusErrorClient {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_getLocations_whenResponseServerError() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .Responses.locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 500,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
_ = try await service.getLocations()
|
||||
} catch LocationsClientError.statusErrorServer {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
func test_getLocations_whenResponseUnexpectedError() async throws {
|
||||
// GIVEN
|
||||
let endpoint = GetLocationsEndpoint()
|
||||
|
||||
url = try makeURLRequest(endpoint: endpoint).url
|
||||
data = .Responses.locations
|
||||
|
||||
MockURLProtocol.mockData[url] = MockURLResponse(
|
||||
status: 302,
|
||||
headers: [:],
|
||||
data: data
|
||||
)
|
||||
|
||||
// WHEN & THEN
|
||||
do {
|
||||
_ = try await service.getLocations()
|
||||
} catch LocationsClientError.statusErrorUnexpected {
|
||||
XCTAssertTrue(true)
|
||||
} catch {
|
||||
XCTAssertTrue(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user