mcp_error_hints_test.go
1 package tools 2 3 import ( 4 "strings" 5 "testing" 6 ) 7 8 func TestNormalizeMCPResult_ModalMissingHintPrefixesError(t *testing.T) { 9 raw := `### Error 10 Error: The tool "browser_file_upload" can only be used when there is related modal state present.` 11 got := normalizeMCPResult("playwright", "browser_file_upload", raw, true) 12 if !strings.HasPrefix(got, "[hint]") { 13 t.Errorf("hint must come FIRST so the model reads it before the raw error; got: %q", got) 14 } 15 if !strings.Contains(got, raw) { 16 t.Error("original error text must still be present after the hint") 17 } 18 if !strings.Contains(got, "STOP calling browser_file_upload") { 19 t.Error("hint should imperatively tell the model to stop retrying") 20 } 21 if strings.Contains(got, "previous upload already succeeded") { 22 t.Error("hint should not over-assert that a previous upload succeeded") 23 } 24 if !strings.Contains(got, "browser_snapshot") { 25 t.Error("hint should point to browser_snapshot for verification") 26 } 27 if !strings.Contains(got, "reopen the chooser") { 28 t.Error("hint should tell the model how to recover if the upload is not present") 29 } 30 } 31 32 func TestNormalizeMCPResult_AllowedRootsHintPrefixesError(t *testing.T) { 33 raw := `### Error 34 Error: File access denied: /tmp/foo.png is outside allowed roots. Allowed roots: /Users/x/Desktop` 35 got := normalizeMCPResult("playwright", "browser_file_upload", raw, true) 36 if !strings.HasPrefix(got, "[hint]") { 37 t.Errorf("hint must come FIRST; got: %q", got) 38 } 39 if !strings.Contains(got, "~/.shannon/tmp/attachments") { 40 t.Error("hint should reference the attachments staging directory") 41 } 42 if !strings.Contains(got, raw) { 43 t.Error("original error must still be present") 44 } 45 } 46 47 func TestNormalizeMCPResult_SuccessAppendsCompletionHint(t *testing.T) { 48 raw := `### Ran Playwright code 49 ` + "```js\nawait fileChooser.setFiles([\"/path/to/file.png\"])\n```\n" 50 got := normalizeMCPResult("playwright", "browser_file_upload", raw, false) 51 if !strings.Contains(got, raw) { 52 t.Error("original success output must be preserved") 53 } 54 if !strings.Contains(got, "attached to the composer") { 55 t.Error("success hint should state the file is attached") 56 } 57 if !strings.Contains(got, "DO NOT call browser_file_upload again") { 58 t.Error("success hint should discourage redundant retry") 59 } 60 if !strings.Contains(got, "browser_type") { 61 t.Error("success hint should direct to the next step (typing)") 62 } 63 } 64 65 func TestNormalizeMCPResult_SuccessNoSetFilesPassesThrough(t *testing.T) { 66 raw := "some unrelated success output" 67 got := normalizeMCPResult("playwright", "browser_file_upload", raw, false) 68 if got != raw { 69 t.Errorf("no completion hint if setFiles marker absent; got %q", got) 70 } 71 } 72 73 func TestNormalizeMCPResult_UnknownErrorPassesThrough(t *testing.T) { 74 raw := "some unexpected error text" 75 got := normalizeMCPResult("playwright", "browser_file_upload", raw, true) 76 if got != raw { 77 t.Errorf("unrecognized error must pass through unchanged; got %q", got) 78 } 79 } 80 81 func TestNormalizeMCPResult_OtherToolsUnaffected(t *testing.T) { 82 raw := `Error: related modal state present` 83 got := normalizeMCPResult("playwright", "browser_click", raw, true) 84 if got != raw { 85 t.Errorf("hint should not fire for non-upload tools; got %q", got) 86 } 87 successRaw := "await fileChooser.setFiles([])" 88 got = normalizeMCPResult("playwright", "browser_click", successRaw, false) 89 if got != successRaw { 90 t.Errorf("success hint should not fire for non-upload tools; got %q", got) 91 } 92 } 93 94 func TestNormalizeMCPResult_EmptyContentPassesThrough(t *testing.T) { 95 if got := normalizeMCPResult("playwright", "browser_file_upload", "", true); got != "" { 96 t.Errorf("empty content must pass through; got %q", got) 97 } 98 if got := normalizeMCPResult("playwright", "browser_file_upload", "", false); got != "" { 99 t.Errorf("empty content must pass through on success; got %q", got) 100 } 101 }