-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRequestProcessorAuthenticationTests.swift
More file actions
174 lines (131 loc) · 5.16 KB
/
RequestProcessorAuthenticationTests.swift
File metadata and controls
174 lines (131 loc) · 5.16 KB
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//
// network-layer
// Copyright © 2025 Space Code. All rights reserved.
//
import Foundation
@testable import NetworkLayer
import NetworkLayerInterfaces
import Typhoon
import XCTest
// MARK: - RequestProcessorAuthenicationTests
final class RequestProcessorAuthenicationTests: XCTestCase {
// MARK: Tests
func test_thatRequestProcessorAuthenticatesRequest_whenTokenIsCorrect() async throws {
// given
let interceptor = AuthInterceptor()
let sut = RequestProcessor.mock(interceptor: interceptor)
interceptor.token = .init(token: .token, expiresDate: Date())
DynamicStubs.register(stubs: [.user], statusCode: 200)
let request = makeRequest(.user)
// when
let _: Response<User> = try await sut.send(request)
}
func test_thatRequestProcessorAuthenticatesRequest_whenTokenIsInvalid() async throws {
// given
let interceptor = AuthInterceptor()
let sut = RequestProcessor.mock(interceptor: interceptor)
interceptor.token = .init(token: .token, expiresDate: Date())
DynamicStubs.register(stubs: [.user], statusCode: 401)
let request = makeRequest(.user)
// when
let _: Response<User> = try await sut.send(request)
}
func test_thatRequestProcessorAuthenticatesRequest_whenTokenExpired() async throws {
// given
let interceptor = AuthInterceptor()
let sut = RequestProcessor.mock(interceptor: interceptor)
interceptor.token = .init(token: .token, expiresDate: Date(timeIntervalSinceNow: 1001))
DynamicStubs.register(stubs: [.user], statusCode: 200)
let request = makeRequest(.user)
// when
let _: Response<User> = try await sut.send(request)
}
func test_thatRequestProcessorThrowsAnError_whenInterceptorAdaptDidFail() async throws {
try await test_failAuthentication(
adaptError: URLError(.unknown),
refreshError: nil,
expectedError: RetryPolicyError.retryLimitExceeded
)
}
func test_thatRequestProcessorThrowsAnError_whenInterceptorRefreshDidFail() async throws {
try await test_failAuthentication(
adaptError: nil,
refreshError: URLError(.unknown),
expectedError: RetryPolicyError.retryLimitExceeded
)
}
// MARK: Private
private func test_failAuthentication(adaptError: Error?, refreshError: Error?, expectedError: Error) async throws {
class FailInterceptor: IAuthenticationInterceptor, @unchecked Sendable {
let adaptError: Error?
let refreshError: Error?
init(adaptError: Error?, refreshError: Error?) {
self.adaptError = adaptError
self.refreshError = refreshError
}
func adapt(request _: inout URLRequest, for _: URLSession) async throws {
guard let adaptError = adaptError else { return }
throw adaptError
}
func refresh(_: URLRequest, with _: HTTPURLResponse, for _: URLSession) async throws {
guard let refreshError = refreshError else { return }
throw refreshError
}
func isRequireRefresh(_: URLRequest, response _: HTTPURLResponse) -> Bool {
true
}
}
// given
let interceptor = FailInterceptor(adaptError: adaptError, refreshError: refreshError)
let sut = RequestProcessor.mock(interceptor: interceptor)
let request = makeRequest(.user)
DynamicStubs.register(stubs: [.user], statusCode: 200)
// when
do {
let _: Response<User> = try await sut.send(request)
} catch {
XCTAssertEqual(error as NSError, expectedError as NSError)
}
}
private func makeRequest(_ path: String) -> IRequest {
let request = RequestStub()
request.stubbedDomainName = "https://github.com"
request.stubbedPath = path
request.stubbedRequiresAuthentication = true
return request
}
}
// MARK: - AuthInterceptor
private final class AuthInterceptor: IAuthenticationInterceptor, @unchecked Sendable {
var token: Token!
private var attempts = 0
func adapt(request: inout URLRequest, for _: URLSession) async throws {
request.addValue("Bearer \(token.token)", forHTTPHeaderField: "Authorization")
}
func refresh(_: URLRequest, with _: HTTPURLResponse, for _: URLSession) async throws {
if token.expiresDate < Date() {
token = Token(token: .token, expiresDate: Date(timeIntervalSinceNow: 1000))
}
}
func isRequireRefresh(_: URLRequest, response: HTTPURLResponse) -> Bool {
if response.statusCode == 401, attempts == 0 {
attempts += 1
return true
}
return false
}
}
// MARK: - Token
private struct Token {
let token: String
let expiresDate: Date
}
// MARK: - Stubs
private extension StubResponse {
static let user = StubResponse(name: .user, fileURL: MockedData.userJSON, httpMethod: .get)
}
// MARK: - Constants
private extension String {
static let user = "user"
static let token = "token"
}