package query import ( "testing" "time" "github.com/AmanTahiliani/box-box/internal/store" ) func contextService(t *testing.T, now time.Time) *Service { t.Helper() base := openTestService(t) return NewServiceWithClock(base.store, func() time.Time { return now }) } func addContextMeeting(t *testing.T, svc *Service, key int, name, start, end string, cancelled bool) { t.Helper() if err := svc.store.UpsertMeeting(store.Meeting{MeetingKey: key, MeetingName: name, MeetingOfficialName: name, CircuitShortName: name, Year: 2026, DateStart: start, DateEnd: end, IsCancelled: cancelled}); err != nil { t.Fatal(err) } } func addContextSession(t *testing.T, svc *Service, key, meeting int, name, start, end string, cancelled bool) { t.Helper() if err := svc.store.UpsertSession(store.Session{SessionKey: key, MeetingKey: meeting, SessionName: name, SessionType: name, DateStart: start, DateEnd: end, IsCancelled: cancelled}); err != nil { t.Fatal(err) } } func completeContextSession(t *testing.T, svc *Service, key, meeting int) { t.Helper() if err := svc.store.UpsertSessionResult(store.SessionResult{SessionKey: key, MeetingKey: meeting, DriverNumber: 1, Position: 1}); err != nil { t.Fatal(err) } } func TestResolveWeekendContextTemporalStates(t *testing.T) { tests := []struct { name string now string seed func(*testing.T, *Service) evidence LiveEvidence want TemporalState }{ {name: "no season", now: "2026-06-01T12:00:00Z", want: TemporalNoSeason}, {name: "between weekends", now: "2026-06-10T12:00:00Z", seed: func(t *testing.T, s *Service) { addContextMeeting(t, s, 1, "Monaco Grand Prix", "2026-06-01T09:00:00Z", "2026-06-02T16:00:00Z", false) addContextSession(t, s, 11, 1, "Race", "2026-06-02T14:00:00Z", "2026-06-02T16:00:00Z", false) completeContextSession(t, s, 11, 1) addContextMeeting(t, s, 2, "Canada Grand Prix", "2026-06-20T09:00:00Z", "2026-06-22T16:00:00Z", false) addContextSession(t, s, 21, 2, "Practice 1", "2026-06-20T09:00:00Z", "2026-06-20T10:00:00Z", false) addContextSession(t, s, 22, 2, "Race", "2026-06-22T14:00:00Z", "2026-06-22T16:00:00Z", false) }, want: TemporalBetweenWeekends}, {name: "pre session", now: "2026-06-19T12:00:00Z", seed: func(t *testing.T, s *Service) { addContextMeeting(t, s, 2, "Canada Grand Prix", "2026-06-20T09:00:00Z", "2026-06-22T16:00:00Z", false) addContextSession(t, s, 21, 2, "Practice 1", "2026-06-20T09:00:00Z", "2026-06-20T10:00:00Z", false) addContextSession(t, s, 22, 2, "Race", "2026-06-22T14:00:00Z", "2026-06-22T16:00:00Z", false) }, want: TemporalPreSession}, {name: "session live overrides schedule", now: "2026-06-20T12:00:00Z", seed: func(t *testing.T, s *Service) { addContextMeeting(t, s, 2, "Canada Grand Prix", "2026-06-20T09:00:00Z", "2026-06-22T16:00:00Z", false) addContextSession(t, s, 21, 2, "Practice 1", "2026-06-20T09:00:00Z", "2026-06-20T10:00:00Z", false) addContextSession(t, s, 22, 2, "Race", "2026-06-22T14:00:00Z", "2026-06-22T16:00:00Z", false) }, evidence: LiveEvidence{Active: true, MeetingName: "Canadian Grand Prix", CircuitName: "Canada Grand Prix", SessionName: "Practice 1", SessionType: "Practice 1"}, want: TemporalSessionLive}, {name: "session settling", now: "2026-06-20T11:00:00Z", seed: func(t *testing.T, s *Service) { addContextMeeting(t, s, 2, "Canada Grand Prix", "2026-06-20T09:00:00Z", "2026-06-22T16:00:00Z", false) addContextSession(t, s, 21, 2, "Practice 1", "2026-06-20T09:00:00Z", "2026-06-20T10:00:00Z", false) addContextSession(t, s, 22, 2, "Race", "2026-06-22T14:00:00Z", "2026-06-22T16:00:00Z", false) }, want: TemporalSessionSettling}, {name: "between sessions", now: "2026-06-20T11:00:00Z", seed: func(t *testing.T, s *Service) { addContextMeeting(t, s, 2, "Canada Grand Prix", "2026-06-20T09:00:00Z", "2026-06-22T16:00:00Z", false) addContextSession(t, s, 21, 2, "Practice 1", "2026-06-20T09:00:00Z", "2026-06-20T10:00:00Z", false) completeContextSession(t, s, 21, 2) addContextSession(t, s, 22, 2, "Race", "2026-06-22T14:00:00Z", "2026-06-22T16:00:00Z", false) }, want: TemporalBetweenSessions}, {name: "post weekend", now: "2026-06-22T18:00:00Z", seed: func(t *testing.T, s *Service) { addContextMeeting(t, s, 2, "Canada Grand Prix", "2026-06-20T09:00:00Z", "2026-06-22T16:00:00Z", false) addContextSession(t, s, 22, 2, "Race", "2026-06-22T14:00:00Z", "2026-06-22T16:00:00Z", false) completeContextSession(t, s, 22, 2) }, want: TemporalPostWeekend}, {name: "season complete", now: "2026-06-30T12:00:00Z", seed: func(t *testing.T, s *Service) { addContextMeeting(t, s, 2, "Canada Grand Prix", "2026-06-20T09:00:00Z", "2026-06-22T16:00:00Z", false) addContextSession(t, s, 22, 2, "Race", "2026-06-22T14:00:00Z", "2026-06-22T16:00:00Z", false) completeContextSession(t, s, 22, 2) }, want: TemporalSeasonComplete}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { now, _ := time.Parse(time.RFC3339, tt.now) svc := contextService(t, now) if tt.seed != nil { tt.seed(t, svc) } got, err := svc.ResolveWeekendContext(tt.evidence) if err != nil { t.Fatal(err) } if got.TemporalState != tt.want { t.Fatalf("state = %s, want %s; context=%+v", got.TemporalState, tt.want, got) } }) } } func TestResolveWeekendContextTruthRules(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-10T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "Pre-Season Testing", "2026-02-01T00:00:00Z", "2026-02-03T00:00:00Z", false) addContextSession(t, svc, 10, 1, "Race", "2026-02-03T10:00:00Z", "2026-02-03T12:00:00Z", false) addContextMeeting(t, svc, 2, "Cancelled Grand Prix", "2026-03-01T00:00:00Z", "2026-03-03T00:00:00Z", true) addContextSession(t, svc, 20, 2, "Race", "2026-03-03T10:00:00Z", "2026-03-03T12:00:00Z", false) addContextMeeting(t, svc, 3, "British Grand Prix", "2026-07-01T00:00:00Z", "2026-07-05T00:00:00Z", false) addContextSession(t, svc, 30, 3, "Practice 1", "2026-07-03T09:00:00Z", "2026-07-03T10:00:00Z", false) addContextSession(t, svc, 31, 3, "Sprint", "2026-07-04T10:00:00Z", "2026-07-04T11:00:00Z", false) addContextSession(t, svc, 32, 3, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) completeContextSession(t, svc, 32, 3) addContextMeeting(t, svc, 4, "Belgian Grand Prix", "2026-07-16T00:00:00Z", "2026-07-18T00:00:00Z", false) addContextSession(t, svc, 40, 4, "Practice 1", "2026-07-17T09:00:00Z", "2026-07-17T10:00:00Z", false) addContextSession(t, svc, 41, 4, "Cancelled Practice", "2026-07-18T09:00:00Z", "2026-07-18T10:00:00Z", true) addContextSession(t, svc, 42, 4, "Race", "2026-07-19T14:00:00Z", "2026-07-19T16:00:00Z", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.TotalChampionshipRounds != 2 || got.ChampionshipRound != 2 { t.Fatalf("rounds = %d/%d, want 2/2", got.ChampionshipRound, got.TotalChampionshipRounds) } if got.DefaultAnalysisSession == nil || got.DefaultAnalysisSession.Session.SessionKey != 32 { t.Fatalf("default analysis = %+v, want completed race 32", got.DefaultAnalysisSession) } if got.NextSession == nil || got.NextSession.Session.SessionKey != 40 { t.Fatalf("next = %+v, want 40", got.NextSession) } if got.NextMeeting.DateStart != "2026-07-17T09:00:00Z" || got.NextMeeting.DateEnd != "2026-07-19T16:00:00Z" { t.Fatalf("display range = %s..%s", got.NextMeeting.DateStart, got.NextMeeting.DateEnd) } } func TestResolveWeekendContextPassedTimeDoesNotCompleteSession(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-05T18:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T00:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.PreviousCompletedSession != nil || got.DefaultAnalysisSession != nil { t.Fatalf("passed schedule was treated complete: %+v", got) } archive, err := svc.ResolveWeekendContext(LiveEvidence{Final: true, MeetingName: "British Grand Prix", CircuitName: "British Grand Prix", SessionName: "Race", SessionType: "Race", ObservedAt: now}) if err != nil { t.Fatal(err) } if archive.PreviousCompletedSession == nil || archive.PreviousCompletedSession.Availability.Archive != "available" { t.Fatalf("final archive not used: %+v", archive) } if archive.DefaultAnalysisSession != nil { t.Fatal("archive without local analysis must not become default analysis") } } func TestResolveWeekendContextAvailabilityUsesTruthfulSources(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-05T18:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T00:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) // Results without laps/stints/positions are meaningful but incomplete local // analysis, so they must not be labelled universally fresh. completeContextSession(t, svc, 11, 1) addContextMeeting(t, svc, 2, "Belgian Grand Prix", "2026-07-17T00:00:00Z", "2026-07-19T16:00:00Z", false) addContextSession(t, svc, 21, 2, "Practice 1", "2026-07-17T09:00:00Z", "2026-07-17T10:00:00Z", false) addContextSession(t, svc, 22, 2, "Race", "2026-07-19T14:00:00Z", "2026-07-19T16:00:00Z", false) local, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got := local.PreviousCompletedSession.Availability; got.Source != "local" || got.Freshness != "partial" || got.LocalAnalysis != "partial" { t.Fatalf("partial local availability = %+v", got) } if got := local.NextSession.Availability; got.Source != "local" || got.Freshness != "local" { t.Fatalf("future local availability = %+v", got) } liveContext, err := svc.ResolveWeekendContext(LiveEvidence{Active: true, MeetingName: "Belgian Grand Prix", CircuitName: "Belgian Grand Prix", SessionName: "Practice 1", SessionType: "Practice 1", ObservedAt: now}) if err != nil { t.Fatal(err) } if got := liveContext.ActiveSession.Availability; got.Source != "mixed" || got.Freshness != "live" || got.LiveSession != "active" { t.Fatalf("FIA + local availability = %+v", got) } archiveContext, err := svc.ResolveWeekendContext(LiveEvidence{Final: true, MeetingName: "British Grand Prix", CircuitName: "British Grand Prix", SessionName: "Race", SessionType: "Race", ObservedAt: now}) if err != nil { t.Fatal(err) } if got := archiveContext.PreviousCompletedSession.Availability; got.Source != "mixed" || got.Freshness != "archive" || got.Archive != "available" { t.Fatalf("FIA archive + local availability = %+v", got) } synthetic, err := svc.ResolveWeekendContext(LiveEvidence{Active: true, MeetingName: "Unscheduled Grand Prix", SessionName: "Race", SessionType: "Race", ObservedAt: now}) if err != nil { t.Fatal(err) } if got := synthetic.ActiveSession.Availability; got.Source != "fia" || got.Freshness != "live" || got.Schedule != "unavailable" { t.Fatalf("synthetic FIA availability = %+v", got) } } func TestResolveWeekendContextNeverUsesFutureAnalysis(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-01T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T00:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) completeContextSession(t, svc, 11, 1) // bad/preloaded data must not make a future session canonical got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.PreviousCompletedSession != nil || got.DefaultAnalysisSession != nil { t.Fatalf("future analysis selected: %+v", got) } } // Regression for the v0.4.0 release blocker (#90). Production held a run // British race (11326) and an unrun Belgian race (11334) two weekends apart. // Analysis must resolve to the race that actually happened, even when the // later meeting carries preloaded rows. func TestResolveWeekendContextPrefersRunRaceOverLaterUnrunRace(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-13T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1289, "British Grand Prix", "2026-07-03T11:30:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11326, 1289, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) completeContextSession(t, svc, 11326, 1289) addContextMeeting(t, svc, 1290, "Belgian Grand Prix", "2026-07-17T11:30:00Z", "2026-07-19T15:00:00Z", false) addContextSession(t, svc, 11334, 1290, "Race", "2026-07-19T13:00:00Z", "2026-07-19T15:00:00Z", false) completeContextSession(t, svc, 11334, 1290) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.DefaultAnalysisSession == nil { t.Fatal("no analysis session resolved") } if key := got.DefaultAnalysisSession.Session.SessionKey; key != 11326 { t.Fatalf("analysis session = %d, want 11326 (British); 11334 is the unrun Belgian race", key) } if got.PreviousCompletedSession == nil || got.PreviousCompletedSession.Session.SessionKey != 11326 { t.Fatalf("previous completed = %+v, want 11326", got.PreviousCompletedSession) } if got.NextSession == nil || got.NextSession.Session.SessionKey != 11334 { t.Fatalf("next session = %+v, want 11334", got.NextSession) } } func TestResolveWeekendContextSprintWeekendHandoff(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-04T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Sprint", "2026-07-04T10:00:00Z", "2026-07-04T11:00:00Z", false) addContextSession(t, svc, 12, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) completeContextSession(t, svc, 11, 1) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.TemporalState != TemporalBetweenSessions || got.PreviousCompletedSession.Session.SessionKey != 11 || got.NextSession.Session.SessionKey != 12 { t.Fatalf("sprint handoff = %+v", got) } if got.TotalChampionshipRounds != 1 { t.Fatalf("sprint created extra championship round: %d", got.TotalChampionshipRounds) } } func TestResolveWeekendContextBoundaryTimestamps(t *testing.T) { t.Run("pre-session window is inclusive", func(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-01T09:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Practice 1", "2026-07-03T09:00:00Z", "2026-07-03T10:00:00Z", false) addContextSession(t, svc, 12, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.TemporalState != TemporalPreSession { t.Fatalf("state = %s", got.TemporalState) } }) t.Run("scheduled end enters settling", func(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-03T10:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Practice 1", "2026-07-03T09:00:00Z", "2026-07-03T10:00:00Z", false) addContextSession(t, svc, 12, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.TemporalState != TemporalSessionSettling { t.Fatalf("state = %s", got.TemporalState) } }) t.Run("post-weekend window is inclusive", func(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-07T16:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 12, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) completeContextSession(t, svc, 12, 1) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.TemporalState != TemporalPostWeekend { t.Fatalf("state = %s", got.TemporalState) } }) } func TestResolveWeekendContextPartialFutureSchedule(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-01T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "", false) addContextSession(t, svc, 11, 1, "Practice 1", "", "", false) addContextSession(t, svc, 12, 1, "Race", "2026-07-05T14:00:00Z", "", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.NextSession == nil || got.NextSession.Session.SessionKey != 12 { t.Fatalf("partial schedule next = %+v", got.NextSession) } if got.FocusMeeting == nil || got.FocusMeeting.DateStart != "2026-07-05T14:00:00Z" || got.FocusMeeting.DateEnd != "2026-07-05T14:00:00Z" { t.Fatalf("partial display range = %+v", got.FocusMeeting) } } func TestResolveWeekendContextActiveSessionIsNotCompletedOrDefault(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-05T15:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 10, 1, "Qualifying", "2026-07-04T14:00:00Z", "2026-07-04T15:00:00Z", false) completeContextSession(t, svc, 10, 1) addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) completeContextSession(t, svc, 11, 1) got, err := svc.ResolveWeekendContext(LiveEvidence{Active: true, MeetingName: "British Grand Prix", CircuitName: "British Grand Prix", SessionName: "Race", SessionType: "Race", ObservedAt: now}) if err != nil { t.Fatal(err) } if got.ActiveSession == nil || got.ActiveSession.Session.SessionKey != 11 { t.Fatalf("active = %+v", got.ActiveSession) } if got.PreviousCompletedSession == nil || got.PreviousCompletedSession.Session.SessionKey != 10 { t.Fatalf("previous = %+v, want earlier completed session", got.PreviousCompletedSession) } if got.DefaultAnalysisSession == nil || got.DefaultAnalysisSession.Session.SessionKey != 10 { t.Fatalf("default = %+v, want earlier completed session", got.DefaultAnalysisSession) } } func TestResolveWeekendContextOldIncompleteSessionDoesNotSuppressNextWeekend(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-16T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) addContextMeeting(t, svc, 2, "Belgian Grand Prix", "2026-07-17T09:00:00Z", "2026-07-19T16:00:00Z", false) addContextSession(t, svc, 21, 2, "Practice 1", "2026-07-17T09:00:00Z", "2026-07-17T10:00:00Z", false) addContextSession(t, svc, 22, 2, "Race", "2026-07-19T14:00:00Z", "2026-07-19T16:00:00Z", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.TemporalState != TemporalPreSession { t.Fatalf("state = %s, want %s; context=%+v", got.TemporalState, TemporalPreSession, got) } if got.FocusMeeting == nil || got.FocusMeeting.MeetingKey != 2 { t.Fatalf("focus = %+v, want Belgian weekend", got.FocusMeeting) } } func TestResolveWeekendContextMissingScheduleDoesNotClaimSeasonComplete(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-01T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 1, "British Grand Prix", "", "", false) addContextSession(t, svc, 11, 1, "Race", "", "", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.TemporalState != TemporalBetweenWeekends { t.Fatalf("state = %s, want limited %s context", got.TemporalState, TemporalBetweenWeekends) } if got.TotalChampionshipRounds != 1 { t.Fatalf("total rounds = %d, want scheduled round retained", got.TotalChampionshipRounds) } } func TestResolveWeekendContextRaceHubDefault(t *testing.T) { seed := func(t *testing.T, svc *Service) { addContextMeeting(t, svc, 1, "British Grand Prix", "2026-07-03T09:00:00Z", "2026-07-05T16:00:00Z", false) addContextSession(t, svc, 11, 1, "Race", "2026-07-05T14:00:00Z", "2026-07-05T16:00:00Z", false) completeContextSession(t, svc, 11, 1) addContextMeeting(t, svc, 2, "Belgian Grand Prix", "2026-07-17T09:00:00Z", "2026-07-19T16:00:00Z", false) addContextSession(t, svc, 21, 2, "Practice 1", "2026-07-17T09:00:00Z", "2026-07-17T10:00:00Z", false) } t.Run("keeps completed analysis before handoff", func(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-16T07:59:59Z") svc := contextService(t, now) seed(t, svc) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.RaceHubDefaultSession == nil || got.RaceHubDefaultSession.Session.SessionKey != 11 || got.RaceHubPreSession { t.Fatalf("race hub default = %+v, pre-session = %t", got.RaceHubDefaultSession, got.RaceHubPreSession) } if got.RaceHubRefreshAt != "2026-07-17T08:00:00Z" { t.Fatalf("refresh = %q", got.RaceHubRefreshAt) } }) t.Run("hands off exactly one hour before first session", func(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-17T08:00:00Z") svc := contextService(t, now) seed(t, svc) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.RaceHubDefaultSession == nil || got.RaceHubDefaultSession.Session.SessionKey != 21 || !got.RaceHubPreSession { t.Fatalf("race hub handoff = %+v, pre-session = %t", got.RaceHubDefaultSession, got.RaceHubPreSession) } if got.RaceHubRefreshAt != "2026-07-17T09:00:00Z" { t.Fatalf("refresh = %q", got.RaceHubRefreshAt) } }) t.Run("active live session wins", func(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-17T08:30:00Z") svc := contextService(t, now) seed(t, svc) got, err := svc.ResolveWeekendContext(LiveEvidence{Active: true, MeetingName: "Belgian Grand Prix", SessionName: "Practice 1", SessionType: "Practice 1"}) if err != nil { t.Fatal(err) } if got.RaceHubDefaultSession == nil || got.RaceHubDefaultSession.Session.SessionKey != 21 || got.RaceHubPreSession { t.Fatalf("live default = %+v, pre-session = %t", got.RaceHubDefaultSession, got.RaceHubPreSession) } }) t.Run("does not select an empty future session before handoff", func(t *testing.T) { now, _ := time.Parse(time.RFC3339, "2026-07-16T12:00:00Z") svc := contextService(t, now) addContextMeeting(t, svc, 2, "Belgian Grand Prix", "2026-07-17T09:00:00Z", "2026-07-19T16:00:00Z", false) addContextSession(t, svc, 21, 2, "Practice 1", "2026-07-17T09:00:00Z", "2026-07-17T10:00:00Z", false) got, err := svc.ResolveWeekendContext(LiveEvidence{}) if err != nil { t.Fatal(err) } if got.RaceHubDefaultSession != nil || got.RaceHubPreSession { t.Fatalf("unexpected empty future default: %+v", got) } }) }