package orchestrator import ( "testing" "github.com/google/uuid" "github.com/yan1h/agent-coding-workflow/internal/project" ) func TestNextPhase_AdvancesLinearly(t *testing.T) { pass := GateResult{Pass: true} cases := []struct { from project.Phase wantNext project.Phase wantTerm bool }{ {project.PhasePlanning, project.PhasePrototyping, false}, {project.PhasePrototyping, project.PhaseAuditing, false}, {project.PhaseAuditing, project.PhaseImplementing, false}, {project.PhaseImplementing, project.PhaseReviewing, false}, {project.PhaseReviewing, project.PhaseDone, false}, {project.PhaseDone, project.PhaseDone, true}, } for _, c := range cases { next, term := NextPhase(c.from, pass) if next != c.wantNext || term != c.wantTerm { t.Errorf("NextPhase(%s,pass) = (%s,%v); want (%s,%v)", c.from, next, term, c.wantNext, c.wantTerm) } } } func TestNextPhase_GateFailLoopsBack(t *testing.T) { fail := GateResult{Pass: false, Retry: true} for _, ph := range project.AllPhases { next, term := NextPhase(ph, fail) if next != ph || term { t.Errorf("NextPhase(%s, fail) = (%s,%v); want (%s,false)", ph, next, term, ph) } } } func TestNextPhase_UnknownPhaseTerminates(t *testing.T) { next, term := NextPhase(project.Phase("bogus"), GateResult{Pass: true}) if !term { t.Errorf("unknown phase should terminate; got next=%s term=%v", next, term) } } func TestPhaseAgentKind_OverridePreferredThenDefault(t *testing.T) { override := uuid.New() def := uuid.New() cfg := RunConfig{PhaseAgentKinds: map[project.Phase]uuid.UUID{project.PhasePlanning: override}} defaults := map[project.Phase]uuid.UUID{project.PhasePlanning: def, project.PhaseAuditing: def} got, err := PhaseAgentKind(cfg, project.PhasePlanning, defaults) if err != nil || got != override { t.Fatalf("override path: got=%s err=%v; want %s", got, err, override) } got, err = PhaseAgentKind(cfg, project.PhaseAuditing, defaults) if err != nil || got != def { t.Fatalf("default fallback: got=%s err=%v; want %s", got, err, def) } } func TestPhaseAgentKind_MissingErrors(t *testing.T) { _, err := PhaseAgentKind(RunConfig{}, project.PhasePlanning, nil) if err == nil { t.Fatal("expected error when no agent kind configured") } } func TestBuildPrompt_IncludesReqAndArtifacts(t *testing.T) { req := &project.Requirement{Number: 7, Title: "Login flow", Description: "Add OAuth login"} arts := []*project.Artifact{ {Phase: project.PhasePlanning, Version: 2, Content: "Plan: use OAuth2 PKCE\nmore detail"}, } out := BuildPrompt(project.PhaseAuditing, req, arts, "") for _, want := range []string{"#7", "Login flow", "Add OAuth login", "Plan: use OAuth2 PKCE", "auditing"} { if !contains(out, want) { t.Errorf("prompt missing %q\n%s", want, out) } } } func TestBuildPrompt_OverrideUsed(t *testing.T) { req := &project.Requirement{Number: 1, Title: "T"} out := BuildPrompt(project.PhasePlanning, req, nil, "DO THE SPECIAL THING") if !contains(out, "DO THE SPECIAL THING") { t.Errorf("override not used:\n%s", out) } if contains(out, "Produce a clear plan") { t.Errorf("default instruction should be suppressed when override present:\n%s", out) } } func contains(s, sub string) bool { return len(s) >= len(sub) && (indexOf(s, sub) >= 0) } func indexOf(s, sub string) int { for i := 0; i+len(sub) <= len(s); i++ { if s[i:i+len(sub)] == sub { return i } } return -1 }