package e2e import ( "encoding/json" "os" "path/filepath" "testing" "github.com/m3tam3re/agent-lib/internal/lockfile" ) func healthySource(t *testing.T, h *harness) { t.Helper() standardFixture(h, t) h.mustRun(t, "vendor", "add", "superpowers", h.upstreamDir) } func mutateLockfile(t *testing.T, h *harness, fn func(*lockfile.Lockfile)) { t.Helper() path := filepath.Join(h.workDir, lockfile.FileName) lf, err := lockfile.Load(path) if err != nil { t.Fatal(err) } fn(lf) if err := lf.Save(path); err != nil { t.Fatal(err) } } func TestValidateHealthy(t *testing.T) { h := newHarness(t) healthySource(t, h) out := h.mustRun(t, "validate") if !contains(out, "valid") { t.Errorf("healthy state must validate:\n%s", out) } jsonOut := h.mustRun(t, "validate", "--json") var result struct { Valid bool `json:"valid"` Errors []struct { Source string `json:"source"` Rule string `json:"rule"` } `json:"errors"` } if err := json.Unmarshal([]byte(jsonOut), &result); err != nil { t.Fatalf("validate --json not parseable: %v\n%s", err, jsonOut) } if !result.Valid || len(result.Errors) != 0 { t.Errorf("healthy state must be valid in json: %+v", result) } } func TestValidateAllWithInclude(t *testing.T) { h := newHarness(t) healthySource(t, h) mutateLockfile(t, h, func(lf *lockfile.Lockfile) { lf.Sources["superpowers"].Selection.Include = []string{"good-skill"} }) out, err := h.run(t, "validate") if err == nil { t.Fatalf("all+include must fail:\n%s", out) } if !contains(out, "superpowers") || !contains(out, "include list is set") { t.Errorf("error must name source and rule:\n%s", out) } } func TestValidateExcludeWithoutAll(t *testing.T) { h := newHarness(t) healthySource(t, h) mutateLockfile(t, h, func(lf *lockfile.Lockfile) { lf.Sources["superpowers"].Selection.Mode = lockfile.ModeInclude lf.Sources["superpowers"].Selection.Include = []string{"good-skill"} lf.Sources["superpowers"].Selection.Exclude = []string{"scout"} }) out, err := h.run(t, "validate") if err == nil { t.Fatalf("exclude without all must fail:\n%s", out) } if !contains(out, "exclude list is only valid in all mode") { t.Errorf("error must name the rule:\n%s", out) } } func TestValidateOrphanedInclude(t *testing.T) { h := newHarness(t) healthySource(t, h) mutateLockfile(t, h, func(lf *lockfile.Lockfile) { lf.Sources["superpowers"].Selection.Mode = lockfile.ModeInclude lf.Sources["superpowers"].Selection.Include = []string{"good-skill", "vanished-item"} }) out, err := h.run(t, "validate") if err == nil { t.Fatalf("orphaned include must fail:\n%s", out) } if !contains(out, "vanished-item") || !contains(out, "pinned inventory") { t.Errorf("error must name source and item:\n%s", out) } jsonOut, _ := h.run(t, "validate", "--json") var result struct { Valid bool `json:"valid"` Errors []struct { Source string `json:"source"` } `json:"errors"` } if err := json.Unmarshal([]byte(jsonOut), &result); err != nil { t.Fatalf("json output not parseable: %v", err) } if result.Valid || len(result.Errors) == 0 || result.Errors[0].Source != "superpowers" { t.Errorf("json must carry the error with source: %+v", result) } } func TestValidateTreeLockfileDivergence(t *testing.T) { h := newHarness(t) healthySource(t, h) os.Remove(filepath.Join(h.workDir, "external/superpowers/agents/scout.md")) out, err := h.run(t, "validate") if err == nil { t.Fatalf("missing tree item must fail:\n%s", out) } if !contains(out, "agents") || !contains(out, "scout") { t.Errorf("error must name type and item:\n%s", out) } stray := filepath.Join(h.workDir, "external/superpowers/commands/ghost.md") os.WriteFile(stray, []byte("# ghost\n"), 0o644) out, err = h.run(t, "validate") if err == nil { t.Fatalf("stray tree item must fail:\n%s", out) } if !contains(out, "ghost") { t.Errorf("error must name the stray item:\n%s", out) } } func TestVendorList(t *testing.T) { h := newHarness(t) healthySource(t, h) h.mustRun(t, "vendor", "add", "curated", h.upstreamDir, "--include", "good-skill", "--rename", "good-skill=curated-good") out := h.mustRun(t, "vendor", "list") for _, want := range []string{"superpowers", "curated", "all", "include", "main"} { if !contains(out, want) { t.Errorf("list output missing %q:\n%s", want, out) } } jsonOut := h.mustRun(t, "vendor", "list", "--json") var parsed struct { Sources []struct { Name string `json:"name"` URL string `json:"url"` Ref string `json:"ref"` Rev string `json:"rev"` Mode string `json:"mode"` Counts map[string]int `json:"counts"` Total int `json:"total"` } `json:"sources"` } if err := json.Unmarshal([]byte(jsonOut), &parsed); err != nil { t.Fatalf("list --json not parseable: %v\n%s", err, jsonOut) } if len(parsed.Sources) != 2 { t.Fatalf("want 2 sources, got %d", len(parsed.Sources)) } super := parsed.Sources[1] if super.Name != "superpowers" || super.Mode != "all" || super.Total != 5 { t.Errorf("superpowers summary wrong: %+v", super) } if super.Counts["skills"] != 2 || super.Counts["mcp"] != 1 { t.Errorf("counts wrong: %+v", super.Counts) } } func TestVendorInspect(t *testing.T) { h := newHarness(t) healthySource(t, h) out := h.mustRun(t, "vendor", "inspect", "superpowers") for _, want := range []string{"good-skill", "broken-skill", "scout", "search", "name=Good Skill", "warning"} { if !contains(out, want) { t.Errorf("inspect output missing %q:\n%s", want, out) } } jsonOut := h.mustRun(t, "vendor", "inspect", "superpowers", "--json") var report struct { Name string `json:"name"` Ref string `json:"ref"` Items map[string][]struct { UpstreamID string `json:"upstream_id"` DeployedID string `json:"deployed_id"` Name string `json:"name"` Description string `json:"description"` Warnings []string `json:"warnings"` } `json:"items"` } if err := json.Unmarshal([]byte(jsonOut), &report); err != nil { t.Fatalf("inspect --json not parseable: %v\n%s", err, jsonOut) } if report.Name != "superpowers" || len(report.Items["skills"]) != 2 { t.Errorf("inspect json wrong: %+v", report) } for _, it := range report.Items["skills"] { if it.UpstreamID == "good-skill" && it.Name != "Good Skill" { t.Errorf("frontmatter metadata missing: %+v", it) } if it.UpstreamID == "broken-skill" && len(it.Warnings) == 0 { t.Errorf("recorded warnings missing: %+v", it) } } } func TestVendorInspectUnknownSource(t *testing.T) { h := newHarness(t) healthySource(t, h) out, err := h.run(t, "vendor", "inspect", "nope") if err == nil { t.Fatalf("unknown source must fail:\n%s", out) } if !contains(out, "not in the lockfile") { t.Errorf("error must be explicit:\n%s", out) } }