* fix(docker): add system ripgrep via apt for keyword search fallback * fix(ci): install ripgrep and npm deps for keyword search in CI * fix(memory-search): warn when ripgrep not resolvable instead of silent skip * fix(memory-doctor): check rg status, arch mismatch, and require errors * test(memory): add keyword search and doctor unit tests * test(memory): unskip e2e keyword tests without RUN_LIVE * docs(handoff): add handoff and ADR for keyword search fix * test(memory): remove unused var in keyword search test * docs(handoff): set PR number to 124 * docs(project-map): update after structural changes (PR#124) --------- Co-authored-by: opencode-agent <agent@opencode.local>
169 lines
No EOL
6.4 KiB
TypeScript
169 lines
No EOL
6.4 KiB
TypeScript
/**
|
|
* Tests for memory-doctor.ts diagnostic gaps.
|
|
*
|
|
* memory-doctor runs ripgrep --version and reports status. This file covers
|
|
* the four gaps fixed in PR for issue #123:
|
|
* G9 — allGreen must check rg --version status===0, not just rgBin!==null
|
|
* G2 — arch-mismatch detection when binary platform != runtime platform
|
|
* G3 — npm require-error surfaced in report when npm package missing
|
|
*
|
|
* Runtime note: opencode ships a standalone binary with Bun bundled inside;
|
|
* there is no separate `bun` CLI on the host (CI runner uses node + pytest).
|
|
* This file documents the intended TS-side test cases and is runnable under
|
|
* `bun test` once a bun runtime is available on the host.
|
|
*
|
|
* Test cases:
|
|
* - test_allGreen_false_on_broken_binary — rgPath exists but --version
|
|
* fails → report says NOT all green (G9)
|
|
* - test_arch_mismatch_detection — rgBin path contains 'darwin' but
|
|
* process.platform === 'linux' → report mentions arch mismatch (G2)
|
|
* - test_npm_missing_hint_in_report — rgBin===null, no system rg → report
|
|
* includes npm ci hint (G3)
|
|
* - test_allGreen_true_when_rg_works — rg --version succeeds → keyword
|
|
* works (sanity, G9 regression guard)
|
|
*/
|
|
|
|
import { describe, test, expect, mock } from "bun:test" with { type: "'bun-test'" }
|
|
import fs from "fs"
|
|
import os from "os"
|
|
import path from "path"
|
|
|
|
const DOCTOR_SRC = path.resolve(
|
|
import.meta.dir,
|
|
"..",
|
|
".opencode",
|
|
"tools",
|
|
"memory-doctor.ts"
|
|
)
|
|
|
|
function ctx(worktree = ".") {
|
|
return {
|
|
sessionID: "t",
|
|
messageID: "t",
|
|
agent: "t",
|
|
directory: ".",
|
|
worktree,
|
|
abort: new AbortController().signal,
|
|
metadata() {},
|
|
async ask() {},
|
|
}
|
|
}
|
|
|
|
describe("memory-doctor allGreen (G9)", () => {
|
|
test("test_allGreen_false_on_broken_binary — rgPath exists but --version fails → NOT all green", async () => {
|
|
const fakeRgPath = "/tmp/opencode/broken-rg"
|
|
mock.module("node:module", () => ({
|
|
createRequire: () => () => ({ rgPath: fakeRgPath }),
|
|
}))
|
|
mock.module("fs", () => ({
|
|
...fs,
|
|
existsSync: (p: string) =>
|
|
p === fakeRgPath || p === os.homedir() + "/.local/share/opencode/opencode-memory",
|
|
readdirSync: () => [],
|
|
statSync: () => ({ size: 0, mtime: new Date() }),
|
|
}))
|
|
mock.module("child_process", () => ({
|
|
spawnSync: (cmd: string, args: string[]) => {
|
|
if (cmd === fakeRgPath && args[0] === "--version") {
|
|
return { status: 1, stdout: "", stderr: "cannot execute binary" }
|
|
}
|
|
if (cmd === "python3") return { status: 0, stdout: "ok\n", stderr: "" }
|
|
if (cmd === "rg") return { status: 1, stdout: "", stderr: "" }
|
|
return { status: 0, stdout: "", stderr: "" }
|
|
},
|
|
}))
|
|
const mod = await import(DOCTOR_SRC + "?t=" + Date.now())
|
|
const report = await mod.default.execute({}, ctx())
|
|
expect(report).toContain("ripgrep missing or broken")
|
|
})
|
|
|
|
test("test_allGreen_true_when_rg_works — rg --version succeeds → keyword works", async () => {
|
|
const fakeRgPath = "/tmp/opencode/good-rg"
|
|
mock.module("node:module", () => ({
|
|
createRequire: () => () => ({ rgPath: fakeRgPath }),
|
|
}))
|
|
mock.module("fs", () => ({
|
|
...fs,
|
|
existsSync: () => true,
|
|
readdirSync: () => [],
|
|
statSync: () => ({ size: 0, mtime: new Date() }),
|
|
}))
|
|
mock.module("child_process", () => ({
|
|
spawnSync: (cmd: string, args: string[]) => {
|
|
if (cmd === fakeRgPath && args[0] === "--version") {
|
|
return { status: 0, stdout: "ripgrep 13.0.0\n", stderr: "" }
|
|
}
|
|
if (cmd === "python3") return { status: 0, stdout: "ok\n", stderr: "" }
|
|
return { status: 0, stdout: "", stderr: "" }
|
|
},
|
|
}))
|
|
const mod = await import(DOCTOR_SRC + "?t=" + Date.now())
|
|
const report = await mod.default.execute({}, ctx())
|
|
expect(report).toContain("All green")
|
|
})
|
|
})
|
|
|
|
describe("memory-doctor arch mismatch (G2)", () => {
|
|
test("test_arch_mismatch_detection — darwin binary on linux → report mentions arch mismatch", async () => {
|
|
// @vscode/ripgrep platform dir names include the OS: e.g.
|
|
// .../@vscode/ripgrep/bin/rg-darwin-arm64 or .../rg-linux-x64.
|
|
const fakeRgPath = "/tmp/opencode/@vscode/ripgrep/bin/rg-darwin-arm64"
|
|
const origPlatform = process.platform
|
|
Object.defineProperty(process, "platform", { value: "linux" })
|
|
try {
|
|
mock.module("node:module", () => ({
|
|
createRequire: () => () => ({ rgPath: fakeRgPath }),
|
|
}))
|
|
mock.module("fs", () => ({
|
|
...fs,
|
|
existsSync: () => true,
|
|
readdirSync: () => [],
|
|
statSync: () => ({ size: 0, mtime: new Date() }),
|
|
}))
|
|
mock.module("child_process", () => ({
|
|
spawnSync: (cmd: string, args: string[]) => {
|
|
if (cmd === fakeRgPath && args[0] === "--version") {
|
|
return { status: 1, stdout: "", stderr: "Exec format error" }
|
|
}
|
|
if (cmd === "python3") return { status: 0, stdout: "ok\n", stderr: "" }
|
|
return { status: 0, stdout: "", stderr: "" }
|
|
},
|
|
}))
|
|
const mod = await import(DOCTOR_SRC + "?t=" + Date.now())
|
|
const report = await mod.default.execute({}, ctx())
|
|
expect(report).toContain("arch mismatch")
|
|
expect(report).toContain("darwin")
|
|
} finally {
|
|
Object.defineProperty(process, "platform", { value: origPlatform })
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("memory-doctor npm missing hint (G3)", () => {
|
|
test("test_npm_missing_hint_in_report — rgBin===null, no system rg → report includes npm ci hint", async () => {
|
|
mock.module("node:module", () => ({
|
|
createRequire: () => () => {
|
|
throw new Error("Cannot find module '@vscode/ripgrep'")
|
|
},
|
|
}))
|
|
mock.module("fs", () => ({
|
|
...fs,
|
|
existsSync: (p: string) =>
|
|
p === os.homedir() + "/.local/share/opencode/opencode-memory",
|
|
readdirSync: () => [],
|
|
statSync: () => ({ size: 0, mtime: new Date() }),
|
|
}))
|
|
mock.module("child_process", () => ({
|
|
spawnSync: (cmd: string) => {
|
|
if (cmd === "rg") return { status: 127, stdout: "", stderr: "rg: not found" }
|
|
if (cmd === "python3") return { status: 0, stdout: "ok\n", stderr: "" }
|
|
return { status: 0, stdout: "", stderr: "" }
|
|
},
|
|
}))
|
|
const mod = await import(DOCTOR_SRC + "?t=" + Date.now())
|
|
const report = await mod.default.execute({}, ctx())
|
|
expect(report).toContain("npm package missing")
|
|
expect(report).toContain("npm ci")
|
|
expect(report).toContain("ripgrep missing or broken")
|
|
})
|
|
}) |