From 9f0a7b776e84c82a91ea3ca8eaff1eead8248c69 Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Sat, 23 May 2026 03:36:11 +0100 Subject: [PATCH] test(e2e): init-apply-diff cycle + show + detect through real binary --- tests/e2e/e2e_test.go | 100 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/e2e/e2e_test.go diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go new file mode 100644 index 0000000..691499f --- /dev/null +++ b/tests/e2e/e2e_test.go @@ -0,0 +1,100 @@ +// Package e2e runs the harness-sync binary end-to-end against fake harness +// installs in a temp HOME. +package e2e + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInitApplyDiffCycle(t *testing.T) { + bin := buildBinary(t) + home := t.TempDir() + root := filepath.Join(home, ".config", "harness-sync") + + // Fake a claude-code install with one skill + require.NoError(t, os.MkdirAll(filepath.Join(home, ".claude", "skills", "hi"), 0o750)) + require.NoError(t, os.WriteFile( + filepath.Join(home, ".claude", "skills", "hi", "SKILL.md"), + []byte("---\nname: hi\n---\nbody"), 0o600)) + + run := func(args ...string) ([]byte, error) { + cmd := exec.Command(bin, args...) + cmd.Env = append(os.Environ(), "HOME="+home) + return cmd.CombinedOutput() + } + + out, err := run("init", "--no-prompt") + require.NoError(t, err, string(out)) + assert.FileExists(t, filepath.Join(root, "config.yaml")) + assert.FileExists(t, filepath.Join(root, "skills", "hi", "SKILL.md")) + + // apply may produce conflicts on a freshly imported tree (because the + // claude-code adapter merges into existing settings.json which differs + // from canonical); accept either clean exit or conflict exit. + out, err = run("apply") + if err != nil { + // Apply with conflicts is acceptable — verify the binary at least ran + assert.Contains(t, string(out), "applied") + } else { + assert.Contains(t, string(out), "applied") + } + + // Diff should always run cleanly (it's read-only) + out, err = run("diff") + require.NoError(t, err, string(out)) +} + +func TestShowEnumeratesTargets(t *testing.T) { + bin := buildBinary(t) + home := t.TempDir() + root := filepath.Join(home, ".config", "harness-sync") + + require.NoError(t, os.MkdirAll(filepath.Join(home, ".claude", "skills"), 0o750)) + + run := func(args ...string) ([]byte, error) { + cmd := exec.Command(bin, args...) + cmd.Env = append(os.Environ(), "HOME="+home) + return cmd.CombinedOutput() + } + + _, err := run("init", "--no-prompt") + require.NoError(t, err) + _ = root // root is implied by HOME + + out, err := run("show") + require.NoError(t, err, string(out)) + assert.Contains(t, string(out), "claude-code") +} + +func TestDetectListsAllAdapters(t *testing.T) { + bin := buildBinary(t) + home := t.TempDir() + + cmd := exec.Command(bin, "detect") + cmd.Env = append(os.Environ(), "HOME="+home) + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + + // None should be detected in an empty HOME + s := string(out) + for _, name := range []string{"claude-code", "crush", "kilo", "opencode", "goose", "cagent", "zed"} { + assert.Contains(t, s, name) + } + assert.Contains(t, s, "not detected") +} + +func buildBinary(t *testing.T) string { + t.Helper() + bin := filepath.Join(t.TempDir(), "harness-sync") + cmd := exec.Command("go", "build", "-o", bin, "./cmd/harness-sync") + cmd.Dir = "../.." + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + return bin +}