package workflow import ( "testing" ) func TestNew(t *testing.T) { wf := New() if wf.Phase != PhaseIdle { t.Errorf("Expected PhaseIdle, got %s", wf.Phase) } if wf.Plan == nil { t.Error("Plan should not be nil") } } func TestStart(t *testing.T) { wf := New() wf.Start("Build a REST API") if wf.Phase != PhaseGathering { t.Errorf("Expected PhaseGathering, got %s", wf.Phase) } if wf.Plan.Goal != "Build a REST API" { t.Errorf("Expected goal 'Build a REST API', got %s", wf.Plan.Goal) } } func TestAddAnswer(t *testing.T) { wf := New() wf.Start("test goal") wf.Plan.Questions = []string{"Q1?", "Q2?"} wf.AddAnswer("A1") if wf.Phase != PhaseGathering { t.Errorf("Should still be gathering, got %s", wf.Phase) } wf.AddAnswer("A2") if wf.Phase != PhasePlanning { t.Errorf("Should move to planning, got %s", wf.Phase) } } func TestSetPlan(t *testing.T) { wf := New() planJSON := `[{"id":"1","title":"Step 1","description":"Do something","agent":"crush","status":"pending"}]` err := wf.SetPlan(planJSON) if err != nil { t.Fatalf("SetPlan failed: %v", err) } if len(wf.Plan.Steps) != 1 { t.Errorf("Expected 1 step, got %d", len(wf.Plan.Steps)) } if wf.Phase != PhaseReviewing { t.Errorf("Expected PhaseReviewing, got %s", wf.Phase) } } func TestApprove(t *testing.T) { wf := New() wf.Start("test") wf.Plan.Steps = []Step{{ID: "1", Title: "Step 1", Status: "pending"}} wf.Phase = PhaseReviewing wf.Approve() if wf.Phase != PhaseExecuting { t.Errorf("Expected PhaseExecuting, got %s", wf.Phase) } if wf.Plan.StepIndex != 0 { t.Errorf("Expected step index 0, got %d", wf.Plan.StepIndex) } } func TestReject(t *testing.T) { wf := New() wf.Phase = PhaseReviewing wf.Reject("too complex") if wf.Phase != PhasePlanning { t.Errorf("Expected PhasePlanning, got %s", wf.Phase) } } func TestAdvanceStep(t *testing.T) { wf := New() wf.Plan.Steps = []Step{ {ID: "1", Title: "Step 1", Status: "pending"}, {ID: "2", Title: "Step 2", Status: "pending"}, } wf.Phase = PhaseExecuting wf.AdvanceStep("output1") if wf.Plan.Steps[0].Status != "done" { t.Error("First step should be done") } if wf.Plan.StepIndex != 1 { t.Errorf("Expected step index 1, got %d", wf.Plan.StepIndex) } if wf.Phase != PhaseExecuting { t.Errorf("Should still be executing, got %s", wf.Phase) } wf.AdvanceStep("output2") if wf.Phase != PhaseDone { t.Errorf("Expected PhaseDone, got %s", wf.Phase) } } func TestFailStep(t *testing.T) { wf := New() wf.Plan.Steps = []Step{{ID: "1", Title: "Step 1"}} wf.Phase = PhaseExecuting wf.FailStep("something broke") if wf.Phase != PhaseError { t.Errorf("Expected PhaseError, got %s", wf.Phase) } if wf.Plan.Steps[0].Status != "error" { t.Error("Step should have error status") } } func TestReset(t *testing.T) { wf := New() wf.Start("test") wf.Phase = PhaseExecuting wf.Reset() if wf.Phase != PhaseIdle { t.Errorf("Expected PhaseIdle, got %s", wf.Phase) } } func TestCurrentStep(t *testing.T) { wf := New() if wf.CurrentStep() != nil { t.Error("Should be nil with no steps") } wf.Plan.Steps = []Step{{ID: "1"}, {ID: "2"}} wf.Plan.StepIndex = 0 step := wf.CurrentStep() if step == nil || step.ID != "1" { t.Error("Should return first step") } wf.Plan.StepIndex = 2 if wf.CurrentStep() != nil { t.Error("Should be nil when past all steps") } } func TestProgress(t *testing.T) { wf := New() wf.Plan.Steps = []Step{ {ID: "1", Status: "done"}, {ID: "2", Status: "pending"}, {ID: "3", Status: "done"}, } done, total := wf.Progress() if done != 2 || total != 3 { t.Errorf("Expected 2/3, got %d/%d", done, total) } } func TestParsePlanResponse(t *testing.T) { resp := `Here is the plan: [ {"id": "1", "title": "Setup", "description": "Init project", "agent": "crush"}, {"id": "2", "title": "Build", "description": "Write code", "agent": "claude"} ]` steps, err := ParsePlanResponse(resp) if err != nil { t.Fatalf("ParsePlanResponse failed: %v", err) } if len(steps) != 2 { t.Errorf("Expected 2 steps, got %d", len(steps)) } if steps[0].ID != "1" { t.Errorf("Expected step ID 1, got %s", steps[0].ID) } for _, s := range steps { if s.Status != "pending" { t.Errorf("Steps should be pending, got %s", s.Status) } } } func TestParsePlanResponseInvalid(t *testing.T) { _, err := ParsePlanResponse("no json here") if err == nil { t.Error("Should fail with no JSON") } } func TestParseApproval(t *testing.T) { tests := []struct { input string approved bool }{ {"plan_approved", true}, {"approved", true}, {"yes", true}, {"ok", true}, {"oui", true}, {"go ahead", true}, {"no", false}, {"plan_rejected: too complex", false}, {"I don't like it", false}, } for _, tt := range tests { approved, feedback := ParseApproval(tt.input) if approved != tt.approved { t.Errorf("ParseApproval(%q) = %v, want %v", tt.input, approved, tt.approved) } if !approved && tt.input == "plan_rejected: too complex" { if feedback != "too complex" { t.Errorf("Expected feedback 'too complex', got %s", feedback) } } } } func TestParsePreviewFiles(t *testing.T) { resp := `Some text <<>> [{"filename":"test.html","content":"

Hello

","type":"html"}] <<>>` files := ParsePreviewFiles(resp) if len(files) != 1 { t.Fatalf("Expected 1 file, got %d", len(files)) } if files[0].Filename != "test.html" { t.Errorf("Expected test.html, got %s", files[0].Filename) } } func TestParsePreviewFilesNone(t *testing.T) { files := ParsePreviewFiles("no preview here") if files != nil { t.Error("Should return nil") } } func TestBuildSystemPrompt(t *testing.T) { prompt := BuildSystemPrompt(PhaseIdle, &Plan{}) if prompt == "" { t.Error("Prompt should not be empty") } if len(prompt) < 100 { t.Error("Prompt seems too short") } prompt = BuildSystemPrompt(PhaseGathering, &Plan{Goal: "test"}) if prompt == "" { t.Error("Gathering prompt should not be empty") } }