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

248 lines
7.3 KiB
Go

package hooks
import (
"strings"
"testing"
)
func withDaemonReachable(t *testing.T, reachable bool) {
t.Helper()
prev := daemonReachableFn
daemonReachableFn = func() bool { return reachable }
t.Cleanup(func() { daemonReachableFn = prev })
}
func TestEnrichGlob_GreedySourcePattern_DaemonUp_Denies(t *testing.T) {
withDaemonReachable(t, true)
result := enrichGlob(map[string]any{"pattern": "**/*.go"})
if !result.deny {
t.Fatal("expected deny for greedy source glob with daemon up")
}
if !strings.Contains(result.reason, "BLOCKED") {
t.Errorf("expected BLOCKED in reason, got: %s", result.reason)
}
if !strings.Contains(result.reason, "get_repo_outline") {
t.Errorf("expected get_repo_outline in reason, got: %s", result.reason)
}
}
func TestEnrichGlob_GreedySourcePattern_DaemonDown_Soft(t *testing.T) {
withDaemonReachable(t, false)
result := enrichGlob(map[string]any{"pattern": "**/*.go"})
if result.deny {
t.Fatal("daemon down ⇒ no enforcement; expected soft guidance")
}
if result.context == "" {
t.Fatal("expected soft guidance text, got empty")
}
if !strings.Contains(result.context, "search_symbols") {
t.Error("expected guidance to mention search_symbols")
}
}
func TestEnrichGlob_NamedSourcePattern_NeverDenies(t *testing.T) {
withDaemonReachable(t, true)
cases := []string{
"**/handler*.go",
"*test*.ts",
"**/Server.go",
"src/**/component_*.tsx",
}
for _, p := range cases {
result := enrichGlob(map[string]any{"pattern": p})
if result.deny {
t.Errorf("name-based pattern %q should not deny, got reason: %s", p, result.reason)
}
if result.context == "" {
t.Errorf("name-based pattern %q should have soft guidance", p)
}
}
}
func TestEnrichGlob_NonSourcePattern(t *testing.T) {
withDaemonReachable(t, true)
result := enrichGlob(map[string]any{"pattern": "**/*.json"})
if result.context != "" || result.deny {
t.Errorf("expected empty for non-source glob, got: ctx=%q deny=%v", result.context, result.deny)
}
}
func TestEnrichGlob_EmptyPattern(t *testing.T) {
result := enrichGlob(map[string]any{"pattern": ""})
if result.context != "" || result.deny {
t.Errorf("expected empty for empty pattern, got: ctx=%q deny=%v", result.context, result.deny)
}
}
func TestIsGreedySourceGlob(t *testing.T) {
cases := []struct {
pattern string
want bool
}{
{"*.go", true},
{"**/*.go", true},
{"src/**/*.tsx", true},
{"**/handler.go", false},
{"**/handler*.go", false},
{"*test*.ts", false},
{"foo.go", false}, // not a wildcard
{"", false},
{"**/*", false}, // no extension
}
for _, c := range cases {
got := isGreedySourceGlob(c.pattern)
if got != c.want {
t.Errorf("isGreedySourceGlob(%q) = %v, want %v", c.pattern, got, c.want)
}
}
}
func TestEnrichRead_NonIndexed_Guidance(t *testing.T) {
// Port 0 means bridge won't respond — file is not indexed.
// Should return advisory guidance (not deny).
result := enrichRead(map[string]any{"file_path": "/tmp/foo.go"}, "")
if result.context == "" {
t.Fatal("expected guidance for unindexed source file, got empty")
}
if result.deny {
t.Error("should not deny when file is not indexed")
}
if !strings.Contains(result.context, "get_symbol_source") {
t.Error("expected guidance to mention get_symbol_source")
}
if !strings.Contains(result.context, "get_editing_context") {
t.Error("expected guidance to mention get_editing_context")
}
}
func TestEnrichRead_NonSourceFile(t *testing.T) {
result := enrichRead(map[string]any{"file_path": "/tmp/config.json"}, "")
if result.context != "" || result.deny {
t.Errorf("expected pass-through for non-source file, got: context=%q deny=%v", result.context, result.deny)
}
}
func TestEnrichRead_NarrowRead_Allowed(t *testing.T) {
// A read with offset+limit is narrow (for editing) — should always pass through.
result := enrichRead(map[string]any{
"file_path": "/tmp/foo.go",
"offset": float64(100),
"limit": float64(20),
}, "")
if result.deny {
t.Error("narrow read (offset+limit) should not be denied")
}
if result.context != "" {
t.Error("narrow read should not produce guidance")
}
}
func TestEnrichRead_OffsetOnly_Allowed(t *testing.T) {
result := enrichRead(map[string]any{
"file_path": "/tmp/foo.go",
"offset": float64(50),
}, "")
if result.deny {
t.Error("read with offset only should not be denied")
}
}
func TestEnrichRead_SmallLimit_Allowed(t *testing.T) {
result := enrichRead(map[string]any{
"file_path": "/tmp/foo.go",
"limit": float64(30),
}, "")
if result.deny {
t.Error("read with small limit should not be denied")
}
}
func TestEnrichRead_LargeLimit_NotNarrow(t *testing.T) {
// Limit > 50 without offset is a whole-file read — should get guidance.
result := enrichRead(map[string]any{
"file_path": "/tmp/foo.go",
"limit": float64(500),
}, "")
// Not indexed (port 0), so advisory only.
if result.deny {
t.Error("should not deny unindexed file")
}
if result.context == "" {
t.Error("expected advisory guidance for large-limit read of source file")
}
}
func TestEnrichGrep_Guidance(t *testing.T) {
// No daemon reachable → should return soft guidance, not deny.
stubProbe(t, nil, errDaemonUnreachable)
result := enrichGrep(map[string]any{"pattern": "handleFindUsages"}, 0)
if result.context == "" {
t.Fatal("expected guidance for grep, got empty")
}
if result.deny {
t.Error("grep should never be denied")
}
if !strings.Contains(result.context, "search_symbols") {
t.Error("expected guidance to mention search_symbols")
}
if !strings.Contains(result.context, "find_usages") {
t.Error("expected guidance to mention find_usages")
}
}
func TestEnrichGrep_ShortPattern(t *testing.T) {
result := enrichGrep(map[string]any{"pattern": "ab"}, 0)
if result.context != "" {
t.Errorf("expected empty for short pattern, got: %s", result.context)
}
}
func TestIsNarrowRead(t *testing.T) {
tests := []struct {
name string
input map[string]any
want bool
}{
{"offset+limit", map[string]any{"offset": float64(10), "limit": float64(20)}, true},
{"offset only", map[string]any{"offset": float64(10)}, true},
{"small limit only", map[string]any{"limit": float64(30)}, true},
{"large limit only", map[string]any{"limit": float64(500)}, false},
{"no offset no limit", map[string]any{}, false},
{"nil values", map[string]any{"file_path": "foo.go"}, false},
}
for _, tt := range tests {
got := isNarrowRead(tt.input)
if got != tt.want {
t.Errorf("isNarrowRead(%s) = %v, want %v", tt.name, got, tt.want)
}
}
}
func TestEnrich_DispatchesCorrectly(t *testing.T) {
tests := []struct {
tool string
input map[string]any
wantNon bool // expect non-empty context or deny
}{
{"Read", map[string]any{"file_path": "/tmp/foo.go"}, true},
{"Grep", map[string]any{"pattern": "handleFoo"}, true},
{"Glob", map[string]any{"pattern": "**/*.ts"}, true},
{"Glob", map[string]any{"pattern": "**/*.json"}, false},
{"Write", map[string]any{}, false},
}
for _, tt := range tests {
result := enrich(HookInput{
HookEventName: "PreToolUse",
ToolName: tt.tool,
ToolInput: tt.input,
}, 0)
hasOutput := result.context != "" || result.deny
if tt.wantNon && !hasOutput {
t.Errorf("enrich(%s) returned no output, expected non-empty", tt.tool)
}
if !tt.wantNon && hasOutput {
t.Errorf("enrich(%s) returned output, expected empty", tt.tool)
}
}
}