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,
|
||||
AutomationStore: mocks.automationStoreCtor,
|
||||
AgentStore: mocks.agentStoreCtor,
|
||||
@@ -474,7 +476,8 @@ vi.mock("@fusion/core", () => ({
|
||||
syncInsightExtractionAutomation: mocks.syncInsightExtractionAutomationMock,
|
||||
INSIGHT_EXTRACTION_SCHEDULE_NAME: "Memory Insight Extraction",
|
||||
processAndAuditInsightExtraction: mocks.processAndAuditInsightExtractionMock,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("@fusion/dashboard", () => ({
|
||||
createServer: mocks.createServerMock,
|
||||
@@ -484,7 +487,9 @@ vi.mock("@fusion/dashboard", () => ({
|
||||
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,
|
||||
ProjectEngineManager: vi.fn().mockImplementation((centralCore: any, options: any) => {
|
||||
const engines = new Map<string, any>();
|
||||
@@ -546,7 +551,8 @@ vi.mock("@fusion/engine", () => ({
|
||||
createAiPromptExecutor: mocks.createAiPromptExecutorMock,
|
||||
HeartbeatMonitor: mocks.heartbeatMonitorCtor,
|
||||
HeartbeatTriggerScheduler: mocks.heartbeatTriggerSchedulerCtor,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
||||
AuthStorage: {
|
||||
|
||||
@@ -105,7 +105,9 @@ function makeMockStore() {
|
||||
|
||||
// ── 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()),
|
||||
CentralCore: vi.fn().mockImplementation(() => ({
|
||||
init: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -205,7 +207,8 @@ vi.mock("@fusion/core", () => ({
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
// ── Hoisted shared mocks ───────────────────────────────────────────
|
||||
|
||||
@@ -305,6 +308,7 @@ const { WorktreePool } = await import("@fusion/engine");
|
||||
|
||||
vi.mock("@fusion/engine", async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import("@fusion/engine")>();
|
||||
const { createCliEngineMock } = await import("../../test/mockCoreEngine");
|
||||
const TriageProcessor = vi.fn().mockImplementation(() => ({
|
||||
start: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
@@ -610,8 +614,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...original,
|
||||
return createCliEngineMock(async () => original, {}, {
|
||||
// Keep real WorktreePool & AgentSemaphore
|
||||
WorktreePool: original.WorktreePool,
|
||||
AgentSemaphore: original.AgentSemaphore,
|
||||
@@ -681,7 +684,7 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
||||
})),
|
||||
scanIdleWorktrees: vi.fn().mockResolvedValue([]),
|
||||
cleanupOrphanedWorktrees: vi.fn().mockResolvedValue(0),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// ── 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,
|
||||
AutomationStore: mocks.automationStoreCtor,
|
||||
AgentStore: mocks.agentStoreCtor,
|
||||
@@ -530,7 +532,8 @@ vi.mock("@fusion/core", () => ({
|
||||
})),
|
||||
GlobalSettingsStore: vi.fn().mockImplementation(() => ({})),
|
||||
resolveGlobalDir: vi.fn().mockReturnValue("/mock/global"),
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("@fusion/dashboard", () => ({
|
||||
createServer: mocks.createServerMock,
|
||||
@@ -540,7 +543,9 @@ vi.mock("@fusion/dashboard", () => ({
|
||||
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,
|
||||
ProjectEngineManager: vi.fn().mockImplementation((centralCore: any, options: any) => {
|
||||
const engines = new Map<string, any>();
|
||||
@@ -606,7 +611,8 @@ vi.mock("@fusion/engine", () => ({
|
||||
createAiPromptExecutor: mocks.createAiPromptExecutorMock,
|
||||
HeartbeatMonitor: mocks.heartbeatMonitorCtor,
|
||||
HeartbeatTriggerScheduler: mocks.heartbeatTriggerSchedulerCtor,
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("@mariozechner/pi-coding-agent", () => ({
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user