* fix #203 NetworkTransport race, with main-isolated variables (#206) Co-authored-by: wti <wti@users.noreply.github.com> * feat/auth: Authentication implementation (#205) * feat: added authentication * feat(auth): improvement to error handling * feat(auth): updated errors and tests in OAuthClientRegistrar * feat(auth): updated ci conformance package version * feat(auth): bumped node version * feat(auth): updated ci conformance step * feat(auth): improvement to Linux support * chore: added docc plugin * chore: added spi yml * chore: added documentation step to ci * chore: increased timeout for test from 5 to 10m --------- Co-authored-by: Paal Øye-Strømme <paal.o.eye@gmail.com> Co-authored-by: wti <wti@users.noreply.github.com> Co-authored-by: yehorsobko <yehorsobko@macpaw.com>
90 lines
3.4 KiB
Swift
90 lines
3.4 KiB
Swift
@preconcurrency import Foundation
|
|
|
|
#if canImport(FoundationNetworking)
|
|
import FoundationNetworking
|
|
#endif
|
|
|
|
/// Key-isolated mock URLProtocol.
|
|
///
|
|
/// Each test creates a session with a unique key and registers its handler under that key.
|
|
/// Multiple suites run concurrently without interference because each test's requests carry
|
|
/// a different key, so they never read the wrong handler.
|
|
#if swift(>=6.1)
|
|
|
|
actor IsolatedMockStorage {
|
|
static let shared = IsolatedMockStorage()
|
|
private var handlers:
|
|
[String: @Sendable (URLRequest) async throws -> (HTTPURLResponse, Data)] = [:]
|
|
|
|
func set(
|
|
key: String,
|
|
handler: @escaping @Sendable (URLRequest) async throws -> (HTTPURLResponse, Data)
|
|
) {
|
|
handlers[key] = handler
|
|
}
|
|
|
|
func execute(key: String, request: URLRequest) async throws -> (HTTPURLResponse, Data) {
|
|
guard let handler = handlers[key] else {
|
|
throw NSError(
|
|
domain: "IsolatedMockError", code: 0,
|
|
userInfo: [NSLocalizedDescriptionKey: "No handler for key '\(key)'"])
|
|
}
|
|
return try await handler(request)
|
|
}
|
|
}
|
|
|
|
final class IsolatedMockURLProtocol: URLProtocol, @unchecked Sendable {
|
|
static let sessionKeyHeader = "X-Mock-Key"
|
|
|
|
static func makeSession(key: String) -> URLSession {
|
|
let config = URLSessionConfiguration.ephemeral
|
|
config.protocolClasses = [IsolatedMockURLProtocol.self]
|
|
config.httpAdditionalHeaders = [sessionKeyHeader: key]
|
|
return URLSession(configuration: config)
|
|
}
|
|
|
|
static func setHandler(
|
|
key: String,
|
|
_ handler: @escaping @Sendable (URLRequest) async throws -> (HTTPURLResponse, Data)
|
|
) async {
|
|
await IsolatedMockStorage.shared.set(key: key, handler: handler)
|
|
}
|
|
|
|
func executeHandler(for request: URLRequest) async throws -> (HTTPURLResponse, Data) {
|
|
let key = request.value(forHTTPHeaderField: IsolatedMockURLProtocol.sessionKeyHeader)
|
|
?? "unknown"
|
|
return try await IsolatedMockStorage.shared.execute(key: key, request: request)
|
|
}
|
|
|
|
override class func canInit(with request: URLRequest) -> Bool { true }
|
|
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
|
|
override func startLoading() {
|
|
Task {
|
|
do {
|
|
let (response, data) = try await self.executeHandler(for: 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() {}
|
|
}
|
|
|
|
// MARK: - Per-test session factory
|
|
|
|
/// Creates an isolated URLSession + unique key for one test.
|
|
///
|
|
/// Usage:
|
|
/// ```swift
|
|
/// let (session, key) = makeIsolatedSession()
|
|
/// await IsolatedMockURLProtocol.setHandler(key: key) { _ in (response, data) }
|
|
/// ```
|
|
func makeIsolatedSession() -> (session: URLSession, key: String) {
|
|
let key = UUID().uuidString
|
|
return (IsolatedMockURLProtocol.makeSession(key: key), key)
|
|
}
|
|
|
|
#endif
|