package orchestrator_test import ( "testing" "vetting/internal/model" "vetting/internal/orchestrator" ) func TestNextForOverride(t *testing.T) { tests := []struct { name string from model.RunState failedStage string want model.RunState wantErr bool }{ {"storage override", model.StateFailedHolding, "Storage", model.StateStorage, false}, {"smart override", model.StateFailedHolding, "SMART", model.StateSMART, false}, {"inventory override", model.StateFailedHolding, "Inventory", model.StateInventoryCheck, false}, {"unknown stage", model.StateFailedHolding, "NotAStage", "", true}, {"not holding", model.StateStorage, "Storage", "", true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := orchestrator.NextForOverride(tc.from, tc.failedStage) if tc.wantErr { if err == nil { t.Fatalf("expected error, got %q", got) } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if got != tc.want { t.Fatalf("got %q, want %q", got, tc.want) } }) } } // TestTriggerRebootCommanded exercises the new heartbeat-first trigger: // Queued → WaitingReboot, and any other current state is an error. func TestTriggerRebootCommanded(t *testing.T) { got, err := orchestrator.Next(model.StateQueued, orchestrator.TriggerRebootCommanded) if err != nil { t.Fatalf("Queued + RebootCommanded: %v", err) } if got != model.StateWaitingReboot { t.Fatalf("got %q, want %q", got, model.StateWaitingReboot) } for _, bad := range []model.RunState{ model.StateRegistered, model.StateBooting, model.StateInventoryCheck, model.StateCompleted, } { if _, err := orchestrator.Next(bad, orchestrator.TriggerRebootCommanded); err == nil { t.Fatalf("RebootCommanded from %q: expected error", bad) } } } // TestTriggerAgentClaimedFromWaitingReboot: the agent's /claim must // advance the run out of WaitingReboot (new happy path) AND out of // legacy WaitingWoL, otherwise live boots wouldn't be recognised. func TestTriggerAgentClaimedFromWaitingReboot(t *testing.T) { for _, from := range []model.RunState{model.StateWaitingReboot, model.StateWaitingWoL, model.StateBooting} { got, err := orchestrator.Next(from, orchestrator.TriggerAgentClaimed) if err != nil { t.Fatalf("AgentClaimed from %q: %v", from, err) } if got != model.StateInventoryCheck { t.Fatalf("AgentClaimed from %q = %q, want InventoryCheck", from, got) } } } func TestNextStageWalk(t *testing.T) { // Walking StageCompleted from each stage should land on the next // one in the canonical order, and from Reporting onto Completed. chain := []model.RunState{ model.StateInventoryCheck, model.StateSpecValidate, model.StateSMART, model.StateCPUStress, model.StateStorage, model.StateNetwork, model.StateGPU, model.StatePSU, model.StateReporting, model.StateCompleted, } for i := 0; i < len(chain)-1; i++ { got, err := orchestrator.Next(chain[i], orchestrator.TriggerStageCompleted) if err != nil { t.Fatalf("Next(%q): %v", chain[i], err) } if got != chain[i+1] { t.Fatalf("Next(%q) = %q, want %q", chain[i], got, chain[i+1]) } } }