**Test-infrastructure fix.** 29 test files. No production code, no altered assertions, no widened timeouts. Now **3 commits** (#2584 merged into this branch): the logger-mock sweep, a cron-runner follow-up from review, and the 4 residual failures the sweep deliberately deferred. **Whole branch: 764 tests, 0 failures** across the touched set. --- ## Commit 1 — the missing `debug` on 27 logger mocks `createLogger`'s real shape is `{ log, debug, warn, error }`. 27 engine test files mock `../logger.js` with logger-shaped literals that **omit `debug`**, so any production path reaching `log.debug` threw: ``` TypeError: schedulerLog.debug is not a function TypeError: runtimeLog.debug is not a function TypeError: log.debug is not a function (SelfHealingManager.start) ``` Measured, same commit, same 27 files: | | Failed | Passed | |---|---|---| | before | **206** | 558 | | after | **4** | 760 | **202 failures fixed by one missing mock export.** Per-file: `notifier` 36→0, `plugin-runner` 56→0, `grok-runtime-routing` 14→0, `self-healing-completion-fanout` 1→0. That last one also leaked an unhandled rejection out of `startMaintenance`, which vitest warns "might cause false positive tests" elsewhere in the file. *A note on the number:* a full `engine-default` run went 283 → 106 across my two sessions, but `main` moved in between (U11 landed), so that spread is **not** attributable here. 206 → 4 is the honest figure: same commit, same file set, only this diff varying. ## Commit 2 — cron-runner's factory (greptile P1) My regex required `log: vi.fn()`; `cron-runner.test.ts` uses `log: cronLoggerSpies.log`, so the `createLogger` factory's returned literal never matched and the logger production received still lacked `debug`. **Measured before claiming a live fix, and the numbers don't support that part:** `cronLoggerSpies.debug.mock.calls.length` is **0** across all 155 tests, and the suite is 155 passed both before and after. The described failure mode — `tick()` hitting `log.debug`, throwing, and being swallowed by its own error handler — is **not reachable today**, because no test exercises those three branches (`cron-runner.ts:377`, `:385`, `:410`). The fix is defensive, not curative. The real gap it surfaced is **missing coverage** for schedule dedupe / scope mismatch / lost atomic claim, which I did not write blind to close a thread. ## Commit 3 — the 4 residuals **`notification-service` (3):** messages moved to DEBUG in production (`:580`, `:846`) while tests asserted `schedulerLog.log`. The token case needed more than a relocation. It asserted `expect(schedulerLog.log).not.toHaveBeenCalledWith(containing("new-token"))`. Moving only the *positive* assertion to `debug` would leave the secrecy check watching a channel the message no longer uses — a token could leak through `debug` and the test would still pass. The negative now runs across all four channels. **Verified it bites:** interpolating the token into the debug line fails the test. **`openclaw-runtime-integration` (1):** `../pi.js` mock missing `wrapToolsWithOutputBudget` (same class as #2547); this suite exercises a non-pi runtime, exactly where that wrapper applies. **Not swept repo-wide, and the measurement is why.** 37 `pi.js` mocks omit that export. Patching 30 moved the set from **11 failed to 10** — thirty files of churn for one test. Reverted. Commit 1 earned its 27-file diff with 202 fixes; this one earned nothing, and a no-op sweep is just future merge conflicts for other workers on this program. --- ## Why none of this is appeasement AGENTS.md forbids making a red test pass by loosening it. This does the opposite: the mocks were **wrong** — they claimed to stand in for `createLogger` while missing part of its interface. Nothing was relaxed; stubs were completed, and the one assertion I did move got **stronger** (four channels instead of one). ## Also deliberately not done Extending `scripts/check-mock-completeness.mjs` to catch this class. Measured first: a naive rule over relative intra-package mocks flags **147** factories of which **146 are green** — almost pure false positives. The barrel heuristic works because `cliSrc` gives a tight import surface; that doesn't transfer. A gate that noisy gets ignored, which is worse than no gate. ## How this was found While characterizing U9's review lane. These files were pre-existing baseline noise under mutation runs — and that noise is exactly what made my own safeguard baseline (#2511, corrected in #2520) report two false verdicts. **A red suite does not merely lack coverage; it makes every nearby measurement untrustworthy.** 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
217 lines
7.5 KiB
TypeScript
217 lines
7.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import type { AgentRuntime } from "../agent-runtime.js";
|
|
import { resolveRuntime } from "../runtime-resolution.js";
|
|
import { createResolvedAgentSession } from "../agent-session-helpers.js";
|
|
import type { PluginRunner } from "../plugin-runner.js";
|
|
import type { PluginRuntimeRegistration } from "@fusion/core";
|
|
|
|
const mockCreateFnAgent = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("../logger.js", () => ({
|
|
createLogger: vi.fn(() => ({
|
|
log: vi.fn(), debug: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
})),
|
|
}));
|
|
|
|
vi.mock("../pi.js", () => ({
|
|
/*
|
|
FNXC:TestInfrastructure 2026-07-29-15:30:
|
|
`wrapCustomToolsForPluginRuntime` (agent-session-helpers.ts:104) applies this as
|
|
the outermost tool wrapper on every NON-PI runtime path — which is exactly what
|
|
this suite exercises — so omitting it threw "No wrapToolsWithOutputBudget export
|
|
is defined on the ../pi.js mock" at session construction. Identity stub: the real
|
|
function returns tools byte-identical for a null budget, and this suite asserts
|
|
runtime selection/metadata, not output budgeting.
|
|
*/
|
|
wrapToolsWithOutputBudget: vi.fn((tools: unknown[]) => tools),
|
|
createFnAgent: mockCreateFnAgent,
|
|
promptWithFallback: vi.fn().mockResolvedValue(undefined),
|
|
describeModel: vi.fn().mockReturnValue("pi/default"),
|
|
// FNXC: pi.js tool-policy wrappers (wrapToolsWithRtkRewrite, wrapToolsWithPermanentAgentGating, wrapToolsWithActionGate) are now imported by agent-session-helpers.ts (wrapCustomToolsForPluginRuntime, called from createResolvedAgentSession). Mocks pass tools through unchanged.
|
|
wrapToolsWithRtkRewrite: vi.fn((tools) => tools),
|
|
wrapToolsWithPermanentAgentGating: vi.fn((tools) => tools),
|
|
wrapToolsWithActionGate: vi.fn((tools) => tools),
|
|
}));
|
|
|
|
function isAgentRuntime(value: unknown): value is AgentRuntime {
|
|
return (
|
|
typeof value === "object" &&
|
|
value !== null &&
|
|
"id" in value &&
|
|
"name" in value &&
|
|
typeof (value as AgentRuntime).createSession === "function" &&
|
|
typeof (value as AgentRuntime).promptWithFallback === "function" &&
|
|
typeof (value as AgentRuntime).describeModel === "function"
|
|
);
|
|
}
|
|
|
|
function createMockPluginRunner(overrides: Partial<PluginRunner> = {}): PluginRunner {
|
|
return {
|
|
getPluginRuntimes: vi.fn().mockReturnValue([]),
|
|
getRuntimeById: vi.fn().mockReturnValue(undefined),
|
|
createRuntimeContext: vi.fn().mockResolvedValue({
|
|
pluginId: "fusion-plugin-openclaw-runtime",
|
|
taskStore: {},
|
|
settings: {},
|
|
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
emitEvent: vi.fn(),
|
|
}),
|
|
...overrides,
|
|
} as unknown as PluginRunner;
|
|
}
|
|
|
|
function createOpenClawRegistration(factoryImpl?: () => unknown): {
|
|
pluginId: string;
|
|
runtime: PluginRuntimeRegistration;
|
|
} {
|
|
return {
|
|
pluginId: "fusion-plugin-openclaw-runtime",
|
|
runtime: {
|
|
metadata: {
|
|
runtimeId: "openclaw",
|
|
name: "OpenClaw Runtime",
|
|
description: "OpenClaw-backed AI session using the local OpenClaw gateway",
|
|
version: "0.1.0",
|
|
},
|
|
factory: vi.fn().mockImplementation(async () =>
|
|
factoryImpl
|
|
? factoryImpl()
|
|
: {
|
|
id: "openclaw",
|
|
name: "OpenClaw Runtime",
|
|
createSession: vi.fn().mockResolvedValue({
|
|
session: { runtime: "openclaw", prompt: vi.fn() },
|
|
sessionFile: "/tmp/openclaw.session.json",
|
|
}),
|
|
promptWithFallback: vi.fn().mockResolvedValue(undefined),
|
|
describeModel: vi.fn().mockReturnValue("openclaw/main"),
|
|
},
|
|
),
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("OpenClaw runtime integration via engine resolution pipeline", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockCreateFnAgent.mockResolvedValue({
|
|
session: { runtime: "pi", prompt: vi.fn() },
|
|
sessionFile: "/tmp/pi.session.json",
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("resolves OpenClaw runtime through PluginRunner lookup when runtimeHint is openclaw", async () => {
|
|
const registration = createOpenClawRegistration();
|
|
const pluginRunner = createMockPluginRunner({
|
|
getRuntimeById: vi.fn().mockReturnValue(registration),
|
|
});
|
|
|
|
const resolved = await resolveRuntime({
|
|
sessionPurpose: "executor",
|
|
runtimeHint: "openclaw",
|
|
pluginRunner,
|
|
});
|
|
|
|
expect(resolved.runtimeId).toBe("openclaw");
|
|
expect(resolved.wasConfigured).toBe(true);
|
|
expect(resolved.runtime.id).toBe("openclaw");
|
|
expect(resolved.runtime.name).toBe("OpenClaw Runtime");
|
|
expect(pluginRunner.getRuntimeById).toHaveBeenCalledWith("openclaw");
|
|
expect(pluginRunner.createRuntimeContext).toHaveBeenCalledWith("fusion-plugin-openclaw-runtime");
|
|
});
|
|
|
|
it("returns a runtime object that conforms to AgentRuntime", async () => {
|
|
const registration = createOpenClawRegistration();
|
|
const pluginRunner = createMockPluginRunner({
|
|
getRuntimeById: vi.fn().mockReturnValue(registration),
|
|
});
|
|
|
|
const resolved = await resolveRuntime({
|
|
sessionPurpose: "executor",
|
|
runtimeHint: "openclaw",
|
|
pluginRunner,
|
|
});
|
|
|
|
expect(isAgentRuntime(resolved.runtime)).toBe(true);
|
|
});
|
|
|
|
it("createResolvedAgentSession uses OpenClaw runtime and reports configured runtime metadata", async () => {
|
|
const runtimeSession = { runtime: "openclaw", prompt: vi.fn() };
|
|
const createSession = vi.fn().mockResolvedValue({
|
|
session: runtimeSession,
|
|
sessionFile: "/tmp/openclaw.session.json",
|
|
});
|
|
const registration = createOpenClawRegistration(() => ({
|
|
id: "openclaw",
|
|
name: "OpenClaw Runtime",
|
|
createSession,
|
|
promptWithFallback: vi.fn().mockResolvedValue(undefined),
|
|
describeModel: vi.fn().mockReturnValue("openclaw/main"),
|
|
}));
|
|
|
|
const pluginRunner = createMockPluginRunner({
|
|
getRuntimeById: vi.fn().mockReturnValue(registration),
|
|
});
|
|
|
|
const customTool = {
|
|
name: "fn_task_show",
|
|
label: "fn_task_show",
|
|
description: "show",
|
|
parameters: { type: "object" },
|
|
execute: vi.fn(),
|
|
} as any;
|
|
|
|
const result = await createResolvedAgentSession({
|
|
sessionPurpose: "executor",
|
|
runtimeHint: "openclaw",
|
|
pluginRunner,
|
|
cwd: "/tmp/project",
|
|
systemPrompt: "You are helpful",
|
|
tools: "coding",
|
|
customTools: [customTool],
|
|
});
|
|
|
|
expect(result.runtimeId).toBe("openclaw");
|
|
expect(result.wasConfigured).toBe(true);
|
|
expect(result.session).toBe(runtimeSession);
|
|
expect(result.sessionFile).toBe("/tmp/openclaw.session.json");
|
|
expect(createSession).toHaveBeenCalledWith(expect.objectContaining({
|
|
cwd: "/tmp/project",
|
|
systemPrompt: "You are helpful",
|
|
tools: "coding",
|
|
customTools: [customTool],
|
|
}));
|
|
});
|
|
|
|
it("falls back to default pi runtime when OpenClaw factory throws", async () => {
|
|
const registration = createOpenClawRegistration(() => {
|
|
throw new Error("factory exploded");
|
|
});
|
|
|
|
const pluginRunner = createMockPluginRunner({
|
|
getRuntimeById: vi.fn().mockReturnValue(registration),
|
|
});
|
|
|
|
const result = await createResolvedAgentSession({
|
|
sessionPurpose: "executor",
|
|
runtimeHint: "openclaw",
|
|
pluginRunner,
|
|
cwd: "/tmp/project",
|
|
systemPrompt: "Use fallback",
|
|
});
|
|
|
|
expect(result.runtimeId).toBe("pi");
|
|
expect(result.wasConfigured).toBe(false);
|
|
expect(mockCreateFnAgent).toHaveBeenCalledWith(expect.objectContaining({
|
|
cwd: "/tmp/project",
|
|
systemPrompt: "Use fallback",
|
|
}));
|
|
});
|
|
});
|