feat(FN-3053): consolidate mock helpers across CLI, dashboard, and engine p
The merge consolidates mock helpers across packages by creating a new shared `mockCoreEngine.ts` in the CLI package and strengthening the core/engine helpers, then migrating both CLI command tests and the dashboard's `AgentsView` tests to use the canonical helpers. It also publishes SSE architecture Fusion-Task-Id: FN-3053
This commit is contained in:
@@ -452,7 +452,9 @@ const mocks = vi.hoisted(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("@fusion/core", () => ({
|
vi.mock("@fusion/core", async (importOriginal) => {
|
||||||
|
const { createCliCoreMock } = await import("../../test/mockCoreEngine");
|
||||||
|
return createCliCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||||
TaskStore: mocks.taskStoreCtor,
|
TaskStore: mocks.taskStoreCtor,
|
||||||
AutomationStore: mocks.automationStoreCtor,
|
AutomationStore: mocks.automationStoreCtor,
|
||||||
AgentStore: mocks.agentStoreCtor,
|
AgentStore: mocks.agentStoreCtor,
|
||||||
@@ -474,7 +476,8 @@ vi.mock("@fusion/core", () => ({
|
|||||||
syncInsightExtractionAutomation: mocks.syncInsightExtractionAutomationMock,
|
syncInsightExtractionAutomation: mocks.syncInsightExtractionAutomationMock,
|
||||||
INSIGHT_EXTRACTION_SCHEDULE_NAME: "Memory Insight Extraction",
|
INSIGHT_EXTRACTION_SCHEDULE_NAME: "Memory Insight Extraction",
|
||||||
processAndAuditInsightExtraction: mocks.processAndAuditInsightExtractionMock,
|
processAndAuditInsightExtraction: mocks.processAndAuditInsightExtractionMock,
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("@fusion/dashboard", () => ({
|
vi.mock("@fusion/dashboard", () => ({
|
||||||
createServer: mocks.createServerMock,
|
createServer: mocks.createServerMock,
|
||||||
@@ -484,7 +487,9 @@ vi.mock("@fusion/dashboard", () => ({
|
|||||||
loadTlsCredentialsFromEnv: vi.fn().mockReturnValue(undefined),
|
loadTlsCredentialsFromEnv: vi.fn().mockReturnValue(undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@fusion/engine", () => ({
|
vi.mock("@fusion/engine", async (importOriginal) => {
|
||||||
|
const { createCliEngineMock } = await import("../../test/mockCoreEngine");
|
||||||
|
return createCliEngineMock(() => importOriginal<typeof import("@fusion/engine")>(), {
|
||||||
ProjectEngine: mocks.projectEngineCtor,
|
ProjectEngine: mocks.projectEngineCtor,
|
||||||
ProjectEngineManager: vi.fn().mockImplementation((centralCore: any, options: any) => {
|
ProjectEngineManager: vi.fn().mockImplementation((centralCore: any, options: any) => {
|
||||||
const engines = new Map<string, any>();
|
const engines = new Map<string, any>();
|
||||||
@@ -546,7 +551,8 @@ vi.mock("@fusion/engine", () => ({
|
|||||||
createAiPromptExecutor: mocks.createAiPromptExecutorMock,
|
createAiPromptExecutor: mocks.createAiPromptExecutorMock,
|
||||||
HeartbeatMonitor: mocks.heartbeatMonitorCtor,
|
HeartbeatMonitor: mocks.heartbeatMonitorCtor,
|
||||||
HeartbeatTriggerScheduler: mocks.heartbeatTriggerSchedulerCtor,
|
HeartbeatTriggerScheduler: mocks.heartbeatTriggerSchedulerCtor,
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
||||||
AuthStorage: {
|
AuthStorage: {
|
||||||
|
|||||||
@@ -105,7 +105,9 @@ function makeMockStore() {
|
|||||||
|
|
||||||
// ── Mock @fusion/core ──────────────────────────────────────────────────
|
// ── Mock @fusion/core ──────────────────────────────────────────────────
|
||||||
|
|
||||||
vi.mock("@fusion/core", () => ({
|
vi.mock("@fusion/core", async (importOriginal) => {
|
||||||
|
const { createCliCoreMock } = await import("../../test/mockCoreEngine");
|
||||||
|
return createCliCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||||
TaskStore: vi.fn().mockImplementation(() => makeMockStore()),
|
TaskStore: vi.fn().mockImplementation(() => makeMockStore()),
|
||||||
CentralCore: vi.fn().mockImplementation(() => ({
|
CentralCore: vi.fn().mockImplementation(() => ({
|
||||||
init: vi.fn().mockResolvedValue(undefined),
|
init: vi.fn().mockResolvedValue(undefined),
|
||||||
@@ -205,7 +207,8 @@ vi.mock("@fusion/core", () => ({
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}),
|
}),
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ── Hoisted shared mocks ───────────────────────────────────────────
|
// ── Hoisted shared mocks ───────────────────────────────────────────
|
||||||
|
|
||||||
@@ -305,6 +308,7 @@ const { WorktreePool } = await import("@fusion/engine");
|
|||||||
|
|
||||||
vi.mock("@fusion/engine", async (importOriginal) => {
|
vi.mock("@fusion/engine", async (importOriginal) => {
|
||||||
const original = await importOriginal<typeof import("@fusion/engine")>();
|
const original = await importOriginal<typeof import("@fusion/engine")>();
|
||||||
|
const { createCliEngineMock } = await import("../../test/mockCoreEngine");
|
||||||
const TriageProcessor = vi.fn().mockImplementation(() => ({
|
const TriageProcessor = vi.fn().mockImplementation(() => ({
|
||||||
start: vi.fn(),
|
start: vi.fn(),
|
||||||
stop: vi.fn(),
|
stop: vi.fn(),
|
||||||
@@ -610,8 +614,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return createCliEngineMock(async () => original, {}, {
|
||||||
...original,
|
|
||||||
// Keep real WorktreePool & AgentSemaphore
|
// Keep real WorktreePool & AgentSemaphore
|
||||||
WorktreePool: original.WorktreePool,
|
WorktreePool: original.WorktreePool,
|
||||||
AgentSemaphore: original.AgentSemaphore,
|
AgentSemaphore: original.AgentSemaphore,
|
||||||
@@ -681,7 +684,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
|||||||
})),
|
})),
|
||||||
scanIdleWorktrees: vi.fn().mockResolvedValue([]),
|
scanIdleWorktrees: vi.fn().mockResolvedValue([]),
|
||||||
cleanupOrphanedWorktrees: vi.fn().mockResolvedValue(0),
|
cleanupOrphanedWorktrees: vi.fn().mockResolvedValue(0),
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Mock @mariozechner/pi-coding-agent ──────────────────────────────
|
// ── Mock @mariozechner/pi-coding-agent ──────────────────────────────
|
||||||
|
|||||||
@@ -511,7 +511,9 @@ const mocks = vi.hoisted(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("@fusion/core", () => ({
|
vi.mock("@fusion/core", async (importOriginal) => {
|
||||||
|
const { createCliCoreMock } = await import("../../test/mockCoreEngine");
|
||||||
|
return createCliCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||||
TaskStore: mocks.taskStoreCtor,
|
TaskStore: mocks.taskStoreCtor,
|
||||||
AutomationStore: mocks.automationStoreCtor,
|
AutomationStore: mocks.automationStoreCtor,
|
||||||
AgentStore: mocks.agentStoreCtor,
|
AgentStore: mocks.agentStoreCtor,
|
||||||
@@ -530,7 +532,8 @@ vi.mock("@fusion/core", () => ({
|
|||||||
})),
|
})),
|
||||||
GlobalSettingsStore: vi.fn().mockImplementation(() => ({})),
|
GlobalSettingsStore: vi.fn().mockImplementation(() => ({})),
|
||||||
resolveGlobalDir: vi.fn().mockReturnValue("/mock/global"),
|
resolveGlobalDir: vi.fn().mockReturnValue("/mock/global"),
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("@fusion/dashboard", () => ({
|
vi.mock("@fusion/dashboard", () => ({
|
||||||
createServer: mocks.createServerMock,
|
createServer: mocks.createServerMock,
|
||||||
@@ -540,7 +543,9 @@ vi.mock("@fusion/dashboard", () => ({
|
|||||||
loadTlsCredentialsFromEnv: vi.fn().mockReturnValue(undefined),
|
loadTlsCredentialsFromEnv: vi.fn().mockReturnValue(undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@fusion/engine", () => ({
|
vi.mock("@fusion/engine", async (importOriginal) => {
|
||||||
|
const { createCliEngineMock } = await import("../../test/mockCoreEngine");
|
||||||
|
return createCliEngineMock(() => importOriginal<typeof import("@fusion/engine")>(), {
|
||||||
ProjectEngine: mocks.projectEngineCtor,
|
ProjectEngine: mocks.projectEngineCtor,
|
||||||
ProjectEngineManager: vi.fn().mockImplementation((centralCore: any, options: any) => {
|
ProjectEngineManager: vi.fn().mockImplementation((centralCore: any, options: any) => {
|
||||||
const engines = new Map<string, any>();
|
const engines = new Map<string, any>();
|
||||||
@@ -606,7 +611,8 @@ vi.mock("@fusion/engine", () => ({
|
|||||||
createAiPromptExecutor: mocks.createAiPromptExecutorMock,
|
createAiPromptExecutor: mocks.createAiPromptExecutorMock,
|
||||||
HeartbeatMonitor: mocks.heartbeatMonitorCtor,
|
HeartbeatMonitor: mocks.heartbeatMonitorCtor,
|
||||||
HeartbeatTriggerScheduler: mocks.heartbeatTriggerSchedulerCtor,
|
HeartbeatTriggerScheduler: mocks.heartbeatTriggerSchedulerCtor,
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
||||||
AuthStorage: {
|
AuthStorage: {
|
||||||
|
|||||||
33
packages/cli/src/test/mockCoreEngine.test.ts
Normal file
33
packages/cli/src/test/mockCoreEngine.test.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
createCliCoreMock,
|
||||||
|
createCliEngineMock,
|
||||||
|
resetCliCoreEngineMockState,
|
||||||
|
} from "./mockCoreEngine";
|
||||||
|
|
||||||
|
describe("cli test mock helpers", () => {
|
||||||
|
it("creates stable fallback functions for missing callable exports", async () => {
|
||||||
|
const module = await createCliCoreMock(async () => ({ known: vi.fn() }));
|
||||||
|
|
||||||
|
const first = module.missingThing as ReturnType<typeof vi.fn>;
|
||||||
|
const second = module.missingThing as ReturnType<typeof vi.fn>;
|
||||||
|
expect(first).toBe(second);
|
||||||
|
|
||||||
|
first("x");
|
||||||
|
expect(first).toHaveBeenCalledWith("x");
|
||||||
|
|
||||||
|
resetCliCoreEngineMockState();
|
||||||
|
expect(first).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps real non-function exports while allowing callable overrides", async () => {
|
||||||
|
const module = await createCliEngineMock(
|
||||||
|
async () => ({ VERSION: "1.0.0", factory: () => "real" }),
|
||||||
|
{},
|
||||||
|
{ factory: vi.fn().mockReturnValue("mocked") },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(module.VERSION).toBe("1.0.0");
|
||||||
|
expect((module.factory as () => string)()).toBe("mocked");
|
||||||
|
});
|
||||||
|
});
|
||||||
59
packages/cli/src/test/mockCoreEngine.ts
Normal file
59
packages/cli/src/test/mockCoreEngine.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Canonical @fusion/core and @fusion/engine mock helpers for CLI command tests.
|
||||||
|
*
|
||||||
|
* When a new commonly-mocked export is added, update defaults here instead of
|
||||||
|
* copying large inline export lists into command suites.
|
||||||
|
*/
|
||||||
|
import { vi, type Mock } from "vitest";
|
||||||
|
|
||||||
|
type AnyModule = Record<string, unknown>;
|
||||||
|
type AnyMock = Mock;
|
||||||
|
|
||||||
|
const fallbackFns = new Map<string, AnyMock>();
|
||||||
|
|
||||||
|
function getFallback(name: string): AnyMock {
|
||||||
|
if (!fallbackFns.has(name)) {
|
||||||
|
fallbackFns.set(name, vi.fn());
|
||||||
|
}
|
||||||
|
return fallbackFns.get(name)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withFallbackFunctions(actual: AnyModule, mocked: AnyModule): AnyModule {
|
||||||
|
return new Proxy(mocked, {
|
||||||
|
get(target, prop, receiver) {
|
||||||
|
if (typeof prop !== "string") return Reflect.get(target, prop, receiver);
|
||||||
|
if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
|
||||||
|
if (["then", "catch", "finally"].includes(prop)) return undefined;
|
||||||
|
|
||||||
|
const actualValue = actual[prop];
|
||||||
|
if (typeof actualValue === "function" || actualValue === undefined) {
|
||||||
|
const fn = getFallback(prop);
|
||||||
|
target[prop] = fn;
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
return actualValue;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCliCoreMock(
|
||||||
|
importActual: () => Promise<AnyModule>,
|
||||||
|
defaults: AnyModule = {},
|
||||||
|
overrides: AnyModule = {},
|
||||||
|
): Promise<AnyModule> {
|
||||||
|
const actual = await importActual();
|
||||||
|
return withFallbackFunctions(actual, { ...actual, ...defaults, ...overrides });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCliEngineMock(
|
||||||
|
importActual: () => Promise<AnyModule>,
|
||||||
|
defaults: AnyModule = {},
|
||||||
|
overrides: AnyModule = {},
|
||||||
|
): Promise<AnyModule> {
|
||||||
|
const actual = await importActual();
|
||||||
|
return withFallbackFunctions(actual, { ...actual, ...defaults, ...overrides });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetCliCoreEngineMockState(): void {
|
||||||
|
for (const fn of fallbackFns.values()) fn.mockReset();
|
||||||
|
}
|
||||||
@@ -6,26 +6,29 @@ import type { Agent, AgentState, AgentCapability, OrgTreeNode } from "../../api"
|
|||||||
import { scopedKey } from "../../utils/projectStorage";
|
import { scopedKey } from "../../utils/projectStorage";
|
||||||
|
|
||||||
// Mock the API module
|
// Mock the API module
|
||||||
vi.mock("../../api", () => ({
|
vi.mock("../../api", async (importOriginal) => {
|
||||||
fetchAgents: vi.fn(),
|
const { createDashboardApiMock } = await import("../../test/mockApi");
|
||||||
fetchAgentStats: vi.fn(),
|
return createDashboardApiMock(() => importOriginal<typeof import("../../api")>(), {
|
||||||
createAgent: vi.fn(),
|
fetchAgents: vi.fn(),
|
||||||
updateAgent: vi.fn(),
|
fetchAgentStats: vi.fn(),
|
||||||
updateAgentState: vi.fn(),
|
createAgent: vi.fn(),
|
||||||
deleteAgent: vi.fn(),
|
updateAgent: vi.fn(),
|
||||||
startAgentRun: vi.fn(),
|
updateAgentState: vi.fn(),
|
||||||
fetchOrgTree: vi.fn(),
|
deleteAgent: vi.fn(),
|
||||||
fetchSettings: vi.fn().mockResolvedValue({ heartbeatMultiplier: 1 }),
|
startAgentRun: vi.fn(),
|
||||||
updateSettings: vi.fn().mockResolvedValue({}),
|
fetchOrgTree: vi.fn(),
|
||||||
fetchModels: vi.fn().mockResolvedValue({ models: [] }),
|
fetchSettings: vi.fn().mockResolvedValue({ heartbeatMultiplier: 1 }),
|
||||||
fetchPluginRuntimes: vi.fn().mockResolvedValue([]),
|
updateSettings: vi.fn().mockResolvedValue({}),
|
||||||
fetchDiscoveredSkills: vi.fn().mockResolvedValue([]),
|
fetchModels: vi.fn().mockResolvedValue({ models: [] }),
|
||||||
startAgentOnboardingStreaming: vi.fn().mockResolvedValue({ sessionId: "onb-1" }),
|
fetchPluginRuntimes: vi.fn().mockResolvedValue([]),
|
||||||
respondToAgentOnboarding: vi.fn().mockResolvedValue({ type: "question", data: { id: "q1", type: "text", question: "?" } }),
|
fetchDiscoveredSkills: vi.fn().mockResolvedValue([]),
|
||||||
retryAgentOnboardingSession: vi.fn().mockResolvedValue({ success: true, sessionId: "onb-1" }),
|
startAgentOnboardingStreaming: vi.fn().mockResolvedValue({ sessionId: "onb-1" }),
|
||||||
stopAgentOnboardingGeneration: vi.fn().mockResolvedValue({ success: true }),
|
respondToAgentOnboarding: vi.fn().mockResolvedValue({ type: "question", data: { id: "q1", type: "text", question: "?" } }),
|
||||||
cancelAgentOnboarding: vi.fn().mockResolvedValue(undefined),
|
retryAgentOnboardingSession: vi.fn().mockResolvedValue({ success: true, sessionId: "onb-1" }),
|
||||||
}));
|
stopAgentOnboardingGeneration: vi.fn().mockResolvedValue({ success: true }),
|
||||||
|
cancelAgentOnboarding: vi.fn().mockResolvedValue(undefined),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock("../AgentDetailView", () => ({
|
vi.mock("../AgentDetailView", () => ({
|
||||||
AgentDetailView: ({ agentId }: { agentId: string }) => <div data-testid="agent-detail-view">Agent detail: {agentId}</div>,
|
AgentDetailView: ({ agentId }: { agentId: string }) => <div data-testid="agent-detail-view">Agent detail: {agentId}</div>,
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
*
|
*
|
||||||
* Add new `app/api.ts` or `app/api/legacy.ts` exports here first before
|
* Add new `app/api.ts` or `app/api/legacy.ts` exports here first before
|
||||||
* introducing ad-hoc per-test `vi.mock("../../api", …)` export lists.
|
* introducing ad-hoc per-test `vi.mock("../../api", …)` export lists.
|
||||||
|
*
|
||||||
|
* Behavior contract:
|
||||||
|
* - preserve real exports by default
|
||||||
|
* - apply canonical/common test mocks
|
||||||
|
* - allow per-suite overrides
|
||||||
|
* - auto-synthesize stable fallback fns for missing callable exports
|
||||||
*/
|
*/
|
||||||
import { vi, type Mock } from "vitest";
|
import { vi, type Mock } from "vitest";
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ describe("dashboard test mock helpers", () => {
|
|||||||
expect(rendered.props.className).toBe("x");
|
expect(rendered.props.className).toBe("x");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("preserves real non-function exports and respects overrides", async () => {
|
||||||
|
const module = await createDashboardApiMock(
|
||||||
|
async () => ({ API_VERSION: "v1", fetchTasks: async () => ["real"] }),
|
||||||
|
{ fetchTasks: vi.fn().mockResolvedValue(["mocked"]) },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(module.API_VERSION).toBe("v1");
|
||||||
|
await expect((module.fetchTasks as () => Promise<string[]>)()).resolves.toEqual(["mocked"]);
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps canonical dashboard api mocks resettable", () => {
|
it("keeps canonical dashboard api mocks resettable", () => {
|
||||||
dashboardApiMocks.fetchSettings.mockResolvedValueOnce({ hello: "world" });
|
dashboardApiMocks.fetchSettings.mockResolvedValueOnce({ hello: "world" });
|
||||||
resetDashboardApiMockState();
|
resetDashboardApiMockState();
|
||||||
|
|||||||
@@ -1,27 +1,56 @@
|
|||||||
/**
|
/**
|
||||||
* Canonical @fusion/core and @fusion/engine mock helpers for dashboard server tests.
|
* Canonical @fusion/core and @fusion/engine mock helpers for dashboard server tests.
|
||||||
*
|
*
|
||||||
* Add new mocked exports here first before duplicating large per-suite mock objects.
|
* If a route test starts failing with "No \"X\" export is defined", update this
|
||||||
|
* helper first instead of adding another full inline export map in the test file.
|
||||||
*/
|
*/
|
||||||
import { vi } from "vitest";
|
import { vi, type Mock } from "vitest";
|
||||||
|
|
||||||
type AnyModule = Record<string, unknown>;
|
type AnyModule = Record<string, unknown>;
|
||||||
|
type AnyMock = Mock;
|
||||||
|
|
||||||
|
const fallbackFns = new Map<string, AnyMock>();
|
||||||
|
|
||||||
|
function getFallback(name: string): AnyMock {
|
||||||
|
if (!fallbackFns.has(name)) fallbackFns.set(name, vi.fn());
|
||||||
|
return fallbackFns.get(name)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withFallbackFunctions(actual: AnyModule, moduleValue: AnyModule): AnyModule {
|
||||||
|
return new Proxy(moduleValue, {
|
||||||
|
get(target, prop, receiver) {
|
||||||
|
if (typeof prop !== "string") return Reflect.get(target, prop, receiver);
|
||||||
|
if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
|
||||||
|
if (["then", "catch", "finally"].includes(prop)) return undefined;
|
||||||
|
|
||||||
|
const actualValue = actual[prop];
|
||||||
|
if (typeof actualValue === "function" || actualValue === undefined) {
|
||||||
|
const fn = getFallback(prop);
|
||||||
|
target[prop] = fn;
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
return actualValue;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function createCoreMock(
|
export async function createCoreMock(
|
||||||
importActual: () => Promise<AnyModule>,
|
importActual: () => Promise<AnyModule>,
|
||||||
overrides: AnyModule = {},
|
overrides: AnyModule = {},
|
||||||
): Promise<AnyModule> {
|
): Promise<AnyModule> {
|
||||||
const actual = await importActual();
|
const actual = await importActual();
|
||||||
return {
|
return withFallbackFunctions(actual, { ...actual, ...overrides });
|
||||||
...actual,
|
|
||||||
...overrides,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEngineMock(overrides: AnyModule = {}): AnyModule {
|
export function createEngineMock(overrides: AnyModule = {}): AnyModule {
|
||||||
return {
|
const actual: AnyModule = {};
|
||||||
|
return withFallbackFunctions(actual, {
|
||||||
createFnAgent: vi.fn(),
|
createFnAgent: vi.fn(),
|
||||||
promptWithFallback: vi.fn(),
|
promptWithFallback: vi.fn(),
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetDashboardServerMockState(): void {
|
||||||
|
for (const fn of fallbackFns.values()) fn.mockReset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,45 @@
|
|||||||
/**
|
/**
|
||||||
* Canonical @fusion/core mock helper for engine tests.
|
* Canonical @fusion/core mock helper for engine tests.
|
||||||
*
|
*
|
||||||
* Prefer extending this helper over suite-local full export lists.
|
* Prefer extending this helper over suite-local full export lists; this is the
|
||||||
|
* first place to update when new @fusion/core exports are consumed by engine tests.
|
||||||
*/
|
*/
|
||||||
import type { Mock } from "vitest";
|
import { vi, type Mock } from "vitest";
|
||||||
|
|
||||||
type AnyModule = Record<string, unknown>;
|
type AnyModule = Record<string, unknown>;
|
||||||
|
|
||||||
|
const fallbackFns = new Map<string, Mock>();
|
||||||
|
|
||||||
|
function getFallback(name: string): Mock {
|
||||||
|
if (!fallbackFns.has(name)) fallbackFns.set(name, vi.fn());
|
||||||
|
return fallbackFns.get(name)!;
|
||||||
|
}
|
||||||
|
|
||||||
export async function createEngineCoreMock(
|
export async function createEngineCoreMock(
|
||||||
importActual: () => Promise<AnyModule>,
|
importActual: () => Promise<AnyModule>,
|
||||||
overrides: AnyModule = {},
|
overrides: AnyModule = {},
|
||||||
): Promise<AnyModule> {
|
): Promise<AnyModule> {
|
||||||
const actual = await importActual();
|
const actual = await importActual();
|
||||||
return {
|
const merged = { ...actual, ...overrides };
|
||||||
...actual,
|
return new Proxy(merged, {
|
||||||
...overrides,
|
get(target, prop, receiver) {
|
||||||
};
|
if (typeof prop !== "string") return Reflect.get(target, prop, receiver);
|
||||||
|
if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
|
||||||
|
if (["then", "catch", "finally"].includes(prop)) return undefined;
|
||||||
|
|
||||||
|
const actualValue = actual[prop];
|
||||||
|
if (typeof actualValue === "function" || actualValue === undefined) {
|
||||||
|
const fn = getFallback(prop);
|
||||||
|
target[prop] = fn;
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
return actualValue;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetEngineCoreMockState(): void {
|
||||||
|
for (const fn of fallbackFns.values()) fn.mockReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MockFn = Mock;
|
export type MockFn = Mock;
|
||||||
|
|||||||
Reference in New Issue
Block a user