Files
agent-lib/internal/e2e/vendor_add_test.go
T

283 lines
8.1 KiB
Go
Raw Normal View History

// Package e2e runs the compiled agent-lib binary as a black box against
// local fixture git repositories — fully offline.
package e2e
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
var binPath string
func TestMain(m *testing.M) {
dir, err := os.MkdirTemp("", "agent-lib-e2e-bin")
if err != nil {
os.Exit(1)
}
binPath = filepath.Join(dir, "agent-lib")
build := exec.Command("go", "build", "-o", binPath, ".")
build.Dir = "../.."
if out, err := build.CombinedOutput(); err != nil {
os.Stderr.Write(out)
os.Exit(1)
}
code := m.Run()
os.RemoveAll(dir)
os.Exit(code)
}
type harness struct {
t *testing.T
workDir string
upstreamDir string
}
func newHarness(t *testing.T) *harness {
t.Helper()
base := t.TempDir()
h := &harness{
t: t,
workDir: filepath.Join(base, "work"),
upstreamDir: filepath.Join(base, "upstream"),
}
for _, d := range []string{h.workDir, h.upstreamDir} {
if err := os.MkdirAll(d, 0o755); err != nil {
t.Fatal(err)
}
}
h.git(t, h.upstreamDir, "init", "-b", "main")
return h
}
func (h *harness) git(t *testing.T, dir string, args ...string) string {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(),
"GIT_AUTHOR_NAME=test", "GIT_AUTHOR_EMAIL=test@test",
"GIT_COMMITTER_NAME=test", "GIT_COMMITTER_EMAIL=test@test",
"GIT_AUTHOR_DATE=1700000000 +0000", "GIT_COMMITTER_DATE=1700000000 +0000",
)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("git %v in %s: %v\n%s", args, dir, err, out)
}
return string(out)
}
func (h *harness) write(t *testing.T, rel, content string) {
t.Helper()
path := filepath.Join(h.upstreamDir, rel)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}
func (h *harness) commitUpstream(t *testing.T, msg string) {
t.Helper()
h.git(t, h.upstreamDir, "add", "-A")
h.git(t, h.upstreamDir, "commit", "-m", msg)
}
func (h *harness) run(t *testing.T, args ...string) (string, error) {
t.Helper()
cmd := exec.Command(binPath, args...)
cmd.Dir = h.workDir
out, err := cmd.CombinedOutput()
return string(out), err
}
func (h *harness) mustRun(t *testing.T, args ...string) string {
t.Helper()
out, err := h.run(t, args...)
if err != nil {
t.Fatalf("agent-lib %v: %v\n%s", args, err, out)
}
return out
}
func standardFixture(h *harness, t *testing.T) {
h.write(t, "skills/good-skill/SKILL.md", "---\nname: Good Skill\ndescription: works\ntags: a, b\n---\n# Good\n")
h.write(t, "skills/good-skill/helper.py", "print('hi')\n")
h.write(t, "skills/broken-skill/SKILL.md", "---\nname: Broken\nthis line has no colon\n")
h.write(t, "commands/review.md", "---\nname: Review\ndescription: review things\n---\nbody\n")
h.write(t, "agents/scout.md", "---\nname: Scout\n---\nbody\n")
h.write(t, "mcp/search.yaml", "servers:\n search:\n command: search\n")
h.write(t, "README.md", "upstream readme\n")
h.commitUpstream(t, "fixture: initial content")
}
func TestVendorAddAllMode(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
out := h.mustRun(t, "vendor", "add", "superpowers", h.upstreamDir)
if !contains(out, "ref: main") {
t.Errorf("report missing resolved ref:\n%s", out)
}
if !contains(out, "4 skills") && !contains(out, "2 skills") {
t.Errorf("report should list skill count:\n%s", out)
}
if !contains(out, "warnings") {
t.Errorf("malformed frontmatter must produce a warning in the report:\n%s", out)
}
for _, p := range []string{
"external/superpowers/skills/good-skill/SKILL.md",
"external/superpowers/skills/good-skill/helper.py",
"external/superpowers/skills/broken-skill/SKILL.md",
"external/superpowers/commands/review.md",
"external/superpowers/agents/scout.md",
"external/superpowers/mcp/search.yaml",
} {
if _, err := os.Stat(filepath.Join(h.workDir, p)); err != nil {
t.Errorf("missing materialized file %s", p)
}
}
if _, err := os.Stat(filepath.Join(h.workDir, "external/superpowers/README.md")); err == nil {
t.Error("README must not be vendored")
}
lockBytes, err := os.ReadFile(filepath.Join(h.workDir, "agent-lib.lock.json"))
if err != nil {
t.Fatal(err)
}
lock := string(lockBytes)
if !contains(lock, `"version": 2`) {
t.Errorf("lockfile must be schema v2:\n%s", lock)
}
if !contains(lock, `"mode": "all"`) {
t.Errorf("default selection must be all:\n%s", lock)
}
if !contains(lock, h.upstreamDir) {
t.Errorf("lockfile must pin the url:\n%s", lock)
}
if !contains(lock, "frontmatter") {
t.Errorf("recorded warnings missing from lockfile:\n%s", lock)
}
}
func TestVendorAddDeterministicRerun(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
h.mustRun(t, "vendor", "add", "superpowers", h.upstreamDir)
secondWork := filepath.Join(t.TempDir(), "work2")
if err := os.MkdirAll(secondWork, 0o755); err != nil {
t.Fatal(err)
}
cmd := exec.Command(binPath, "vendor", "add", "superpowers", h.upstreamDir)
cmd.Dir = secondWork
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("second add: %v\n%s", err, out)
}
b1 := readFile(t, filepath.Join(h.workDir, "agent-lib.lock.json"))
b2 := readFile(t, filepath.Join(secondWork, "agent-lib.lock.json"))
if b1 != b2 {
t.Fatalf("same upstream commit must produce identical lockfiles:\n%s\n---\n%s", b1, b2)
}
}
func TestVendorAddIncludeMode(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
h.mustRun(t, "vendor", "add", "curated", h.upstreamDir, "--include", "good-skill,review")
if _, err := os.Stat(filepath.Join(h.workDir, "external/curated/skills/good-skill/SKILL.md")); err != nil {
t.Error("included skill must be materialized")
}
if _, err := os.Stat(filepath.Join(h.workDir, "external/curated/commands/review.md")); err != nil {
t.Error("included command must be materialized")
}
if _, err := os.Stat(filepath.Join(h.workDir, "external/curated/agents/scout.md")); err == nil {
t.Error("non-included agent must NOT be materialized")
}
lock := readFile(t, filepath.Join(h.workDir, "agent-lib.lock.json"))
if !contains(lock, `"mode": "include"`) {
t.Errorf("selection mode must be include:\n%s", lock)
}
if !contains(lock, `"include": [`) {
t.Errorf("include list missing:\n%s", lock)
}
}
func TestVendorAddIncludeTypoFailsClean(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
out, err := h.run(t, "vendor", "add", "curated", h.upstreamDir, "--include", "good-skill,nope")
if err == nil {
t.Fatalf("typo in include list must fail:\n%s", out)
}
if !contains(out, "nope") {
t.Errorf("error must name the missing entry:\n%s", out)
}
if _, err := os.Stat(filepath.Join(h.workDir, "agent-lib.lock.json")); err == nil {
t.Error("failed add must not write a lockfile")
}
if _, err := os.Stat(filepath.Join(h.workDir, "external")); err == nil {
t.Error("failed add must not create the external area")
}
}
func TestVendorAddDuplicateName(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
h.mustRun(t, "vendor", "add", "superpowers", h.upstreamDir)
out, err := h.run(t, "vendor", "add", "superpowers", h.upstreamDir)
if err == nil {
t.Fatalf("duplicate source name must fail:\n%s", out)
}
if !contains(out, "already exists") {
t.Errorf("error must be explicit about the duplicate:\n%s", out)
}
}
func TestVendorAddOrphanExternalDir(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
os.MkdirAll(filepath.Join(h.workDir, "external", "ghost"), 0o755)
out, err := h.run(t, "vendor", "add", "ghost", h.upstreamDir)
if err == nil {
t.Fatalf("orphan external dir must fail:\n%s", out)
}
if !contains(out, "not in the lockfile") {
t.Errorf("error must explain the orphan:\n%s", out)
}
}
func TestVendorAddAtRef(t *testing.T) {
h := newHarness(t)
standardFixture(h, t)
h.git(t, h.upstreamDir, "tag", "v1.0")
out := h.mustRun(t, "vendor", "add", "pinned", h.upstreamDir, "--ref", "v1.0")
if !contains(out, "ref: v1.0") {
t.Errorf("tag ref must be pinned:\n%s", out)
}
}
func contains(haystack, needle string) bool {
return strings.Contains(haystack, needle)
}
func readFile(t *testing.T, path string) string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return string(data)
}