refactor(tests): rewrite 5 over-mocked test files to test real behavior

- formatter.test.ts: use dynamic imports with cache-busting to avoid mock pollution from runner.test.ts; test real format output instead of dispatch mocking
- hook.test.ts: rewrite with proper branch coverage (7 tests), add success/guard/subagent paths
- background-update-check.test.ts: rewrite with 10 tests covering all branches (early returns, pinned versions, auto-update success/failure)
- directory-agents-injector/injector.test.ts: replace finder/storage mocks with real filesystem + temp directories, verify actual AGENTS.md injection content
- directory-readme-injector/injector.test.ts: same pattern as agents-injector but for README.md, verifies root inclusion behavior
This commit is contained in:
YeonGyu-Kim
2026-02-22 16:43:56 +09:00
parent ffa2a255d9
commit 7e05bd2b8e
5 changed files with 776 additions and 440 deletions
@@ -1,161 +1,212 @@
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"
import { randomUUID } from "node:crypto"
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
const findReadmeMdUpMock = mock((_: { startDir: string; rootDir: string }) => [] as string[])
const resolveFilePathMock = mock((_: string, path: string) => path)
const loadInjectedPathsMock = mock((_: string) => new Set<string>())
const saveInjectedPathsMock = mock((_: string, __: Set<string>) => {})
import type { PluginInput } from "@opencode-ai/plugin"
const storageMaps = new Map<string, Set<string>>()
mock.module("./storage", () => ({
loadInjectedPaths: (sessionID: string) => storageMaps.get(sessionID) ?? new Set<string>(),
saveInjectedPaths: (sessionID: string, paths: Set<string>) => {
storageMaps.set(sessionID, paths)
},
}))
function createPluginContext(directory: string): PluginInput {
return { directory } as PluginInput
}
function countReadmeMarkers(output: string): number {
return output.split("[Project README:").length - 1
}
function createTruncator(input?: { truncated?: boolean; result?: string }) {
return {
truncate: async (_sessionID: string, content: string) => ({
result: input?.result ?? content,
truncated: input?.truncated ?? false,
}),
getUsage: async (_sessionID: string) => null,
truncateSync: (output: string) => ({ result: output, truncated: false }),
}
}
describe("processFilePathForReadmeInjection", () => {
let testRoot = ""
beforeEach(() => {
findReadmeMdUpMock.mockClear()
resolveFilePathMock.mockClear()
loadInjectedPathsMock.mockClear()
saveInjectedPathsMock.mockClear()
testRoot = join(
tmpdir(),
`directory-readme-injector-${Date.now()}-${Math.random().toString(16).slice(2)}`
)
testRoot = join(tmpdir(), `directory-readme-injector-${randomUUID()}`)
mkdirSync(testRoot, { recursive: true })
storageMaps.clear()
})
afterEach(() => {
mock.restore()
rmSync(testRoot, { recursive: true, force: true })
storageMaps.clear()
})
it("does not save when all discovered paths are already cached", async () => {
//#given
const sessionID = "session-1"
const repoRoot = join(testRoot, "repo")
const readmePath = join(repoRoot, "src", "README.md")
const cachedDirectory = join(repoRoot, "src")
mkdirSync(join(repoRoot, "src"), { recursive: true })
writeFileSync(readmePath, "# README")
loadInjectedPathsMock.mockReturnValueOnce(new Set([cachedDirectory]))
findReadmeMdUpMock.mockReturnValueOnce([readmePath])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
mock.module("./finder", () => ({
findReadmeMdUp: findReadmeMdUpMock,
resolveFilePath: resolveFilePathMock,
}))
mock.module("./storage", () => ({
loadInjectedPaths: loadInjectedPathsMock,
saveInjectedPaths: saveInjectedPathsMock,
}))
it("injects README.md content from file's parent directory into output", async () => {
// given
const sourceDirectory = join(testRoot, "src")
mkdirSync(sourceDirectory, { recursive: true })
writeFileSync(join(sourceDirectory, "README.md"), "# Source README\nlocal context")
const { processFilePathForReadmeInjection } = await import("./injector")
const output = { title: "Result", output: "base", metadata: {} }
const truncator = createTruncator()
//#when
// when
await processFilePathForReadmeInjection({
ctx: { directory: repoRoot } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: join(repoRoot, "src", "file.ts"),
sessionID,
output: { title: "Result", output: "", metadata: {} },
ctx: createPluginContext(testRoot),
truncator,
sessionCaches: new Map<string, Set<string>>(),
filePath: join(sourceDirectory, "file.ts"),
sessionID: "session-parent",
output,
})
//#then
expect(saveInjectedPathsMock).not.toHaveBeenCalled()
// then
expect(output.output).toContain("[Project README:")
expect(output.output).toContain("# Source README")
expect(output.output).toContain("local context")
})
it("saves when a new path is injected", async () => {
//#given
const sessionID = "session-2"
const repoRoot = join(testRoot, "repo")
const readmePath = join(repoRoot, "src", "README.md")
const injectedDirectory = join(repoRoot, "src")
mkdirSync(join(repoRoot, "src"), { recursive: true })
writeFileSync(readmePath, "# README")
loadInjectedPathsMock.mockReturnValueOnce(new Set())
findReadmeMdUpMock.mockReturnValueOnce([readmePath])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
mock.module("./finder", () => ({
findReadmeMdUp: findReadmeMdUpMock,
resolveFilePath: resolveFilePathMock,
}))
mock.module("./storage", () => ({
loadInjectedPaths: loadInjectedPathsMock,
saveInjectedPaths: saveInjectedPathsMock,
}))
it("includes root-level README.md (unlike agents-injector)", async () => {
// given
writeFileSync(join(testRoot, "README.md"), "# Root README\nroot context")
const { processFilePathForReadmeInjection } = await import("./injector")
const output = { title: "Result", output: "", metadata: {} }
const truncator = createTruncator()
//#when
// when
await processFilePathForReadmeInjection({
ctx: { directory: repoRoot } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: join(repoRoot, "src", "file.ts"),
sessionID,
output: { title: "Result", output: "", metadata: {} },
ctx: createPluginContext(testRoot),
truncator,
sessionCaches: new Map<string, Set<string>>(),
filePath: join(testRoot, "file.ts"),
sessionID: "session-root",
output,
})
//#then
expect(saveInjectedPathsMock).toHaveBeenCalledTimes(1)
const saveCall = saveInjectedPathsMock.mock.calls[0]
expect(saveCall[0]).toBe(sessionID)
expect((saveCall[1] as Set<string>).has(injectedDirectory)).toBe(true)
// then
expect(output.output).toContain("[Project README:")
expect(output.output).toContain("# Root README")
expect(output.output).toContain("root context")
})
it("saves once when cached and new paths are mixed", async () => {
//#given
const sessionID = "session-3"
const repoRoot = join(testRoot, "repo")
const cachedReadmePath = join(repoRoot, "already-cached", "README.md")
const newReadmePath = join(repoRoot, "new-dir", "README.md")
mkdirSync(join(repoRoot, "already-cached"), { recursive: true })
mkdirSync(join(repoRoot, "new-dir"), { recursive: true })
writeFileSync(cachedReadmePath, "# README")
writeFileSync(newReadmePath, "# README")
loadInjectedPathsMock.mockReturnValueOnce(new Set([join(repoRoot, "already-cached")]))
findReadmeMdUpMock.mockReturnValueOnce([cachedReadmePath, newReadmePath])
const truncator = {
truncate: mock(async () => ({ result: "trimmed", truncated: false })),
}
mock.module("./finder", () => ({
findReadmeMdUp: findReadmeMdUpMock,
resolveFilePath: resolveFilePathMock,
}))
mock.module("./storage", () => ({
loadInjectedPaths: loadInjectedPathsMock,
saveInjectedPaths: saveInjectedPathsMock,
}))
it("injects multiple README.md when walking up directory tree", async () => {
// given
const sourceDirectory = join(testRoot, "src")
const componentsDirectory = join(sourceDirectory, "components")
mkdirSync(componentsDirectory, { recursive: true })
writeFileSync(join(testRoot, "README.md"), "# Root README")
writeFileSync(join(sourceDirectory, "README.md"), "# Src README")
writeFileSync(join(componentsDirectory, "README.md"), "# Components README")
writeFileSync(join(componentsDirectory, "button.ts"), "export const button = true")
const { processFilePathForReadmeInjection } = await import("./injector")
const output = { title: "Result", output: "", metadata: {} }
const truncator = createTruncator()
//#when
// when
await processFilePathForReadmeInjection({
ctx: { directory: repoRoot } as never,
truncator: truncator as never,
sessionCaches: new Map(),
filePath: join(repoRoot, "new-dir", "file.ts"),
sessionID,
output: { title: "Result", output: "", metadata: {} },
ctx: createPluginContext(testRoot),
truncator,
sessionCaches: new Map<string, Set<string>>(),
filePath: join(componentsDirectory, "button.ts"),
sessionID: "session-multi",
output,
})
//#then
expect(saveInjectedPathsMock).toHaveBeenCalledTimes(1)
const saveCall = saveInjectedPathsMock.mock.calls[0]
expect((saveCall[1] as Set<string>).has(join(repoRoot, "new-dir"))).toBe(true)
// then
expect(countReadmeMarkers(output.output)).toBe(3)
expect(output.output).toContain("# Root README")
expect(output.output).toContain("# Src README")
expect(output.output).toContain("# Components README")
})
it("does not re-inject already cached directories", async () => {
// given
const sourceDirectory = join(testRoot, "src")
mkdirSync(sourceDirectory, { recursive: true })
writeFileSync(join(sourceDirectory, "README.md"), "# Source README")
const { processFilePathForReadmeInjection } = await import("./injector")
const sessionCaches = new Map<string, Set<string>>()
const sessionID = "session-cache"
const truncator = createTruncator()
const firstOutput = { title: "Result", output: "", metadata: {} }
const secondOutput = { title: "Result", output: "", metadata: {} }
// when
await processFilePathForReadmeInjection({
ctx: createPluginContext(testRoot),
truncator,
sessionCaches,
filePath: join(sourceDirectory, "a.ts"),
sessionID,
output: firstOutput,
})
await processFilePathForReadmeInjection({
ctx: createPluginContext(testRoot),
truncator,
sessionCaches,
filePath: join(sourceDirectory, "b.ts"),
sessionID,
output: secondOutput,
})
// then
expect(countReadmeMarkers(firstOutput.output)).toBe(1)
expect(secondOutput.output).toBe("")
})
it("shows truncation notice when content is truncated", async () => {
// given
const sourceDirectory = join(testRoot, "src")
mkdirSync(sourceDirectory, { recursive: true })
writeFileSync(join(sourceDirectory, "README.md"), "# Truncated README")
const { processFilePathForReadmeInjection } = await import("./injector")
const output = { title: "Result", output: "", metadata: {} }
const truncator = createTruncator({ result: "trimmed content", truncated: true })
// when
await processFilePathForReadmeInjection({
ctx: createPluginContext(testRoot),
truncator,
sessionCaches: new Map<string, Set<string>>(),
filePath: join(sourceDirectory, "file.ts"),
sessionID: "session-truncated",
output,
})
// then
expect(output.output).toContain("trimmed content")
expect(output.output).toContain("[Note: Content was truncated")
})
it("does nothing when filePath cannot be resolved", async () => {
// given
const { processFilePathForReadmeInjection } = await import("./injector")
const output = { title: "Result", output: "unchanged", metadata: {} }
const truncator = createTruncator()
// when
await processFilePathForReadmeInjection({
ctx: createPluginContext(testRoot),
truncator,
sessionCaches: new Map<string, Set<string>>(),
filePath: "",
sessionID: "session-empty-path",
output,
})
// then
expect(output.output).toBe("unchanged")
})
})