fix: strengthen plugin regression test with strong error signals

- Use TS2345 error instead of loose 'errors' word
- Add try/finally for temp directory cleanup
- Remove duplicate placeholder test block
- Clarify quality gate placement in parseWorkspaceMemoryCandidates()
This commit is contained in:
Ralph Chang
2026-04-26 12:26:42 +08:00
parent 9f9763c0e1
commit 2d7cb6cdf4
@@ -204,38 +204,44 @@ test("tool.execute.after: undefined exitCode does NOT create open error", async
// 1. Temp directory for isolated file I/O // 1. Temp directory for isolated file I/O
const tmpDir = await mkdtemp(join(tmpdir(), "memory-plugin-test-")); const tmpDir = await mkdtemp(join(tmpdir(), "memory-plugin-test-"));
try {
// 2. Mock client — root session, no user messages // 2. Mock client — root session, no user messages
const client = mockRootClient(); const client = mockRootClient();
// 3. Instantiate plugin // 3. Instantiate plugin
const plugin = await MemoryV2Plugin({ directory: tmpDir, client }); const plugin = await MemoryV2Plugin({ directory: tmpDir, client });
// 4. Simulate bash output with NO exitCode, but output contains "errors" // 4. Simulate bash output with NO exitCode, but output contains TS error
// This would create an open error if exitCode was non-zero
// Using STRONG error signal to catch the bug where undefined !== 0
await (plugin as Record<string, Function>)["tool.execute.after"]( await (plugin as Record<string, Function>)["tool.execute.after"](
{ {
tool: "bash", tool: "bash",
sessionID: "test-session-1", sessionID: "test-session-1",
args: { command: "cd /repo && rtk git log --oneline -5" }, args: { command: "npm run typecheck" },
}, },
{ {
// exitCode deliberately absent // exitCode deliberately absent (undefined !== 0 is the bug we're testing)
output: "4832b38 fix: silence memory load errors in working-memory\nabcd123 feat: add feature", output: "src/index.ts(10,3): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'",
} }
); );
// 5. Assert: session state has ZERO open errors // 5. Assert: session state has ZERO open errors
const state = await loadSessionState(tmpDir, "test-session-1"); const state = await loadSessionState(tmpDir, "test-session-1");
assert.equal(state.openErrors.length, 0, assert.equal(state.openErrors.length, 0,
"exitCode === undefined must not create open errors"); "exitCode === undefined must not create open errors even with strong error signal");
} finally {
// Cleanup // Cleanup
await rm(tmpDir, { recursive: true, force: true }); await rm(tmpDir, { recursive: true, force: true });
}
}); });
test("tool.execute.after: undefined exitCode does NOT clear existing open error", async () => { test("tool.execute.after: undefined exitCode does NOT clear existing open error", async () => {
// 1. Temp directory // 1. Temp directory
const tmpDir = await mkdtemp(join(tmpdir(), "memory-plugin-test-")); const tmpDir = await mkdtemp(join(tmpdir(), "memory-plugin-test-"));
try {
// 2. Pre-populate session state with a real open error // 2. Pre-populate session state with a real open error
const preExistingError: OpenError = { const preExistingError: OpenError = {
id: "err_critical_abc", id: "err_critical_abc",
@@ -258,6 +264,7 @@ test("tool.execute.after: undefined exitCode does NOT clear existing open error"
const plugin = await MemoryV2Plugin({ directory: tmpDir, client }); const plugin = await MemoryV2Plugin({ directory: tmpDir, client });
// 5. Simulate bash output with NO exitCode (inspection command) // 5. Simulate bash output with NO exitCode (inspection command)
// Using STRONG error signal (TS error) to verify undefined exitCode doesn't clear
await (plugin as Record<string, Function>)["tool.execute.after"]( await (plugin as Record<string, Function>)["tool.execute.after"](
{ {
tool: "bash", tool: "bash",
@@ -265,8 +272,9 @@ test("tool.execute.after: undefined exitCode does NOT clear existing open error"
args: { command: "rtk cat ~/.local/share/opencode-working-memory/session.json" }, args: { command: "rtk cat ~/.local/share/opencode-working-memory/session.json" },
}, },
{ {
// exitCode deliberately absent // exitCode deliberately absent (undefined)
output: '"openErrors": [{"id": "err_critical_abc", "status": "open"}]', // Even with TS error in output, should NOT clear existing error
output: "src/other.ts(5,10): error TS2794: Expected 0 arguments, but got 1",
} }
); );
@@ -277,28 +285,10 @@ test("tool.execute.after: undefined exitCode does NOT clear existing open error"
assert.equal(state.openErrors[0].fingerprint, "ee7b3f9a1c2d", assert.equal(state.openErrors[0].fingerprint, "ee7b3f9a1c2d",
"The original open error must remain intact"); "The original open error must remain intact");
} finally {
// Cleanup // Cleanup
await rm(tmpDir, { recursive: true, force: true }); await rm(tmpDir, { recursive: true, force: true });
}); }
```
**新增 `tests/plugin.test.ts`** - Plugin hook regression test
```ts
import test from "node:test";
import assert from "node:assert/strict";
// 這個 test 需要 mock plugin hook 環境
// 確保 exitCode === undefined 不會產生 open error
test("plugin hook with undefined exitCode does not create open error", async () => {
// 模擬 tool.execute.after hook
// hookOutput 沒有 exitCode
// output 包含 "errors" 字樣
// assert session state 沒有新增 open error
// 這是 integration test,需要完整的 plugin test harness
// 目前先用 unit test 覆蓋 extractor,後續補 integration test
}); });
``` ```
@@ -848,13 +838,12 @@ test("extractExplicitMemories captures multiple memories in same message", () =>
#### 檔案 #### 檔案
- `src/extractors.ts` - 新增 `shouldAcceptWorkspaceMemoryCandidate` - `src/extractors.ts` - `parseWorkspaceMemoryCandidates()` 內部套用 `shouldAcceptWorkspaceMemoryCandidate()`
- `src/plugin.ts` - `parseWorkspaceMemoryCandidates` push 前套用 - `tests/extractors.test.ts` - 透過 `parseWorkspaceMemoryCandidates()` 驗證 reject/accept 行為
- `tests/extractors.test.ts` - 新增 quality gate 測試
#### 實作要點 #### 實作要點
**1. 明確的 predicate** **1. 明確的 predicate(放在 extractors.ts 內部)**
```ts ```ts
function shouldAcceptWorkspaceMemoryCandidate(entry: { function shouldAcceptWorkspaceMemoryCandidate(entry: {