ajhahn.de
← eeco
Go 190 lines
package cockpit

import (
	"os"
	"path/filepath"
	"testing"
)

func aggSet(t *testing.T) []Playbook {
	t.Helper()
	return []Playbook{loadHandover(t), synthPlaybook("zeta")}
}

// TestGenerateAllOffAll_ReversibleUserDirSurvives is the headline correctness
// proof: an aggregate emit is fully reversible and never removes the private
// tree (the aggregate dir is cfg.UserDir, Created=false → off only removes the
// file).
func TestGenerateAllOffAll_ReversibleUserDirSurvives(t *testing.T) {
	cfg := testConfig(t)
	set := aggSet(t)
	dst := filepath.Join(cfg.UserDir, "AGENTS.md")

	res, err := GenerateAll(cfg, set, "agents")
	if err != nil {
		t.Fatalf("GenerateAll: %v", err)
	}
	if res.Action != "generated" || res.Fidelity != EnforcementAdvisory {
		t.Fatalf("unexpected result action=%q fidelity=%v", res.Action, res.Fidelity)
	}
	if _, err := os.Stat(dst); err != nil {
		t.Fatalf("AGENTS.md not written: %v", err)
	}

	off, err := OffAll(cfg, "agents")
	if err != nil {
		t.Fatalf("OffAll: %v", err)
	}
	if !off.Changed {
		t.Error("OffAll reported no change")
	}
	if _, err := os.Stat(dst); !os.IsNotExist(err) {
		t.Errorf("AGENTS.md should be gone, stat err=%v", err)
	}
	if _, err := os.Stat(cfg.UserDir); err != nil {
		t.Errorf("UserDir must survive off, stat err=%v", err)
	}
}

// TestGenerateAll_Idempotent: re-emitting unchanged bytes is a no-op (no
// backup churn, "already current").
func TestGenerateAll_Idempotent(t *testing.T) {
	cfg := testConfig(t)
	set := aggSet(t)
	if _, err := GenerateAll(cfg, set, "agents"); err != nil {
		t.Fatal(err)
	}
	res, err := GenerateAll(cfg, set, "agents")
	if err != nil {
		t.Fatal(err)
	}
	if res.Action != "already current" {
		t.Errorf("second GenerateAll action=%q, want already current", res.Action)
	}
	if res.Backup != "" {
		t.Errorf("idempotent re-gen produced a backup %q", res.Backup)
	}
}

// TestCoexistence_PerPlaybookAndAggregate proves a per-playbook record
// (claude/handover) and an aggregate record (agents) coexist under distinct
// ledger keys, and that off of the aggregate leaves the per-playbook artifact
// and record untouched (the orphan-bug guard).
func TestCoexistence_PerPlaybookAndAggregate(t *testing.T) {
	cfg := testConfig(t)
	pb := loadHandover(t)
	set := aggSet(t)

	if _, err := Generate(cfg, pb, "claude"); err != nil {
		t.Fatalf("Generate claude: %v", err)
	}
	if _, err := GenerateAll(cfg, set, "agents"); err != nil {
		t.Fatalf("GenerateAll agents: %v", err)
	}

	claudeFile := filepath.Join(cfg.UserDir, ".claude", "skills", "handover", "SKILL.md")
	agentsFile := filepath.Join(cfg.UserDir, "AGENTS.md")
	for _, f := range []string{claudeFile, agentsFile} {
		if _, err := os.Stat(f); err != nil {
			t.Fatalf("expected %s present: %v", f, err)
		}
	}

	l, _ := loadLedger(cfg)
	if l.find("claude", "handover") < 0 || l.findAgg("agents") < 0 {
		t.Fatalf("ledger missing a record: %+v", l.Records)
	}

	if _, err := OffAll(cfg, "agents"); err != nil {
		t.Fatalf("OffAll agents: %v", err)
	}
	// The per-playbook artifact + record survive.
	if _, err := os.Stat(claudeFile); err != nil {
		t.Errorf("claude artifact removed by aggregate off: %v", err)
	}
	if _, err := os.Stat(agentsFile); !os.IsNotExist(err) {
		t.Errorf("AGENTS.md should be gone: %v", err)
	}
	l2, _ := loadLedger(cfg)
	if l2.find("claude", "handover") < 0 {
		t.Error("aggregate off cleared the per-playbook record")
	}
	if l2.findAgg("agents") >= 0 {
		t.Error("aggregate record not cleared after off")
	}
}

// TestGenerateAll_ForeignBackupRestore: a pre-existing foreign AGENTS.md is
// backed up on generate and restored byte-for-byte on off.
func TestGenerateAll_ForeignBackupRestore(t *testing.T) {
	cfg := testConfig(t)
	set := aggSet(t)
	dst := filepath.Join(cfg.UserDir, "AGENTS.md")
	if err := os.MkdirAll(cfg.UserDir, 0o755); err != nil {
		t.Fatal(err)
	}
	foreign := "# Someone else's AGENTS.md\n\nhand-written.\n"
	if err := os.WriteFile(dst, []byte(foreign), 0o644); err != nil {
		t.Fatal(err)
	}

	res, err := GenerateAll(cfg, set, "agents")
	if err != nil {
		t.Fatalf("GenerateAll: %v", err)
	}
	if res.Action != "updated" || res.Backup == "" {
		t.Fatalf("expected updated+backup, got action=%q backup=%q", res.Action, res.Backup)
	}

	if _, err := OffAll(cfg, "agents"); err != nil {
		t.Fatalf("OffAll: %v", err)
	}
	restored, err := os.ReadFile(dst)
	if err != nil {
		t.Fatalf("foreign AGENTS.md not restored: %v", err)
	}
	if string(restored) != foreign {
		t.Errorf("restored content != original foreign:\n%s", restored)
	}
}

// TestVerifyAll_DriftDetected: a hand-edit drifts the aggregate artifact.
func TestVerifyAll_DriftDetected(t *testing.T) {
	cfg := testConfig(t)
	set := aggSet(t)
	if _, err := GenerateAll(cfg, set, "agents"); err != nil {
		t.Fatal(err)
	}
	vr, err := VerifyAll(cfg, set, "agents")
	if err != nil {
		t.Fatal(err)
	}
	if !vr.Clean {
		t.Fatalf("fresh emit should verify clean: %q", vr.Detail)
	}
	dst := filepath.Join(cfg.UserDir, "AGENTS.md")
	if err := os.WriteFile(dst, []byte("tampered\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	vr2, err := VerifyAll(cfg, set, "agents")
	if err != nil {
		t.Fatal(err)
	}
	if vr2.Clean {
		t.Error("expected drift to be detected")
	}
}

// TestAggregateGuards: the per-playbook entry points reject an aggregate
// target, and the aggregate entry points reject a per-playbook target.
func TestAggregateGuards(t *testing.T) {
	cfg := testConfig(t)
	pb := loadHandover(t)
	if _, err := Generate(cfg, pb, "agents"); err == nil {
		t.Error("Generate should reject an aggregate target")
	}
	if _, err := GenerateAll(cfg, aggSet(t), "claude"); err == nil {
		t.Error("GenerateAll should reject a per-playbook target")
	}
}