177 lines
5.9 KiB
Swift
177 lines
5.9 KiB
Swift
import Foundation
|
|
import XCTest
|
|
import VelodyDomain
|
|
@testable import VelodyNetworking
|
|
|
|
final class URLSessionVelodyAPIClientAuthorizationTests: XCTestCase {
|
|
override func tearDown() {
|
|
RecordingURLProtocol.handler = nil
|
|
super.tearDown()
|
|
}
|
|
|
|
func testFetchRemoteLibrarySendsAuthorizationHeaderWhenDeviceTokenIsAvailable() async throws {
|
|
RecordingURLProtocol.handler = { request in
|
|
XCTAssertEqual(
|
|
request.value(forHTTPHeaderField: "Authorization"),
|
|
"Bearer test-device-access-token"
|
|
)
|
|
XCTAssertEqual(request.url?.path, "/api/v1/library/tracks")
|
|
XCTAssertEqual(request.url?.query, "deviceId=device-123")
|
|
|
|
return (
|
|
HTTPURLResponse(
|
|
url: try XCTUnwrap(request.url),
|
|
statusCode: 200,
|
|
httpVersion: nil,
|
|
headerFields: ["Content-Type": "application/json"]
|
|
)!,
|
|
Data(#"{"tracks":[]}"#.utf8)
|
|
)
|
|
}
|
|
|
|
let client = URLSessionVelodyAPIClient(
|
|
environment: ServerEnvironment(
|
|
baseURL: URL(string: "http://127.0.0.1:3007")!,
|
|
appVersion: "Tests"
|
|
),
|
|
session: makeSession(),
|
|
deviceAccessTokenProvider: {
|
|
"test-device-access-token"
|
|
}
|
|
)
|
|
|
|
let response = try await client.fetchRemoteLibrary(deviceId: "device-123")
|
|
XCTAssertEqual(response.tracks.count, 0)
|
|
}
|
|
|
|
func testFetchSyncChangesSendsAuthorizationHeaderAndCursorQuery() async throws {
|
|
RecordingURLProtocol.handler = { request in
|
|
XCTAssertEqual(
|
|
request.value(forHTTPHeaderField: "Authorization"),
|
|
"Bearer test-device-access-token"
|
|
)
|
|
XCTAssertEqual(request.url?.path, "/api/v1/sync/changes")
|
|
XCTAssertEqual(request.url?.query, "cursor=12")
|
|
|
|
return (
|
|
HTTPURLResponse(
|
|
url: try XCTUnwrap(request.url),
|
|
statusCode: 200,
|
|
httpVersion: nil,
|
|
headerFields: ["Content-Type": "application/json"]
|
|
)!,
|
|
Data(
|
|
"""
|
|
{
|
|
"nextCursor": "12",
|
|
"hasMore": false,
|
|
"requiresBootstrap": false,
|
|
"events": [],
|
|
"serverTime": "2026-06-15T12:00:00.000Z"
|
|
}
|
|
""".utf8
|
|
)
|
|
)
|
|
}
|
|
|
|
let client = URLSessionVelodyAPIClient(
|
|
environment: ServerEnvironment(
|
|
baseURL: URL(string: "http://127.0.0.1:3007")!,
|
|
appVersion: "Tests"
|
|
),
|
|
session: makeSession(),
|
|
deviceAccessTokenProvider: {
|
|
"test-device-access-token"
|
|
}
|
|
)
|
|
|
|
let response = try await client.fetchSyncChanges(cursor: SyncCursor(value: "12"))
|
|
XCTAssertEqual(response.nextCursor.value, "12")
|
|
XCTAssertEqual(response.events.count, 0)
|
|
}
|
|
|
|
func testRegisterDeviceDoesNotSendAuthorizationHeader() async throws {
|
|
RecordingURLProtocol.handler = { request in
|
|
XCTAssertNil(request.value(forHTTPHeaderField: "Authorization"))
|
|
XCTAssertEqual(request.url?.path, "/api/v1/devices/register")
|
|
|
|
return (
|
|
HTTPURLResponse(
|
|
url: try XCTUnwrap(request.url),
|
|
statusCode: 200,
|
|
httpVersion: nil,
|
|
headerFields: ["Content-Type": "application/json"]
|
|
)!,
|
|
Data(
|
|
"""
|
|
{
|
|
"deviceId": "device-123",
|
|
"deviceAccessToken": "registered-device-token",
|
|
"bootstrapToken": "bootstrap-token",
|
|
"serverTime": "2026-06-09T10:00:00.000Z"
|
|
}
|
|
""".utf8
|
|
)
|
|
)
|
|
}
|
|
|
|
let client = URLSessionVelodyAPIClient(
|
|
environment: ServerEnvironment(
|
|
baseURL: URL(string: "http://127.0.0.1:3007")!,
|
|
appVersion: "Tests"
|
|
),
|
|
session: makeSession(),
|
|
deviceAccessTokenProvider: {
|
|
"should-not-be-sent"
|
|
}
|
|
)
|
|
|
|
let response = try await client.registerDevice(
|
|
DeviceRegistrationPayload(
|
|
platform: .iphone,
|
|
deviceName: "Test iPhone",
|
|
appVersion: "Tests"
|
|
)
|
|
)
|
|
|
|
XCTAssertEqual(response.deviceId, "device-123")
|
|
XCTAssertEqual(response.deviceAccessToken, "registered-device-token")
|
|
}
|
|
|
|
private func makeSession() -> URLSession {
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
configuration.protocolClasses = [RecordingURLProtocol.self]
|
|
return URLSession(configuration: configuration)
|
|
}
|
|
}
|
|
|
|
private final class RecordingURLProtocol: URLProtocol {
|
|
static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
|
|
|
|
override class func canInit(with request: URLRequest) -> Bool {
|
|
request.url?.host == "127.0.0.1"
|
|
}
|
|
|
|
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
|
request
|
|
}
|
|
|
|
override func startLoading() {
|
|
guard let handler = Self.handler else {
|
|
XCTFail("RecordingURLProtocol.handler must be set before use.")
|
|
return
|
|
}
|
|
|
|
do {
|
|
let (response, data) = try handler(request)
|
|
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
|
client?.urlProtocol(self, didLoad: data)
|
|
client?.urlProtocolDidFinishLoading(self)
|
|
} catch {
|
|
client?.urlProtocol(self, didFailWithError: error)
|
|
}
|
|
}
|
|
|
|
override func stopLoading() {}
|
|
}
|