Files
box-box/internal/chapters/chapters_test.go

219 lines
5.6 KiB
Go
Raw Permalink Normal View History

package chapters
import (
"fmt"
"testing"
"github.com/AmanTahiliani/box-box/internal/models"
)
func TestDetectStructuralChapters(t *testing.T) {
chapters := Detect(nil, nil, testLaps(10, nil), 10)
if len(chapters) < 2 {
t.Fatalf("chapters len = %d, want at least start and finish", len(chapters))
}
if got := chapters[0]; got.Kind != KindStart || got.StartLap != 1 || got.EndLap != 1 {
t.Fatalf("start chapter = %+v, want L1-L1", got)
}
got := chapters[len(chapters)-1]
if got.Kind != KindFinish || got.StartLap != 9 || got.EndLap != 10 {
t.Fatalf("finish chapter = %+v, want L9-L10", got)
}
}
func TestDetectReturnsEmptyWithoutData(t *testing.T) {
if chapters := Detect(nil, nil, nil, 0); len(chapters) != 0 {
t.Fatalf("chapters = %+v, want empty", chapters)
}
}
func TestDetectFlagPeriods(t *testing.T) {
tests := []struct {
name string
start RaceControl
end RaceControl
wantKind string
wantTitle string
}{
{
name: "safety car",
start: rc(12, models.CategorySafetyCar, "", "SAFETY CAR DEPLOYED"),
end: rc(15, models.CategorySafetyCar, "", "SAFETY CAR IN THIS LAP"),
wantKind: KindSafetyCar,
wantTitle: "Safety Car (L12-L15)",
},
{
name: "virtual safety car",
start: rc(22, models.CategoryOther, "", "VSC DEPLOYED"),
end: rc(24, models.CategoryOther, "", "VSC ENDING"),
wantKind: KindVirtualSafetyCar,
wantTitle: "Virtual Safety Car (L22-L24)",
},
{
name: "red flag",
start: rc(31, models.CategoryFlag, models.FlagRed, "RED FLAG"),
end: rc(33, models.CategoryFlag, models.FlagGreen, "GREEN FLAG"),
wantKind: KindRedFlag,
wantTitle: "Red Flag (L31-L33)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
chapters := Detect([]RaceControl{tt.start, tt.end}, nil, testLaps(40, nil), 40)
got := findKind(chapters, tt.wantKind)
if got == nil {
t.Fatalf("chapters = %+v, want %s", chapters, tt.wantKind)
}
if got.StartLap != rcLap(tt.start) || got.EndLap != rcLap(tt.end) || got.Title != tt.wantTitle {
t.Fatalf("flag chapter = %+v, want %s", *got, tt.wantTitle)
}
})
}
}
func TestDetectPitPhaseFromPitOutLapCluster(t *testing.T) {
pitOuts := map[int][]int{
5: {1},
10: {2},
20: {3},
21: {4},
22: {5},
30: {6},
35: {7},
40: {8},
45: {9},
50: {10},
}
chapters := Detect(nil, nil, testLaps(55, pitOuts), 55)
got := findKind(chapters, KindPitPhase)
if got == nil {
t.Fatalf("chapters = %+v, want pit phase", chapters)
}
if got.StartLap != 20 || got.EndLap != 22 {
t.Fatalf("pit phase = %+v, want L20-L22", *got)
}
if len(got.DriverNumbers) != 3 || got.DriverNumbers[0] != 3 || got.DriverNumbers[2] != 5 {
t.Fatalf("pit phase drivers = %v, want [3 4 5]", got.DriverNumbers)
}
}
func TestDetectDecisiveSwingPersistsToFinish(t *testing.T) {
positions := []PositionSample{
pos(1, 1, 1),
pos(1, 16, 4),
pos(1, 55, 3),
pos(6, 16, 3),
pos(6, 55, 4),
pos(8, 44, 5),
pos(8, 63, 6),
pos(10, 44, 6),
}
chapters := Detect(nil, positions, testLaps(12, nil), 12)
got := findKind(chapters, KindDecisiveSwing)
if got == nil {
t.Fatalf("chapters = %+v, want decisive swing", chapters)
}
if got.StartLap != 6 || got.EndLap != 6 {
t.Fatalf("swing lap = %+v, want L6", *got)
}
if len(got.DriverNumbers) != 2 || got.DriverNumbers[0] != 16 || got.DriverNumbers[1] != 55 {
t.Fatalf("swing drivers = %v, want [16 55]", got.DriverNumbers)
}
}
func TestDetectFlagPeriodsWinOverConflictingChapters(t *testing.T) {
pitOuts := map[int][]int{
12: {1, 2},
13: {3, 4},
14: {5, 6},
}
rcs := []RaceControl{
rc(12, models.CategorySafetyCar, "", "SAFETY CAR DEPLOYED"),
rc(15, models.CategorySafetyCar, "", "SAFETY CAR IN THIS LAP"),
}
chapters := Detect(rcs, nil, testLaps(20, pitOuts), 20)
if got := findKind(chapters, KindSafetyCar); got == nil || got.StartLap != 12 || got.EndLap != 15 {
t.Fatalf("chapters = %+v, want safety car L12-L15", chapters)
}
if got := findKind(chapters, KindPitPhase); got != nil {
t.Fatalf("pit phase = %+v, want omitted under safety car", *got)
}
for i := 1; i < len(chapters); i++ {
if chapters[i].StartLap <= chapters[i-1].EndLap {
t.Fatalf("chapters overlap at %d: %+v then %+v", i, chapters[i-1], chapters[i])
}
}
}
func rcLap(r RaceControl) int {
if r.LapNumber == nil {
return 0
}
return *r.LapNumber
}
func findKind(chapters []Chapter, kind string) *Chapter {
for i := range chapters {
if chapters[i].Kind == kind {
return &chapters[i]
}
}
return nil
}
func testLaps(total int, pitOuts map[int][]int) []Lap {
var laps []Lap
for lap := 1; lap <= total; lap++ {
drivers := []int{1}
if pitDrivers := pitOuts[lap]; len(pitDrivers) > 0 {
drivers = pitDrivers
}
for _, driver := range drivers {
laps = append(laps, Lap{
DriverNumber: driver,
LapNumber: lap,
DateStart: lapTime(lap),
IsPitOutLap: containsDriver(pitOuts[lap], driver),
})
}
}
return laps
}
func rc(lap int, category models.RaceControlCategory, flag models.Flag, message string) RaceControl {
return RaceControl{
Category: category,
Flag: flag,
Message: message,
LapNumber: &lap,
Date: lapTime(lap),
}
}
func pos(lap int, driver int, position int) PositionSample {
return PositionSample{
DriverNumber: driver,
Position: position,
Date: lapTime(lap),
}
}
func lapTime(lap int) string {
minute := lap - 1
return fmt.Sprintf("2025-05-25T13:%02d:00Z", minute)
}
func containsDriver(drivers []int, driver int) bool {
for _, candidate := range drivers {
if candidate == driver {
return true
}
}
return false
}