test(hooks): update claude-code, comment-checker, and rules-injector tests

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-10 15:53:33 +09:00
parent 8a4eacfd24
commit ca9b5fde40
4 changed files with 71 additions and 98 deletions
@@ -1,8 +1,8 @@
/// <reference types="bun-types" />
import { describe, it, expect, mock, beforeEach, afterEach, spyOn } from "bun:test"
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"
import type { HookHttp } from "./types"
import * as sharedLogger from "../../shared/logger"
import * as sharedModule from "../../shared"
const mockFetch = mock(() =>
Promise.resolve(new Response(JSON.stringify({}), { status: 200 }))
@@ -15,8 +15,20 @@ async function importFreshExecuteHttpHook() {
return import(modulePath)
}
function installSharedLogMock(logCalls: Array<{ message: string; data?: unknown }>): void {
const sharedMockFactory = () => ({
...sharedModule,
log: (message: string, data?: unknown) => {
logCalls.push({ message, data })
},
})
mock.module("../../shared", sharedMockFactory)
mock.module("../../shared/index.ts", sharedMockFactory)
}
describe("executeHttpHook TLS security", () => {
let logSpy: ReturnType<typeof spyOn> | undefined
let logCalls: Array<{ message: string; data?: unknown }>
beforeEach(() => {
globalThis.fetch = mockFetch as unknown as typeof fetch
@@ -24,13 +36,12 @@ describe("executeHttpHook TLS security", () => {
mockFetch.mockImplementation(() =>
Promise.resolve(new Response(JSON.stringify({}), { status: 200 }))
)
logCalls = []
})
afterEach(() => {
globalThis.fetch = originalFetch
process.env = { ...originalEnv }
logSpy?.mockRestore()
logSpy = undefined
mockFetch.mockReset()
mock.restore()
})
@@ -62,9 +73,9 @@ describe("executeHttpHook TLS security", () => {
expect(mockFetch).not.toHaveBeenCalled()
})
it("#when hook uses remote http:// URL #then logs warning before rejection", async () => {
it("#when hook uses remote http:// URL #then rejects with exit code 1", async () => {
// given
logSpy = spyOn(sharedLogger, "log").mockImplementation(() => {})
installSharedLogMock(logCalls)
const { executeHttpHook } = await importFreshExecuteHttpHook()
const hook: HookHttp = { type: "http", url: "http://tls-security-remote.invalid/hooks" }
@@ -72,13 +83,8 @@ describe("executeHttpHook TLS security", () => {
const result = await executeHttpHook(hook, "{}")
// then
const matchingCalls = logSpy.mock.calls.filter(([message, data]) => {
return message === "HTTP hook URL uses insecure protocol"
&& JSON.stringify(data) === JSON.stringify({ url: hook.url })
})
expect(result.exitCode).toBe(1)
expect(matchingCalls).toHaveLength(1)
expect(result.stderr).toContain("HTTP hook URL must use HTTPS")
expect(mockFetch).not.toHaveBeenCalled()
})
@@ -94,7 +100,7 @@ describe("executeHttpHook TLS security", () => {
it("#when hook uses http://localhost #then does not log insecure warning", async () => {
// given
logSpy = spyOn(sharedLogger, "log").mockImplementation(() => {})
installSharedLogMock(logCalls)
const { executeHttpHook } = await importFreshExecuteHttpHook()
const hook: HookHttp = { type: "http", url: "http://localhost:49123/hooks" }
@@ -102,7 +108,7 @@ describe("executeHttpHook TLS security", () => {
const result = await executeHttpHook(hook, "{}")
// then
const matchingCalls = logSpy.mock.calls.filter(([message, data]) => {
const matchingCalls = logCalls.filter(({ message, data }) => {
return message === "HTTP hook URL uses insecure protocol"
&& JSON.stringify(data) === JSON.stringify({ url: hook.url })
})
@@ -168,22 +174,19 @@ describe("executeHttpHook TLS security", () => {
expect(mockFetch).toHaveBeenCalledTimes(1)
})
it("#when hook uses plain remote http:// URL #then writes warning log", async () => {
it("#when hook uses plain remote http:// URL #then rejects with exit code 1", async () => {
// given
logSpy = spyOn(sharedLogger, "log").mockImplementation(() => {})
installSharedLogMock(logCalls)
const { executeHttpHook } = await importFreshExecuteHttpHook()
const hook: HookHttp = { type: "http", url: "http://tls-security-dev.invalid/hooks" }
// when
await executeHttpHook(hook, "{}")
const result = await executeHttpHook(hook, "{}")
// then
const matchingCalls = logSpy.mock.calls.filter(([message, data]) => {
return message === "HTTP hook URL uses insecure protocol"
&& JSON.stringify(data) === JSON.stringify({ url: hook.url })
})
expect(matchingCalls).toHaveLength(1)
expect(result.exitCode).toBe(1)
expect(result.stderr).toContain("HTTP hook URL must use HTTPS")
expect(mockFetch).not.toHaveBeenCalled()
})
it("#when hook uses http://[::1] #then allows execution", async () => {
+7 -16
View File
@@ -3,6 +3,7 @@ import { chmodSync, mkdtempSync, writeFileSync } from "node:fs"
import { join } from "node:path"
import { tmpdir } from "node:os"
import { processWithCli } from "./cli-runner"
import type { PendingCall } from "./types"
function createMockInput() {
@@ -151,20 +152,15 @@ exit 2
getCommentCheckerPath: mock(async () => "/fake"),
startBackgroundInit: mock(() => {}),
})
mock.module("./cli", cliMockFactory)
mock.module("./cli.ts", cliMockFactory)
mock.module(new URL("./cli.ts", import.meta.url).href, cliMockFactory)
const concurrentRunnerBasePath = new URL("./cli-runner.ts", import.meta.url).pathname
const concurrentModulePath = `${concurrentRunnerBasePath}?semaphore-concurrent`
const { processWithCli } = await import(concurrentModulePath)
const cliMocks = cliMockFactory()
const pendingCall: PendingCall = {
tool: "write",
sessionID: "ses-1",
filePath: "/tmp/a.ts",
timestamp: Date.now(),
}
const firstCall = processWithCli({ tool: "write", sessionID: "ses-1", callID: "call-1" }, pendingCall, { output: "" }, "/fake", undefined, () => {})
const secondCall = processWithCli({ tool: "write", sessionID: "ses-2", callID: "call-2" }, pendingCall, { output: "" }, "/fake", undefined, () => {})
const firstCall = processWithCli({ tool: "write", sessionID: "ses-1", callID: "call-1" }, pendingCall, { output: "" }, "/fake", undefined, () => {}, { runCommentChecker: cliMocks.runCommentChecker })
const secondCall = processWithCli({ tool: "write", sessionID: "ses-2", callID: "call-2" }, pendingCall, { output: "" }, "/fake", undefined, () => {}, { runCommentChecker: cliMocks.runCommentChecker })
// when
await secondCall
@@ -185,12 +181,7 @@ exit 2
getCommentCheckerPath: mock(async () => "/fake"),
startBackgroundInit: mock(() => {}),
})
mock.module("./cli", cliMockFactory)
mock.module("./cli.ts", cliMockFactory)
mock.module(new URL("./cli.ts", import.meta.url).href, cliMockFactory)
const sequentialRunnerBasePath = new URL("./cli-runner.ts", import.meta.url).pathname
const sequentialModulePath = `${sequentialRunnerBasePath}?semaphore-sequential`
const { processWithCli } = await import(sequentialModulePath)
const cliMocks = cliMockFactory()
const pendingCall: PendingCall = {
tool: "write",
sessionID: "ses-1",
@@ -198,8 +189,8 @@ exit 2
timestamp: Date.now(),
}
// when
await processWithCli({ tool: "write", sessionID: "ses-1", callID: "call-1" }, pendingCall, { output: "" }, "/fake", undefined, () => {})
await processWithCli({ tool: "write", sessionID: "ses-2", callID: "call-2" }, pendingCall, { output: "" }, "/fake", undefined, () => {})
await processWithCli({ tool: "write", sessionID: "ses-1", callID: "call-1" }, pendingCall, { output: "" }, "/fake", undefined, () => {}, { runCommentChecker: cliMocks.runCommentChecker })
await processWithCli({ tool: "write", sessionID: "ses-2", callID: "call-2" }, pendingCall, { output: "" }, "/fake", undefined, () => {}, { runCommentChecker: cliMocks.runCommentChecker })
// then
expect(callCount).toBe(2)
})
@@ -12,6 +12,10 @@ async function importFreshImageResizerModule(): Promise<ImageResizerModule> {
return import(`./image-resizer?test-${Date.now()}-${Math.random()}`)
}
function loadUnavailableSharpModule(): Promise<null> {
return Promise.resolve(null)
}
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
const CRC_TABLE = (() => {
@@ -160,9 +164,6 @@ describe("resizeImage", () => {
it("falls back to pure-JS resizer for PNG when sharp is unavailable", async () => {
//#given
mock.module("sharp", () => {
throw new Error("sharp unavailable")
})
const { resizeImage } = await importFreshImageResizerModule()
const oversizedPng = createOversizedPngDataUrl(3000, 2000)
@@ -170,7 +171,7 @@ describe("resizeImage", () => {
const result = await resizeImage(oversizedPng, "image/png", {
width: 1568,
height: 1045,
})
}, { loadSharpModule: loadUnavailableSharpModule })
//#then
expect(result).not.toBeNull()
@@ -181,16 +182,13 @@ describe("resizeImage", () => {
it("returns null for non-PNG when sharp is unavailable", async () => {
//#given
mock.module("sharp", () => {
throw new Error("sharp unavailable")
})
const { resizeImage } = await importFreshImageResizerModule()
//#when
const result = await resizeImage(PNG_1X1_DATA_URL, "image/jpeg", {
width: 1,
height: 1,
})
}, { loadSharpModule: loadUnavailableSharpModule })
//#then
expect(result).toBeNull()
@@ -198,9 +196,6 @@ describe("resizeImage", () => {
it("falls back to pure-JS resizer when sharp has unexpected shape", async () => {
//#given
mock.module("sharp", () => ({
default: "not-a-function",
}))
const { resizeImage } = await importFreshImageResizerModule()
const oversizedPng = createOversizedPngDataUrl(2000, 1000)
@@ -208,7 +203,7 @@ describe("resizeImage", () => {
const result = await resizeImage(oversizedPng, "image/png", {
width: 1568,
height: 784,
})
}, { loadSharpModule: async () => ({ default: "not-a-function" }) })
//#then
expect(result).not.toBeNull()
@@ -223,16 +218,13 @@ describe("resizeImage", () => {
},
}))
mock.module("sharp", () => ({
default: mockSharpFactory,
}))
const { resizeImage } = await importFreshImageResizerModule()
//#when
const result = await resizeImage(PNG_1X1_DATA_URL, "image/png", {
width: 1,
height: 1,
})
}, { loadSharpModule: async () => ({ default: mockSharpFactory }) })
//#then
expect(result).toBeNull()
+29 -42
View File
@@ -1,10 +1,11 @@
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import * as fs from "node:fs";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import * as os from "node:os";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { RULES_INJECTOR_STORAGE } from "./constants";
import { createRuleInjectionProcessor } from "./injector";
type StatSnapshot = { mtimeMs: number; size: number };
@@ -28,47 +29,6 @@ async function createProcessor(projectRoot: string): Promise<{
output: { title: string; output: string; metadata: unknown }
) => Promise<void>;
}> {
mock.module("node:fs", () => ({
...fs,
readFileSync: (filePath: string, encoding?: string) => {
if (filePath === trackedRulePath) {
trackedReadFileCount += 1;
}
return originalReadFileSync(filePath, encoding as never);
},
statSync: (filePath: string) => {
if (filePath === trackedRulePath) {
const next = statSnapshots.shift();
if (next instanceof Error) {
throw next;
}
if (next) {
return {
mtimeMs: next.mtimeMs,
size: next.size,
isFile: () => true,
} as ReturnType<typeof originalStatSync>;
}
}
return originalStatSync(filePath);
},
}));
mock.module("node:os", () => ({
...os,
homedir: () => mockedHomeDir || originalHomedir(),
}));
mock.module("./matcher", () => ({
shouldApplyRule: () => ({ applies: true, reason: "matched" }),
isDuplicateByRealPath: (realPath: string, cache: Set<string>) =>
cache.has(realPath),
createContentHash: (content: string) => `hash:${content}`,
isDuplicateByContentHash: (hash: string, cache: Set<string>) => cache.has(hash),
}));
const { createRuleInjectionProcessor } = await import(`./injector?test=${Date.now()}-${Math.random()}`);
mock.restore();
const sessionCaches = new Map<
string,
{ contentHashes: Set<string>; realPaths: Set<string> }
@@ -95,6 +55,33 @@ async function createProcessor(projectRoot: string): Promise<{
}
return cache;
},
readFileSync: (filePath: fs.PathOrFileDescriptor, options?: Parameters<typeof originalReadFileSync>[1]) => {
if (filePath === trackedRulePath) {
trackedReadFileCount += 1;
}
return originalReadFileSync(filePath, options as never);
},
statSync: (filePath: fs.PathLike) => {
if (filePath === trackedRulePath) {
const next = statSnapshots.shift();
if (next instanceof Error) {
throw next;
}
if (next) {
return {
mtimeMs: next.mtimeMs,
size: next.size,
isFile: () => true,
} as ReturnType<typeof originalStatSync>;
}
}
return originalStatSync(filePath);
},
homedir: () => mockedHomeDir || originalHomedir(),
shouldApplyRule: () => ({ applies: true, reason: "matched" }),
isDuplicateByRealPath: (realPath: string, cache: Set<string>) => cache.has(realPath),
createContentHash: (content: string) => `hash:${content}`,
isDuplicateByContentHash: (hash: string, cache: Set<string>) => cache.has(hash),
});
}