Files
16gagent/test/memory-core/search-shadow.test.mjs
2026-06-06 10:40:48 +08:00

144 lines
5.7 KiB
JavaScript

/**
* PR-3: Search Shadow Migration Test
*
* Run: node --test test/memory-core/search-shadow.test.mjs
*
* Tests the shadow comparison tool:
* 1. Shadow tool is runnable
* 2. All 22 queries complete
* 3. Shape compatibility reported for each
* 4. Empty query handled gracefully
* 5. Production check still passes
*/
import { describe, it, before } from "node:test";
import assert from "node:assert";
import { join, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const __dirname = join(fileURLToPath(import.meta.url), "..", "..", "..");
const ROOT = resolve(__dirname);
const SHADOW_TOOL = join(ROOT, "scripts", "memory-search-shadow.mjs");
describe("PR-3: Search Shadow Migration Test", () => {
let shadowResult;
before(async () => {
// Run shadow tool with --json for structured output
const result = spawnSync("node", [SHADOW_TOOL, "--json"], {
cwd: ROOT,
encoding: "utf8",
timeout: 60000,
maxBuffer: 2 * 1024 * 1024,
});
const output = result.stdout + result.stderr;
// Extract JSON from output (may have other text)
const jsonMatch = output.match(/\{[\s\S]*"results"[\s\S]*\}/);
if (jsonMatch) {
try {
shadowResult = JSON.parse(jsonMatch[0]);
} catch {
// Fall back to running without --json and parsing text
shadowResult = null;
}
}
console.log(output.slice(-500));
});
// ─── Test 1 ──────────────────────────────────────────
it("shadow tool is runnable and produces results", () => {
assert.ok(shadowResult, "Shadow tool should produce JSON output");
assert.ok(typeof shadowResult.totalQueries === "number", "Should have totalQueries");
assert.ok(shadowResult.totalQueries >= 20, `Expected >=20 queries, got ${shadowResult.totalQueries}`);
});
// ─── Test 2 ──────────────────────────────────────────
it("all queries complete without crash", () => {
assert.ok(Array.isArray(shadowResult.results), "results should be array");
assert.equal(shadowResult.results.length, shadowResult.totalQueries,
"Results count should match total queries");
});
// ─── Test 3 ──────────────────────────────────────────
it("each query reports shapeCompatible", () => {
for (const r of shadowResult.results) {
assert.ok(typeof r.shapeCompatible === "boolean",
`shapeCompatible should be boolean for query "${r.query}"`);
}
});
// ─── Test 4 ──────────────────────────────────────────
it("empty query handled gracefully (documented as known difference)", () => {
const emptyResult = shadowResult.results.find(r => r.query === "");
assert.ok(emptyResult, "Should have empty query test case");
// Empty query: legacy FTS5 returns all, facade rejects
// This is a KNOWN difference — facade is more correct
assert.equal(emptyResult.legacyOk, true,
"Legacy treats empty as match-all (known quirk)");
assert.equal(emptyResult.facadeOk, false,
"Facade rejects empty query (correct behavior)");
// shapeCompatible may be false here — that's OK, documented
});
// ─── Test 5 ──────────────────────────────────────────
it("high shape compatibility rate (>90%)", () => {
const rate = shadowResult.passed / shadowResult.totalQueries;
assert.ok(rate >= 0.9,
`Shape compatibility rate ${Math.round(rate*100)}% below 90% threshold`);
});
// ─── Test 6 ──────────────────────────────────────────
it("results have required fields", () => {
const requiredFields = [
"query", "legacyOk", "facadeOk", "legacyCount", "facadeCount",
"topOverlap", "shapeCompatible", "latencyLegacyMs", "latencyFacadeMs",
];
for (const r of shadowResult.results) {
for (const field of requiredFields) {
assert.ok(field in r, `Missing field "${field}" in result for "${r.query}"`);
}
}
});
// ─── Test 7 ──────────────────────────────────────────
it("production check still passes after shadow", { timeout: 180000 }, () => {
const result = spawnSync("bash", [join(ROOT, "scripts", "production-check.sh")], {
cwd: ROOT,
encoding: "utf8",
timeout: 170000,
maxBuffer: 2 * 1024 * 1024,
});
const output = result.stdout + result.stderr;
assert.equal(result.status, 0,
`Production check failed (exit ${result.status})`);
assert.ok(output.includes("PRODUCTION CHECK PASSED"),
"Production check should report PASSED");
});
// ─── Test 8 ──────────────────────────────────────────
it("shadow compare does not modify any memory", async () => {
const { readFileSync } = await import("node:fs");
const memContent = readFileSync(join(ROOT, "MEMORY.md"), "utf8");
// Re-run shadow tool
spawnSync("node", [SHADOW_TOOL], {
cwd: ROOT,
encoding: "utf8",
timeout: 60000,
});
// Verify MEMORY.md unchanged
const memAfter = readFileSync(join(ROOT, "MEMORY.md"), "utf8");
assert.equal(memAfter, memContent, "MEMORY.md must not be modified by shadow");
});
});