ajhahn.de
← eeco
Go 94 lines
package hooks

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/ajhahnde/eeco/internal/cockpit"
	"github.com/ajhahnde/eeco/internal/config"
	"github.com/ajhahnde/eeco/internal/playbooks"
)

// orientCfg builds a config with a workspace + private user dir for the
// SessionStart orient blocks (no git: LatestSemverTag degrades to "").
func orientCfg(t *testing.T) *config.Config {
	t.Helper()
	root := t.TempDir()
	ws := filepath.Join(root, "tester", ".eeco")
	if err := os.MkdirAll(filepath.Join(ws, "state"), 0o755); err != nil {
		t.Fatal(err)
	}
	return &config.Config{
		RepoRoot:      root,
		UserDir:       filepath.Join(root, "tester"),
		WorkspaceName: ".eeco",
		Workspace:     ws,
	}
}

func TestLiveStateBlock_ShowsNewestNote(t *testing.T) {
	cfg := orientCfg(t)
	notesDir := filepath.Join(cfg.Workspace, "notes")
	if err := os.MkdirAll(notesDir, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(notesDir, "2026-06-05-150000-resume.md"), []byte("resume here next time"), 0o644); err != nil {
		t.Fatal(err)
	}
	got := liveStateBlock(cfg)
	if !strings.Contains(got, "newest handover") || !strings.Contains(got, "resume here next time") {
		t.Errorf("liveStateBlock missing the handover note: %q", got)
	}
}

func TestLiveStateBlock_EmptyWhenNothing(t *testing.T) {
	if got := liveStateBlock(orientCfg(t)); got != "" {
		t.Errorf("liveStateBlock should be empty with no tag/note, got %q", got)
	}
}

func TestNewestHandover_GlobWins(t *testing.T) {
	cfg := orientCfg(t)
	if err := os.WriteFile(filepath.Join(cfg.RepoRoot, "HANDOVER.md"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	cfg.HandoverGlob = "HANDOVER*.md"
	if got := newestHandover(cfg); got != "HANDOVER.md" {
		t.Errorf("newestHandover with glob = %q, want HANDOVER.md", got)
	}
}

func TestDriftBlock_SilentThenFlagsDrift(t *testing.T) {
	cfg := orientCfg(t)
	// No cockpit generated → silent (empty-ledger gate).
	if got := driftBlock(cfg); got != "" {
		t.Errorf("driftBlock should be silent on an unused cockpit, got %q", got)
	}
	// Narrow the selection to handover so generating it alone leaves no
	// "missing" findings for the other playbooks (the default selection is all).
	if err := cockpit.SaveSelection(cfg, cockpit.Selection{Targets: []string{"claude"}, Playbooks: []string{"handover"}}); err != nil {
		t.Fatal(err)
	}
	pb, err := playbooks.Get("handover")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := cockpit.Generate(cfg, pb, "claude"); err != nil {
		t.Fatal(err)
	}
	// Clean → still silent.
	if got := driftBlock(cfg); got != "" {
		t.Errorf("driftBlock should be silent when clean, got %q", got)
	}
	// Hand-edit → drift surfaces.
	dst := filepath.Join(cfg.UserDir, ".claude", "skills", "handover", "SKILL.md")
	if err := os.WriteFile(dst, []byte("edited\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	if got := driftBlock(cfg); !strings.Contains(got, "cockpit drift") {
		t.Errorf("driftBlock should report drift after a hand-edit, got %q", got)
	}
}