Vier Punkte: Player-Hänger, kaputte Downloads, Stufenliste, Kopfleiste

Player hing sich auf
  Die Qualitätsauswahl war ein Menu. Schloss man es durch einen Tipp ins
  Bild, blieb eine unsichtbare Ebene zurück, die alle weiteren Tipps
  schluckte — die Bedienelemente kamen nicht mehr, pausieren ging nicht
  mehr. Jetzt ein confirmationDialog, der sich selbst wegräumt. Nach dem
  Schließen werden die Bedienelemente eingeblendet und die Ausblendfrist
  neu gestartet.

Downloads galten als fertig, waren aber leer
  Zwei Fehler übereinander. Erstens gilt ein URLSessionDownloadTask auch
  bei 404 oder 500 als erfolgreich und legt die Fehlerseite als Datei ab.
  Zweitens überschrieb ich danach die erwartete Größe mit der empfangenen
  — womit die Anzeige auf hundert Prozent stand und der Beweis weg war.
  873 MB waren in einer Sekunde „fertig", und beim Abspielen kam nur
  „Wiedergabe wurde unterbrochen".

  Jetzt wird der Statuscode geprüft, die erwartete Größe bleibt stehen,
  und eine unplausible Datei wird verworfen statt verbucht: unter 100 KB
  ist keine Videodatei, unter der Hälfte der angekündigten Größe ist eine
  abgebrochene Übertragung. Sechs Tests dazu.

Stufenliste ergab keinen Sinn
  Für einen 720p-Inhalt standen Original, Full HD und HD mit derselben
  geschätzten Größe untereinander — das sah aus, als wäre die Schätzung
  kaputt. Sie war es nicht, die Stufen waren es: hochrechnen bringt nur
  eine größere Datei. Angeboten wird jetzt nur noch, was unter der
  Auflösung der Datei liegt, und beim Original steht dabei, was es ist:
  „Original · 720p · 873 MB".

Kopfleiste
  Kein Hintergrund mehr statt eines weichgezeichneten — die Knöpfe
  bringen ihre eigene Glasfläche mit. Der Titel ist weg: der Name der App
  gehört auf den Homescreen, nicht über jeden Bildschirm.

145 Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-09-11 22:32:19 +02:00
co-authored by Claude Opus 5
parent 8a77ab6600
commit 6150b018ec
9 changed files with 291 additions and 40 deletions
@@ -152,3 +152,47 @@ struct DownloadRulesTests {
#expect(rules.seriesNeedingRefill(in: [item("1"), item("2")]).isEmpty)
}
}
@MainActor
@Suite("Plausibilität fertiger Downloads")
struct DownloadPlausibilityTests {
@Test("Eine Fehlerseite geht nicht als Film durch")
func tinyFileIsRejected() {
// Der Fall aus der Praxis: 873 MB angekündigt, in einer Sekunde
// „fertig". Angekommen war eine HTML-Fehlerseite, und weil die
// empfangene Größe auch als erwartete notiert wurde, stand die
// Anzeige auf hundert Prozent.
#expect(DownloadCoordinator.implausibility(written: 2_048, expected: 873_000_000) != nil)
}
@Test("Auch ohne angekündigte Größe fällt eine Fehlerseite auf")
func tinyFileWithoutExpectation() {
// Beim umgerechneten Download kennt der Server die Endgröße vorab
// nicht — die Untergrenze muss deshalb allein tragen.
#expect(DownloadCoordinator.implausibility(written: 900, expected: 0) != nil)
}
@Test("Eine abgebrochene Übertragung fällt auf")
func truncatedTransferIsRejected() {
#expect(DownloadCoordinator.implausibility(written: 200_000_000, expected: 873_000_000) != nil)
}
@Test("Eine vollständige Datei geht durch")
func completeFilePasses() {
#expect(DownloadCoordinator.implausibility(written: 873_000_000, expected: 873_000_000) == nil)
}
@Test("Kleinere Abweichungen sind erlaubt")
func smallDeviationPasses() {
// Der Transcoder arbeitet mit variabler Bitrate und trifft die
// geschätzte Größe nie genau. Zu streng zu prüfen hieße, gültige
// Downloads zu verwerfen.
#expect(DownloadCoordinator.implausibility(written: 800_000_000, expected: 873_000_000) == nil)
}
@Test("Ohne Erwartung geht eine normale Datei durch")
func noExpectationPasses() {
#expect(DownloadCoordinator.implausibility(written: 500_000_000, expected: 0) == nil)
}
}