FN-8670: reuse model runtime fixtures in engine tests
Share a warmed Pi model runtime across engine catalog tests. - Add an isolated in-memory model registry fixture backed by a per-file shared runtime. - Warm the runtime before catalog tests and verify custom-provider registry isolation. - Document the required fixture pattern for real Pi SDK catalog tests. Files changed: docs/testing.md | 1 + .../engine/src/__tests__/_model-runtime-fixture.ts | 40 ++++++++++++++++++++++ .../custom-providers-openai-completions.test.ts | 15 +++----- .../custom-providers-openai-responses.test.ts | 15 +++----- .../src/__tests__/provider-registration.test.ts | 33 ++++++++++++------ 5 files changed, 73 insertions(+), 31 deletions(-) Fusion-Task-Id: FN-8670 Fusion-Task-Lineage: 7410d114-1853-44cc-a09b-a997fbc7f119 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -631,6 +631,7 @@ When a test owns a process-wide singleton that has asynchronous owners (timers,
|
||||
- Prefer fake timers over real polling/time waits (FN-2707 pattern: advance timers inside `act(...)`, restore with `afterEach(() => vi.useRealTimers())`).
|
||||
- Do **not** mask slowness by raising worker/concurrency knobs (`FUSION_TEST_TOTAL_WORKERS`, `FUSION_TEST_CONCURRENCY`, `VITEST_MAX_WORKERS`, workspace concurrency settings).
|
||||
- Do **not** add net-new real-network calls, real-`setTimeout` polling loops, or mock-the-world component shells when a narrower seam exists.
|
||||
- Real Pi SDK catalog tests in the engine package must use `src/__tests__/_model-runtime-fixture.ts`: warm its shared runtime in `beforeAll` and request a fresh registry rather than constructing `ModelRuntime` inside timed test bodies.
|
||||
- Use the canonical taxonomy in **What NOT to write** and **What TO keep unconditionally** when deciding trim vs keep.
|
||||
- See `docs/test-speed-audit-FN-5048.md` for the measured baseline offender list and optimization priorities.
|
||||
|
||||
|
||||
40
packages/engine/src/__tests__/_model-runtime-fixture.ts
Normal file
40
packages/engine/src/__tests__/_model-runtime-fixture.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
/*
|
||||
FNXC:ModelCatalog 2026-08-01-08:14:
|
||||
FN-8670 moves real Pi SDK catalog construction out of timed test bodies into beforeAll. The runtime is
|
||||
memoized per test file because Vitest file-level isolation gives each file its own module graph.
|
||||
ModelRegistry.registerProvider writes into its shared ModelRuntime, so each handed-out registry clears
|
||||
prior extension providers before use. The SDK remains real, not stubbed, because this coverage catches
|
||||
catalog regressions such as FN-8564's native Kimi K3; widening timeouts or adding retries is forbidden here.
|
||||
*/
|
||||
let sharedModelRuntime: Promise<ModelRuntime> | undefined;
|
||||
|
||||
export function getSharedModelRuntime(): Promise<ModelRuntime> {
|
||||
sharedModelRuntime ??= ModelRuntime.create({
|
||||
credentials: {
|
||||
read: async () => undefined,
|
||||
list: async () => [],
|
||||
modify: async (_id, fn) => fn(undefined),
|
||||
delete: async () => undefined,
|
||||
},
|
||||
modelsPath: null,
|
||||
allowModelNetwork: false,
|
||||
});
|
||||
return sharedModelRuntime;
|
||||
}
|
||||
|
||||
export async function warmSharedModelRuntime(): Promise<void> {
|
||||
await getSharedModelRuntime();
|
||||
}
|
||||
|
||||
export async function createInMemoryModelRegistry(): Promise<ModelRegistry> {
|
||||
const modelRegistry = new ModelRegistry(await getSharedModelRuntime());
|
||||
|
||||
for (const providerId of modelRegistry.getRegisteredProviderIds()) {
|
||||
modelRegistry.unregisterProvider(providerId);
|
||||
}
|
||||
await modelRegistry.refresh();
|
||||
|
||||
return modelRegistry;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
||||
/*
|
||||
FNXC:Dependencies 2026-07-01-08:16:
|
||||
@@ -7,6 +6,7 @@ The pi 0.80 SDK keeps compatibility helpers under ./compat and exposes provider
|
||||
*/
|
||||
import { convertMessages } from "@earendil-works/pi-ai/api/openai-completions";
|
||||
import { customProviderRegistryKey, type CustomProvider } from "@fusion/core";
|
||||
import { createInMemoryModelRegistry, warmSharedModelRuntime } from "./_model-runtime-fixture.js";
|
||||
|
||||
function createSseResponse(): Response {
|
||||
const stream = new ReadableStream({
|
||||
@@ -21,14 +21,9 @@ function createSseResponse(): Response {
|
||||
}
|
||||
|
||||
|
||||
async function createInMemoryModelRegistry(): Promise<ModelRegistry> {
|
||||
const runtime = await ModelRuntime.create({
|
||||
credentials: { read: async () => undefined, list: async () => [], modify: async (_id, fn) => fn(undefined), delete: async () => undefined },
|
||||
modelsPath: null,
|
||||
allowModelNetwork: false,
|
||||
});
|
||||
return new ModelRegistry(runtime);
|
||||
}
|
||||
beforeAll(async () => {
|
||||
await warmSharedModelRuntime();
|
||||
});
|
||||
|
||||
describe("custom providers openai-completions regression", () => {
|
||||
afterEach(() => {
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { customProviderRegistryKey, type CustomProvider } from "@fusion/core";
|
||||
import { readCustomProviders } from "../custom-providers.js";
|
||||
import { createInMemoryModelRegistry, warmSharedModelRuntime } from "./_model-runtime-fixture.js";
|
||||
|
||||
async function createInMemoryModelRegistry(): Promise<ModelRegistry> {
|
||||
const runtime = await ModelRuntime.create({
|
||||
credentials: { read: async () => undefined, list: async () => [], modify: async (_id, fn) => fn(undefined), delete: async () => undefined },
|
||||
modelsPath: null,
|
||||
allowModelNetwork: false,
|
||||
});
|
||||
return new ModelRegistry(runtime);
|
||||
}
|
||||
beforeAll(async () => {
|
||||
await warmSharedModelRuntime();
|
||||
});
|
||||
|
||||
describe("custom providers openai-responses regression", () => {
|
||||
let homeDir: string;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { customProviderRegistryKey, type CustomProvider } from "@fusion/core";
|
||||
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
||||
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
||||
import { seedDashboardProviders } from "../provider-registration.js";
|
||||
import { registerCustomProviders } from "../custom-provider-registry.js";
|
||||
import { createInMemoryModelRegistry, warmSharedModelRuntime } from "./_model-runtime-fixture.js";
|
||||
|
||||
/*
|
||||
FNXC:ProviderRegistration 2026-07-07-00:00:
|
||||
@@ -36,15 +36,6 @@ function makeAuthStorage() {
|
||||
} as any;
|
||||
}
|
||||
|
||||
async function createInMemoryModelRegistry(): Promise<ModelRegistry> {
|
||||
const runtime = await ModelRuntime.create({
|
||||
credentials: { read: async () => undefined, list: async () => [], modify: async (_id, fn) => fn(undefined), delete: async () => undefined },
|
||||
modelsPath: null,
|
||||
allowModelNetwork: false,
|
||||
});
|
||||
return new ModelRegistry(runtime);
|
||||
}
|
||||
|
||||
function makeModelRegistry() {
|
||||
const registeredProviders = new Map<string, { models: Array<{ provider: string; id: string }> }>();
|
||||
return {
|
||||
@@ -94,6 +85,10 @@ function makeStore(initialCustomProviders?: CustomProvider[]) {
|
||||
};
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await warmSharedModelRuntime();
|
||||
});
|
||||
|
||||
const customProvider = (overrides: Partial<CustomProvider> = {}): CustomProvider => ({
|
||||
id: "550e8400-e29b-41d4-a716-446655440000",
|
||||
name: "Acme AI",
|
||||
@@ -144,6 +139,22 @@ describe("seedDashboardProviders", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("hands each real registry an isolated custom-provider catalog", async () => {
|
||||
const provider = customProvider({ id: "isolation-provider-id", name: "Fixture Isolation Provider" });
|
||||
const registryA = await createInMemoryModelRegistry();
|
||||
registryA.registerProvider(customProviderRegistryKey(provider, [provider]), {
|
||||
baseUrl: provider.baseUrl,
|
||||
api: "openai-completions",
|
||||
apiKey: provider.apiKey,
|
||||
models: [{ id: "acme-1", name: "Acme Model 1", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 16384 }],
|
||||
});
|
||||
|
||||
const registryB = await createInMemoryModelRegistry();
|
||||
|
||||
expect(registryB.find("fixture-isolation-provider", "acme-1")).toBeUndefined();
|
||||
expect(registryB.find("kimi-coding", "k3")).toMatchObject({ provider: "kimi-coding", id: "k3" });
|
||||
});
|
||||
|
||||
it("registers one custom provider alongside built-ins", async () => {
|
||||
const store = makeStore([customProvider()]);
|
||||
const authStorage = makeAuthStorage();
|
||||
|
||||
Reference in New Issue
Block a user