230 lines
8.8 KiB
TypeScript
230 lines
8.8 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { compressToolResult } from "../../../open-sse/services/compression/toolResultCompressor.ts";
|
|
import type { ToolStrategiesConfig } from "../../../open-sse/services/compression/types.ts";
|
|
|
|
const ALL_ON: ToolStrategiesConfig = {
|
|
fileContent: true,
|
|
grepSearch: true,
|
|
shellOutput: true,
|
|
json: true,
|
|
errorMessage: true,
|
|
};
|
|
|
|
describe("compressToolResult", () => {
|
|
it("returns 'none' strategy for plain prose input", () => {
|
|
const result = compressToolResult("Hello, this is a simple message.", ALL_ON);
|
|
assert.equal(result.strategy, "none");
|
|
assert.equal(result.saved, 0);
|
|
assert.equal(result.compressed, "Hello, this is a simple message.");
|
|
});
|
|
|
|
it("returns 'none' for empty string", () => {
|
|
const result = compressToolResult("", ALL_ON);
|
|
assert.equal(result.strategy, "none");
|
|
});
|
|
|
|
it("compresses file content with code indicators", () => {
|
|
const lines = Array.from({ length: 100 }, (_, i) => `line ${i}`);
|
|
const code = `import x from "y";\nfunction f() {\n${lines.join("\n")}\n}`;
|
|
const result = compressToolResult(code, ALL_ON);
|
|
assert.equal(result.strategy, "fileContent");
|
|
assert.ok(result.saved > 0);
|
|
assert.ok(result.compressed.includes("…") || result.compressed.includes("elided"));
|
|
});
|
|
|
|
it("compresses grep search output", () => {
|
|
const lines = Array.from(
|
|
{ length: 100 },
|
|
(_, i) => `src/file${i % 5}.ts:${i + 1}:10: match found`
|
|
);
|
|
const content = lines.join("\n");
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "grepSearch");
|
|
assert.ok(result.saved > 0);
|
|
assert.ok(result.compressed.includes("… [") || result.compressed.includes("more matches"));
|
|
});
|
|
|
|
it("compresses shell output with ANSI codes", () => {
|
|
const content =
|
|
"\x1b[32mSuccess\x1b[0m\n" +
|
|
Array.from({ length: 60 }, (_, i) => `output line ${i}`).join("\n");
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "shellOutput");
|
|
assert.ok(result.saved > 0);
|
|
assert.ok(!result.compressed.includes("\x1b["));
|
|
});
|
|
|
|
it("compresses shell output with $ prompt", () => {
|
|
const content = "$ ls -la\ntotal 42\nfile1.txt\nfile2.txt";
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "shellOutput");
|
|
});
|
|
|
|
it("compresses large JSON array", () => {
|
|
const arr = Array.from({ length: 100 }, (_, i) => ({
|
|
id: i,
|
|
name: `item${i}`,
|
|
data: "x".repeat(50),
|
|
}));
|
|
const content = JSON.stringify(arr);
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "json");
|
|
assert.ok(result.saved > 0);
|
|
assert.ok(result.compressed.includes("total"));
|
|
});
|
|
|
|
it("compresses large JSON object", () => {
|
|
const obj: Record<string, unknown> = {};
|
|
for (let i = 0; i < 30; i++) {
|
|
obj[`key${i}`] = { nested: "value".repeat(20) };
|
|
}
|
|
const content = JSON.stringify(obj);
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "json");
|
|
assert.ok(result.saved > 0);
|
|
});
|
|
|
|
it("does not compress small JSON", () => {
|
|
const content = JSON.stringify({ a: 1, b: 2 });
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "none");
|
|
});
|
|
|
|
it("compresses error messages with stack trace", () => {
|
|
const frames = Array.from(
|
|
{ length: 20 },
|
|
(_, i) => ` at func${i} (file${i}.ts:${i + 1}:${i + 10})`
|
|
);
|
|
const content = `TypeError: Cannot read property 'x' of undefined\n${frames.join("\n")}`;
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "errorMessage");
|
|
assert.ok(result.saved > 0);
|
|
});
|
|
|
|
it("compresses Python Traceback", () => {
|
|
const content =
|
|
"Traceback (most recent call last):\n File 'app.py', line 42\n x = undefined()\nException: NameError";
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "errorMessage");
|
|
});
|
|
|
|
it("skips strategy when toggle is off", () => {
|
|
const code =
|
|
`import x from "y";\nfunction f() {}\n` +
|
|
Array.from({ length: 50 }, (_, i) => `line ${i}`).join("\n");
|
|
const opts: ToolStrategiesConfig = { ...ALL_ON, fileContent: false };
|
|
const result = compressToolResult(code, opts);
|
|
assert.notEqual(result.strategy, "fileContent");
|
|
});
|
|
|
|
it("skips grep strategy when toggle is off", () => {
|
|
const content = "src/file.ts:10:5: match\nsrc/file.ts:20:8: match";
|
|
const opts: ToolStrategiesConfig = { ...ALL_ON, grepSearch: false };
|
|
const result = compressToolResult(content, opts);
|
|
assert.notEqual(result.strategy, "grepSearch");
|
|
});
|
|
|
|
it("skips shell strategy when toggle is off", () => {
|
|
const content = "\x1b[32moutput\x1b[0m\n$ command";
|
|
const opts: ToolStrategiesConfig = { ...ALL_ON, shellOutput: false };
|
|
const result = compressToolResult(content, opts);
|
|
assert.notEqual(result.strategy, "shellOutput");
|
|
});
|
|
|
|
it("skips json strategy when toggle is off", () => {
|
|
const arr = Array.from({ length: 100 }, (_, i) => ({ id: i }));
|
|
const content = JSON.stringify(arr);
|
|
const opts: ToolStrategiesConfig = { ...ALL_ON, json: false };
|
|
const result = compressToolResult(content, opts);
|
|
assert.notEqual(result.strategy, "json");
|
|
});
|
|
|
|
it("skips error strategy when toggle is off", () => {
|
|
const content = "Error: something went wrong\n at func (file.ts:1:1)";
|
|
const opts: ToolStrategiesConfig = { ...ALL_ON, errorMessage: false };
|
|
const result = compressToolResult(content, opts);
|
|
assert.notEqual(result.strategy, "errorMessage");
|
|
});
|
|
|
|
it("all toggles off returns 'none'", () => {
|
|
const content = "Error: bad\nsrc/file.ts:10:5: match\n$ ls";
|
|
const opts: ToolStrategiesConfig = {
|
|
fileContent: false,
|
|
grepSearch: false,
|
|
shellOutput: false,
|
|
json: false,
|
|
errorMessage: false,
|
|
};
|
|
const result = compressToolResult(content, opts);
|
|
assert.equal(result.strategy, "none");
|
|
assert.equal(result.saved, 0);
|
|
});
|
|
|
|
it("deduplicates consecutive identical lines in shell output", () => {
|
|
const lines = ["$ ls", "file1.txt", "file1.txt", "file1.txt", "file2.txt"];
|
|
const content = lines.join("\n");
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "shellOutput");
|
|
const deduped = result.compressed.split("\n");
|
|
const file1Count = deduped.filter((l) => l === "file1.txt").length;
|
|
assert.ok(file1Count <= 2, `Expected <= 2 'file1.txt' lines, got ${file1Count}`);
|
|
});
|
|
|
|
it("handles malformed JSON gracefully", () => {
|
|
const content = "{invalid json" + "x".repeat(3000);
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.notEqual(result.strategy, "json");
|
|
});
|
|
|
|
it("handles short code without compression", () => {
|
|
const content = "import x from 'y';\nconst z = 1;";
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "none");
|
|
});
|
|
|
|
it("fileContent keeps first 20 and last 5 lines", () => {
|
|
const lines = Array.from({ length: 100 }, (_, i) => `line ${i}`);
|
|
const code = `import x from "y";\nfunction f() {\n${lines.join("\n")}\n}`;
|
|
const result = compressToolResult(code, ALL_ON);
|
|
assert.equal(result.strategy, "fileContent");
|
|
assert.ok(result.compressed.includes("line 0"));
|
|
assert.ok(result.compressed.includes("line 99"));
|
|
});
|
|
|
|
it("grepSearch deduplicates paths", () => {
|
|
const lines = Array.from({ length: 50 }, (_, i) => `src/app.ts:${i + 1}:10: match`);
|
|
const content = lines.join("\n");
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "grepSearch");
|
|
assert.ok(result.compressed.includes("Files:"));
|
|
assert.ok(result.compressed.includes("src/app.ts"));
|
|
});
|
|
|
|
it("errorMessage keeps error type and stack frames", () => {
|
|
const content =
|
|
"Error: TS2304 Cannot find name 'x'\n at func1 (a.ts:1:1)\n at func2 (b.ts:2:2)\n at func3 (c.ts:3:3)";
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.strategy, "errorMessage");
|
|
assert.ok(result.compressed.includes("Error: TS2304"));
|
|
});
|
|
|
|
it("json array compression preserves first 5 and last 2 elements", () => {
|
|
const arr = Array.from({ length: 20 }, (_, i) => ({ id: i, name: `item${i}` }));
|
|
const content = JSON.stringify(arr);
|
|
const padded = content.padEnd(3000, " ");
|
|
const result = compressToolResult(padded, ALL_ON);
|
|
if (result.strategy === "json") {
|
|
assert.ok(result.compressed.includes("total"));
|
|
}
|
|
});
|
|
|
|
it("returns original content unchanged for 'none' strategy", () => {
|
|
const content = "Just some plain text without any patterns.";
|
|
const result = compressToolResult(content, ALL_ON);
|
|
assert.equal(result.compressed, content);
|
|
assert.equal(result.strategy, "none");
|
|
assert.equal(result.saved, 0);
|
|
});
|
|
});
|