import Foundation import Testing import MediaKit @testable import FlaroServerKit /// Die Folge nach dieser — über die Staffelgrenze hinweg. /// /// **Der Anlass.** Im Web stand am Abspann „Nächste Folge", in der App nur /// „Abspann überspringen". Der Server war es nicht: beide fragen denselben /// Endpunkt, und er ordnet nach Staffel und dann nach Nummer, findet also /// auch die erste Folge der nächsten Staffel. /// /// **Es war der Dekoder.** `nachbar` baute sich einen frischen /// `JSONDecoder()`, und der kennt das Datumsformat des Servers nicht. /// Solange die Nachbarfolge weder `publishAt` noch `lastPlayedAt` hatte, /// ging es gut — mit eingeschalteter Prüfung hat sie ein `publishAt`. Das /// `try?` schluckte den Fehler, und von aussen sah es aus, als gäbe es /// keine nächste Folge. @Suite("Flaro: Nachbarfolge") struct NachbarfolgeTests { private func backend(_ transport: any FlaroTransport) -> FlaroBackend { FlaroBackend( client: FlaroClient( baseURL: Proben.adresse, identity: Proben.kennung, token: "t", transport: transport, cookieJar: nil), account: FlaroAccount( baseURL: Proben.adresse, serverName: "Wohnzimmer", userID: "u1", userName: "guido", isAdministrator: false)) } private static func folge( id: String, staffel: Int, nummer: Int, publishAt: String = "null", zuletzt: String = "null" ) -> String { """ { "id": "\(id)", "season": \(staffel), "episode": \(nummer), "title": "Neuanfang", "overview": null, "stillImage": null, "files": [{ "id": "d-\(id)", "fileName": "F.mkv", "sizeBytes": 1, "container": "mkv", "durationSeconds": 2700, "streams": [] }], "state": { "positionSeconds": 0, "runtimeSeconds": 2700, "watched": false, "playCount": 0, "favourite": false, "rating": null, "dismissed": false, "lastPlayedAt": \(zuletzt), "inProgress": false }, "publishAt": \(publishAt), "seriesId": "serie-1", "markers": [] } """ } private static let folgeJetzt = MediaItem( id: "s1e10", kind: .episode, title: "Finale", index: 10, parentIndex: 1) /// Das Staffelfinale: die nächste Folge ist die erste der nächsten Staffel. @Test("Über die Staffelgrenze hinweg") func ueberDieStaffelgrenze() async throws { let backend = backend(Attrappe(rumpf: Self.folge(id: "s2e1", staffel: 2, nummer: 1))) let naechste = try #require(await backend.nextEpisode(after: Self.folgeJetzt)) #expect(naechste.id == "s2e1") #expect(naechste.parentIndex == 2) #expect(naechste.index == 1) } /// **Der Fehler selbst.** Mit `publishAt` und `lastPlayedAt` scheiterte /// das Lesen stillschweigend, und der Knopf blieb weg. Mit dem frischen /// `JSONDecoder()` von vorher ist diese Prüfung rot. @Test("Auch mit Datumsangaben — daran scheiterte es") func mitDatumsangaben() async throws { let backend = backend(Attrappe(rumpf: Self.folge( id: "s2e1", staffel: 2, nummer: 1, publishAt: "\"2026-10-01T18:00:00Z\"", zuletzt: "\"2026-10-11T21:14:03.482Z\""))) let naechste = try #require(await backend.nextEpisode(after: Self.folgeJetzt)) #expect(naechste.id == "s2e1") #expect(naechste.availableFrom != nil) } /// Nach der letzten Folge kommt keine — 204, und das ist kein Fehler. @Test("Hinter der letzten Folge kommt nichts") func hinterDerLetzten() async throws { let backend = backend(Attrappe(rumpf: "", status: 204)) #expect(try await backend.nextEpisode(after: Self.folgeJetzt) == nil) } /// Ein Film hat keine Nachbarn — und fragt gar nicht erst. @Test("Ein Film fragt nicht nach Nachbarn") func filmFragtNicht() async throws { let attrappe = Attrappe(rumpf: Self.folge(id: "x", staffel: 1, nummer: 1)) let film = MediaItem(id: "f1", kind: .movie, title: "Arrival") #expect(try await backend(attrappe).nextEpisode(after: film) == nil) #expect(attrappe.anfragen.isEmpty) } }