|
9 | 9 | } from "node:fs"; |
10 | 10 | import { tmpdir } from "node:os"; |
11 | 11 | import { join } from "node:path"; |
12 | | -import { CompactClient } from "@morphllm/morphsdk"; |
| 12 | +import { CompactClient, WarpGrepClient } from "@morphllm/morphsdk"; |
13 | 13 |
|
14 | 14 | // These are internal to the plugin but duplicated here for testing. |
15 | 15 | const EXISTING_CODE_MARKER = "// ... existing code ..."; |
@@ -991,3 +991,110 @@ describe("ToolContext path resolution", () => { |
991 | 991 | } |
992 | 992 | }); |
993 | 993 | }); |
| 994 | + |
| 995 | +// --------------------------------------------------------------------------- |
| 996 | +// WarpGrep malformed Windows result detection (Issue #7) |
| 997 | +// --------------------------------------------------------------------------- |
| 998 | + |
| 999 | +describe("warpgrep_codebase_search malformed Windows results", () => { |
| 1000 | + /** |
| 1001 | + * Helper: patch WarpGrepClient.prototype.execute to return a fake result, |
| 1002 | + * import the plugin fresh, call the tool, then restore the original. |
| 1003 | + */ |
| 1004 | + async function executeSearchWithMockedResult(fakeResult: unknown): Promise<string> { |
| 1005 | + const original = WarpGrepClient.prototype.execute; |
| 1006 | + // The plugin calls warpGrep!.execute() which is an async generator. |
| 1007 | + // We mock it to yield nothing and return the fakeResult. |
| 1008 | + WarpGrepClient.prototype.execute = function* () { |
| 1009 | + return fakeResult; |
| 1010 | + } as any; |
| 1011 | + |
| 1012 | + try { |
| 1013 | + const { default: MorphPlugin } = await importPluginWithEnv({ |
| 1014 | + MORPH_API_KEY: "sk-test-key", |
| 1015 | + }); |
| 1016 | + const hooks = await MorphPlugin(makePluginInput("/tmp/morph-warpgrep-test")); |
| 1017 | + const result = await hooks.tool.warpgrep_codebase_search.execute( |
| 1018 | + { search_term: "test query" }, |
| 1019 | + makeToolContext("/tmp/morph-warpgrep-test"), |
| 1020 | + ); |
| 1021 | + return result as string; |
| 1022 | + } finally { |
| 1023 | + WarpGrepClient.prototype.execute = original; |
| 1024 | + } |
| 1025 | + } |
| 1026 | + |
| 1027 | + test("malformed result with file:'C' returns actionable Windows error", async () => { |
| 1028 | + const result = await executeSearchWithMockedResult({ |
| 1029 | + success: true, |
| 1030 | + contexts: [ |
| 1031 | + { file: "C", content: "", lines: "*" }, |
| 1032 | + ], |
| 1033 | + }); |
| 1034 | + |
| 1035 | + expect(result).toContain("malformed file contexts on Windows"); |
| 1036 | + expect(result).toContain("`C`"); |
| 1037 | + expect(result).toContain("upstream SDK"); |
| 1038 | + expect(result).toContain("grep"); |
| 1039 | + expect(result).toContain("read"); |
| 1040 | + }); |
| 1041 | + |
| 1042 | + test("multiple malformed contexts still triggers error", async () => { |
| 1043 | + const result = await executeSearchWithMockedResult({ |
| 1044 | + success: true, |
| 1045 | + contexts: [ |
| 1046 | + { file: "C", content: "", lines: "*" }, |
| 1047 | + { file: "D", content: "", lines: "*" }, |
| 1048 | + ], |
| 1049 | + }); |
| 1050 | + |
| 1051 | + expect(result).toContain("malformed file contexts on Windows"); |
| 1052 | + }); |
| 1053 | + |
| 1054 | + test("missing SDK error string does not produce 'Search failed: undefined'", async () => { |
| 1055 | + const result = await executeSearchWithMockedResult({ |
| 1056 | + success: false, |
| 1057 | + error: undefined, |
| 1058 | + }); |
| 1059 | + |
| 1060 | + expect(result).not.toContain("undefined"); |
| 1061 | + expect(result).toContain("Search failed"); |
| 1062 | + expect(result).toContain("no error details"); |
| 1063 | + }); |
| 1064 | + |
| 1065 | + test("explicit SDK error string is preserved", async () => { |
| 1066 | + const result = await executeSearchWithMockedResult({ |
| 1067 | + success: false, |
| 1068 | + error: "timeout after 60s", |
| 1069 | + }); |
| 1070 | + |
| 1071 | + expect(result).toContain("Search failed: timeout after 60s"); |
| 1072 | + }); |
| 1073 | + |
| 1074 | + test("valid search results still format normally", async () => { |
| 1075 | + const result = await executeSearchWithMockedResult({ |
| 1076 | + success: true, |
| 1077 | + contexts: [ |
| 1078 | + { |
| 1079 | + file: "src/auth.ts", |
| 1080 | + content: "export function login() { return true; }", |
| 1081 | + lines: [[1, 5]] as Array<[number, number]>, |
| 1082 | + }, |
| 1083 | + ], |
| 1084 | + }); |
| 1085 | + |
| 1086 | + expect(result).toContain("Relevant context found:"); |
| 1087 | + expect(result).toContain("src/auth.ts"); |
| 1088 | + expect(result).toContain("export function login()"); |
| 1089 | + expect(result).not.toContain("malformed"); |
| 1090 | + }); |
| 1091 | + |
| 1092 | + test("empty contexts returns 'no relevant code' message", async () => { |
| 1093 | + const result = await executeSearchWithMockedResult({ |
| 1094 | + success: true, |
| 1095 | + contexts: [], |
| 1096 | + }); |
| 1097 | + |
| 1098 | + expect(result).toContain("No relevant code found"); |
| 1099 | + }); |
| 1100 | +}); |
0 commit comments