mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-05 12:28:23 +00:00
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:
+12
-2
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
Reference in New Issue
Block a user