219 lines
7.9 KiB
TypeScript
219 lines
7.9 KiB
TypeScript
/**
|
|
* TDD tests for RTK filter learning (R6/R7/N7).
|
|
*
|
|
* Scope: pure functions operating on in-memory CommandSample arrays.
|
|
* No DB reads, no I/O. DB wiring (reading from call_logs) is a follow-up task.
|
|
*/
|
|
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { discoverRepeatedNoise } from "../../../open-sse/services/compression/engines/rtk/discover.ts";
|
|
import { suggestFilter } from "../../../open-sse/services/compression/engines/rtk/learn.ts";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Synthetic test fixture
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Build 10 `npm install` output samples.
|
|
*
|
|
* Each sample has:
|
|
* - A noisy deprecated-warning line whose package name and version vary.
|
|
* - A unique summary line indicating how many packages were added.
|
|
* - An error line with ERR!/E404.
|
|
* - A line that appears only in one sample (truly unique — should NOT be suggested).
|
|
*/
|
|
function makeSamples() {
|
|
const packages = [
|
|
["left-pad", "1.3.0"],
|
|
["moment", "2.29.4"],
|
|
["lodash", "4.17.21"],
|
|
["underscore", "1.13.6"],
|
|
["request", "2.88.2"],
|
|
["express", "4.18.2"],
|
|
["debug", "4.3.4"],
|
|
["chalk", "5.2.0"],
|
|
["semver", "7.5.1"],
|
|
["bluebird", "3.7.2"],
|
|
];
|
|
|
|
return packages.map(([pkg, ver], i) => ({
|
|
command: "npm install",
|
|
output: [
|
|
`npm WARN deprecated ${pkg}@${ver}: Use a newer version`,
|
|
`added ${100 + i * 3} packages in ${i + 1}s`,
|
|
`npm ERR! code E404`,
|
|
`npm ERR! 404 Not Found - GET https://registry.npmjs.org/${pkg}`,
|
|
// One truly unique line appears only in sample index 3
|
|
...(i === 3 ? ["unique line only in sample 3"] : []),
|
|
].join("\n"),
|
|
}));
|
|
}
|
|
|
|
const samples = makeSamples();
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// discoverRepeatedNoise
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("discoverRepeatedNoise", () => {
|
|
it("surfaces the deprecated-warning template as a high-hit candidate", () => {
|
|
const hits = discoverRepeatedNoise(samples);
|
|
// The normalised form of 'npm WARN deprecated <pkg>@<ver>: Use a newer version'
|
|
// should appear with hits === samples.length (10).
|
|
const deprecatedCandidate = hits.find((h) => h.pattern.includes("WARN deprecated"));
|
|
assert.ok(
|
|
deprecatedCandidate !== undefined,
|
|
"should surface a deprecated-warning drop candidate"
|
|
);
|
|
assert.ok(
|
|
deprecatedCandidate.hits >= samples.length,
|
|
`expected hits >= ${samples.length}, got ${deprecatedCandidate.hits}`
|
|
);
|
|
});
|
|
|
|
it("does NOT surface a line that appears only once as a drop candidate", () => {
|
|
const hits = discoverRepeatedNoise(samples);
|
|
const uniqueCandidate = hits.find((h) => h.pattern.includes("unique line only in sample"));
|
|
assert.equal(uniqueCandidate, undefined, "unique lines must not appear as drop candidates");
|
|
});
|
|
|
|
it("returns candidates sorted descending by hits", () => {
|
|
const hits = discoverRepeatedNoise(samples);
|
|
for (let i = 1; i < hits.length; i++) {
|
|
assert.ok(hits[i].hits <= hits[i - 1].hits, "results should be sorted descending by hits");
|
|
}
|
|
});
|
|
|
|
it("returns an empty array for an empty sample set", () => {
|
|
const hits = discoverRepeatedNoise([]);
|
|
assert.deepEqual(hits, []);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// suggestFilter
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("suggestFilter", () => {
|
|
it("returns a filter whose match.commands pattern anchors the command", () => {
|
|
const filter = suggestFilter("npm install", samples);
|
|
assert.ok(filter.match.commands.length > 0, "should have at least one command pattern");
|
|
const pattern = filter.match.commands[0];
|
|
assert.ok(
|
|
new RegExp(pattern).test("npm install"),
|
|
`command pattern '${pattern}' should match 'npm install'`
|
|
);
|
|
});
|
|
|
|
it("includes a dropPattern that matches the deprecated warnings", () => {
|
|
const filter = suggestFilter("npm install", samples);
|
|
const hasDeprecatedDrop = filter.rules.dropPatterns.some((p) => {
|
|
try {
|
|
return new RegExp(p, "i").test("npm WARN deprecated left-pad@1.3.0: Use a newer version");
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
assert.ok(hasDeprecatedDrop, "dropPatterns should cover deprecated warnings");
|
|
});
|
|
|
|
it("includes an errorPattern that covers the ERR!/E404 line", () => {
|
|
const filter = suggestFilter("npm install", samples);
|
|
const errLine = "npm ERR! code E404";
|
|
const hasErrorPattern = filter.preserve.errorPatterns.some((p) => {
|
|
try {
|
|
return new RegExp(p, "i").test(errLine);
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
assert.ok(hasErrorPattern, `errorPatterns should cover '${errLine}'`);
|
|
});
|
|
|
|
it("includes a summaryPattern that covers the 'added N packages' line", () => {
|
|
const filter = suggestFilter("npm install", samples);
|
|
const summaryLine = "added 120 packages in 5s";
|
|
const hasSummaryPattern = filter.preserve.summaryPatterns.some((p) => {
|
|
try {
|
|
return new RegExp(p, "i").test(summaryLine);
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
assert.ok(hasSummaryPattern, `summaryPatterns should cover '${summaryLine}'`);
|
|
});
|
|
|
|
it("no dropPattern matches a preserved error line (no preserve-vs-drop conflict)", () => {
|
|
const filter = suggestFilter("npm install", samples);
|
|
const preservedLines = [
|
|
"npm ERR! code E404",
|
|
"npm ERR! 404 Not Found - GET https://registry.npmjs.org/left-pad",
|
|
"added 120 packages in 5s",
|
|
];
|
|
const allPreservePatterns = [
|
|
...filter.preserve.errorPatterns,
|
|
...filter.preserve.summaryPatterns,
|
|
];
|
|
|
|
for (const line of preservedLines) {
|
|
const matchedByPreserve = allPreservePatterns.some((p) => {
|
|
try {
|
|
return new RegExp(p, "i").test(line);
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
if (!matchedByPreserve) continue; // line not in preserve scope — skip conflict check
|
|
|
|
const droppedByDrop = filter.rules.dropPatterns.some((p) => {
|
|
try {
|
|
return new RegExp(p, "i").test(line);
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
assert.equal(
|
|
droppedByDrop,
|
|
false,
|
|
`preserved line '${line}' must NOT be matched by a dropPattern`
|
|
);
|
|
}
|
|
});
|
|
|
|
it("unique line is NOT in dropPatterns", () => {
|
|
const filter = suggestFilter("npm install", samples);
|
|
const uniqueLine = "unique line only in sample 3";
|
|
const droppedByDrop = filter.rules.dropPatterns.some((p) => {
|
|
try {
|
|
return new RegExp(p, "i").test(uniqueLine);
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
assert.equal(droppedByDrop, false, "unique lines must not appear in dropPatterns");
|
|
});
|
|
|
|
it("returns a filter with the expected RtkFilterPack shape", () => {
|
|
const filter = suggestFilter("npm install", samples);
|
|
// Check required top-level shape fields
|
|
assert.equal(typeof filter.id, "string");
|
|
assert.equal(typeof filter.label, "string");
|
|
assert.ok(filter.id.length > 0);
|
|
assert.ok(Array.isArray(filter.match.commands));
|
|
assert.ok(Array.isArray(filter.match.patterns));
|
|
assert.ok(Array.isArray(filter.match.outputTypes));
|
|
assert.ok(Array.isArray(filter.rules.dropPatterns));
|
|
assert.ok(Array.isArray(filter.preserve.errorPatterns));
|
|
assert.ok(Array.isArray(filter.preserve.summaryPatterns));
|
|
assert.equal(typeof filter.rules.stripAnsi, "boolean");
|
|
});
|
|
|
|
it("returns an empty-patterns filter for an empty sample set without throwing", () => {
|
|
const filter = suggestFilter("unknown-command", []);
|
|
assert.equal(filter.rules.dropPatterns.length, 0);
|
|
assert.ok(Array.isArray(filter.preserve.errorPatterns));
|
|
assert.ok(Array.isArray(filter.preserve.summaryPatterns));
|
|
});
|
|
});
|