summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadRESTTests/Mocks/AnyTransport.swift
blob: ff5414f7657a4dceb955331e71f11a4bea1c7d66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//
//  AnyTransport.swift
//  MullvadRESTTests
//
//  Created by pronebird on 25/08/2023.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

@preconcurrency import Foundation
import MullvadTypes

@testable import MullvadREST

/// Mock implementation of REST transport that can be used to handle requests without doing any actual networking.
class AnyTransport: RESTTransport, @unchecked Sendable {
    typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void

    private let handleRequest: () -> AnyResponse

    private let completionLock = NSLock()
    private var completionHandlers: [UUID: CompletionHandler] = [:]

    init(block: @escaping @Sendable () -> AnyResponse) {
        handleRequest = block
    }

    var name: String {
        return "any-transport"
    }

    func sendRequest(
        _ request: URLRequest,
        completion: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void
    ) -> Cancellable {
        let response = handleRequest()
        let id = storeCompletion(completionHandler: completion)

        let dispatchWork = DispatchWorkItem {
            let data = (try? response.encode()) ?? Data()
            let httpResponse = HTTPURLResponse(
                url: request.url!,
                statusCode: response.statusCode,
                httpVersion: "1.0",
                headerFields: [:]
            )!
            self.sendCompletion(requestID: id, completion: .success((data, httpResponse)))
        }

        DispatchQueue.global().asyncAfter(deadline: .now() + response.delay, execute: dispatchWork)

        return AnyCancellable {
            dispatchWork.cancel()

            self.sendCompletion(requestID: id, completion: .failure(URLError(.cancelled)))
        }
    }

    private func storeCompletion(completionHandler: @escaping CompletionHandler) -> UUID {
        return completionLock.withLock {
            let id = UUID()
            completionHandlers[id] = completionHandler
            return id
        }
    }

    private func sendCompletion(requestID: UUID, completion: Result<(Data, URLResponse), Error>) {
        let complationHandler = completionLock.withLock {
            return completionHandlers.removeValue(forKey: requestID)
        }
        switch completion {
        case let .success((data, response)):
            complationHandler?(data, response, nil)
        case let .failure(error):
            complationHandler?(nil, nil, error)
        }
    }
}

struct Response<T: Encodable>: AnyResponse {
    var delay: TimeInterval
    var statusCode: Int
    var value: T

    func encode() throws -> Data {
        return try REST.Coding.makeJSONEncoder().encode(value)
    }
}

protocol AnyResponse {
    var delay: TimeInterval { get }
    var statusCode: Int { get }

    func encode() throws -> Data
}