package operation import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestOpKindIsKnown(t *testing.T) { for _, kind := range AllOpKinds() { assert.Truef(t, kind.IsKnown(), "expected %q known", kind) } assert.False(t, OpKind("").IsKnown()) assert.False(t, OpKind("rollback").IsKnown()) } func TestAllOpKindsCoverFrozenSet(t *testing.T) { assert.ElementsMatch(t, []OpKind{ OpKindStart, OpKindStop, OpKindRestart, OpKindPatch, OpKindCleanupContainer, OpKindReconcileAdopt, OpKindReconcileDispose, }, AllOpKinds(), ) } func TestOpSourceIsKnown(t *testing.T) { for _, source := range AllOpSources() { assert.Truef(t, source.IsKnown(), "expected %q known", source) } assert.False(t, OpSource("").IsKnown()) assert.False(t, OpSource("manual").IsKnown()) } func TestAllOpSourcesCoverFrozenSet(t *testing.T) { assert.ElementsMatch(t, []OpSource{ OpSourceLobbyStream, OpSourceGMRest, OpSourceAdminRest, OpSourceAutoTTL, OpSourceAutoReconcile, }, AllOpSources(), ) } func TestOutcomeIsKnown(t *testing.T) { for _, outcome := range AllOutcomes() { assert.Truef(t, outcome.IsKnown(), "expected %q known", outcome) } assert.False(t, Outcome("").IsKnown()) assert.False(t, Outcome("partial").IsKnown()) } func TestAllOutcomesCoverFrozenSet(t *testing.T) { assert.ElementsMatch(t, []Outcome{OutcomeSuccess, OutcomeFailure}, AllOutcomes(), ) } func successEntry() OperationEntry { started := time.Date(2026, 4, 27, 12, 0, 0, 0, time.UTC) finished := started.Add(time.Second) return OperationEntry{ GameID: "game-test", OpKind: OpKindStart, OpSource: OpSourceLobbyStream, SourceRef: "1700000000000-0", ImageRef: "galaxy/game:1.0.0", ContainerID: "container-1", Outcome: OutcomeSuccess, StartedAt: started, FinishedAt: &finished, } } func TestOperationEntryValidateHappy(t *testing.T) { require.NoError(t, successEntry().Validate()) } func TestOperationEntryValidateAcceptsReplayNoOp(t *testing.T) { entry := successEntry() entry.ErrorCode = "replay_no_op" assert.NoError(t, entry.Validate()) } func TestOperationEntryValidateAcceptsInFlight(t *testing.T) { entry := successEntry() entry.FinishedAt = nil assert.NoError(t, entry.Validate()) } func TestOperationEntryValidateRejects(t *testing.T) { tests := []struct { name string mutate func(*OperationEntry) }{ {"empty game id", func(e *OperationEntry) { e.GameID = "" }}, {"unknown op kind", func(e *OperationEntry) { e.OpKind = "exotic" }}, {"unknown op source", func(e *OperationEntry) { e.OpSource = "exotic" }}, {"unknown outcome", func(e *OperationEntry) { e.Outcome = "partial" }}, {"zero started at", func(e *OperationEntry) { e.StartedAt = time.Time{} }}, {"zero finished at", func(e *OperationEntry) { zero := time.Time{} e.FinishedAt = &zero }}, {"finished before started", func(e *OperationEntry) { before := e.StartedAt.Add(-time.Second) e.FinishedAt = &before }}, {"failure without error code", func(e *OperationEntry) { e.Outcome = OutcomeFailure e.ErrorCode = "" }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { entry := successEntry() tt.mutate(&entry) assert.Error(t, entry.Validate()) }) } }