mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
186 lines
5.7 KiB
Go
186 lines
5.7 KiB
Go
package web
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/AmanTahiliani/box-box/internal/live"
|
|
)
|
|
|
|
func TestSSEHubArchivesTerminalSessionSnapshot(t *testing.T) {
|
|
hub := newSSEHub()
|
|
now := time.Date(2026, 7, 4, 14, 0, 0, 0, time.UTC)
|
|
active := live.LiveStreamData{
|
|
SessionStatus: "Started",
|
|
Drivers: map[string]live.LiveDriverData{
|
|
"1": {RacingNumber: "1", Position: 1},
|
|
},
|
|
Positions: map[string]live.LivePositionData{
|
|
"1": {X: 100, Y: -50, Z: 2, Status: "OnTrack"},
|
|
},
|
|
PositionUpdated: true,
|
|
SnapshotUpdated: true,
|
|
}
|
|
if state := hub.applySnapshot(active, now); !state.IsLive || state.Data == nil {
|
|
t.Fatalf("active state = %+v, want live data", state)
|
|
}
|
|
|
|
terminal := active
|
|
terminal.SessionStatus = "Finished"
|
|
terminal.Positions = nil
|
|
terminal.PositionUpdated = false
|
|
state := hub.applySnapshot(terminal, now.Add(time.Minute))
|
|
|
|
if state.IsLive {
|
|
t.Fatal("terminal SessionStatus should not be live")
|
|
}
|
|
if state.Data != nil {
|
|
t.Fatalf("inactive state data = %+v, want nil", state.Data)
|
|
}
|
|
if state.LastSnapshot == nil || state.LastSnapshot.SessionStatus != "Finished" {
|
|
t.Fatalf("last snapshot = %+v, want terminal snapshot", state.LastSnapshot)
|
|
}
|
|
if got := state.LastPositions["1"]; got.X != 100 || got.Status != "OnTrack" {
|
|
t.Fatalf("last positions = %+v, want carried active positions", state.LastPositions)
|
|
}
|
|
if state.LastSnapshotAt == nil || !state.LastSnapshotAt.Equal(now.Add(time.Minute)) {
|
|
t.Fatalf("last snapshot time = %v, want %v", state.LastSnapshotAt, now.Add(time.Minute))
|
|
}
|
|
}
|
|
|
|
func TestSnapshotIsLive(t *testing.T) {
|
|
redFlag := live.LiveStreamData{
|
|
SessionStatus: "Inactive",
|
|
TrackStatus: "2",
|
|
Clock: "00:03:27",
|
|
Session: live.LiveSessionMeta{MeetingName: "Belgian Grand Prix", SessionName: "Practice 2", SessionType: "Practice"},
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
data live.LiveStreamData
|
|
want bool
|
|
}{
|
|
{"started", live.LiveStreamData{SessionStatus: "Started"}, true},
|
|
{"resumed", live.LiveStreamData{SessionStatus: "Resumed"}, true},
|
|
{"red flag inactive with clock and session", redFlag, true},
|
|
{"terminal finished with clock", func() live.LiveStreamData {
|
|
d := redFlag
|
|
d.SessionStatus = "Finished"
|
|
return d
|
|
}(), false},
|
|
{"inactive spent clock", func() live.LiveStreamData {
|
|
d := redFlag
|
|
d.Clock = "00:00:00"
|
|
return d
|
|
}(), false},
|
|
{"inactive no clock", func() live.LiveStreamData {
|
|
d := redFlag
|
|
d.Clock = ""
|
|
return d
|
|
}(), false},
|
|
{"inactive no session metadata", func() live.LiveStreamData {
|
|
d := redFlag
|
|
d.Session = live.LiveSessionMeta{}
|
|
return d
|
|
}(), false},
|
|
}
|
|
for _, tt := range tests {
|
|
if got := snapshotIsLive(tt.data); got != tt.want {
|
|
t.Errorf("%s: snapshotIsLive = %v, want %v", tt.name, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSSEHubKeepsRedFlagSessionLive(t *testing.T) {
|
|
hub := newSSEHub()
|
|
now := time.Date(2026, 7, 26, 14, 0, 0, 0, time.UTC)
|
|
|
|
redFlag := live.LiveStreamData{
|
|
SessionStatus: "Inactive",
|
|
TrackStatus: "2",
|
|
Clock: "00:03:27",
|
|
Session: live.LiveSessionMeta{MeetingName: "Belgian Grand Prix", SessionName: "Practice 2", SessionType: "Practice"},
|
|
SnapshotUpdated: true,
|
|
}
|
|
state := hub.applySnapshot(redFlag, now)
|
|
|
|
if !state.IsLive {
|
|
t.Fatal("red-flag paused session should report is_live=true")
|
|
}
|
|
if state.Data == nil {
|
|
t.Fatal("red-flag paused session must still send the active snapshot")
|
|
}
|
|
if state.Data.TrackStatus != "2" {
|
|
t.Fatalf("track status = %q, want the paused state \"2\"", state.Data.TrackStatus)
|
|
}
|
|
if state.LastSnapshot != nil {
|
|
t.Fatalf("red-flag session should not be archived: %+v", state.LastSnapshot)
|
|
}
|
|
}
|
|
|
|
func TestSSEHubStaleInactiveSnapshotNotLive(t *testing.T) {
|
|
hub := newSSEHub()
|
|
now := time.Date(2026, 7, 26, 14, 0, 0, 0, time.UTC)
|
|
|
|
// Inactive snapshot with session metadata but no remaining clock: this is a
|
|
// stale/no-live-evidence snapshot and must not be surfaced as live.
|
|
stale := live.LiveStreamData{
|
|
SessionStatus: "Inactive",
|
|
Clock: "00:00:00",
|
|
Session: live.LiveSessionMeta{MeetingName: "Belgian Grand Prix", SessionName: "Practice 2"},
|
|
SnapshotUpdated: true,
|
|
}
|
|
state := hub.applySnapshot(stale, now)
|
|
|
|
if state.IsLive {
|
|
t.Fatal("stale inactive snapshot should report is_live=false")
|
|
}
|
|
if state.Data != nil {
|
|
t.Fatalf("stale inactive snapshot leaked into active data: %+v", state.Data)
|
|
}
|
|
if state.LastSnapshot == nil {
|
|
t.Fatal("stale inactive snapshot with metadata should be archived")
|
|
}
|
|
}
|
|
|
|
func TestHandleLiveStateKeepsArchiveOutOfActiveData(t *testing.T) {
|
|
hub := newSSEHub()
|
|
now := time.Date(2026, 7, 4, 14, 0, 0, 0, time.UTC)
|
|
hub.applySnapshot(live.LiveStreamData{
|
|
SessionStatus: "Finished",
|
|
Session: live.LiveSessionMeta{MeetingName: "British Grand Prix", SessionName: "Race"},
|
|
Drivers: map[string]live.LiveDriverData{
|
|
"44": {RacingNumber: "44", Position: 1},
|
|
},
|
|
SnapshotUpdated: true,
|
|
}, now)
|
|
|
|
srv := &Server{hub: hub}
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/live/state", nil)
|
|
rec := httptest.NewRecorder()
|
|
srv.handleLiveState(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want 200", rec.Code)
|
|
}
|
|
var resp liveStatePayload
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("decode response: %v", err)
|
|
}
|
|
if resp.IsLive {
|
|
t.Fatal("archived snapshot should report is_live=false")
|
|
}
|
|
if resp.Data != nil {
|
|
t.Fatalf("archived snapshot leaked into data: %+v", resp.Data)
|
|
}
|
|
if resp.LastSnapshot == nil || resp.LastSnapshot.Session.MeetingName != "British Grand Prix" {
|
|
t.Fatalf("last snapshot = %+v, want archived race", resp.LastSnapshot)
|
|
}
|
|
if resp.LastSnapshotAt == nil {
|
|
t.Fatal("last_snapshot_at should be present for archived snapshots")
|
|
}
|
|
}
|