Go 163 lines
package cockpit
import (
"os"
"path/filepath"
"testing"
)
func TestGenerateOff_Reversible(t *testing.T) {
cfg := testConfig(t)
pb := loadHandover(t)
dst := filepath.Join(cfg.UserDir, ".claude", "skills", "handover", "SKILL.md")
leaf := filepath.Dir(dst)
res, err := Generate(cfg, pb, "claude")
if err != nil {
t.Fatalf("Generate: %v", err)
}
if res.Action != "generated" {
t.Errorf("first generate action = %q, want generated", res.Action)
}
if _, err := os.Stat(dst); err != nil {
t.Fatalf("SKILL.md not written: %v", err)
}
if _, err := os.Stat(ledgerPath(cfg)); err != nil {
t.Fatalf("ledger not written: %v", err)
}
off, err := Off(cfg, pb, "claude")
if err != nil {
t.Fatalf("Off: %v", err)
}
if !off.Changed {
t.Error("Off reported no change for an installed artifact")
}
if _, err := os.Stat(dst); !os.IsNotExist(err) {
t.Error("SKILL.md still present after off")
}
if _, err := os.Stat(leaf); !os.IsNotExist(err) {
t.Error("leaf skill dir not pruned after off")
}
// Record cleared.
l, _ := loadLedger(cfg)
if l.find("claude", "handover") >= 0 {
t.Error("ledger record not cleared after off")
}
}
func TestVerify_DriftAndOffLeavesEdited(t *testing.T) {
cfg := testConfig(t)
pb := loadHandover(t)
dst := filepath.Join(cfg.UserDir, ".claude", "skills", "handover", "SKILL.md")
if _, err := Generate(cfg, pb, "claude"); err != nil {
t.Fatalf("Generate: %v", err)
}
// Clean verify.
vr, err := Verify(cfg, pb, "claude", "")
if err != nil {
t.Fatalf("Verify: %v", err)
}
if !vr.Clean {
t.Errorf("verify not clean on a fresh emit: %s", vr.Detail)
}
// Hand-edit → drift.
if err := os.WriteFile(dst, []byte("hand edited\n"), 0o644); err != nil {
t.Fatal(err)
}
vr, err = Verify(cfg, pb, "claude", "")
if err != nil {
t.Fatalf("Verify after edit: %v", err)
}
if vr.Clean {
t.Error("verify reported clean on a hand-edited artifact")
}
// Off leaves the edited file untouched.
off, err := Off(cfg, pb, "claude")
if err != nil {
t.Fatalf("Off after edit: %v", err)
}
if off.Changed {
t.Error("Off removed a hand-edited artifact")
}
b, err := os.ReadFile(dst)
if err != nil || string(b) != "hand edited\n" {
t.Errorf("hand-edited file not preserved by off: %q (%v)", string(b), err)
}
}
func TestGenerate_Idempotent(t *testing.T) {
cfg := testConfig(t)
pb := loadHandover(t)
dst := filepath.Join(cfg.UserDir, ".claude", "skills", "handover", "SKILL.md")
if _, err := Generate(cfg, pb, "claude"); err != nil {
t.Fatalf("first Generate: %v", err)
}
file1, _ := os.ReadFile(dst)
ledger1, _ := os.ReadFile(ledgerPath(cfg))
res, err := Generate(cfg, pb, "claude")
if err != nil {
t.Fatalf("second Generate: %v", err)
}
if res.Action != "already current" {
t.Errorf("second generate action = %q, want already current", res.Action)
}
file2, _ := os.ReadFile(dst)
ledger2, _ := os.ReadFile(ledgerPath(cfg))
if string(file1) != string(file2) {
t.Error("SKILL.md changed on a no-op re-generate")
}
if string(ledger1) != string(ledger2) {
t.Error("ledger changed on a no-op re-generate")
}
// No backup churn: state/backups should be empty/absent.
backups, _ := os.ReadDir(filepath.Join(cfg.Workspace, "state", "backups"))
if len(backups) != 0 {
t.Errorf("re-generate created %d backup(s), want 0", len(backups))
}
}
func TestLedger_RoundTrip(t *testing.T) {
cfg := testConfig(t)
l := ledger{Records: []record{{
Installed: true, Target: "claude", Playbook: "handover",
Path: "/x/SKILL.md", SHA256: "abc", Created: true, At: "2026-06-05T00:00:00Z",
}}}
if err := saveLedger(cfg, l); err != nil {
t.Fatal(err)
}
b1, _ := os.ReadFile(ledgerPath(cfg))
got, err := loadLedger(cfg)
if err != nil {
t.Fatal(err)
}
if err := saveLedger(cfg, got); err != nil {
t.Fatal(err)
}
b2, _ := os.ReadFile(ledgerPath(cfg))
if string(b1) != string(b2) {
t.Error("ledger save→load→save is not byte-identical")
}
}
func TestStatus_Transitions(t *testing.T) {
cfg := testConfig(t)
pb := loadHandover(t)
if got := Status(cfg)[0]; got != "claude/handover: not emitted" {
t.Errorf("status before generate = %q", got)
}
if _, err := Generate(cfg, pb, "claude"); err != nil {
t.Fatal(err)
}
if got := Status(cfg)[0]; got != "claude/handover: on" {
t.Errorf("status after generate = %q", got)
}
}