Skip to content

Commit a0b228c

Browse files
committed
Add tests and Request type
1 parent 2259d8a commit a0b228c

File tree

5 files changed

+188
-49
lines changed

5 files changed

+188
-49
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// RefreshRequest.swift
3+
//
4+
//
5+
// Created by Dave Snabel-Caunt on 24/04/2024.
6+
//
7+
8+
import Foundation
9+
10+
extension Request {
11+
static func refresh(
12+
token: String
13+
) -> Request {
14+
15+
return .init(
16+
path: "/v2/token/refresh",
17+
method: .post,
18+
body: Data(token.utf8),
19+
headers: [
20+
"Content-Type": "application/x-www-form-urlencoded"
21+
]
22+
)
23+
}
24+
}

Sources/UID2/Networking/Request.swift

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// Request.swift
3+
//
4+
//
5+
// Created by Dave Snabel-Caunt on 09/04/2024.
6+
//
7+
8+
import Foundation
9+
10+
enum Method: String {
11+
case get = "GET"
12+
case post = "POST"
13+
}
14+
15+
struct Request {
16+
var method: Method
17+
var path: String
18+
var queryItems: [URLQueryItem]
19+
var body: Data?
20+
var headers: [String: String]
21+
22+
init(
23+
path: String,
24+
method: Method = .get,
25+
queryItems: [URLQueryItem] = [],
26+
body: Data? = nil,
27+
headers: [String: String] = [:]
28+
) {
29+
self.path = path
30+
self.method = method
31+
self.queryItems = queryItems
32+
self.body = body
33+
self.headers = headers
34+
}
35+
}

Sources/UID2/UID2Client.swift

