Files
superwork/tui/internal/git/hooks_test.go
T
2026-06-23 05:02:15 +08:00

246 lines
7.3 KiB
Go

package git_test
import (
"context"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"superwork-tui/internal/git"
)
func makeHookCtx(workspaceRoot, worktreePath, branch string, issue int, main string) git.HookContext {
return git.HookContext{
WorkspaceRoot: workspaceRoot,
WorktreePath: worktreePath,
Branch: branch,
IssueNumber: issue,
MainBranch: main,
}
}
func writeScript(t *testing.T, dir, name, content string) string {
t.Helper()
p := filepath.Join(dir, name)
if err := os.WriteFile(p, []byte(content), 0o755); err != nil {
t.Fatal(err)
}
return p
}
// setupWorkspace returns workspaceRoot with .spx/ created.
func setupWorkspace(t *testing.T) (workspaceRoot, spxDir string) {
t.Helper()
workspaceRoot = t.TempDir()
spxDir = filepath.Join(workspaceRoot, ".spx")
if err := os.MkdirAll(spxDir, 0o755); err != nil {
t.Fatal(err)
}
return
}
// --- RunPostCreateHook ---
func TestRunPostCreateHook_Missing(t *testing.T) {
root, _ := setupWorkspace(t)
ctx := makeHookCtx(root, root, "feat/x", 42, "main")
// No script on disk → skipped.
result := git.RunPostCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusSkipped {
t.Errorf("want skipped, got %v", result.Status)
}
}
func TestRunPostCreateHook_Ok(t *testing.T) {
root, spxDir := setupWorkspace(t)
worktree := t.TempDir()
// Script writes env vars to a file so we can assert them.
envFile := filepath.Join(root, "env-dump.txt")
script := `#!/bin/bash
echo "WORKTREE_PATH=$WORKTREE_PATH" >> "` + envFile + `"
echo "WORKSPACE_ROOT=$WORKSPACE_ROOT" >> "` + envFile + `"
echo "BRANCH=$BRANCH" >> "` + envFile + `"
echo "ISSUE_NUMBER=$ISSUE_NUMBER" >> "` + envFile + `"
echo "MAIN_BRANCH=$MAIN_BRANCH" >> "` + envFile + `"
exit 0
`
writeScript(t, spxDir, "worktree-post-create.sh", script)
ctx := makeHookCtx(root, worktree, "feat/my-branch", 99, "develop")
result := git.RunPostCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusOk {
t.Fatalf("want ok, got %v (err=%s stderr=%s)", result.Status, result.ErrorMessage, result.Stderr)
}
if result.ExitCode != 0 {
t.Errorf("want exit code 0, got %d", result.ExitCode)
}
if result.ScriptPath == "" {
t.Error("ScriptPath must not be empty")
}
envData, err := os.ReadFile(envFile)
if err != nil {
t.Fatalf("env dump not written: %v", err)
}
env := string(envData)
checks := map[string]string{
"WORKTREE_PATH": worktree,
"WORKSPACE_ROOT": root,
"BRANCH": "feat/my-branch",
"ISSUE_NUMBER": "99",
"MAIN_BRANCH": "develop",
}
for k, v := range checks {
if !strings.Contains(env, k+"="+v) {
t.Errorf("env missing %s=%s; got:\n%s", k, v, env)
}
}
}
func TestRunPostCreateHook_Failed(t *testing.T) {
root, spxDir := setupWorkspace(t)
writeScript(t, spxDir, "worktree-post-create.sh", "#!/bin/bash\nexit 3\n")
ctx := makeHookCtx(root, root, "feat/x", 1, "main")
result := git.RunPostCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusFailed {
t.Errorf("want failed, got %v", result.Status)
}
if result.ExitCode != 3 {
t.Errorf("want ExitCode=3, got %d", result.ExitCode)
}
}
// --- RunPreRemoveHook ---
func TestRunPreRemoveHook_Missing(t *testing.T) {
root, _ := setupWorkspace(t)
ctx := makeHookCtx(root, root, "feat/x", 1, "main")
result := git.RunPreRemoveHook(context.Background(), ctx)
if result.Status != git.HookStatusSkipped {
t.Errorf("want skipped, got %v", result.Status)
}
}
func TestRunPreRemoveHook_Ok(t *testing.T) {
root, spxDir := setupWorkspace(t)
writeScript(t, spxDir, "worktree-pre-remove.sh", "#!/bin/bash\nexit 0\n")
ctx := makeHookCtx(root, root, "feat/x", 1, "main")
result := git.RunPreRemoveHook(context.Background(), ctx)
if result.Status != git.HookStatusOk {
t.Errorf("want ok, got %v", result.Status)
}
}
// --- RunImplTabPreCreateHook ---
func TestRunImplTabPreCreateHook_Missing(t *testing.T) {
root, _ := setupWorkspace(t)
ctx := makeHookCtx(root, root, "feat/x", 1, "main")
result := git.RunImplTabPreCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusSkipped {
t.Errorf("want skipped, got %v", result.Status)
}
}
func TestRunImplTabPreCreateHook_Ok(t *testing.T) {
root, spxDir := setupWorkspace(t)
writeScript(t, spxDir, "impl-tab-pre-create.sh", "#!/bin/bash\nexit 0\n")
ctx := makeHookCtx(root, root, "feat/x", 7, "main")
result := git.RunImplTabPreCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusOk {
t.Errorf("want ok, got %v", result.Status)
}
if result.ExitCode != 0 {
t.Errorf("want exit 0, got %d", result.ExitCode)
}
}
func TestRunImplTabPreCreateHook_Failed(t *testing.T) {
root, spxDir := setupWorkspace(t)
writeScript(t, spxDir, "impl-tab-pre-create.sh", "#!/bin/bash\nexit 7\n")
ctx := makeHookCtx(root, root, "feat/x", 1, "main")
result := git.RunImplTabPreCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusFailed {
t.Errorf("want failed, got %v", result.Status)
}
if result.ExitCode != 7 {
t.Errorf("want ExitCode=7, got %d", result.ExitCode)
}
}
// --- RunImplTabPostCloseHook ---
func TestRunImplTabPostCloseHook_Missing(t *testing.T) {
root, _ := setupWorkspace(t)
ctx := makeHookCtx(root, root, "feat/x", 1, "main")
result := git.RunImplTabPostCloseHook(context.Background(), ctx)
if result.Status != git.HookStatusSkipped {
t.Errorf("want skipped, got %v", result.Status)
}
}
func TestRunImplTabPostCloseHook_Ok(t *testing.T) {
root, spxDir := setupWorkspace(t)
writeScript(t, spxDir, "impl-tab-post-close.sh", "#!/bin/bash\nexit 0\n")
ctx := makeHookCtx(root, root, "feat/x", 3, "main")
result := git.RunImplTabPostCloseHook(context.Background(), ctx)
if result.Status != git.HookStatusOk {
t.Errorf("want ok, got %v", result.Status)
}
}
// --- CustomScriptPath override ---
func TestRunPostCreateHook_CustomScriptPath(t *testing.T) {
root, _ := setupWorkspace(t)
customDir := t.TempDir()
customScript := filepath.Join(customDir, "my-hook.sh")
if err := os.WriteFile(customScript, []byte("#!/bin/bash\nexit 0\n"), 0o755); err != nil {
t.Fatal(err)
}
ctx := git.HookContext{
WorkspaceRoot: root,
WorktreePath: root,
Branch: "feat/x",
IssueNumber: 1,
MainBranch: "main",
CustomScriptPath: customScript, // absolute override
}
result := git.RunPostCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusOk {
t.Errorf("want ok with custom path, got %v", result.Status)
}
if result.ScriptPath != customScript {
t.Errorf("ScriptPath: want %q, got %q", customScript, result.ScriptPath)
}
}
// IssueNumber env value must be the decimal string, not anything else.
func TestRunPostCreateHook_IssueNumberEnv(t *testing.T) {
root, spxDir := setupWorkspace(t)
envFile := filepath.Join(root, "issue-num.txt")
script := "#!/bin/bash\necho -n \"$ISSUE_NUMBER\" > \"" + envFile + "\"\nexit 0\n"
writeScript(t, spxDir, "worktree-post-create.sh", script)
ctx := makeHookCtx(root, root, "feat/x", 12345, "main")
result := git.RunPostCreateHook(context.Background(), ctx)
if result.Status != git.HookStatusOk {
t.Fatalf("want ok, got %v", result.Status)
}
data, err := os.ReadFile(envFile)
if err != nil {
t.Fatal(err)
}
got := strings.TrimSpace(string(data))
if got != strconv.Itoa(12345) {
t.Errorf("ISSUE_NUMBER env: want %q, got %q", "12345", got)
}
}