package workflow import ( "os" "path/filepath" "testing" ) func TestLoad_Valid(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.yaml") os.WriteFile(path, []byte(` workflow: name: test-wf version: 1 description: "Test workflow" states: [draft, pending, approved, rejected] gates: - role: reviewer label: "Review" required: true rules: any_reject: rejected all_required_approve: approved `), 0644) w, err := Load(path) if err != nil { t.Fatalf("Load() error: %v", err) } if w.Name != "test-wf" { t.Errorf("Name = %q, want %q", w.Name, "test-wf") } if w.Version != 1 { t.Errorf("Version = %d, want 1", w.Version) } if len(w.Gates) != 1 { t.Fatalf("Gates count = %d, want 1", len(w.Gates)) } if w.Gates[0].Role != "reviewer" { t.Errorf("Gates[0].Role = %q, want %q", w.Gates[0].Role, "reviewer") } } func TestLoad_MissingState(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "bad.yaml") os.WriteFile(path, []byte(` workflow: name: bad states: [draft, pending] gates: - role: r label: "R" required: true `), 0644) _, err := Load(path) if err == nil { t.Fatal("expected error for missing required states") } } func TestLoad_NoGates(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "no-gates.yaml") os.WriteFile(path, []byte(` workflow: name: no-gates states: [draft, pending, approved, rejected] gates: [] `), 0644) _, err := Load(path) if err == nil { t.Fatal("expected error for no gates") } } func TestLoadAll(t *testing.T) { dir := t.TempDir() os.WriteFile(filepath.Join(dir, "a.yaml"), []byte(` workflow: name: alpha states: [draft, pending, approved, rejected] gates: - role: r label: "R" required: true rules: any_reject: rejected all_required_approve: approved `), 0644) os.WriteFile(filepath.Join(dir, "b.yml"), []byte(` workflow: name: beta states: [draft, pending, approved, rejected] gates: - role: r label: "R" required: true rules: any_reject: rejected `), 0644) // Non-yaml file should be ignored os.WriteFile(filepath.Join(dir, "readme.txt"), []byte("ignore me"), 0644) wfs, err := LoadAll(dir) if err != nil { t.Fatalf("LoadAll() error: %v", err) } if len(wfs) != 2 { t.Fatalf("LoadAll() count = %d, want 2", len(wfs)) } if wfs["alpha"] == nil { t.Error("missing workflow 'alpha'") } if wfs["beta"] == nil { t.Error("missing workflow 'beta'") } } func TestRequiredGates(t *testing.T) { w := &Workflow{ Gates: []Gate{ {Role: "engineer", Label: "Eng", Required: true}, {Role: "quality", Label: "QA", Required: false}, {Role: "manager", Label: "Mgr", Required: true}, }, } rg := w.RequiredGates() if len(rg) != 2 { t.Fatalf("RequiredGates() count = %d, want 2", len(rg)) } if rg[0].Role != "engineer" || rg[1].Role != "manager" { t.Errorf("RequiredGates() roles = %v, want [engineer, manager]", rg) } } func TestHasRole(t *testing.T) { w := &Workflow{ Gates: []Gate{ {Role: "engineer", Label: "Eng", Required: true}, }, } if !w.HasRole("engineer") { t.Error("HasRole(engineer) = false, want true") } if w.HasRole("manager") { t.Error("HasRole(manager) = true, want false") } } func TestValidate_InvalidRuleState(t *testing.T) { w := &Workflow{ Name: "bad-rule", States: []string{"draft", "pending", "approved", "rejected"}, Gates: []Gate{{Role: "r", Label: "R", Required: true}}, Rules: Rules{AnyReject: "nonexistent"}, } if err := w.Validate(); err == nil { t.Fatal("expected error for invalid rule state reference") } }