Skip to content

Commit 0b11d45

Browse files
committed
test(issue-417): add Fix-Must1b DM fallback regression test
1 parent 6914947 commit 0b11d45

1 file changed

Lines changed: 107 additions & 0 deletions

File tree

test/smart-extractor-branches.mjs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,5 +1404,112 @@ assert.ok(cumulativeResult.smartExtractionTriggered,
14041404
assert.ok(cumulativeResult.smartExtractionSkipped,
14051405
"Turn 1 should skip smart extraction (cumulative=1 < 2). Logs: " +
14061406
cumulativeResult.logs.map((e) => e[1]).join(" | "));
1407+
// ============================================================
1408+
// Test: DM fallback — Fix-Must1b regression
1409+
// Scenario: DM conversation (no pending ingress texts).
1410+
// Smart extraction runs, LLM returns empty.
1411+
// Fix-Must1b: boundarySkipped=0 → early return → NO regex fallback.
1412+
// ============================================================
1413+
1414+
async function runDmFallbackMustfixScenario() {
1415+
const workDir = mkdtempSync(path.join(tmpdir(), "memory-dm-fallback-mustfix-"));
1416+
const dbPath = path.join(workDir, "db");
1417+
const logs = [];
1418+
let llmCalls = 0;
1419+
const embeddingServer = createEmbeddingServer();
1420+
1421+
// LLM mock: ALWAYS returns empty memories.
1422+
// Simulates DM conversation where LLM finds no extractable content.
1423+
const llmServer = http.createServer(async (req, res) => {
1424+
if (req.method !== "POST" || req.url !== "/chat/completions") {
1425+
res.writeHead(404); res.end(); return;
1426+
}
1427+
llmCalls += 1;
1428+
res.writeHead(200, { "Content-Type": "application/json" });
1429+
res.end(JSON.stringify({
1430+
id: "chatcmpl-test", object: "chat.completion",
1431+
created: Math.floor(Date.now() / 1000), model: "mock-memory-model",
1432+
choices: [{ index: 0, message: { role: "assistant",
1433+
content: JSON.stringify({ memories: [] }) }, finish_reason: "stop" }],
1434+
}));
1435+
});
1436+
1437+
await new Promise((resolve) => embeddingServer.listen(0, "127.0.0.1", resolve));
1438+
await new Promise((resolve) => llmServer.listen(0, "127.0.0.1", resolve));
1439+
const embeddingPort = embeddingServer.address().port;
1440+
const llmPort = llmServer.address().port;
1441+
process.env.TEST_EMBEDDING_BASE_URL = `http://127.0.0.1:${embeddingPort}/v1`;
1442+
1443+
try {
1444+
// extractMinMessages=1: first agent_end triggers smart extraction immediately.
1445+
// No message_received: pendingIngressTexts=[] (mimics DM with no conversationId).
1446+
const api = createMockApi(
1447+
dbPath, `http://127.0.0.1:${embeddingPort}/v1`,
1448+
`http://127.0.0.1:${llmPort}`, logs,
1449+
{ extractMinMessages: 1, smartExtraction: true },
1450+
);
1451+
plugin.register(api);
1452+
const sessionKey = "agent:main:discord:dm:user456";
1453+
1454+
await runAgentEndHook(api, {
1455+
success: true,
1456+
// No conversationId: simulates DM without pending ingress texts.
1457+
// sessionKey extracts to "discord:dm:user456" (truthy), but since
1458+
// message_received was never called, pendingIngressTexts Map has no entry.
1459+
messages: [{ role: "user", content: "hi" }, { role: "user", content: "hello?" }],
1460+
}, { agentId: "main", sessionKey });
1461+
1462+
const freshStore = new MemoryStore({ dbPath, vectorDim: EMBEDDING_DIMENSIONS });
1463+
const entries = await freshStore.list(["global", "agent:main"], undefined, 10, 0);
1464+
return { entries, llmCalls, logs };
1465+
} finally {
1466+
delete process.env.TEST_EMBEDDING_BASE_URL;
1467+
await new Promise((resolve) => embeddingServer.close(resolve));
1468+
await new Promise((resolve) => llmServer.close(resolve));
1469+
rmSync(workDir, { recursive: true, force: true });
1470+
}
1471+
}
1472+
1473+
const dmFallbackResult = await runDmFallbackMustfixScenario();
1474+
1475+
// Assert 1: Smart extraction LLM was called exactly once
1476+
assert.equal(dmFallbackResult.llmCalls, 1,
1477+
"Smart extraction should be called once. Logs: " +
1478+
dmFallbackResult.logs.map((e) => e[1]).join(" | "));
1479+
1480+
// Assert 2: No memories stored (regex fallback did NOT capture garbage)
1481+
assert.equal(dmFallbackResult.entries.length, 0,
1482+
"No memories should be stored. Entries: " +
1483+
JSON.stringify(dmFallbackResult.entries.map((e) => e.text)));
1484+
1485+
// Assert 3 (Fix-Must1b core): Early return triggered — skip regex fallback
1486+
assert.ok(
1487+
dmFallbackResult.logs.some((entry) =>
1488+
entry[1].includes("skipping regex fallback")),
1489+
"Fix-Must1b: should log 'skipping regex fallback'. Logs: " +
1490+
dmFallbackResult.logs.map((e) => e[1]).join(" | ")
1491+
);
1492+
1493+
// Assert 4: Regex fallback did NOT run
1494+
assert.ok(
1495+
dmFallbackResult.logs.every((entry) =>
1496+
!entry[1].includes("running regex fallback")),
1497+
"Regex fallback should NOT run. Logs: " +
1498+
dmFallbackResult.logs.map((e) => e[1]).join(" | ")
1499+
);
1500+
1501+
// Assert 5: Smart extractor confirmed no memories extracted
1502+
assert.ok(
1503+
dmFallbackResult.logs.some((entry) =>
1504+
entry[1].includes("no memories extracted")),
1505+
"Smart extractor should report no memories extracted. Logs: " +
1506+
dmFallbackResult.logs.map((e) => e[1]).join(" | ")
1507+
);
1508+
1509+
// ============================================================
1510+
// End: Fix-Must1b regression test
1511+
// ============================================================
1512+
1513+
14071514

14081515
console.log("OK: smart extractor branch regression test passed");

0 commit comments

Comments
 (0)