import Foundation import Testing @testable import PlexKit /// Woher die Senderliste kommt. /// /// Drei Adressen, unter denen die Senderliste stehen sollte, antworteten auf /// einem echten Server mit 404 — geraten war jede. Das Programmraster ist /// keine Vermutung: die Weboberfläche von Plex zeichnet ihren Guide daraus, /// und jeder Eintrag trägt den Sender mit sich. Diese Tests halten fest, dass /// die Sender dort auch dann herauskommen, wenn sonst nichts antwortet. @Suite("Senderliste aus dem Programmraster") struct LiveTVGuideTests { private func server(routes: [String: String]) -> PlexServer { PlexServer( resource: PlexResource( name: "Test", clientIdentifier: "srv", accessToken: "token", connections: [PlexConnection(uri: "http://server:32400", local: true)] ), connection: PlexConnection(uri: "http://server:32400", local: true), identity: .test, transport: FakeTransport.routes(routes) )! } private func fixture(_ name: String) throws -> String { let url = try #require(Bundle.module.url(forResource: "Fixtures/\(name)", withExtension: "json")) return try String(contentsOf: url, encoding: .utf8) } private var dvr: PlexDVR { PlexDVR(key: "2", epgIdentifier: "tv.plex.providers.epg.cloud:2") } @Test("Jeder Sender kommt genau einmal, obwohl er in mehreren Sendungen steht") func derivesChannelsFromGrid() async throws { let server = server(routes: [ "/tv.plex.providers.epg.cloud:2/grid": try fixture("livetv-grid"), ]) let guide = await server.liveTVGuide(dvr: dvr) #expect(guide.channels.count == 2) #expect(guide.channels.map(\.title) == ["SRF 1 HD", "SRF2"]) } @Test("Fehlt der Sendername, trägt das Kürzel den Sender") func fallsBackToCallSign() async throws { let server = server(routes: [ "/tv.plex.providers.epg.cloud:2/grid": try fixture("livetv-grid"), ]) let guide = await server.liveTVGuide(dvr: dvr) let second = try #require(guide.channels.last) #expect(second.title == "SRF2") // Die Sendernummer kommt mal als Zahl, mal als Zeichenkette — beides // muss lesbar sein, sonst fehlt sie bei der Hälfte der Sender. #expect(second.channelIdentifier == "2") } @Test("Sendungen landen beim richtigen Sender, nach Anfangszeit sortiert") func groupsProgramsByChannel() async throws { let server = server(routes: [ "/tv.plex.providers.epg.cloud:2/grid": try fixture("livetv-grid"), ]) let guide = await server.liveTVGuide(dvr: dvr) let first = try #require(guide.channels.first) let programs = try #require(guide.programs[first.id]) #expect(programs.map(\.title) == ["Tagesschau", "Meteo"]) #expect(guide.programs.count == 2) } @Test("Ein Sender ohne Namen taucht nicht als Strich auf") func skipsNamelessChannels() async throws { let json = """ {"MediaContainer":{"size":1,"Metadata":[ {"ratingKey":"x","title":"Irgendwas","beginsAt":1757682000,"endsAt":1757685600, "Media":[{"channelIdentifier":"nur-eine-kennung","Part":[{"key":"/livetv/channels/9"}]}]} ]}} """ let server = server(routes: ["/tv.plex.providers.epg.cloud:2/grid": json]) let guide = await server.liveTVGuide(dvr: dvr) #expect(guide.channels.isEmpty) } @Test("Die Einschaltadresse reist mit dem Sender mit") func keepsTuneKey() async throws { let server = server(routes: [ "/tv.plex.providers.epg.cloud:2/grid": try fixture("livetv-grid"), ]) let guide = await server.liveTVGuide(dvr: dvr) #expect(guide.channels.first?.key == "/livetv/channels/11") } }