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")
}
}