@@ -260,6 +260,103 @@ func TestFilesystemTool_EditFile(t *testing.T) {
260260 assert .Contains (t , result .Output , "old text not found" )
261261}
262262
263+ func TestEditFileArgs_UnmarshalJSON (t * testing.T ) {
264+ t .Parallel ()
265+
266+ tests := []struct {
267+ name string
268+ input string
269+ wantPath string
270+ wantEdits []Edit
271+ wantErr bool
272+ wantErrMsg string
273+ }{
274+ {
275+ name : "normal array edits" ,
276+ input : `{"path": "test.txt", "edits": [{"oldText": "hello", "newText": "world"}]}` ,
277+ wantPath : "test.txt" ,
278+ wantEdits : []Edit {
279+ {OldText : "hello" , NewText : "world" },
280+ },
281+ },
282+ {
283+ name : "double-serialized string edits" ,
284+ input : `{"path": "test.txt", "edits": "[{\"oldText\": \"hello\", \"newText\": \"world\"}]"}` ,
285+ wantPath : "test.txt" ,
286+ wantEdits : []Edit {
287+ {OldText : "hello" , NewText : "world" },
288+ },
289+ },
290+ {
291+ name : "double-serialized multiple edits" ,
292+ input : `{"path": "f.go", "edits": "[{\"oldText\": \"a\", \"newText\": \"b\"}, {\"oldText\": \"c\", \"newText\": \"d\"}]"}` ,
293+ wantPath : "f.go" ,
294+ wantEdits : []Edit {
295+ {OldText : "a" , NewText : "b" },
296+ {OldText : "c" , NewText : "d" },
297+ },
298+ },
299+ {
300+ name : "invalid JSON" ,
301+ input : `not json at all` ,
302+ wantErr : true ,
303+ wantErrMsg : "invalid character" ,
304+ },
305+ {
306+ name : "edits is neither array nor string" ,
307+ input : `{"path": "test.txt", "edits": 42}` ,
308+ wantErr : true ,
309+ wantErrMsg : "edits field is neither an array nor a JSON string" ,
310+ },
311+ {
312+ name : "double-serialized but inner JSON is invalid" ,
313+ input : `{"path": "test.txt", "edits": "not valid json"}` ,
314+ wantErr : true ,
315+ wantErrMsg : "failed to parse double-serialized edits string" ,
316+ },
317+ {
318+ name : "missing edits field (partial/streaming args)" ,
319+ input : `{"path": "/tmp/test.txt"}` ,
320+ wantPath : "/tmp/test.txt" ,
321+ },
322+ {
323+ name : "null edits field" ,
324+ input : `{"path": "test.txt", "edits": null}` ,
325+ wantPath : "test.txt" ,
326+ },
327+ {
328+ name : "missing path with double-serialized edits" ,
329+ input : `{"edits": "[{\"oldText\": \"a\", \"newText\": \"b\"}]"}` ,
330+ wantEdits : []Edit {
331+ {OldText : "a" , NewText : "b" },
332+ },
333+ },
334+ {
335+ name : "missing path with normal array edits" ,
336+ input : `{"edits": [{"oldText": "a", "newText": "b"}]}` ,
337+ wantEdits : []Edit {
338+ {OldText : "a" , NewText : "b" },
339+ },
340+ },
341+ }
342+
343+ for _ , tc := range tests {
344+ t .Run (tc .name , func (t * testing.T ) {
345+ t .Parallel ()
346+ var args EditFileArgs
347+ err := json .Unmarshal ([]byte (tc .input ), & args )
348+ if tc .wantErr {
349+ require .Error (t , err )
350+ assert .Contains (t , err .Error (), tc .wantErrMsg )
351+ return
352+ }
353+ require .NoError (t , err )
354+ assert .Equal (t , tc .wantPath , args .Path )
355+ assert .Equal (t , tc .wantEdits , args .Edits )
356+ })
357+ }
358+ }
359+
263360func TestFilesystemTool_SearchFilesContent (t * testing.T ) {
264361 t .Parallel ()
265362 tmpDir := t .TempDir ()
0 commit comments