Files
QueueCube/ios/Tests/QueueCubeShareCoreTests/RequestBuilderTests.swift
T

114 lines
4.6 KiB
Swift
Raw Normal View History

import Foundation
import XCTest
@testable import QueueCubeShareCore
final class RequestBuilderTests: XCTestCase {
override func setUp() {
super.setUp()
URLProtocol.registerClass(StubURLProtocol.self)
}
override func tearDown() {
StubURLProtocol.setResponse(status: 200, body: Data())
URLProtocol.unregisterClass(StubURLProtocol.self)
super.tearDown()
}
func testConfiguredTimeoutAndBodySurviveEndpointBuilding() throws {
let baseURL = URL(string: "https://queuecube-test.invalid/api")!
let request = RequestBuilder(url: baseURL, timeout: 7)
.path("/playlist")
.body(["url": "https://video.example/watch?v=one"])
.method(.post)
.build()
XCTAssertEqual(request.url?.path, "/api/playlist")
XCTAssertEqual(request.timeoutInterval, 7)
XCTAssertEqual(request.httpMethod, "POST")
XCTAssertEqual(request.value(forHTTPHeaderField: "Content-Type"), "application/json")
XCTAssertEqual(request.value(forHTTPHeaderField: "Cache-Control"), "no-cache")
XCTAssertEqual(try JSONDecoder().decode([String: String].self, from: XCTUnwrap(request.httpBody)), ["url": "https://video.example/watch?v=one"])
XCTAssertEqual(RequestBuilder(url: baseURL, timeout: 7).pathString("/search?q=one").build().timeoutInterval, 7)
}
func testPOSTRejectsAuthenticationFailureInsteadOfReportingSuccess() async throws {
StubURLProtocol.setResponse(status: 401, body: Data(#"{"error":"Authentication required"}"#.utf8))
do {
try await RequestBuilder(url: URL(string: "https://queuecube-test.invalid/api/playlist")!).post()
XCTFail("A non-2xx mutation must fail")
} catch RequestBuilder.RequestError.httpError(let status, let message) {
XCTAssertEqual(status, 401)
XCTAssertEqual(message, "Authentication required")
}
}
func testNonJSONServerFailureStillReportsItsStatus() async throws {
StubURLProtocol.setResponse(status: 502, body: Data("upstream unavailable".utf8))
do {
try await RequestBuilder(url: URL(string: "https://queuecube-test.invalid/api/play")!).post()
XCTFail("A non-JSON server failure must fail")
} catch RequestBuilder.RequestError.httpError(let status, let message) {
XCTAssertEqual(status, 502)
XCTAssertNil(message)
}
}
func testSuccessfulEmptyMutationResponseIsAccepted() async throws {
StubURLProtocol.setResponse(status: 204, body: Data())
try await RequestBuilder(url: URL(string: "https://queuecube-test.invalid/api/play")!).post()
}
func testAPIReadsExistingPlaylistEndpointAndDecodesIDs() async throws {
StubURLProtocol.setResponse(status: 200, body: Data(#"[{"id":42,"filename":"https://video.example/one"}]"#.utf8))
let api = API(baseURL: URL(string: "https://queuecube-test.invalid/api")!, requestTimeout: 7)
let playlist = try await api.fetchPlaylist()
XCTAssertEqual(playlist.map(\.id), [42])
let request = try XCTUnwrap(StubURLProtocol.lastRequest())
XCTAssertEqual(request.url?.path, "/api/playlist")
XCTAssertEqual(request.httpMethod, "GET")
XCTAssertEqual(request.timeoutInterval, 7)
}
}
// Intercepts only this test's reserved host. No network or real player is used.
private final class StubURLProtocol: URLProtocol {
private static let lock = NSLock()
private static var status = 200
private static var body = Data()
private static var request: URLRequest?
static func setResponse(status: Int, body: Data) {
lock.lock()
defer { lock.unlock() }
Self.status = status
Self.body = body
Self.request = nil
}
static func lastRequest() -> URLRequest? {
lock.lock()
defer { lock.unlock() }
return request
}
override class func canInit(with request: URLRequest) -> Bool {
request.url?.host == "queuecube-test.invalid"
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }
override func startLoading() {
Self.lock.lock()
let status = Self.status
let body = Self.body
Self.request = request
Self.lock.unlock()
let response = HTTPURLResponse(url: request.url!, statusCode: status, httpVersion: "HTTP/1.1", headerFields: [:])!
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: body)
client?.urlProtocolDidFinishLoading(self)
}
override func stopLoading() {}
}