f512d94c74
git-subtree-dir: clients/ds-app git-subtree-mainline:a24e3e6f22git-subtree-split:5206cf3b0c
87 lines
3.2 KiB
Swift
87 lines
3.2 KiB
Swift
import Foundation
|
|
|
|
/// 테스트용 URLProtocol — URLSession 을 가로채 canned 응답/에러를 돌려주고, 나간 요청을 기록.
|
|
/// 라이브 네트워크 0 으로 LocalMLX 의 probe/complete call-shape 를 검증.
|
|
final class MockURLProtocol: URLProtocol {
|
|
/// (request) -> (response, body). throw 하면 URLSession 에러 경로.
|
|
nonisolated(unsafe) static var handler: (@Sendable (URLRequest) throws -> (HTTPURLResponse, Data))?
|
|
/// 마지막으로 가로챈 요청(body 포함) 기록.
|
|
nonisolated(unsafe) static var recorder = RequestRecorder()
|
|
|
|
static func reset() {
|
|
handler = nil
|
|
recorder = RequestRecorder()
|
|
}
|
|
|
|
override class func canInit(with request: URLRequest) -> Bool { true }
|
|
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
|
|
|
|
override func startLoading() {
|
|
Self.recorder.record(request)
|
|
guard let handler = Self.handler else {
|
|
client?.urlProtocol(self, didFailWithError: URLError(.unsupportedURL))
|
|
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() {}
|
|
|
|
// MARK: helpers
|
|
|
|
static func session() -> URLSession {
|
|
let config = URLSessionConfiguration.ephemeral
|
|
config.protocolClasses = [MockURLProtocol.self]
|
|
return URLSession(configuration: config)
|
|
}
|
|
|
|
static func ok(_ url: URL, json: Data) -> (HTTPURLResponse, Data) {
|
|
(HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil)!, json)
|
|
}
|
|
|
|
static func status(_ url: URL, _ code: Int, body: String = "") -> (HTTPURLResponse, Data) {
|
|
(HTTPURLResponse(url: url, statusCode: code, httpVersion: nil, headerFields: nil)!, Data(body.utf8))
|
|
}
|
|
}
|
|
|
|
/// 나간 요청 기록(body 는 httpBody 또는 httpBodyStream 에서 추출 — URLProtocol 은 보통 stream 으로 전달).
|
|
final class RequestRecorder: @unchecked Sendable {
|
|
private(set) var lastURL: URL?
|
|
private(set) var lastMethod: String?
|
|
private(set) var lastBody: Data?
|
|
private(set) var callCount = 0
|
|
|
|
func record(_ request: URLRequest) {
|
|
callCount += 1
|
|
lastURL = request.url
|
|
lastMethod = request.httpMethod
|
|
lastBody = request.bodyData
|
|
}
|
|
}
|
|
|
|
extension URLRequest {
|
|
/// URLProtocol 단계에서 body 추출 — httpBody 가 nil 이면 httpBodyStream 에서 읽음.
|
|
var bodyData: Data? {
|
|
if let httpBody { return httpBody }
|
|
guard let stream = httpBodyStream else { return nil }
|
|
stream.open()
|
|
defer { stream.close() }
|
|
var data = Data()
|
|
let bufSize = 8192
|
|
var buffer = [UInt8](repeating: 0, count: bufSize)
|
|
while stream.hasBytesAvailable {
|
|
let read = stream.read(&buffer, maxLength: bufSize)
|
|
if read <= 0 { break }
|
|
data.append(buffer, count: read)
|
|
}
|
|
return data
|
|
}
|
|
}
|