Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

287 lines
7.6 KiB
Go

package githooks
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// initRepo creates a fresh git repo at tmp and returns the root path.
func initRepo(t *testing.T) string {
t.Helper()
tmp := t.TempDir()
for _, args := range [][]string{
{"init", "--quiet"},
{"config", "user.email", "test@example.com"},
{"config", "user.name", "Tester"},
} {
cmd := exec.Command("git", args...)
cmd.Dir = tmp
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v: %s", args, err, out)
}
}
return tmp
}
func TestInstallPostCommit_FreshFile(t *testing.T) {
repo := initRepo(t)
path, err := InstallPostCommit(repo, InstallOpts{RegenMermaid: true, RegenWiki: true, Binary: "gortex"})
if err != nil {
t.Fatalf("Install: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read hook: %v", err)
}
got := string(body)
for _, want := range []string{
"#!/bin/sh",
MarkerBegin,
MarkerEnd,
"gortex export --format mermaid",
"gortex wiki",
} {
if !strings.Contains(got, want) {
t.Errorf("hook missing %q. Body:\n%s", want, got)
}
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("stat hook: %v", err)
}
if mode := info.Mode().Perm(); mode&0o100 == 0 {
t.Errorf("hook not executable: mode = %v", mode)
}
}
func TestInstallPostCommit_Idempotent(t *testing.T) {
repo := initRepo(t)
for i := range 3 {
if _, err := InstallPostCommit(repo, InstallOpts{RegenMermaid: true}); err != nil {
t.Fatalf("install %d: %v", i, err)
}
}
rep, err := Status(repo)
if err != nil {
t.Fatalf("Status: %v", err)
}
if !rep.Managed {
t.Error("after install, status should report managed")
}
if c := strings.Count(rep.Body, MarkerBegin); c != 1 {
t.Errorf("expected one MarkerBegin, got %d", c)
}
if c := strings.Count(rep.Body, MarkerEnd); c != 1 {
t.Errorf("expected one MarkerEnd, got %d", c)
}
}
func TestInstallPostCommit_PreservesUserContent(t *testing.T) {
repo := initRepo(t)
hookPath, err := HookPath(repo)
if err != nil {
t.Fatalf("HookPath: %v", err)
}
preexisting := `#!/bin/sh
# my custom hook
echo "hello from user hook"
`
if err := os.WriteFile(hookPath, []byte(preexisting), 0o755); err != nil {
t.Fatalf("write preexisting: %v", err)
}
if _, err := InstallPostCommit(repo, InstallOpts{RegenMermaid: true}); err != nil {
t.Fatalf("Install: %v", err)
}
body, err := os.ReadFile(hookPath)
if err != nil {
t.Fatalf("read hook: %v", err)
}
got := string(body)
if !strings.Contains(got, `echo "hello from user hook"`) {
t.Errorf("install should preserve user content; got:\n%s", got)
}
if !strings.Contains(got, MarkerBegin) {
t.Errorf("install should add marker block; got:\n%s", got)
}
}
func TestUninstallPostCommit_RemovesBlock(t *testing.T) {
repo := initRepo(t)
hookPath, err := HookPath(repo)
if err != nil {
t.Fatalf("HookPath: %v", err)
}
preexisting := `#!/bin/sh
# my custom hook
echo "hello"
`
if err := os.WriteFile(hookPath, []byte(preexisting), 0o755); err != nil {
t.Fatalf("write preexisting: %v", err)
}
if _, err := InstallPostCommit(repo, InstallOpts{RegenWiki: true}); err != nil {
t.Fatalf("Install: %v", err)
}
path, removed, err := UninstallPostCommit(repo)
if err != nil {
t.Fatalf("Uninstall: %v", err)
}
if !removed {
t.Error("Uninstall should report removed=true")
}
if path != hookPath {
t.Errorf("Uninstall path mismatch: %q vs %q", path, hookPath)
}
body, err := os.ReadFile(hookPath)
if err != nil {
t.Fatalf("read hook after uninstall: %v", err)
}
got := string(body)
if strings.Contains(got, MarkerBegin) || strings.Contains(got, MarkerEnd) {
t.Errorf("Uninstall should remove markers; got:\n%s", got)
}
if !strings.Contains(got, `echo "hello"`) {
t.Errorf("Uninstall should preserve user content; got:\n%s", got)
}
}
func TestUninstallPostCommit_RemovesFileWhenStubOnly(t *testing.T) {
repo := initRepo(t)
if _, err := InstallPostCommit(repo, InstallOpts{RegenMermaid: true}); err != nil {
t.Fatalf("Install: %v", err)
}
path, removed, err := UninstallPostCommit(repo)
if err != nil {
t.Fatalf("Uninstall: %v", err)
}
if !removed {
t.Error("expected removed=true on fresh-install uninstall")
}
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Errorf("expected hook file removed; stat returned %v", err)
}
}
func TestUninstallPostCommit_Noop(t *testing.T) {
repo := initRepo(t)
path, removed, err := UninstallPostCommit(repo)
if err != nil {
t.Fatalf("Uninstall: %v", err)
}
if removed {
t.Error("Uninstall on non-existent hook should report removed=false")
}
if path == "" {
t.Error("Uninstall should still return resolved hook path")
}
}
func TestStatus_NewRepo(t *testing.T) {
repo := initRepo(t)
rep, err := Status(repo)
if err != nil {
t.Fatalf("Status: %v", err)
}
if rep.Exists {
t.Error("fresh repo shouldn't have a hook")
}
if rep.Managed {
t.Error("fresh repo shouldn't be managed")
}
}
func TestInstallHook_PostMergeAndChurn(t *testing.T) {
repo := initRepo(t)
path, err := InstallHook(repo, "post-merge", InstallOpts{RegenChurn: true, ChurnBranch: "origin/main"})
if err != nil {
t.Fatalf("InstallHook post-merge: %v", err)
}
if filepath.Base(path) != "post-merge" {
t.Errorf("expected post-merge hook file, got %s", path)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read hook: %v", err)
}
got := string(body)
for _, want := range []string{
"# gortex-managed:post-merge:begin",
"# gortex-managed:post-merge:end",
"gortex enrich churn",
`--branch="origin/main"`,
} {
if !strings.Contains(got, want) {
t.Errorf("hook missing %q. Body:\n%s", want, got)
}
}
// Post-commit and post-merge should be independently managed.
if _, err := InstallHook(repo, "post-commit", InstallOpts{RegenChurn: true}); err != nil {
t.Fatalf("InstallHook post-commit: %v", err)
}
if _, removed, err := UninstallHook(repo, "post-merge"); err != nil || !removed {
t.Fatalf("UninstallHook post-merge removed=%v err=%v", removed, err)
}
// Post-commit hook should still exist after we uninstalled post-merge.
postCommitPath, err := HookPathFor(repo, "post-commit")
if err != nil {
t.Fatalf("HookPathFor: %v", err)
}
if _, err := os.Stat(postCommitPath); err != nil {
t.Errorf("post-commit hook should survive post-merge uninstall: %v", err)
}
}
func TestInstallHook_RegenReleases(t *testing.T) {
repo := initRepo(t)
path, err := InstallHook(repo, "post-merge", InstallOpts{
RegenReleases: true,
ReleasesBranch: "origin/main",
})
if err != nil {
t.Fatalf("InstallHook post-merge: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read hook: %v", err)
}
got := string(body)
for _, want := range []string{
"gortex enrich releases",
`--branch="origin/main"`,
} {
if !strings.Contains(got, want) {
t.Errorf("hook missing %q. Body:\n%s", want, got)
}
}
}
func TestInstallHook_RejectsUnsupportedHook(t *testing.T) {
repo := initRepo(t)
if _, err := InstallHook(repo, "pre-push", InstallOpts{RegenMermaid: true}); err == nil {
t.Fatal("expected error for unsupported hook pre-push")
}
}
func TestHookPath_HonoursCoreHooksPath(t *testing.T) {
repo := initRepo(t)
customHooks := filepath.Join(repo, "custom-hooks")
if err := os.MkdirAll(customHooks, 0o755); err != nil {
t.Fatalf("mkdir custom: %v", err)
}
cmd := exec.Command("git", "config", "core.hooksPath", customHooks)
cmd.Dir = repo
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("config core.hooksPath: %v: %s", err, out)
}
path, err := HookPath(repo)
if err != nil {
t.Fatalf("HookPath: %v", err)
}
if filepath.Dir(path) != customHooks {
t.Errorf("HookPath should honour core.hooksPath, got %q under %q (want %q)",
path, filepath.Dir(path), customHooks)
}
}