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

172 lines
6.5 KiB
Go

package hooks
import (
"encoding/json"
"strings"
"testing"
)
// withForceCompress flips GORTEX_HOOK_FORCE_COMPRESS for one test and
// restores the previous value (including unset) on cleanup.
func withForceCompress(t *testing.T, on bool) {
t.Helper()
t.Setenv(gortexForceCompressEnvVar, map[bool]string{true: "1", false: ""}[on])
}
func TestGortexReadNudge_NudgesFullSourceRead(t *testing.T) {
msg := gortexReadNudge(gortexReadFileTool, map[string]any{"path": "internal/resolver/resolver.go"})
if msg == "" {
t.Fatal("expected a nudge for a full-body source read")
}
for _, want := range []string{"compress_bodies", "search_text", "keep", "read_file"} {
if !strings.Contains(msg, want) {
t.Errorf("nudge missing %q:\n%s", want, msg)
}
}
}
func TestGortexReadNudge_SilentWhenAlreadyEconomical(t *testing.T) {
cases := []struct {
name string
input map[string]any
}{
{"compress_bodies true", map[string]any{"path": "a.go", "compress_bodies": true}},
{"compress_bodies string true", map[string]any{"path": "a.go", "compress_bodies": "true"}},
{"non-source file", map[string]any{"path": "README.md"}},
{"config file", map[string]any{"path": "config.yaml"}},
{"capped by max_lines", map[string]any{"path": "a.go", "max_lines": float64(80)}},
{"capped by max_bytes", map[string]any{"path": "a.go", "max_bytes": float64(4000)}},
{"capped by max_tokens", map[string]any{"path": "a.go", "max_tokens": float64(2000)}},
{"no path", map[string]any{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if msg := gortexReadNudge(gortexReadFileTool, c.input); msg != "" {
t.Errorf("expected silence, got nudge:\n%s", msg)
}
})
}
}
func TestGortexReadNudge_ZeroCapStillNudges(t *testing.T) {
// An explicit max_tokens:0 is an opt-out of the cap, not a cap — the
// read is still unbounded, so the nudge should fire.
if msg := gortexReadNudge(gortexReadFileTool, map[string]any{"path": "a.go", "max_tokens": float64(0)}); msg == "" {
t.Error("max_tokens:0 is not a cap; expected the nudge to fire")
}
}
func TestEnrichGortexRead_DefaultIsSoftContext(t *testing.T) {
withForceCompress(t, false)
result := enrichGortexRead(gortexReadFileTool, map[string]any{"path": "a.go"})
if result.deny {
t.Fatal("default posture must not deny")
}
if result.context == "" {
t.Error("expected soft advisory context")
}
}
func TestEnrichGortexRead_GateOnDenies(t *testing.T) {
withForceCompress(t, true)
result := enrichGortexRead(gortexEditingContextTool, map[string]any{"path": "a.go"})
if !result.deny {
t.Fatal("GORTEX_HOOK_FORCE_COMPRESS=1 must deny a full-body read")
}
if !strings.Contains(result.reason, "compress_bodies") {
t.Errorf("deny reason should name compress_bodies:\n%s", result.reason)
}
}
func TestEnrichGortexRead_EconomicalCallPassesThroughEvenWhenGated(t *testing.T) {
withForceCompress(t, true)
result := enrichGortexRead(gortexReadFileTool, map[string]any{"path": "a.go", "compress_bodies": true})
if result.deny || result.context != "" {
t.Errorf("already-compressed read must pass silently even with the gate on; got deny=%v ctx=%q", result.deny, result.context)
}
}
func TestEnrich_DispatchesGortexReadTools(t *testing.T) {
withForceCompress(t, false)
for _, tool := range []string{gortexReadFileTool, gortexEditingContextTool} {
input := HookInput{ToolName: tool, ToolInput: map[string]any{"path": "a.go"}}
if result := enrich(input, 0); result.context == "" {
t.Errorf("enrich must route %s to enrichGortexRead", tool)
}
}
}
func TestRunPreToolUse_GortexRead_EmitsAdditionalContext(t *testing.T) {
withForceCompress(t, false)
payload := []byte(`{"hook_event_name":"PreToolUse","tool_name":"mcp__gortex__read_file","tool_input":{"path":"internal/x.go"}}`)
out := captureStdout(t, func() { runPreToolUse(payload, 0, ModeDeny) })
var dec HookOutput
if err := json.Unmarshal([]byte(out), &dec); err != nil {
t.Fatalf("invalid JSON: %v\n%s", err, out)
}
if dec.HookSpecificOutput.PermissionDecision == "deny" {
t.Error("default posture must not hard-deny a Gortex read")
}
if !strings.Contains(dec.HookSpecificOutput.AdditionalContext, "compress_bodies") {
t.Errorf("expected compress_bodies advice in additionalContext:\n%s", out)
}
}
func TestRunPreToolUse_GortexRead_CompressedIsSilent(t *testing.T) {
withForceCompress(t, false)
payload := []byte(`{"hook_event_name":"PreToolUse","tool_name":"mcp__gortex__read_file","tool_input":{"path":"internal/x.go","compress_bodies":true}}`)
out := captureStdout(t, func() { runPreToolUse(payload, 0, ModeDeny) })
if strings.TrimSpace(out) != "" {
t.Errorf("a compressed read should emit nothing, got:\n%s", out)
}
}
func TestRunPreToolUse_GortexRead_PermissiveCarriesNudge(t *testing.T) {
withForceCompress(t, false)
// Under a permissive permission mode the call is auto-approved, but
// the read-discipline nudge must still ride along as soft context.
payload := []byte(`{"hook_event_name":"PreToolUse","tool_name":"mcp__gortex__read_file","tool_input":{"path":"internal/x.go"},"permission_mode":"acceptEdits"}`)
out := captureStdout(t, func() { runPreToolUse(payload, 0, ModeDeny) })
var dec HookOutput
if err := json.Unmarshal([]byte(out), &dec); err != nil {
t.Fatalf("invalid JSON: %v\n%s", err, out)
}
if dec.HookSpecificOutput.PermissionDecision != "allow" {
t.Errorf("permissive mode must keep the allow decision, got %q", dec.HookSpecificOutput.PermissionDecision)
}
if !strings.Contains(dec.HookSpecificOutput.AdditionalContext, "compress_bodies") {
t.Errorf("permissive auto-approve should still carry the nudge:\n%s", out)
}
}
func TestRunPreToolUse_GortexRead_PermissiveNeverDeniesEvenWhenGated(t *testing.T) {
withForceCompress(t, true)
payload := []byte(`{"hook_event_name":"PreToolUse","tool_name":"mcp__gortex__read_file","tool_input":{"path":"internal/x.go"},"permission_mode":"acceptEdits"}`)
out := captureStdout(t, func() { runPreToolUse(payload, 0, ModeDeny) })
var dec HookOutput
if err := json.Unmarshal([]byte(out), &dec); err != nil {
t.Fatalf("invalid JSON: %v\n%s", err, out)
}
if dec.HookSpecificOutput.PermissionDecision != "allow" {
t.Errorf("auto-approve must hold even with the gate on, got %q", dec.HookSpecificOutput.PermissionDecision)
}
}
func TestAsBool(t *testing.T) {
truthy := []any{true, "true", "TRUE", "1", "yes", "on"}
for _, v := range truthy {
if !asBool(v) {
t.Errorf("asBool(%#v) = false, want true", v)
}
}
falsy := []any{false, "false", "0", "no", "off", "", nil, 1, "maybe"}
for _, v := range falsy {
if asBool(v) {
t.Errorf("asBool(%#v) = true, want false", v)
}
}
}