package runtime import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func validRunningRecord() RuntimeRecord { created := time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC) started := created.Add(time.Minute) updated := started.Add(time.Minute) next := updated.Add(time.Hour) return RuntimeRecord{ GameID: "game-1", Status: StatusRunning, EngineEndpoint: "http://galaxy-game-1:8080", CurrentImageRef: "ghcr.io/galaxy/game:v1.2.3", CurrentEngineVersion: "v1.2.3", TurnSchedule: "0 18 * * *", CurrentTurn: 0, NextGenerationAt: &next, CreatedAt: created, UpdatedAt: updated, StartedAt: &started, } } func TestStatusIsKnown(t *testing.T) { for _, status := range AllStatuses() { assert.True(t, status.IsKnown(), "want known: %q", status) } assert.False(t, Status("exotic").IsKnown()) assert.False(t, Status("").IsKnown()) } func TestStatusIsTerminal(t *testing.T) { assert.True(t, StatusFinished.IsTerminal()) for _, status := range AllStatuses() { if status == StatusFinished { continue } assert.False(t, status.IsTerminal(), "%q must not be terminal", status) } } func TestAllStatusesStable(t *testing.T) { first := AllStatuses() second := AllStatuses() assert.Equal(t, first, second) assert.Len(t, first, 7) } func TestRuntimeRecordValidateHappy(t *testing.T) { require.NoError(t, validRunningRecord().Validate()) } func TestRuntimeRecordValidateAcceptsStarting(t *testing.T) { record := validRunningRecord() record.Status = StatusStarting record.StartedAt = nil record.NextGenerationAt = nil assert.NoError(t, record.Validate()) } func TestRuntimeRecordValidateRequiresFinishedAt(t *testing.T) { record := validRunningRecord() record.Status = StatusFinished record.FinishedAt = nil assert.Error(t, record.Validate()) finished := record.UpdatedAt.Add(time.Minute) record.FinishedAt = &finished assert.NoError(t, record.Validate()) } func TestRuntimeRecordValidateRequiresStoppedAtForStopped(t *testing.T) { record := validRunningRecord() record.Status = StatusStopped assert.Error(t, record.Validate()) stopped := record.UpdatedAt.Add(time.Minute) record.StoppedAt = &stopped assert.NoError(t, record.Validate()) } func TestRuntimeRecordValidateRejects(t *testing.T) { tests := []struct { name string mutate func(*RuntimeRecord) }{ {"empty game id", func(r *RuntimeRecord) { r.GameID = "" }}, {"unknown status", func(r *RuntimeRecord) { r.Status = "exotic" }}, {"empty engine endpoint", func(r *RuntimeRecord) { r.EngineEndpoint = "" }}, {"empty image ref", func(r *RuntimeRecord) { r.CurrentImageRef = "" }}, {"empty engine version", func(r *RuntimeRecord) { r.CurrentEngineVersion = "" }}, {"empty turn schedule", func(r *RuntimeRecord) { r.TurnSchedule = "" }}, {"negative turn", func(r *RuntimeRecord) { r.CurrentTurn = -1 }}, {"zero created at", func(r *RuntimeRecord) { r.CreatedAt = time.Time{} }}, {"zero updated at", func(r *RuntimeRecord) { r.UpdatedAt = time.Time{} }}, {"updated before created", func(r *RuntimeRecord) { r.UpdatedAt = r.CreatedAt.Add(-time.Minute) }}, {"started before created", func(r *RuntimeRecord) { before := r.CreatedAt.Add(-time.Minute) r.StartedAt = &before }}, {"running missing started at", func(r *RuntimeRecord) { r.StartedAt = nil }}, {"starting with started at", func(r *RuntimeRecord) { r.Status = StatusStarting // keep StartedAt set }}, {"zero next generation at", func(r *RuntimeRecord) { zero := time.Time{} r.NextGenerationAt = &zero }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { record := validRunningRecord() tt.mutate(&record) assert.Error(t, record.Validate()) }) } }