/ internal / tools / mcp_error_hints_test.go
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  }