Files
zzet--gortex/internal/mcp/batch_edit_crlf_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

244 lines
7.7 KiB
Go

package mcp
import (
"encoding/json"
"os"
"path/filepath"
"testing"
mcplib "github.com/mark3labs/mcp-go/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func decodeBatchEditResults(t *testing.T, res *mcplib.CallToolResult) []map[string]any {
t.Helper()
require.NotEmpty(t, res.Content)
var resp map[string]any
require.NoError(t, json.Unmarshal([]byte(res.Content[0].(mcplib.TextContent).Text), &resp))
raw, ok := resp["results"].([]any)
require.True(t, ok, "results array expected: %v", resp)
out := make([]map[string]any, len(raw))
for i, r := range raw {
out[i] = r.(map[string]any)
}
return out
}
func TestBatchEdit_FileOpCRLF(t *testing.T) {
srv, dir := setupTestServer(t)
target := filepath.Join(dir, "win.md")
require.NoError(t, os.WriteFile(target,
[]byte("alpha\r\nbeta\r\ngamma\r\n"), 0o644))
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_file",
"path": target,
"old_string": "beta\ngamma",
"new_string": "BETA\nGAMMA",
},
}})
require.False(t, res.IsError, "%v", res.Content)
results := decodeBatchEditResults(t, res)
require.Len(t, results, 1)
assert.Equal(t, "applied", results[0]["status"])
assert.Equal(t, true, results[0]["eol_normalized"])
got, err := os.ReadFile(target)
require.NoError(t, err)
assert.Equal(t, "alpha\r\nBETA\r\nGAMMA\r\n", string(got))
assertUniformCRLF(t, string(got))
}
func TestBatchEdit_FileOpCRLFReplaceAll(t *testing.T) {
srv, dir := setupTestServer(t)
target := filepath.Join(dir, "win2.md")
require.NoError(t, os.WriteFile(target,
[]byte("TODO x\r\nkeep\r\nTODO x\r\n"), 0o644))
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_file",
"path": target,
"old_string": "TODO x\n",
"new_string": "DONE x\n",
"replace_all": true,
},
}})
require.False(t, res.IsError, "%v", res.Content)
results := decodeBatchEditResults(t, res)
require.Len(t, results, 1)
assert.Equal(t, "applied", results[0]["status"])
got, err := os.ReadFile(target)
require.NoError(t, err)
assert.Equal(t, "DONE x\r\nkeep\r\nDONE x\r\n", string(got))
assertUniformCRLF(t, string(got))
}
func TestBatchEdit_FileOpCRLFAmbiguousRejected(t *testing.T) {
srv, dir := setupTestServer(t)
target := filepath.Join(dir, "win3.md")
original := "TODO x\r\nkeep\r\nTODO x\r\n"
require.NoError(t, os.WriteFile(target, []byte(original), 0o644))
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_file",
"path": target,
"old_string": "TODO x\n",
"new_string": "DONE x\n",
},
}})
results := decodeBatchEditResults(t, res)
require.Len(t, results, 1)
assert.Equal(t, "failed", results[0]["status"])
assert.Contains(t, results[0]["error"], "matches 2 locations")
assert.Contains(t, results[0]["error"], "first match lines 1, 3")
got, err := os.ReadFile(target)
require.NoError(t, err)
assert.Equal(t, original, string(got), "file must be untouched")
}
func TestBatchEdit_SymbolOpCRLF(t *testing.T) {
srv, dir := setupTestServer(t)
path := writeCRLFGoFixture(t, srv, dir)
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_symbol",
"id": "crlf.go::CrlfTarget",
"old_source": "\ta := 1\n\tb := 2",
"new_source": "\ta := 10\n\tb := 20",
},
}})
require.False(t, res.IsError, "%v", res.Content)
results := decodeBatchEditResults(t, res)
require.Len(t, results, 1)
assert.Equal(t, "applied", results[0]["status"])
assert.Equal(t, true, results[0]["eol_normalized"])
got, err := os.ReadFile(path)
require.NoError(t, err)
assert.Contains(t, string(got), "\ta := 10\r\n\tb := 20\r\n",
"new_source must be written with CRLF endings")
assert.Contains(t, string(got), "func crlfSentinel() {\r\n\tc := 3\r\n",
"bytes after the edited symbol must be untouched — offset arithmetic")
assertUniformCRLF(t, string(got))
}
func TestBatchEdit_SymbolOpCRLFExactStillPreferred(t *testing.T) {
srv, dir := setupTestServer(t)
path := writeCRLFGoFixture(t, srv, dir)
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_symbol",
"id": "crlf.go::CrlfTarget",
"old_source": "\ta := 1\r\n\tb := 2",
"new_source": "\ta := 10\r\n\tb := 20",
},
}})
require.False(t, res.IsError, "%v", res.Content)
results := decodeBatchEditResults(t, res)
require.Len(t, results, 1)
assert.Equal(t, "applied", results[0]["status"])
assert.Nil(t, results[0]["eol_normalized"], "byte-exact match must not report eol_normalized")
got, err := os.ReadFile(path)
require.NoError(t, err)
assert.Contains(t, string(got), "\ta := 10\r\n\tb := 20\r\n")
}
func TestBatchEdit_SymbolOpCRLFDocCommentIncludedInFragment(t *testing.T) {
srv, dir := setupTestServer(t)
path := writeCRLFGoFixture(t, srv, dir)
// Parity with edit_symbol: the batch op must also expand the search
// window over the preceding doc comment, EOL-tolerantly.
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_symbol",
"id": "crlf.go::CrlfTarget",
"old_source": "// CrlfTarget exercises CRLF-file symbol editing.\nfunc CrlfTarget() {",
"new_source": "// CrlfTarget exercises CRLF-file symbol editing (edited).\nfunc CrlfTarget() {",
},
}})
require.False(t, res.IsError, "%v", res.Content)
results := decodeBatchEditResults(t, res)
require.Len(t, results, 1)
assert.Equal(t, "applied", results[0]["status"], "error: %v", results[0]["error"])
got, err := os.ReadFile(path)
require.NoError(t, err)
assert.Contains(t, string(got),
"// CrlfTarget exercises CRLF-file symbol editing (edited).\r\nfunc CrlfTarget() {\r\n")
assertUniformCRLF(t, string(got))
}
func TestBatchEdit_SymbolOpIdenticalSourcesRejected(t *testing.T) {
srv, dir := setupTestServer(t)
path := writeCRLFGoFixture(t, srv, dir)
before, err := os.ReadFile(path)
require.NoError(t, err)
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_symbol",
"id": "crlf.go::CrlfTarget",
"old_source": "\ta := 1\r\n\tb := 2",
"new_source": "\ta := 1\r\n\tb := 2",
},
}})
results := decodeBatchEditResults(t, res)
require.Len(t, results, 1)
assert.Equal(t, "failed", results[0]["status"])
assert.Contains(t, results[0]["error"], "identical")
after, err := os.ReadFile(path)
require.NoError(t, err)
assert.Equal(t, string(before), string(after), "no silent no-op write")
}
func TestBatchEdit_MixedOpsCRLF(t *testing.T) {
srv, dir := setupTestServer(t)
goPath := writeCRLFGoFixture(t, srv, dir)
mdPath := filepath.Join(dir, "notes.md")
require.NoError(t, os.WriteFile(mdPath,
[]byte("intro\r\nbody text\r\n"), 0o644))
res := callBatchEdit(t, srv, map[string]any{"edits": []any{
map[string]any{
"op": "edit_symbol",
"id": "crlf.go::CrlfTarget",
"old_source": "\t_ = a\n\t_ = b",
"new_source": "\t_, _ = a, b",
},
map[string]any{
"op": "edit_file",
"path": mdPath,
"old_string": "intro\nbody",
"new_string": "INTRO\nBODY",
},
}})
require.False(t, res.IsError, "%v", res.Content)
var resp map[string]any
require.NoError(t, json.Unmarshal([]byte(res.Content[0].(mcplib.TextContent).Text), &resp))
summary := resp["summary"].(map[string]any)
require.EqualValues(t, 2, summary["applied"])
require.EqualValues(t, 0, summary["failed"])
gotGo, err := os.ReadFile(goPath)
require.NoError(t, err)
assert.Contains(t, string(gotGo), "\t_, _ = a, b\r\n")
assertUniformCRLF(t, string(gotGo))
gotMd, err := os.ReadFile(mdPath)
require.NoError(t, err)
assert.Equal(t, "INTRO\r\nBODY text\r\n", string(gotMd))
assertUniformCRLF(t, string(gotMd))
}