Go 103 lines
package cockpit
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestGenerateOff_RestoresForeignFile proves the reversibility contract for
// a pre-existing foreign artifact: generate backs it up and overwrites;
// off removes eeco's emit and restores the original byte-for-byte.
func TestGenerateOff_RestoresForeignFile(t *testing.T) {
cfg := testConfig(t)
pb := loadHandover(t)
dst := filepath.Join(cfg.UserDir, ".claude", "skills", "handover", "SKILL.md")
foreign := "---\nname: handover\ndescription: someone else's skill\nallowed-tools: Read\n---\n# Theirs\n"
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(dst, []byte(foreign), 0o644); err != nil {
t.Fatal(err)
}
res, err := Generate(cfg, pb, "claude")
if err != nil {
t.Fatalf("Generate: %v", err)
}
if res.Action != "updated" || res.Backup == "" {
t.Fatalf("expected an updated action with a backup, got action=%q backup=%q", res.Action, res.Backup)
}
if _, err := os.Stat(res.Backup); err != nil {
t.Fatalf("backup file not present: %v", err)
}
cur, _ := os.ReadFile(dst)
if string(cur) == foreign {
t.Error("generate did not overwrite the foreign file")
}
off, err := Off(cfg, pb, "claude")
if err != nil {
t.Fatalf("Off: %v", err)
}
if !off.Changed {
t.Error("Off reported no change")
}
restored, err := os.ReadFile(dst)
if err != nil {
t.Fatalf("foreign file not restored: %v", err)
}
if string(restored) != foreign {
t.Errorf("restored content != original foreign file:\n%s", string(restored))
}
}
// TestVerify_ParityPath exercises the optional --parity branch of Verify
// against a synthetic answer key matching the emitted shape.
func TestVerify_ParityPath(t *testing.T) {
cfg := testConfig(t)
pb := loadHandover(t)
if _, err := Generate(cfg, pb, "claude"); err != nil {
t.Fatal(err)
}
// A synthetic answer key in the skill layout whose allowlist core is a
// subset of the emitted one and carries no write-git verb.
keyRoot := t.TempDir()
key := filepath.Join(keyRoot, ".claude", "skills", "handover", "SKILL.md")
if err := os.MkdirAll(filepath.Dir(key), 0o755); err != nil {
t.Fatal(err)
}
answer := "---\nname: handover\ndescription: x\nallowed-tools: Read, Write, Bash(git status:*)\n---\n" +
"# Handover\n## Step 0 — a\n## Step 1 — b\n## Step 2 — c\n## Step 3 — d\n## Step 4 — e\n## Output\nx\n"
if err := os.WriteFile(key, []byte(answer), 0o644); err != nil {
t.Fatal(err)
}
vr, err := Verify(cfg, pb, "claude", key)
if err != nil {
t.Fatalf("Verify --parity: %v", err)
}
if !vr.Clean || !strings.Contains(vr.Detail, "parity OK") {
t.Errorf("expected clean + parity OK, got clean=%v detail=%q", vr.Clean, vr.Detail)
}
}
func TestAccessors(t *testing.T) {
if (claudeRenderer{}).Target() != "claude" {
t.Error("claudeRenderer.Target")
}
if got := Targets(); len(got) == 0 || got[0] != "claude" {
t.Errorf("Targets() = %v (want claude first)", got)
}
if _, ok := rendererFor("nosuchharness"); ok {
t.Error("rendererFor returned a renderer for an unknown target")
}
r := GenerateResult{Path: "/p", Action: "generated", Backup: "/b"}
if msg := r.Message(); !strings.Contains(msg, "/p") || !strings.Contains(msg, "/b") {
t.Errorf("GenerateResult.Message = %q", msg)
}
}