ajhahn.de
← eeco
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)
	}
}