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