package credits import "testing" // Confidence, and the stability rule that decides whether anything is written at all. // // The governing principle under test throughout: prefer no marker to a wrong marker. Almost // every case here is about the combination refusing to answer. func visual(startMs int64, confidence float64) Detection { return Detection{Found: true, StartMs: startMs, Confidence: confidence, Method: MethodVisual} } func cluster(startMs int64, users int, next bool) BehaviourEvidence { nextCount := 0 if next { nextCount = users } return BehaviourEvidence{ Found: true, StartMs: startMs, UserCount: users, NextEpisodeCount: nextCount, SpreadMs: 4000, } } // The brief's worked example: a visual detector at 44:01 and a Tracearr cluster at 44:05 // should produce a marker around 44:03 with very high confidence. func TestAgreeingSignalsStrengthenEachOther(t *testing.T) { detection, ok := Combine(visual(2_641_000, 0.80), cluster(2_645_000, 3, true)) if !ok { t.Fatal("two agreeing signals were rejected") } if detection.Method != MethodCombined { t.Fatalf("method = %q, want %q", detection.Method, MethodCombined) } if detection.Confidence <= 0.80 { t.Fatalf("confidence %.2f; agreement must beat either signal alone", detection.Confidence) } // The earlier of the two. A skip that starts at the first frame of the roll is right; // one that starts a few seconds in has already shown what it was asked to skip. if detection.StartMs != 2_641_000 { t.Fatalf("StartMs = %d, want the earlier reading", detection.StartMs) } } func TestDisagreeingSignalsUsuallyProduceNothing(t *testing.T) { // Three minutes apart is not two readings of one transition. _, ok := Combine(visual(2_400_000, 0.80), cluster(2_580_000, 3, true)) if ok { t.Fatal("contradicting signals produced a marker; nothing is the right answer") } } func TestWeakVisualAloneIsRejected(t *testing.T) { if _, ok := Combine(visual(2_600_000, 0.55), BehaviourEvidence{}); ok { t.Fatal("a detection below the confidence threshold was accepted") } } func TestBehaviourAloneCanWriteAMarker(t *testing.T) { // The whole reason the behavioural half is worth having: on a well-watched show it // settles the question with no media access at all. detection, ok := Combine(Detection{}, cluster(2_645_000, 4, true)) if !ok { t.Fatal("a four-viewer cluster produced no marker") } if detection.Method != MethodBehaviour { t.Fatalf("method = %q, want %q", detection.Method, MethodBehaviour) } } func TestThinBehaviourAloneIsRejected(t *testing.T) { if _, ok := Combine(Detection{}, cluster(2_645_000, 2, false)); ok { t.Fatal("two plain stops wrote a marker with nothing else agreeing") } } func TestNothingFoundIsNotAMarker(t *testing.T) { if _, ok := Combine(Detection{}, BehaviourEvidence{}); ok { t.Fatal("no evidence at all produced a marker") } } // Marker stability: readings of the same episode wobble by a few seconds, and rewriting the // row each time would break the "one write per episode, ever" promise. func TestSmallVariationDoesNotRewrite(t *testing.T) { existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.90, DetectionMethod: MethodCombined} for _, offset := range []int64{-4000, -1000, 2000, 3000} { if ShouldRewrite(existing, visual(existing.CreditsStartMs+offset, 0.91)) { t.Fatalf("a %dms difference triggered a rewrite", offset) } } } func TestSubstantiallyBetterEvidenceRewrites(t *testing.T) { existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.71, DetectionMethod: MethodVisual} better := Detection{ Found: true, StartMs: 2_644_500, Confidence: 0.95, Method: MethodCombined, } if !ShouldRewrite(existing, better) { t.Fatal("a much stronger reading of the same position did not rewrite") } } func TestADifferentPositionNeedsBetterEvidence(t *testing.T) { existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.90, DetectionMethod: MethodCombined} // A different answer that is no better must not win, or two detectors that disagree // would rewrite the row past each other for ever. if ShouldRewrite(existing, visual(2_500_000, 0.85)) { t.Fatal("a weaker reading at a different position overwrote a strong marker") } if !ShouldRewrite(existing, Detection{ Found: true, StartMs: 2_500_000, Confidence: 0.96, Method: MethodCombined, }) { t.Fatal("stronger evidence at a different position should win") } } func TestManualMarkersAreNeverOverwritten(t *testing.T) { existing := Marker{CreditsStartMs: 2_600_000, Confidence: 0.5, DetectionMethod: MethodManual} if ShouldRewrite(existing, visual(2_400_000, 0.98)) { t.Fatal("an automatic detection overwrote an operator's correction") } } func TestBelowThresholdNeverRewrites(t *testing.T) { existing := Marker{CreditsStartMs: 2_644_000, Confidence: 0.72, DetectionMethod: MethodVisual} if ShouldRewrite(existing, visual(2_644_100, 0.50)) { t.Fatal("a sub-threshold detection was written over an accepted marker") } }