package credits import ( "testing" "time" ) // The scan window is where the performance claim is kept or given away, so these tests are // about size: how much of a file each source of evidence lets us avoid reading. const bbRuntime = 44*60*1000 + 12*1000 // 44:12 func TestGenericTailIsBoundedBothWays(t *testing.T) { // A short episode still gets a usable window. short := GenericTailWindow(18 * 60 * 1000) if short.DurationMs() < genericTailMinimum.Milliseconds() { t.Fatalf("short episode window was %v, below the floor", short.DurationMs()) } // A three-hour film does not turn into a thirty-six minute scan. long := GenericTailWindow(3 * 60 * 60 * 1000) if long.DurationMs() > genericTailMaximum.Milliseconds() { t.Fatalf("film window was %v, past the ceiling", long.DurationMs()) } if long.EndMs != 3*60*60*1000 { t.Fatal("the window must run to the end of the file") } if long.Source != WindowGeneric { t.Fatalf("source = %q, want %q", long.Source, WindowGeneric) } } func TestNoRuntimeMeansNoWindow(t *testing.T) { if GenericTailWindow(0).Valid() { t.Fatal("a file with no runtime produced a scannable window") } } // The optimisation the brief calls one of the most important: knowing where this show puts // its credits should turn a ten-minute tail into a couple of minutes. func TestSeasonHistoryNarrowsTheScan(t *testing.T) { // Blue Bloods S06E04–06, credits around 40:50 of a ~44 minute episode. history := []Marker{ {CreditsStartMs: 40*60*1000 + 51*1000, Confidence: 0.92}, {CreditsStartMs: 40*60*1000 + 47*1000, Confidence: 0.90}, {CreditsStartMs: 40*60*1000 + 50*1000, Confidence: 0.94}, } runtimeOf := func(Marker) int64 { return bbRuntime } generic := GenericTailWindow(bbRuntime) narrowed := NarrowWindow(bbRuntime, history, runtimeOf, BehaviourEvidence{}) if narrowed.Source != WindowSeason { t.Fatalf("source = %q, want %q", narrowed.Source, WindowSeason) } if narrowed.DurationMs() >= generic.DurationMs() { t.Fatalf("season history did not narrow anything: %v vs generic %v", narrowed.DurationMs(), generic.DurationMs()) } // The brief's example expects roughly 39:30–42:00 for an expected 40:45. expected := int64(40*60+50) * 1000 if narrowed.StartMs > expected || narrowed.EndMs < expected { t.Fatalf("window %v–%v does not contain the expected position %v", narrowed.StartMs, narrowed.EndMs, expected) } } // The rule that stops one wrong marker propagating through a whole season. func TestASingleMarkerIsNotEnoughToNarrow(t *testing.T) { history := []Marker{{CreditsStartMs: 40*60*1000 + 51*1000, Confidence: 0.95}} window := NarrowWindow(bbRuntime, history, func(Marker) int64 { return bbRuntime }, BehaviourEvidence{}) if window.Source != WindowGeneric { t.Fatalf("source = %q; one marker is an anecdote, not a pattern", window.Source) } } func TestInconsistentSeasonFallsBackToTheGenericTail(t *testing.T) { // A show whose known markers are minutes apart is telling us its episodes are not // structurally alike, so their average points nowhere useful. history := []Marker{ {CreditsStartMs: 36 * 60 * 1000, Confidence: 0.9}, {CreditsStartMs: 41 * 60 * 1000, Confidence: 0.9}, {CreditsStartMs: 39 * 60 * 1000, Confidence: 0.9}, } window := NarrowWindow(bbRuntime, history, func(Marker) int64 { return bbRuntime }, BehaviourEvidence{}) if window.Source != WindowGeneric { t.Fatalf("source = %q, want the generic fallback", window.Source) } } func TestLowConfidenceHistoryDoesNotSteerAScan(t *testing.T) { history := []Marker{ {CreditsStartMs: 40*60*1000 + 51*1000, Confidence: 0.4}, {CreditsStartMs: 40*60*1000 + 47*1000, Confidence: 0.3}, } window := NarrowWindow(bbRuntime, history, func(Marker) int64 { return bbRuntime }, BehaviourEvidence{}) if window.Source != WindowGeneric { t.Fatalf("source = %q; a doubtful marker must not decide where everything else looks", window.Source) } } // Behaviour is a direct observation of this episode, where season history is an inference // from its neighbours, so it wins. func TestBehaviourOutranksSeasonHistory(t *testing.T) { history := []Marker{ {CreditsStartMs: 40*60*1000 + 51*1000, Confidence: 0.92}, {CreditsStartMs: 40*60*1000 + 47*1000, Confidence: 0.90}, } evidence := BehaviourEvidence{ Found: true, StartMs: 41*60*1000 + 30*1000, UserCount: 3, SpreadMs: 3000, } window := NarrowWindow(bbRuntime, history, func(Marker) int64 { return bbRuntime }, evidence) if window.Source != WindowBehaviour { t.Fatalf("source = %q, want %q", window.Source, WindowBehaviour) } if window.StartMs > evidence.StartMs || window.EndMs < evidence.StartMs { t.Fatal("the window does not contain the observed cluster") } } func TestNarrowedWindowsCarryAMargin(t *testing.T) { evidence := BehaviourEvidence{ Found: true, StartMs: 40 * 60 * 1000, UserCount: 3, SpreadMs: 2000, } window := NarrowWindow(bbRuntime, nil, nil, evidence) // Nothing here is precise enough to scan a single instant, and being slightly early is // the harmless direction — but the margin has to exist on both sides. if window.StartMs >= evidence.StartMs || window.EndMs <= evidence.StartMs { t.Fatalf("window %v–%v has no margin around %v", window.StartMs, window.EndMs, evidence.StartMs) } if window.DurationMs() > 2*narrowMargin.Milliseconds()+time.Second.Milliseconds() { t.Fatalf("narrowed window is %v; wider than the margin allows", window.DurationMs()) } }