fix(tests): replace leaking mock.module in pi tests with injected deps

CI run 35723560562 failed 3 tests in tests/utils/daemon-client.test.ts:
getDaemonBaseUrl returned the default http://127.0.0.1:8008 even for an
explicit daemonUrl. Root cause: PR #111 added mock.module over
src/utils/daemon-client in tests/integrations/pi.test.ts. Bun's
mock.module overrides persist for the rest of the run in the same
worker, so when worker scheduling runs pi.test.ts after daemon-client.ts
has been imported (cached) but before tests/utils/daemon-client.test.ts,
the mocked getDaemonBaseUrl (host/daemonUrl-ignoring) replaces the real
one. Deterministic on CI's Linux ordering, non-reproducible locally.

mock.restore() does not revert mock.module, so fix at the seam instead:
installPiIntegration now accepts optional { callDaemon, getDaemonBaseUrl }
deps (defaulting to the real implementations) and pi.test.ts injects the
mock. No mock.module remains for src/utils/daemon-client, eliminating the
cross-file leak entirely.
This commit is contained in:
redshift
2026-09-22 15:58:19 +03:00
parent 43110c7307
commit bc1dbeb998
2 changed files with 26 additions and 12 deletions
+12 -2
View File
@@ -165,17 +165,27 @@ type PiConfig = {
providers?: Record<string, PiProviderConfig>;
};
export type PiIntegrationDeps = {
callDaemon: typeof callDaemon;
getDaemonBaseUrl: typeof getDaemonBaseUrl;
};
export async function installPiIntegration(
config: RoutstrdConfig,
apiKey: string,
integrationConfig: IntegrationConfig,
// Injectable I/O so tests don't need mock.module, whose overrides leak
// across test files for the rest of the run under bun's runner.
deps: Partial<PiIntegrationDeps> = {},
): Promise<void> {
const { name, configPath } = integrationConfig;
const callDaemonFn = deps.callDaemon ?? callDaemon;
const getDaemonBaseUrlFn = deps.getDaemonBaseUrl ?? getDaemonBaseUrl;
console.log("\nInstalling routstr models in pi models.json...");
console.log(`Using API key for ${name}`);
const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
const baseUrl = `${getDaemonBaseUrlFn(config)}/v1`;
let piConfig: PiConfig = {};
@@ -197,7 +207,7 @@ export async function installPiIntegration(
// Ensure directory exists
mkdirSync(dirname(configPath), { recursive: true });
const data = await callDaemon("/models");
const data = await callDaemonFn("/models");
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
if (models.length === 0) {
+14 -10
View File
@@ -1,4 +1,4 @@
import { describe, expect, it, mock } from "bun:test";
import { describe, expect, it } from "bun:test";
import { mkdtempSync, readFileSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
@@ -10,12 +10,16 @@ import {
buildPiModelEntry,
deriveThinkingFields,
installPiIntegration,
type PiIntegrationDeps,
type PiModelEntry,
} from "../../src/integrations/pi";
import type { RoutstrdConfig } from "../../src/utils/config";
// Install the mock before importing modules that pull it in transitively.
mock.module("../../src/utils/daemon-client", () => ({
// Injected I/O instead of mock.module: bun's mock.module overrides leak
// across test files for the rest of the run and made tests/utils/
// daemon-client.test.ts fail non-deterministically depending on worker
// scheduling (see CI run 35723560562).
const MOCK_DEPS: Partial<PiIntegrationDeps> = {
callDaemon: async () => ({
output: {
models: [
@@ -36,7 +40,7 @@ mock.module("../../src/utils/daemon-client", () => ({
}),
getDaemonBaseUrl: (config: RoutstrdConfig) =>
`http://127.0.0.1:${config.port}`,
}));
};
const CONFIG: RoutstrdConfig = { port: 8008 } as RoutstrdConfig;
@@ -55,7 +59,7 @@ describe("installPiIntegration", () => {
it("pins supportsDeveloperRole=false for deepseek* models", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-models-"));
const configPath = join(dir, "models.json");
await installPiIntegration(CONFIG, "key", makeIntegration(configPath));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath), MOCK_DEPS);
const provider = await readProvider(configPath);
const deepseek = provider.models.find((m) => m.id === "deepseek-v4.1-flash");
@@ -65,7 +69,7 @@ describe("installPiIntegration", () => {
it("leaves non-deepseek models without a compat block", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-models-"));
const configPath = join(dir, "models.json");
await installPiIntegration(CONFIG, "key", makeIntegration(configPath));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath), MOCK_DEPS);
const provider = await readProvider(configPath);
const glm = provider.models.find((m) => m.id === "glm-5.3");
@@ -75,7 +79,7 @@ describe("installPiIntegration", () => {
it("preserves user compat keys on deepseek* models while pinning the role", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-models-"));
const configPath = join(dir, "models.json");
await installPiIntegration(CONFIG, "key", makeIntegration(configPath));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath), MOCK_DEPS);
// Simulate a user-curated refresh: seed reasoning/compat, run again.
const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as {
@@ -89,7 +93,7 @@ describe("installPiIntegration", () => {
const { writeFileSync } = await import("fs");
writeFileSync(configPath, JSON.stringify(parsed));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath), MOCK_DEPS);
const provider = await readProvider(configPath);
const updated = provider.models.find((m) => m.id === "deepseek-v4.1-flash");
expect(updated?.compat).toEqual({
@@ -102,7 +106,7 @@ describe("installPiIntegration", () => {
it("preserves compat untouched for non-deepseek models across refreshes", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-models-"));
const configPath = join(dir, "models.json");
await installPiIntegration(CONFIG, "key", makeIntegration(configPath));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath), MOCK_DEPS);
const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as {
providers: Record<string, { models: Array<Record<string, unknown>> }>;
@@ -112,7 +116,7 @@ describe("installPiIntegration", () => {
const { writeFileSync } = await import("fs");
writeFileSync(configPath, JSON.stringify(parsed));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath));
await installPiIntegration(CONFIG, "key", makeIntegration(configPath), MOCK_DEPS);
const provider = await readProvider(configPath);
const updated = provider.models.find((m) => m.id === "glm-5.3");
expect(updated?.compat).toEqual({ supportsDeveloperRole: true });