Files
filepuff-mcp/internal/server/handlers_lsp_test.go
T
lukaszraczylo 5ad975ee7a V2/token optimization (#11)
* v2.0: token-optimization overhaul

Additive (backward-compatible flags):
- file_read: skeleton mode, strip (imports/license/block_comments),
  compact_line_numbers, 8-char etag with prefix-match compat
- ast_query: format=verbose|compact|location, pagination cursor
- file_search: cluster mode, pagination cursor
- lsp_query (references): compact output

Breaking (v2):
- Preambles removed; opt-in verbose=true restores
- edit_apply: response=count|diff|none, default count
- ping tool removed
- symbol_at/find_definition/find_references merged into lsp_query
- Tool descriptions trimmed -83%, help moved to filepuff://help/<tool>
- Batch file_read dedups by etag

Protocol:
- ResourceLink returned for file_read >64 KiB (force_inline override)
- OnAfterInitialize hook reads capabilities.experimental.filepuff
  for session defaults (default_format, default_max_results,
  default_cluster, compact_refs, line_numbers,
  resource_link_threshold)

* fix: drop --max-total-count from ripgrep args

The flag does not exist in stable ripgrep (confirmed up to 15.1.0 --
"unrecognized flag --max-total-count, similar flags that are
available: --max-count"). Every file_search call failed on hosts with
stock rg. --max-count is per-file, not a drop-in replacement, so rely
on the in-process truncation in parseOutput that was already the
documented safety net.
2026-04-19 19:56:49 +01:00

182 lines
5.7 KiB
Go

package server
import (
"strings"
"testing"
"github.com/lukaszraczylo/mcp-filepuff/internal/lsp"
)
// makeLocation builds an lsp.Location with a file:// URI.
func makeLocation(file string, line, col int) lsp.Location {
return lsp.Location{
URI: "file://" + file,
Range: lsp.Range{
Start: lsp.Position{Line: line - 1, Character: col - 1},
End: lsp.Position{Line: line - 1, Character: col - 1},
},
}
}
// groupLocations is a helper that mirrors the grouping logic in lspReferences.
func groupLocations(locations []lsp.Location) (map[string][]lsp.Location, []string) {
fileGroups := make(map[string][]lsp.Location)
fileOrder := make([]string, 0)
for _, loc := range locations {
filePath := lsp.URIToFile(loc.URI)
if _, seen := fileGroups[filePath]; !seen {
fileOrder = append(fileOrder, filePath)
}
fileGroups[filePath] = append(fileGroups[filePath], loc)
}
return fileGroups, fileOrder
}
// TestFormatReferencesVerbose verifies the default (non-compact) format is unchanged.
func TestFormatReferencesVerbose(t *testing.T) {
locs := []lsp.Location{
makeLocation("/a/foo.go", 12, 5),
makeLocation("/a/foo.go", 13, 8),
makeLocation("/a/bar.go", 15, 1),
}
groups, order := groupLocations(locs)
out := formatReferences(groups, order, len(locs), false, true)
if !strings.Contains(out, "Found 3 reference(s):") {
t.Errorf("missing header, got:\n%s", out)
}
if !strings.Contains(out, "**") {
t.Errorf("verbose format should use **file** markers, got:\n%s", out)
}
if !strings.Contains(out, "L12:5") {
t.Errorf("missing L12:5 in verbose output, got:\n%s", out)
}
if !strings.Contains(out, "L13:8") {
t.Errorf("missing L13:8 in verbose output, got:\n%s", out)
}
if !strings.Contains(out, "L15:1") {
t.Errorf("missing L15:1 in verbose output, got:\n%s", out)
}
}
// TestFormatReferencesCompactBasic verifies compact output for distinct lines.
func TestFormatReferencesCompactBasic(t *testing.T) {
locs := []lsp.Location{
makeLocation("/a/foo.go", 12, 5),
makeLocation("/a/foo.go", 13, 8),
makeLocation("/a/foo.go", 15, 12),
}
groups, order := groupLocations(locs)
out := formatReferences(groups, order, len(locs), true, true)
if !strings.Contains(out, "Found 3 reference(s):") {
t.Errorf("missing header, got:\n%s", out)
}
// Should contain "foo.go:[12:5, 13:8, 15:12] (3)"
if !strings.Contains(out, "12:5") {
t.Errorf("missing 12:5 in compact output, got:\n%s", out)
}
if !strings.Contains(out, "13:8") {
t.Errorf("missing 13:8 in compact output, got:\n%s", out)
}
if !strings.Contains(out, "15:12") {
t.Errorf("missing 15:12 in compact output, got:\n%s", out)
}
if !strings.Contains(out, "(3)") {
t.Errorf("missing (3) count, got:\n%s", out)
}
// Compact format must NOT use ** markers
if strings.Contains(out, "**") {
t.Errorf("compact format must not use ** markers, got:\n%s", out)
}
// Must not have L prefix
if strings.Contains(out, "L12") {
t.Errorf("compact format must not have L prefix, got:\n%s", out)
}
}
// TestFormatReferencesCompactSameLineCollapse verifies same-line columns collapse.
func TestFormatReferencesCompactSameLineCollapse(t *testing.T) {
locs := []lsp.Location{
makeLocation("/a/foo.go", 12, 5),
makeLocation("/a/foo.go", 12, 8),
makeLocation("/a/foo.go", 15, 3),
}
groups, order := groupLocations(locs)
out := formatReferences(groups, order, len(locs), true, false)
// Line 12 has two refs → should be collapsed: 12:{5,8}
if !strings.Contains(out, "12:{5,8}") {
t.Errorf("same-line refs should collapse to 12:{5,8}, got:\n%s", out)
}
// Line 15 is single → 15:3
if !strings.Contains(out, "15:3") {
t.Errorf("single ref on line 15 should be 15:3, got:\n%s", out)
}
}
// TestFormatReferencesCompactMultiFile verifies compact output across multiple files.
func TestFormatReferencesCompactMultiFile(t *testing.T) {
locs := []lsp.Location{
makeLocation("/a/foo.go", 5, 1),
makeLocation("/b/bar.go", 10, 3),
makeLocation("/b/bar.go", 10, 7),
}
groups, order := groupLocations(locs)
out := formatReferences(groups, order, len(locs), true, true)
if !strings.Contains(out, "Found 3 reference(s):") {
t.Errorf("missing header, got:\n%s", out)
}
// foo.go: one ref
if !strings.Contains(out, "5:1") {
t.Errorf("missing 5:1 for foo.go, got:\n%s", out)
}
// bar.go: two refs on same line → collapsed
if !strings.Contains(out, "10:{3,7}") {
t.Errorf("missing 10:{3,7} collapse for bar.go, got:\n%s", out)
}
}
// TestFormatReferencesCompactSingleRef verifies single-reference compact output.
func TestFormatReferencesCompactSingleRef(t *testing.T) {
locs := []lsp.Location{
makeLocation("/a/only.go", 7, 2),
}
groups, order := groupLocations(locs)
out := formatReferences(groups, order, len(locs), true, false)
if !strings.Contains(out, "7:2") {
t.Errorf("missing 7:2, got:\n%s", out)
}
if !strings.Contains(out, "(1)") {
t.Errorf("missing (1), got:\n%s", out)
}
}
// TestFormatReferencesCompactNoLPrefix verifies the L prefix is absent in compact mode.
func TestFormatReferencesCompactNoLPrefix(t *testing.T) {
locs := []lsp.Location{
makeLocation("/a/x.go", 3, 4),
}
groups, order := groupLocations(locs)
out := formatReferences(groups, order, len(locs), true, false)
if strings.Contains(out, "L3") {
t.Errorf("compact output must not contain L prefix, got:\n%s", out)
}
}
// TestFormatReferencesVerboseNoChange verifies compact=false preserves old L-prefix format.
func TestFormatReferencesVerbosePreservesLPrefix(t *testing.T) {
locs := []lsp.Location{
makeLocation("/a/x.go", 3, 4),
}
groups, order := groupLocations(locs)
out := formatReferences(groups, order, len(locs), false, true)
if !strings.Contains(out, "L3:4") {
t.Errorf("verbose output must contain L3:4, got:\n%s", out)
}
}