Files
zzet--gortex/cmd/gortex/daemon_mcp_case_identity_test.go
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

101 lines
3.6 KiB
Go

package main
import (
"context"
"encoding/json"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/daemon"
"github.com/zzet/gortex/internal/pathkey"
)
// forceCaseInsensitivePaths flips the process-global
// pathkey.CaseInsensitivePaths for the test and restores it afterwards.
// Tests using it must not run in parallel.
func forceCaseInsensitivePaths(t *testing.T, v bool) {
t.Helper()
prev := pathkey.CaseInsensitivePaths
pathkey.CaseInsensitivePaths = v
t.Cleanup(func() { pathkey.CaseInsensitivePaths = prev })
}
// toggleBaseCase returns p with the case of every ASCII letter inverted,
// so it names the same directory on a case-insensitive filesystem while
// differing byte-wise. Toggling the whole path (not just the final
// component) guarantees a byte difference even when the final component is
// all digits, as t.TempDir's "001" suffix is.
func toggleBaseCase(p string) string {
b := []byte(p)
for i := range b {
switch {
case b[i] >= 'a' && b[i] <= 'z':
b[i] -= 32
case b[i] >= 'A' && b[i] <= 'Z':
b[i] += 32
}
}
return string(b)
}
// A case-variant cwd of a tracked root must be recognised as tracked on a
// case-insensitive filesystem. This is the #277 Windows / macOS 0-tools
// fix at the MCP dispatcher boundary (isCWDTracked is pure folding, no
// stat, so it is deterministic on every platform once folding is forced).
func TestDispatcher_CaseMismatchedCWD_Tracked(t *testing.T) {
forceCaseInsensitivePaths(t, true)
tracked := t.TempDir()
d, _ := trackedPathMCPSetup(t, tracked)
variant := toggleBaseCase(tracked)
require.NotEqual(t, tracked, variant, "temp dir base must contain letters to toggle")
assert.True(t, d.isCWDTracked(variant),
"case-variant of tracked root must be recognised as tracked")
assert.True(t, d.isCWDTracked(filepath.Join(variant, "internal", "auth")),
"case-variant subdirectory must be recognised as tracked")
}
// With case-sensitive comparison forced, a byte-different variant must be
// rejected — the fold must not over-merge on genuinely case-sensitive
// volumes.
func TestDispatcher_CaseSensitiveCWD_RejectsVariant(t *testing.T) {
forceCaseInsensitivePaths(t, false)
tracked := t.TempDir()
d, _ := trackedPathMCPSetup(t, tracked)
variant := toggleBaseCase(tracked)
require.NotEqual(t, tracked, variant)
assert.False(t, d.isCWDTracked(variant),
"case-sensitive: byte-different variant must not be recognised as tracked")
}
// The INACTIVE initialize instructions must name the tracked roots so the
// mismatch is self-diagnosing (#277 explicit ask).
func TestDispatcher_UntrackedInstructions_IncludeTrackedRoots(t *testing.T) {
tracked := t.TempDir()
untracked := t.TempDir()
d, _ := trackedPathMCPSetup(t, tracked)
sess := &daemon.Session{ID: "sess_roots", CWD: untracked}
frame := []byte(`{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}`)
reply, err := d.Dispatch(context.Background(), sess, frame)
require.NoError(t, err)
require.NotNil(t, reply)
var parsed map[string]any
require.NoError(t, json.Unmarshal(reply, &parsed))
result, ok := parsed["result"].(map[string]any)
require.True(t, ok, "initialize must return a result: %v", parsed)
instr, ok := result["instructions"].(string)
require.True(t, ok, "initialize result must carry instructions")
assert.Contains(t, instr, "INACTIVE")
assert.Contains(t, instr, "Tracked repository roots",
"instructions must list tracked roots for self-diagnosis")
assert.Contains(t, instr, tracked,
"instructions must name the actual tracked root path")
}