Anmelde-Adresse wurde doppelt kodiert URLComponents.fragment kodiert beim Setzen erneut, aus %5B wurde %255B. Plex antwortete mit „Diese Anfrage konnte nicht abgeschlossen werden". Die Adresse wird jetzt von Hand zusammengesetzt. Verbindungssuche hing bis zu anderthalb Minuten probeTimeout war deklariert, aber nirgends angewendet — es galt der URLSession-Standard von 60 Sekunden. Zusammen mit der Prüfung Rang für Rang kostete eine schweigende lokale Adresse die volle Zeit, bevor der direkte Weg überhaupt begann. Jetzt laufen alle Adressen gleichzeitig, mit Zeitgrenze, und die Rangordnung entscheidet unter den erreichbaren. Kein Zurück während des Ladens Die Kopfzeile mit dem Schließen-Knopf wurde nur im Zustand .playing gezeichnet. Bei langsamer Verbindung saß man ohne Ausweg vor einem Ladekreis. Sie liegt jetzt als eigene Ebene darüber und erscheint auch beim Vorbereiten — samt Titel, damit erkennbar bleibt, worauf gewartet wird. Sechs neue Tests, darunter drei, die die Zeitgrenze der Verbindungssuche an einer schweigenden Adresse nachstellen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
82 lines
2.8 KiB
Swift
82 lines
2.8 KiB
Swift
import Foundation
|
|
@testable import PlexKit
|
|
|
|
/// Ein Transport, der vorbereitete Antworten liefert.
|
|
///
|
|
/// Statt `URLProtocol` zu unterwandern: das ist global und bringt bei parallel
|
|
/// laufenden Tests die Antworten durcheinander. Hier hat jeder Test seinen
|
|
/// eigenen Transport.
|
|
struct FakeTransport: PlexTransport {
|
|
let handler: @Sendable (URLRequest) async throws -> (Data, URLResponse)
|
|
|
|
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
|
|
try await handler(request)
|
|
}
|
|
|
|
/// Antwortet auf jeden Pfad, der als Schlüssel hinterlegt ist, und mit 404
|
|
/// auf alle übrigen.
|
|
static func routes(
|
|
_ routes: [String: String],
|
|
status: Int = 200
|
|
) -> FakeTransport {
|
|
FakeTransport { request in
|
|
let path = request.url?.path() ?? ""
|
|
guard let body = routes.first(where: { path.hasSuffix($0.key) })?.value else {
|
|
return (Data(), response(for: request, status: 404))
|
|
}
|
|
return (Data(body.utf8), response(for: request, status: status))
|
|
}
|
|
}
|
|
|
|
/// Verweigert jede Verbindung — für die Wege, die im Test nicht gewinnen
|
|
/// sollen.
|
|
static func unreachable() -> FakeTransport {
|
|
FakeTransport { _ in throw URLError(.cannotConnectToHost) }
|
|
}
|
|
|
|
/// Antwortet je nach Adresse unterschiedlich — und lässt einzelne Adressen
|
|
/// ins Leere laufen.
|
|
///
|
|
/// Bildet den Fall nach, der in der Wirklichkeit am meisten weh tut: eine
|
|
/// lokale Adresse, die nicht ablehnt, sondern schweigt. Ein abgelehnter
|
|
/// Verbindungsversuch ist sofort vorbei; ein schweigender läuft bis zur
|
|
/// Zeitgrenze.
|
|
static func hosts(
|
|
answering answers: [String: String],
|
|
hanging: Set<String> = []
|
|
) -> FakeTransport {
|
|
FakeTransport { request in
|
|
let host = request.url?.host() ?? ""
|
|
if hanging.contains(host) {
|
|
// Länger als jede Zeitgrenze, die ein Test setzen würde.
|
|
try await Task.sleep(for: .seconds(3600))
|
|
}
|
|
guard let body = answers[host] else {
|
|
throw URLError(.cannotConnectToHost)
|
|
}
|
|
return (Data(body.utf8), response(for: request, status: 200))
|
|
}
|
|
}
|
|
|
|
static func response(for request: URLRequest, status: Int) -> HTTPURLResponse {
|
|
HTTPURLResponse(
|
|
url: request.url ?? URL(string: "http://example.invalid")!,
|
|
statusCode: status,
|
|
httpVersion: nil,
|
|
headerFields: nil
|
|
)!
|
|
}
|
|
}
|
|
|
|
extension PlexClientIdentity {
|
|
static let test = PlexClientIdentity(
|
|
product: "FlixyTests",
|
|
version: "0",
|
|
clientIdentifier: "test-client",
|
|
platform: "iOS",
|
|
platformVersion: "26.0",
|
|
device: "iPhone",
|
|
deviceName: "Testgerät"
|
|
)
|
|
}
|