+58-43
Original file line numberDiff line numberDiff line change
@@ -25,50 +25,65 @@ internal final class UID2Client {
2525

2626
func refreshIdentity(refreshToken: String, refreshResponseKey: String) async throws -> RefreshAPIPackage {
2727

28-
var components = URLComponents(string: uid2APIURL)
29-
components?.path = "/v2/token/refresh"
30-
31-
guard let urlPath = components?.url?.absoluteString,
32-
let url = URL(string: urlPath) else {
33-
throw UID2Error.urlGeneration
34-
}
35-
36-
var request = URLRequest(url: url)
37-
request.httpMethod = "POST"
38-
request.addValue(clientVersion, forHTTPHeaderField: "X-UID2-Client-Version")
39-
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
40-
request.httpBody = refreshToken.data(using: .utf8)
41-
42-
let dataResponse = try await session.loadData(for: request)
43-
let data = dataResponse.0
44-
let statusCode = dataResponse.1
45-
46-
let decoder = JSONDecoder()
47-
decoder.keyDecodingStrategy = .convertFromSnakeCase
48-
49-
// Only Decrypt If HTTP Status is 200 (Success or Opt Out)
50-
if statusCode != 200 {
51-
do {
52-
let tokenResponse = try decoder.decode(RefreshTokenResponse.self, from: data)
53-
throw UID2Error.refreshTokenServer(status: tokenResponse.status, message: tokenResponse.message)
54-
} catch {
55-
throw UID2Error.refreshTokenServerDecoding(httpStatus: statusCode, message: error.localizedDescription)
56-
}
57-
}
58-
59-
// Decrypt Data Envelop
60-
// https://github.com/UnifiedID2/uid2docs/blob/main/api/v2/encryption-decryption.md
61-
guard let payloadData = DataEnvelope.decrypt(refreshResponseKey, data, true) else {
62-
throw UID2Error.decryptPayloadData
63-
}
64-
65-
let tokenResponse = try decoder.decode(RefreshTokenResponse.self, from: payloadData)
66-
67-
guard let refreshAPIPackage = tokenResponse.toRefreshAPIPackage() else {
68-
throw UID2Error.refreshResponseToRefreshAPIPackage
28+
let request = Request.refresh(token: refreshToken)
29+
let (data, statusCode) = try await execute(request)
30+
31+
let decoder = JSONDecoder()
32+
decoder.keyDecodingStrategy = .convertFromSnakeCase
33+
34+
// Only Decrypt If HTTP Status is 200 (Success or Opt Out)
35+
if statusCode != 200 {
36+
do {
37+
let tokenResponse = try decoder.decode(RefreshTokenResponse.self, from: data)
38+
throw UID2Error.refreshTokenServer(status: tokenResponse.status, message: tokenResponse.message)
39+
} catch {
40+
throw UID2Error.refreshTokenServerDecoding(httpStatus: statusCode, message: error.localizedDescription)
6941
}
70-
71-
return refreshAPIPackage
7242
}
7343

44+
// Decrypt Data Envelop
45+
// https://github.com/UnifiedID2/uid2docs/blob/main/api/v2/encryption-decryption.md
46+
guard let payloadData = DataEnvelope.decrypt(refreshResponseKey, data, true) else {
47+
throw UID2Error.decryptPayloadData
48+
}
49+
50+
let tokenResponse = try decoder.decode(RefreshTokenResponse.self, from: payloadData)
51+
52+
guard let refreshAPIPackage = tokenResponse.toRefreshAPIPackage() else {
53+
throw UID2Error.refreshResponseToRefreshAPIPackage
54+
}
55+
56+
return refreshAPIPackage
57+
}
58+
59+
// MARK: - Request Execution
60+
61+
internal func urlRequest(
62+
_ request: Request,
63+
baseURL: URL
64+
) -> URLRequest {
65+
var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: true)!
66+
urlComponents.path = request.path
67+
urlComponents.queryItems = request.queryItems.isEmpty ? nil : request.queryItems
68+
69+
var urlRequest = URLRequest(url: urlComponents.url!)
70+
urlRequest.httpMethod = request.method.rawValue
71+
if request.method == .post {
72+
urlRequest.httpBody = request.body
73+
}
74+
75+
request.headers.forEach { field, value in
76+
urlRequest.addValue(value, forHTTPHeaderField: field)
77+
}
78+
urlRequest.addValue(clientVersion, forHTTPHeaderField: "X-UID2-Client-Version")
79+
return urlRequest
80+
}
81+
82+
private func execute(_ request: Request) async throws -> (Data, Int) {
83+
let urlRequest = urlRequest(
84+
request,
85+
baseURL: URL(string: uid2APIURL)!
86+
)
87+
return try await session.loadData(for: urlRequest)
88+
}
7489
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// RefreshRequestTests.swift
3+
//
4+
//
5+
// Created by Dave Snabel-Caunt on 24/04/2024.
6+
//
7+
8+
import XCTest
9+
@testable import UID2
10+
11+
final class RefreshRequestTests: XCTestCase {
12+
13+
func testRequest() async throws {
14+
let request = Request.refresh(token: "im-a-refresh-token")
15+
let client = UID2Client(
16+
uid2APIURL: "https://prod.uidapi.com",
17+
sdkVersion: "1.2.3"
18+
)
19+
let urlRequest = client.urlRequest(request, baseURL: URL(string: "https://prod.uidapi.com")!)
20+
21+
var expected = URLRequest(url: URL(string: "https://prod.uidapi.com/v2/token/refresh")!)
22+
expected.httpMethod = "POST"
23+
expected.httpBody = Data("im-a-refresh-token".utf8)
24+
25+
#if os(tvOS)
26+
expected.allHTTPHeaderFields = [
27+
"Content-Type": "application/x-www-form-urlencoded",
28+
"X-UID2-Client-Version": "tvos-1.2.3"
29+
]
30+
#else
31+
expected.allHTTPHeaderFields = [
32+
"Content-Type": "application/x-www-form-urlencoded",
33+
"X-UID2-Client-Version": "ios-1.2.3"
34+
]
35+
#endif
36+
XCTAssertEqual(urlRequest, expected)
37+
38+
// The above equality test doesn't print useful information on failure, so
39+
// it's useful to check properties below for diagnostics
40+
XCTAssertEqual(urlRequest.url, expected.url)
41+
XCTAssertEqual(urlRequest.httpMethod, expected.httpMethod)
42+
XCTAssertEqual(urlRequest.httpBody, expected.httpBody)
43+
XCTAssertEqual(urlRequest.allHTTPHeaderFields, expected.allHTTPHeaderFields)
44+
}
45+
}

Tests/UID2Tests/RefreshTokenAPITests.swift

+26-6
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ final class RefreshTokenAPITests: XCTestCase {
2626
}
2727

2828
// Load UID2Client Mocked
29-
let client = UID2Client(uid2APIURL: "", sdkVersion: "TEST", MockNetworkSession("refresh-token-200-success-encrypted", "txt"))
29+
let client = UID2Client(
30+
uid2APIURL: "https://prod.uidapi.com",
31+
sdkVersion: "TEST",
32+
MockNetworkSession("refresh-token-200-success-encrypted", "txt")
33+
)
3034

3135
// Call RefreshToken using refreshToken and refreshResponseKey from Step 1 to decrypt
3236
let refreshToken = try await client.refreshIdentity(refreshToken: generateToken.refreshToken,
@@ -65,7 +69,11 @@ final class RefreshTokenAPITests: XCTestCase {
6569
}
6670

6771
// Load UID2Client Mocked
68-
let client = UID2Client(uid2APIURL: "", sdkVersion: "TEST", MockNetworkSession("refresh-token-200-optout-encrypted", "txt"))
72+
let client = UID2Client(
73+
uid2APIURL: "https://prod.uidapi.com",
74+
sdkVersion: "TEST",
75+
MockNetworkSession("refresh-token-200-optout-encrypted", "txt")
76+
)
6977

7078
// Call RefreshToken using refreshToken and refreshResponseKey from Step 1 to decrypt
7179
let refreshToken = try await client.refreshIdentity(refreshToken: generateToken.refreshToken,
@@ -85,8 +93,12 @@ final class RefreshTokenAPITests: XCTestCase {
8593

8694
do {
8795
// Load UID2Client Mocked
88-
let client = UID2Client(uid2APIURL: "", sdkVersion: "TEST", MockNetworkSession("refresh-token-400-client-error", "json", 400))
89-
96+
let client = UID2Client(
97+
uid2APIURL: "https://prod.uidapi.com",
98+
sdkVersion: "TEST",
99+
MockNetworkSession("refresh-token-400-client-error", "json", 400)
100+
)
101+
90102
// Call RefreshToken using refreshToken and refreshResponseKey from Step 1 to decrypt
91103
let _ = try await client.refreshIdentity(refreshToken: "token", refreshResponseKey: "key")
92104
XCTFail("refreshUID2Token() did not throw an error.")
@@ -111,7 +123,11 @@ final class RefreshTokenAPITests: XCTestCase {
111123

112124
do {
113125
// Load UID2Client Mocked
114-
let client = UID2Client(uid2APIURL: "", sdkVersion: "TEST", MockNetworkSession("refresh-token-400-invalid-token", "json", 400))
126+
let client = UID2Client(
127+
uid2APIURL: "https://prod.uidapi.com",
128+
sdkVersion: "TEST",
129+
MockNetworkSession("refresh-token-400-invalid-token", "json", 400)
130+
)
115131

116132
// Call RefreshToken using refreshToken and refreshResponseKey from Step 1 to decrypt
117133
let _ = try await client.refreshIdentity(refreshToken: "token", refreshResponseKey: "key")
@@ -137,7 +153,11 @@ final class RefreshTokenAPITests: XCTestCase {
137153

138154
do {
139155
// Load UID2Client Mocked
140-
let client = UID2Client(uid2APIURL: "", sdkVersion: "TEST", MockNetworkSession("refresh-token-401-unauthorized", "json", 401))
156+
let client = UID2Client(
157+
uid2APIURL: "https://prod.uidapi.com",
158+
sdkVersion: "TEST",
159+
MockNetworkSession("refresh-token-401-unauthorized", "json", 401)
160+
)
141161

142162
// Call RefreshToken using refreshToken and refreshResponseKey from Step 1 to decrypt
143163
let _ = try await client.refreshIdentity(refreshToken: "token", refreshResponseKey: "key")

0 commit comments

Comments
 (0)