|
1 | 1 | // Copyright © 2019 The Bow Authors. |
2 | 2 |
|
3 | 3 | import Foundation |
| 4 | +import Bow |
| 5 | + |
| 6 | +struct StubbedResponse { |
| 7 | + let statusCode: Int |
| 8 | + let content: Either<Error, Data> |
| 9 | + |
| 10 | + init(data: Data, statusCode: Int = 200) { |
| 11 | + self.statusCode = statusCode |
| 12 | + self.content = .right(data) |
| 13 | + } |
| 14 | + |
| 15 | + init(error: Error, statusCode: Int = 404) { |
| 16 | + self.statusCode = statusCode |
| 17 | + self.content = .left(error) |
| 18 | + } |
| 19 | +} |
4 | 20 |
|
5 | 21 | class StubURL: URLProtocol { |
6 | | - private(set) static var data: Data? |
7 | | - private(set) static var error: Error? |
8 | | - private(set) static var statusCode: Int = 200 |
| 22 | + static let anyEndpoint = ">>>ANY-ENDPOINT<<<" |
| 23 | + private static var stubs: [String: [StubbedResponse]] = [:] |
| 24 | + |
| 25 | + private static func stub(response: StubbedResponse, for endpoint: String) { |
| 26 | + if let responses = stubs[endpoint] { |
| 27 | + stubs[endpoint] = responses + [response] |
| 28 | + } else { |
| 29 | + stubs[endpoint] = [response] |
| 30 | + } |
| 31 | + } |
9 | 32 |
|
10 | | - /// Stubs methods |
11 | | - class func stub(data: Data, code: Int = 200) { |
12 | | - StubURL.data = data |
13 | | - StubURL.statusCode = code |
| 33 | + /// Stub methods |
| 34 | + static func stub(data: Data, code: Int = 200, endpoint: String = anyEndpoint) { |
| 35 | + stub(response: StubbedResponse(data: data, statusCode: code), |
| 36 | + for: endpoint) |
14 | 37 | } |
15 | 38 |
|
16 | | - class func stub(error: Error, code: Int = 400) { |
17 | | - StubURL.error = error |
18 | | - StubURL.statusCode = code |
| 39 | + static func stub(error: Error, code: Int = 404, endpoint: String = anyEndpoint) { |
| 40 | + stub(response: StubbedResponse(error: error, statusCode: code), |
| 41 | + for: endpoint) |
19 | 42 | } |
20 | 43 |
|
21 | | - class func stub(json: String, code: Int = 200) { |
22 | | - stub(data: json.data(using: .utf8)!, code: code) |
| 44 | + static func stub(json: String, code: Int = 200, endpoint: String = anyEndpoint) { |
| 45 | + stub(response: StubbedResponse(data: json.data(using: .utf8)!), |
| 46 | + for: endpoint) |
23 | 47 | } |
24 | 48 |
|
25 | | - class func stub(contentsOfFile url: URL, code: Int = 200) { |
| 49 | + static func stub(contentsOfFile url: URL, code: Int = 200, endpoint: String = anyEndpoint) { |
26 | 50 | let content = try! String(contentsOf: url, encoding: .utf8) |
27 | | - stub(json: content, code: code) |
| 51 | + stub(json: content, code: code, endpoint: endpoint) |
28 | 52 | } |
29 | 53 |
|
30 | | - /// Reset the stub to default values: data to nil, error to nil and status code to 200. |
31 | | - class func reset() { |
32 | | - StubURL.data = nil |
33 | | - StubURL.error = nil |
34 | | - StubURL.statusCode = 200 |
| 54 | + /// Clears all stubs. |
| 55 | + static func reset() { |
| 56 | + stubs = [:] |
| 57 | + } |
| 58 | + |
| 59 | + static func response(for request: URLRequest) -> StubbedResponse? { |
| 60 | + if let url = request.url?.absoluteString { |
| 61 | + for endpoint in stubs { |
| 62 | + if url.hasSuffix(endpoint.key) { |
| 63 | + return consume(endpoint.key) |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + return consume(anyEndpoint) |
| 68 | + } |
| 69 | + |
| 70 | + static func consume(_ endpoint: String) -> StubbedResponse? { |
| 71 | + let queue = stubs[endpoint] |
| 72 | + let response = queue?.first |
| 73 | + stubs[endpoint] = Array(queue?.dropFirst() ?? []) |
| 74 | + return response |
35 | 75 | } |
36 | 76 |
|
37 | 77 | // MARK: - URLProtocol methods |
38 | | - override class func canInit(with request:URLRequest) -> Bool { |
| 78 | + override static func canInit(with request:URLRequest) -> Bool { |
39 | 79 | return true |
40 | 80 | } |
41 | 81 |
|
42 | | - override class func canonicalRequest(for request: URLRequest) -> URLRequest { |
| 82 | + override static func canonicalRequest(for request: URLRequest) -> URLRequest { |
43 | 83 | return request |
44 | 84 | } |
45 | 85 |
|
46 | 86 | override func startLoading() { |
| 87 | + guard let stub = StubURL.response(for: request) else { |
| 88 | + fatalError("No response stubbed for request: \(request)") |
| 89 | + } |
| 90 | + |
47 | 91 | let header = request.allHTTPHeaderFields |
48 | | - let response = HTTPURLResponse(url: request.url!, statusCode: StubURL.statusCode, httpVersion: nil, headerFields: header)! |
| 92 | + |
| 93 | + let response = HTTPURLResponse(url: request.url!, statusCode: stub.statusCode, httpVersion: nil, headerFields: header)! |
49 | 94 | client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) |
50 | 95 |
|
51 | | - if let error = StubURL.error { |
52 | | - client?.urlProtocol(self, didFailWithError: error) |
53 | | - } else { |
54 | | - client?.urlProtocol(self, didLoad: StubURL.data ?? Data()) |
55 | | - client?.urlProtocolDidFinishLoading(self) |
56 | | - } |
| 96 | + stub.content.fold( |
| 97 | + { error in |
| 98 | + client?.urlProtocol(self, didFailWithError: error) |
| 99 | + return |
| 100 | + }, |
| 101 | + { data in |
| 102 | + client?.urlProtocol(self, didLoad: data) |
| 103 | + client?.urlProtocolDidFinishLoading(self) |
| 104 | + return |
| 105 | + }) |
57 | 106 | } |
58 | 107 |
|
59 | 108 | override func stopLoading() { } |
|
0 commit comments