import Foundation import Testing @testable import PlexKit @Suite("Verbindungsauswahl") struct ConnectionSelectorTests { private let serverID = "server-abc-123" private func identityResponse(_ machineIdentifier: String) -> String { #"{"MediaContainer":{"machineIdentifier":"\#(machineIdentifier)","version":"1.42.0"}}"# } private func resource(connections: [PlexConnection]) -> PlexResource { PlexResource( name: "Wohnzimmer", clientIdentifier: serverID, accessToken: "server-token", connections: connections ) } @Test("Der lokale Weg gewinnt, auch wenn die Vermittlung erreichbar ist") func localWinsOverRelay() async { // Der eigentliche Zweck der Rangordnung: die Vermittlung von Plex // antwortet im Zweifel schneller, ist aber gedrosselt. Ein Rennen ohne // Rang würde ausgerechnet den langsamsten Weg krönen. let transport = FakeTransport.routes(["/identity": identityResponse(serverID)]) let selector = PlexConnectionSelector(identity: .test, transport: transport) let chosen = await selector.selectConnection(for: resource(connections: [ PlexConnection(uri: "https://relay.plex.direct:443", relay: true), PlexConnection(uri: "http://192.168.1.10:32400", local: true), PlexConnection(uri: "https://1-2-3-4.abc.plex.direct:32400"), ])) #expect(chosen?.uri == "http://192.168.1.10:32400") } @Test("Der direkte Weg von außen kommt vor der Vermittlung") func directRemoteBeatsRelay() async { let transport = FakeTransport.routes(["/identity": identityResponse(serverID)]) let selector = PlexConnectionSelector(identity: .test, transport: transport) let chosen = await selector.selectConnection(for: resource(connections: [ PlexConnection(uri: "https://relay.plex.direct:443", relay: true), PlexConnection(uri: "https://1-2-3-4.abc.plex.direct:32400"), ])) #expect(chosen?.uri == "https://1-2-3-4.abc.plex.direct:32400") } @Test("Ein Server mit fremder Kennung wird abgelehnt") func rejectsWrongServer() async { // Eine veraltete plex.direct-Adresse kann auf einen fremden Server // zeigen. Ohne Abgleich der Kennung zeigte die App fremde Mediatheken. let transport = FakeTransport.routes(["/identity": identityResponse("ein-anderer-server")]) let selector = PlexConnectionSelector(identity: .test, transport: transport) let chosen = await selector.selectConnection(for: resource(connections: [ PlexConnection(uri: "http://192.168.1.10:32400", local: true), ])) #expect(chosen == nil) } @Test("Ohne erreichbaren Weg gibt es kein Ergebnis") func noReachableConnection() async { let selector = PlexConnectionSelector(identity: .test, transport: FakeTransport.unreachable()) let chosen = await selector.selectConnection(for: resource(connections: [ PlexConnection(uri: "http://192.168.1.10:32400", local: true), PlexConnection(uri: "https://relay.plex.direct:443", relay: true), ])) #expect(chosen == nil) } @Test("Ohne Server-Token wird gar nicht erst probiert") func missingAccessTokenFailsFast() async { // Bei geteilten Servern liefert plex.tv gelegentlich keinen Token — // etwa wenn die Freigabe zurückgezogen wurde. Anfragen ohne ihn // beantwortet der Server wortlos mit 401. let selector = PlexConnectionSelector(identity: .test, transport: FakeTransport.unreachable()) let resource = PlexResource( name: "Geteilt", clientIdentifier: serverID, accessToken: nil, connections: [PlexConnection(uri: "http://192.168.1.10:32400", local: true)] ) #expect(await selector.selectConnection(for: resource) == nil) } @Test("Die Rangordnung der Wege stimmt") func tierOrdering() { #expect(PlexConnection(uri: "x", local: true).tier == 0) #expect(PlexConnection(uri: "x").tier == 1) #expect(PlexConnection(uri: "x", relay: true).tier == 2) } } @Suite("Serverliste") struct ServerDirectoryTests { private let resourcesJSON = """ [ { "name": "Wohnzimmer", "product": "Plex Media Server", "clientIdentifier": "server-abc", "provides": "server", "owned": true, "accessToken": "token-eigen", "connections": [ { "uri": "http://192.168.1.10:32400", "address": "192.168.1.10", "port": 32400, "local": true, "relay": false, "protocol": "http", "IPv6": false }, { "uri": "https://1-2-3-4.abc.plex.direct:32400", "local": false, "relay": false, "protocol": "https" } ] }, { "name": "iPhone von Guido", "clientIdentifier": "client-xyz", "provides": "client,player", "owned": true, "connections": [] }, { "name": "Server eines Freundes", "clientIdentifier": "server-def", "provides": "server", "owned": false, "accessToken": "token-geteilt", "connections": [ { "uri": "https://5-6-7-8.def.plex.direct:32400", "local": false, "relay": false } ] } ] """ @Test("Nur Server erscheinen in der Liste, keine Abspielgeräte") func filtersOutNonServers() async throws { // Das Konto listet auch jedes iPhone und jeden Fernseher, der je // angemeldet war. Ungefiltert stünden sie alle in der Serverauswahl. let directory = PlexServerDirectory( identity: .test, token: "konto-token", transport: FakeTransport.routes(["/api/v2/resources": resourcesJSON]) ) let servers = try await directory.servers() #expect(servers.count == 2) #expect(!servers.contains { $0.clientIdentifier == "client-xyz" }) } @Test("Geteilte Server behalten ihren eigenen Token") func sharedServersKeepTheirOwnToken() async throws { let directory = PlexServerDirectory( identity: .test, token: "konto-token", transport: FakeTransport.routes(["/api/v2/resources": resourcesJSON]) ) let servers = try await directory.servers() let shared = try #require(servers.first { !$0.owned }) #expect(shared.accessToken == "token-geteilt") #expect(shared.accessToken != "konto-token") } }