🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user