test: dedupe repeated test fixtures and assertions

This commit is contained in:
Peter Steinberger
2026-02-22 18:37:01 +00:00
parent 0e4f3ccbdf
commit 53ed7a0f5c
10 changed files with 166 additions and 212 deletions

View File

@@ -5,6 +5,23 @@ import "./test-helpers/fast-coding-tools.js";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
function createMockUsage(input: number, output: number) {
return {
input,
output,
cacheRead: 0,
cacheWrite: 0,
totalTokens: input + output,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
};
}
vi.mock("@mariozechner/pi-coding-agent", async () => {
const actual = await vi.importActual<typeof import("@mariozechner/pi-coding-agent")>(
"@mariozechner/pi-coding-agent",
@@ -40,20 +57,7 @@ vi.mock("@mariozechner/pi-ai", async () => {
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 1,
output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
usage: createMockUsage(1, 1),
timestamp: Date.now(),
});
@@ -65,20 +69,7 @@ vi.mock("@mariozechner/pi-ai", async () => {
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
usage: createMockUsage(0, 0),
timestamp: Date.now(),
});
@@ -314,20 +305,7 @@ describe.concurrent("runEmbeddedPiAgent", () => {
api: "openai-responses",
provider: "openai",
model: "mock-1",
usage: {
input: 1,
output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
usage: createMockUsage(1, 1),
timestamp: Date.now(),
});

View File

@@ -4,6 +4,32 @@ import { createAssistantMessageEventStream } from "@mariozechner/pi-ai";
import { describe, expect, it } from "vitest";
import { applyExtraParamsToAgent } from "./extra-params.js";
type StreamPayload = {
messages: Array<{
role: string;
content: unknown;
}>;
};
function runOpenRouterPayload(payload: StreamPayload, modelId: string) {
const baseStreamFn: StreamFn = (_model, _context, options) => {
options?.onPayload?.(payload);
return createAssistantMessageEventStream();
};
const agent = { streamFn: baseStreamFn };
applyExtraParamsToAgent(agent, undefined, "openrouter", modelId);
const model = {
api: "openai-completions",
provider: "openrouter",
id: modelId,
} as Model<"openai-completions">;
const context: Context = { messages: [] };
void agent.streamFn?.(model, context, {});
}
describe("extra-params: OpenRouter Anthropic cache_control", () => {
it("injects cache_control into system message for OpenRouter Anthropic models", () => {
const payload = {
@@ -12,22 +38,8 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
{ role: "user", content: "Hello" },
],
};
const baseStreamFn: StreamFn = (_model, _context, options) => {
options?.onPayload?.(payload);
return createAssistantMessageEventStream();
};
const agent = { streamFn: baseStreamFn };
applyExtraParamsToAgent(agent, undefined, "openrouter", "anthropic/claude-opus-4-6");
const model = {
api: "openai-completions",
provider: "openrouter",
id: "anthropic/claude-opus-4-6",
} as Model<"openai-completions">;
const context: Context = { messages: [] };
void agent.streamFn?.(model, context, {});
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
expect(payload.messages[0].content).toEqual([
{ type: "text", text: "You are a helpful assistant.", cache_control: { type: "ephemeral" } },
@@ -47,22 +59,8 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
},
],
};
const baseStreamFn: StreamFn = (_model, _context, options) => {
options?.onPayload?.(payload);
return createAssistantMessageEventStream();
};
const agent = { streamFn: baseStreamFn };
applyExtraParamsToAgent(agent, undefined, "openrouter", "anthropic/claude-opus-4-6");
const model = {
api: "openai-completions",
provider: "openrouter",
id: "anthropic/claude-opus-4-6",
} as Model<"openai-completions">;
const context: Context = { messages: [] };
void agent.streamFn?.(model, context, {});
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
const content = payload.messages[0].content as Array<Record<string, unknown>>;
expect(content[0]).toEqual({ type: "text", text: "Part 1" });
@@ -77,23 +75,19 @@ describe("extra-params: OpenRouter Anthropic cache_control", () => {
const payload = {
messages: [{ role: "system", content: "You are a helpful assistant." }],
};
const baseStreamFn: StreamFn = (_model, _context, options) => {
options?.onPayload?.(payload);
return createAssistantMessageEventStream();
};
const agent = { streamFn: baseStreamFn };
applyExtraParamsToAgent(agent, undefined, "openrouter", "google/gemini-3-pro");
const model = {
api: "openai-completions",
provider: "openrouter",
id: "google/gemini-3-pro",
} as Model<"openai-completions">;
const context: Context = { messages: [] };
void agent.streamFn?.(model, context, {});
runOpenRouterPayload(payload, "google/gemini-3-pro");
expect(payload.messages[0].content).toBe("You are a helpful assistant.");
});
it("leaves payload unchanged when no system message exists", () => {
const payload = {
messages: [{ role: "user", content: "Hello" }],
};
runOpenRouterPayload(payload, "anthropic/claude-opus-4-6");
expect(payload.messages[0].content).toBe("Hello");
});
});

View File

@@ -35,6 +35,23 @@ function buildResolvedConfig(): ResolvedBrowserConfig {
describe("startBrowserBridgeServer auth", () => {
const servers: Array<{ stop: () => Promise<void> }> = [];
async function expectAuthFlow(
authConfig: { authToken?: string; authPassword?: string },
headers: Record<string, string>,
) {
const bridge = await startBrowserBridgeServer({
resolved: buildResolvedConfig(),
...authConfig,
});
servers.push({ stop: () => stopBrowserBridgeServer(bridge.server) });
const unauth = await fetch(`${bridge.baseUrl}/`);
expect(unauth.status).toBe(401);
const authed = await fetch(`${bridge.baseUrl}/`, { headers });
expect(authed.status).toBe(200);
}
afterEach(async () => {
while (servers.length) {
const s = servers.pop();
@@ -45,35 +62,14 @@ describe("startBrowserBridgeServer auth", () => {
});
it("rejects unauthenticated requests when authToken is set", async () => {
const bridge = await startBrowserBridgeServer({
resolved: buildResolvedConfig(),
authToken: "secret-token",
});
servers.push({ stop: () => stopBrowserBridgeServer(bridge.server) });
const unauth = await fetch(`${bridge.baseUrl}/`);
expect(unauth.status).toBe(401);
const authed = await fetch(`${bridge.baseUrl}/`, {
headers: { Authorization: "Bearer secret-token" },
});
expect(authed.status).toBe(200);
await expectAuthFlow({ authToken: "secret-token" }, { Authorization: "Bearer secret-token" });
});
it("accepts x-openclaw-password when authPassword is set", async () => {
const bridge = await startBrowserBridgeServer({
resolved: buildResolvedConfig(),
authPassword: "secret-password",
});
servers.push({ stop: () => stopBrowserBridgeServer(bridge.server) });
const unauth = await fetch(`${bridge.baseUrl}/`);
expect(unauth.status).toBe(401);
const authed = await fetch(`${bridge.baseUrl}/`, {
headers: { "x-openclaw-password": "secret-password" },
});
expect(authed.status).toBe(200);
await expectAuthFlow(
{ authPassword: "secret-password" },
{ "x-openclaw-password": "secret-password" },
);
});
it("requires auth params", async () => {

View File

@@ -17,33 +17,42 @@ import { execFileSync } from "node:child_process";
import * as fs from "node:fs";
describe("browser default executable detection", () => {
beforeEach(() => {
vi.clearAllMocks();
});
const launchServicesPlist = "com.apple.launchservices.secure.plist";
const chromeExecutablePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
it("prefers default Chromium browser on macOS", () => {
function mockMacDefaultBrowser(bundleId: string, appPath = ""): void {
vi.mocked(execFileSync).mockImplementation((cmd, args) => {
const argsStr = Array.isArray(args) ? args.join(" ") : "";
if (cmd === "/usr/bin/plutil" && argsStr.includes("LSHandlers")) {
return JSON.stringify([
{ LSHandlerURLScheme: "http", LSHandlerRoleAll: "com.google.Chrome" },
]);
return JSON.stringify([{ LSHandlerURLScheme: "http", LSHandlerRoleAll: bundleId }]);
}
if (cmd === "/usr/bin/osascript" && argsStr.includes("path to application id")) {
return "/Applications/Google Chrome.app";
return appPath;
}
if (cmd === "/usr/bin/defaults") {
return "Google Chrome";
}
return "";
});
}
function mockChromeExecutableExists(): void {
vi.mocked(fs.existsSync).mockImplementation((p) => {
const value = String(p);
if (value.includes("com.apple.launchservices.secure.plist")) {
if (value.includes(launchServicesPlist)) {
return true;
}
return value.includes("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
return value.includes(chromeExecutablePath);
});
}
beforeEach(() => {
vi.clearAllMocks();
});
it("prefers default Chromium browser on macOS", () => {
mockMacDefaultBrowser("com.google.Chrome", "/Applications/Google Chrome.app");
mockChromeExecutableExists();
const exe = resolveBrowserExecutableForPlatform(
{} as Parameters<typeof resolveBrowserExecutableForPlatform>[0],
@@ -55,22 +64,8 @@ describe("browser default executable detection", () => {
});
it("falls back when default browser is non-Chromium on macOS", () => {
vi.mocked(execFileSync).mockImplementation((cmd, args) => {
const argsStr = Array.isArray(args) ? args.join(" ") : "";
if (cmd === "/usr/bin/plutil" && argsStr.includes("LSHandlers")) {
return JSON.stringify([
{ LSHandlerURLScheme: "http", LSHandlerRoleAll: "com.apple.Safari" },
]);
}
return "";
});
vi.mocked(fs.existsSync).mockImplementation((p) => {
const value = String(p);
if (value.includes("com.apple.launchservices.secure.plist")) {
return true;
}
return value.includes("Google Chrome.app/Contents/MacOS/Google Chrome");
});
mockMacDefaultBrowser("com.apple.Safari");
mockChromeExecutableExists();
const exe = resolveBrowserExecutableForPlatform(
{} as Parameters<typeof resolveBrowserExecutableForPlatform>[0],

View File

@@ -22,6 +22,15 @@ async function readJson(filePath: string): Promise<Record<string, unknown>> {
return JSON.parse(raw) as Record<string, unknown>;
}
async function readDefaultProfileFromLocalState(
userDataDir: string,
): Promise<Record<string, unknown>> {
const localState = await readJson(path.join(userDataDir, "Local State"));
const profile = localState.profile as Record<string, unknown>;
const infoCache = profile.info_cache as Record<string, unknown>;
return infoCache.Default as Record<string, unknown>;
}
describe("browser chrome profile decoration", () => {
let fixtureRoot = "";
let fixtureCount = 0;
@@ -53,10 +62,7 @@ describe("browser chrome profile decoration", () => {
const expectedSignedArgb = ((0xff << 24) | 0xff4500) >> 0;
const localState = await readJson(path.join(userDataDir, "Local State"));
const profile = localState.profile as Record<string, unknown>;
const infoCache = profile.info_cache as Record<string, unknown>;
const def = infoCache.Default as Record<string, unknown>;
const def = await readDefaultProfileFromLocalState(userDataDir);
expect(def.name).toBe(DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME);
expect(def.shortcut_name).toBe(DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME);
@@ -84,10 +90,7 @@ describe("browser chrome profile decoration", () => {
it("best-effort writes name when color is invalid", async () => {
const userDataDir = await createUserDataDir();
decorateOpenClawProfile(userDataDir, { color: "lobster-orange" });
const localState = await readJson(path.join(userDataDir, "Local State"));
const profile = localState.profile as Record<string, unknown>;
const infoCache = profile.info_cache as Record<string, unknown>;
const def = infoCache.Default as Record<string, unknown>;
const def = await readDefaultProfileFromLocalState(userDataDir);
expect(def.name).toBe(DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME);
expect(def.profile_color_seed).toBeUndefined();

View File

@@ -14,6 +14,7 @@ import type { TelegramProbe } from "../../telegram/probe.js";
import type { TelegramTokenResolution } from "../../telegram/token.js";
import {
createChannelTestPluginBase,
createMSTeamsTestPluginBase,
createOutboundTestPlugin,
createTestRegistry,
} from "../../test-utils/channel-plugins.js";
@@ -131,20 +132,7 @@ const msteamsOutbound: ChannelOutboundAdapter = {
};
const msteamsPlugin: ChannelPlugin = {
id: "msteams",
meta: {
id: "msteams",
label: "Microsoft Teams",
selectionLabel: "Microsoft Teams (Bot Framework)",
docsPath: "/channels/msteams",
blurb: "Bot Framework; enterprise support.",
aliases: ["teams"],
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: () => [],
resolveAccount: () => ({}),
},
...createMSTeamsTestPluginBase(),
outbound: msteamsOutbound,
};

View File

@@ -50,6 +50,16 @@ vi.mock("../imessage/send.js", () => {
});
describe("createDefaultDeps", () => {
function expectUnusedModulesNotLoaded(exclude: keyof typeof moduleLoads): void {
const keys = Object.keys(moduleLoads) as Array<keyof typeof moduleLoads>;
for (const key of keys) {
if (key === exclude) {
continue;
}
expect(moduleLoads[key]).not.toHaveBeenCalled();
}
}
beforeEach(() => {
vi.clearAllMocks();
});
@@ -71,11 +81,7 @@ describe("createDefaultDeps", () => {
expect(moduleLoads.telegram).toHaveBeenCalledTimes(1);
expect(sendFns.telegram).toHaveBeenCalledTimes(1);
expect(moduleLoads.whatsapp).not.toHaveBeenCalled();
expect(moduleLoads.discord).not.toHaveBeenCalled();
expect(moduleLoads.slack).not.toHaveBeenCalled();
expect(moduleLoads.signal).not.toHaveBeenCalled();
expect(moduleLoads.imessage).not.toHaveBeenCalled();
expectUnusedModulesNotLoaded("telegram");
});
it("reuses module cache after first dynamic import", async () => {

View File

@@ -104,6 +104,17 @@ function makeRuntime() {
};
}
function expectModelRegistryUnavailable(
runtime: ReturnType<typeof makeRuntime>,
expectedDetail: string,
) {
expect(runtime.error).toHaveBeenCalledTimes(1);
expect(runtime.error.mock.calls[0]?.[0]).toContain("Model registry unavailable:");
expect(runtime.error.mock.calls[0]?.[0]).toContain(expectedDetail);
expect(runtime.log).not.toHaveBeenCalled();
expect(process.exitCode).toBe(1);
}
beforeEach(() => {
previousExitCode = process.exitCode;
process.exitCode = undefined;
@@ -432,12 +443,8 @@ describe("models list/status", () => {
const runtime = makeRuntime();
await modelsListCommand({ json: true }, runtime);
expect(runtime.error).toHaveBeenCalledTimes(1);
expect(runtime.error.mock.calls[0]?.[0]).toContain("Model registry unavailable:");
expect(runtime.error.mock.calls[0]?.[0]).toContain("model discovery failed");
expectModelRegistryUnavailable(runtime, "model discovery failed");
expect(runtime.error.mock.calls[0]?.[0]).not.toContain("configured models may appear missing");
expect(runtime.log).not.toHaveBeenCalled();
expect(process.exitCode).toBe(1);
});
it("models list fails fast when registry model discovery is unavailable", async () => {
@@ -452,11 +459,7 @@ describe("models list/status", () => {
modelRegistryState.available = [];
await modelsListCommand({ json: true }, runtime);
expect(runtime.error).toHaveBeenCalledTimes(1);
expect(runtime.error.mock.calls[0]?.[0]).toContain("Model registry unavailable:");
expect(runtime.error.mock.calls[0]?.[0]).toContain("model discovery unavailable");
expect(runtime.log).not.toHaveBeenCalled();
expect(process.exitCode).toBe(1);
expectModelRegistryUnavailable(runtime, "model discovery unavailable");
});
it("loadModelRegistry throws when model discovery is unavailable", async () => {

View File

@@ -51,6 +51,27 @@ export const createChannelTestPluginBase = (params: {
},
});
export const createMSTeamsTestPluginBase = (): Pick<
ChannelPlugin,
"id" | "meta" | "capabilities" | "config"
> => {
const base = createChannelTestPluginBase({
id: "msteams",
label: "Microsoft Teams",
docsPath: "/channels/msteams",
config: { listAccountIds: () => [], resolveAccount: () => ({}) },
});
return {
...base,
meta: {
...base.meta,
selectionLabel: "Microsoft Teams (Bot Framework)",
blurb: "Bot Framework; enterprise support.",
aliases: ["teams"],
},
};
};
export const createOutboundTestPlugin = (params: {
id: ChannelId;
outbound: ChannelOutboundAdapter;

View File

@@ -1,43 +1,13 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { ChannelPlugin } from "../channels/plugins/types.js";
import type { PluginRegistry } from "../plugins/registry.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { createMSTeamsTestPluginBase, createTestRegistry } from "../test-utils/channel-plugins.js";
import { resolveGatewayMessageChannel } from "./message-channel.js";
const createRegistry = (channels: PluginRegistry["channels"]): PluginRegistry => ({
plugins: [],
tools: [],
hooks: [],
typedHooks: [],
channels,
commands: [],
providers: [],
gatewayHandlers: {},
httpHandlers: [],
httpRoutes: [],
cliRegistrars: [],
services: [],
diagnostics: [],
});
const emptyRegistry = createRegistry([]);
const msteamsPlugin = {
id: "msteams",
meta: {
id: "msteams",
label: "Microsoft Teams",
selectionLabel: "Microsoft Teams (Bot Framework)",
docsPath: "/channels/msteams",
blurb: "Bot Framework; enterprise support.",
aliases: ["teams"],
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: () => [],
resolveAccount: () => ({}),
},
} satisfies ChannelPlugin;
const emptyRegistry = createTestRegistry([]);
const msteamsPlugin: ChannelPlugin = {
...createMSTeamsTestPluginBase(),
};
describe("message-channel", () => {
beforeEach(() => {
@@ -57,7 +27,7 @@ describe("message-channel", () => {
it("normalizes plugin aliases when registered", () => {
setActivePluginRegistry(
createRegistry([{ pluginId: "msteams", plugin: msteamsPlugin, source: "test" }]),
createTestRegistry([{ pluginId: "msteams", plugin: msteamsPlugin, source: "test" }]),
);
expect(resolveGatewayMessageChannel("teams")).toBe("msteams");
});