import Testing import Foundation @testable import FlaroPlayback @Suite("Wann der teure Weg erlaubt ist") struct DowngradeRuleTests { /// Der Normalfall, in dem abgesenkt werden soll: keine Leiter da, es /// hakt dauerhaft, und es läuft lange genug. private func notlage( isAutomatic: Bool = true, hasLadder: Bool = false, isAtLowestRung: Bool = false, condition: StreamCondition = .failing, stalls: Int = 3, longest: Duration = .seconds(4), playedFor: Duration = .seconds(120), sinceLastSeek: Duration? = nil, sinceLastDowngrade: Duration? = nil, used: Int = 0, budget: Int = 2 ) -> DowngradeRule.Facts { .init( isAutomatic: isAutomatic, hasLadder: hasLadder, isAtLowestRung: isAtLowestRung, condition: condition, stallsInWindow: stalls, longestStallInWindow: longest, playedFor: playedFor, sinceLastSeek: sinceLastSeek, sinceLastDowngrade: sinceLastDowngrade, used: used, budget: budget ) } @Test("Ohne Leiter und mit drei Rücklern: ja") func downgradesWhenNoLadder() { #expect(DowngradeRule.verdict(notlage()) == .downgrade) } /// Eine ausdrücklich gewählte Stufe ist eine Ansage. `automatic` ist die /// Erlaubnis einzugreifen, alles andere nicht. @Test("Eine gewählte Stufe wird nicht überstimmt") func neverOverridesAChoice() { #expect(DowngradeRule.verdict(notlage(isAutomatic: false)) == .notAutomatic) } /// Solange es noch eine Sprosse tiefer gibt, kostet der billige Hebel /// kein schwarzes Bild. @Test("Solange die Leiter trägt, bleibt es beim billigen Hebel") func prefersTheCheapLever() { let facts = notlage(hasLadder: true, isAtLowestRung: false) #expect(DowngradeRule.verdict(facts) == .cheaperPathRemains) } @Test("Auf der untersten Sprosse ist der billige Hebel erschöpft") func lowestRungAllowsTheExpensivePath() { let facts = notlage(hasLadder: true, isAtLowestRung: true) #expect(DowngradeRule.verdict(facts) == .downgrade) } @Test("Zwei Rückler genügen nicht") func twoStallsAreNotEnough() { #expect(DowngradeRule.verdict(notlage(stalls: 2)) == .notBadEnough) } /// Fünfzehn Sekunden Stillstand sind keine Schwankung. Darauf zu warten, /// bis drei zusammenkommen, hieße viereinhalb Minuten zusehen. @Test("Ein einzelner langer Rückler genügt allein") func oneLongStallIsEnough() { let facts = notlage(stalls: 1, longest: .seconds(20)) #expect(DowngradeRule.verdict(facts) == .downgrade) } @Test("Ein gesunder Stream löst nichts aus") func healthyNeverTriggers() { #expect(DowngradeRule.verdict(notlage(condition: .healthy)) == .notBadEnough) } /// Das anfängliche Füllen des Puffers sieht aus wie eine Notlage und ist /// keine. @Test("In den ersten Sekunden passiert nichts") func waitsAfterStart() { #expect(DowngradeRule.verdict(notlage(playedFor: .seconds(5))) == .tooEarly) } /// Ein Sprung leert den Puffer immer. Sich dafür zu bestrafen wäre /// absurd. @Test("Kurz nach einem Sprung passiert nichts") func waitsAfterSeek() { let facts = notlage(sinceLastSeek: .seconds(3)) #expect(DowngradeRule.verdict(facts) == .tooEarly) } @Test("Ein länger zurückliegender Sprung stört nicht") func oldSeekIsFine() { #expect(DowngradeRule.verdict(notlage(sinceLastSeek: .seconds(30))) == .downgrade) } @Test("Innerhalb der Sperrfrist nicht noch einmal") func respectsCooldown() { let facts = notlage(sinceLastDowngrade: .seconds(60), used: 1) #expect(DowngradeRule.verdict(facts) == .cooling) } @Test("Nach der Sperrfrist wieder") func afterCooldownAgain() { let facts = notlage(sinceLastDowngrade: .seconds(200), used: 1) #expect(DowngradeRule.verdict(facts) == .downgrade) } /// Lautlos die ganze Leiter bis 240p hinunterzugehen ist schlimmer, als /// es zu sagen. @Test("Das Budget ist endlich") func budgetIsFinite() { let facts = notlage(sinceLastDowngrade: .seconds(300), used: 2, budget: 2) #expect(DowngradeRule.verdict(facts) == .budgetSpent) } /// Bei Live-TV nur einmal: jeder Neuaufbau stimmt den Tuner neu ab, und /// davon gibt es vier Stück. @Test("Bei Live-TV ist nach einem Mal Schluss") func liveBudgetIsOne() { let facts = notlage(sinceLastDowngrade: .seconds(300), used: 1, budget: 1) #expect(DowngradeRule.verdict(facts) == .budgetSpent) } }