feat(FN-3044): add canonical mock helpers and migrate representative test s
This merge introduces canonical mock helper modules across the dashboard and engine packages, and migrates representative test suites to use them for consistency. The changes add four new mock helpers (`mockApi.ts`, `mockLucide.ts`, `mockCore.ts`, `mockCoreEngine.ts`) and harden the API mock proxy's Fusion-Task-Id: FN-3044
This commit is contained in:
@@ -28,9 +28,8 @@ vi.mock("../../sse-bus", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../../api", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../api")>();
|
||||
return {
|
||||
...actual,
|
||||
const { createDashboardApiMock } = await import("../../test/mockApi");
|
||||
return createDashboardApiMock(() => importOriginal<typeof import("../../api")>(), {
|
||||
fetchTasks: vi.fn(() => Promise.resolve([])),
|
||||
fetchConfig: vi.fn(() => Promise.resolve({ maxConcurrent: 2, rootDir: "/workspace/project" })),
|
||||
fetchSettings: vi.fn(() => Promise.resolve({ ...defaultSettings })),
|
||||
@@ -60,7 +59,7 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
fetchScripts: vi.fn(() => Promise.resolve({ build: "npm run build", test: "pnpm test" })),
|
||||
runScript: vi.fn(() => Promise.resolve({ sessionId: "sess-script-1", command: "echo hello" })),
|
||||
killPtyTerminalSession: vi.fn(() => Promise.resolve({ killed: true })),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const mockUseTasks = vi.fn(() => ({
|
||||
|
||||
@@ -50,51 +50,54 @@ const mockFetchRemoteUrl = vi.fn();
|
||||
const mockTriggerMemoryDreams = vi.fn();
|
||||
const mockUseWorkspaceFileBrowser = vi.fn();
|
||||
|
||||
vi.mock("../../api", () => ({
|
||||
fetchSettings: (...args: unknown[]) => mockFetchSettings(...args),
|
||||
fetchSettingsByScope: (...args: unknown[]) => mockFetchSettingsByScope(...args),
|
||||
updateSettings: (...args: unknown[]) => mockUpdateSettings(...args),
|
||||
updateGlobalSettings: (...args: unknown[]) => mockUpdateGlobalSettings(...args),
|
||||
exportSettings: (...args: unknown[]) => mockExportSettings(...args),
|
||||
importSettings: (...args: unknown[]) => mockImportSettings(...args),
|
||||
fetchAuthStatus: (...args: unknown[]) => mockFetchAuthStatus(...args),
|
||||
loginProvider: (...args: unknown[]) => mockLoginProvider(...args),
|
||||
logoutProvider: (...args: unknown[]) => mockLogoutProvider(...args),
|
||||
saveApiKey: (...args: unknown[]) => mockSaveApiKey(...args),
|
||||
fetchModels: (...args: unknown[]) => mockFetchModels(...args),
|
||||
fetchCustomProviders: (...args: unknown[]) => mockFetchCustomProviders(...args),
|
||||
createCustomProvider: (...args: unknown[]) => mockCreateCustomProvider(...args),
|
||||
updateCustomProvider: (...args: unknown[]) => mockUpdateCustomProvider(...args),
|
||||
deleteCustomProvider: (...args: unknown[]) => mockDeleteCustomProvider(...args),
|
||||
testNtfyNotification: (...args: unknown[]) => mockTestNtfyNotification(...args),
|
||||
testNotification: (...args: unknown[]) => mockTestNotification(...args),
|
||||
fetchBackups: (...args: unknown[]) => mockFetchBackups(...args),
|
||||
createBackup: (...args: unknown[]) => mockCreateBackup(...args),
|
||||
fetchMemoryFiles: (...args: unknown[]) => mockFetchMemoryFiles(...args),
|
||||
fetchMemoryFile: (...args: unknown[]) => mockFetchMemoryFile(...args),
|
||||
saveMemoryFile: (...args: unknown[]) => mockSaveMemoryFile(...args),
|
||||
compactMemory: (...args: unknown[]) => mockCompactMemory(...args),
|
||||
fetchGlobalConcurrency: (...args: unknown[]) => mockFetchGlobalConcurrency(...args),
|
||||
updateGlobalConcurrency: (...args: unknown[]) => mockUpdateGlobalConcurrency(...args),
|
||||
fetchMemoryBackendStatus: (...args: unknown[]) => mockFetchMemoryBackendStatus(...args),
|
||||
testMemoryRetrieval: (...args: unknown[]) => mockTestMemoryRetrieval(...args),
|
||||
installQmd: (...args: unknown[]) => mockInstallQmd(...args),
|
||||
fetchGitRemotesDetailed: (...args: unknown[]) => mockFetchGitRemotesDetailed(...args),
|
||||
fetchDashboardHealth: (...args: unknown[]) => mockFetchDashboardHealth(...args),
|
||||
checkForUpdates: (...args: unknown[]) => mockCheckForUpdates(...args),
|
||||
fetchRemoteSettings: (...args: unknown[]) => mockFetchRemoteSettings(...args),
|
||||
updateRemoteSettings: (...args: unknown[]) => mockUpdateRemoteSettings(...args),
|
||||
fetchRemoteStatus: (...args: unknown[]) => mockFetchRemoteStatus(...args),
|
||||
installCloudflared: (...args: unknown[]) => mockInstallCloudflared(...args),
|
||||
startRemoteTunnel: (...args: unknown[]) => mockStartRemoteTunnel(...args),
|
||||
stopRemoteTunnel: (...args: unknown[]) => mockStopRemoteTunnel(...args),
|
||||
killExternalTunnel: (...args: unknown[]) => mockKillExternalTunnel(...args),
|
||||
regenerateRemotePersistentToken: (...args: unknown[]) => mockRegenerateRemotePersistentToken(...args),
|
||||
generateShortLivedRemoteToken: (...args: unknown[]) => mockGenerateShortLivedRemoteToken(...args),
|
||||
fetchRemoteQr: (...args: unknown[]) => mockFetchRemoteQr(...args),
|
||||
fetchRemoteUrl: (...args: unknown[]) => mockFetchRemoteUrl(...args),
|
||||
triggerMemoryDreams: (...args: unknown[]) => mockTriggerMemoryDreams(...args),
|
||||
}));
|
||||
vi.mock("../../api", async (importOriginal) => {
|
||||
const { createDashboardApiMock } = await import("../../test/mockApi");
|
||||
return createDashboardApiMock(() => importOriginal<typeof import("../../api")>(), {
|
||||
fetchSettings: (...args: unknown[]) => mockFetchSettings(...args),
|
||||
fetchSettingsByScope: (...args: unknown[]) => mockFetchSettingsByScope(...args),
|
||||
updateSettings: (...args: unknown[]) => mockUpdateSettings(...args),
|
||||
updateGlobalSettings: (...args: unknown[]) => mockUpdateGlobalSettings(...args),
|
||||
exportSettings: (...args: unknown[]) => mockExportSettings(...args),
|
||||
importSettings: (...args: unknown[]) => mockImportSettings(...args),
|
||||
fetchAuthStatus: (...args: unknown[]) => mockFetchAuthStatus(...args),
|
||||
loginProvider: (...args: unknown[]) => mockLoginProvider(...args),
|
||||
logoutProvider: (...args: unknown[]) => mockLogoutProvider(...args),
|
||||
saveApiKey: (...args: unknown[]) => mockSaveApiKey(...args),
|
||||
fetchModels: (...args: unknown[]) => mockFetchModels(...args),
|
||||
fetchCustomProviders: (...args: unknown[]) => mockFetchCustomProviders(...args),
|
||||
createCustomProvider: (...args: unknown[]) => mockCreateCustomProvider(...args),
|
||||
updateCustomProvider: (...args: unknown[]) => mockUpdateCustomProvider(...args),
|
||||
deleteCustomProvider: (...args: unknown[]) => mockDeleteCustomProvider(...args),
|
||||
testNtfyNotification: (...args: unknown[]) => mockTestNtfyNotification(...args),
|
||||
testNotification: (...args: unknown[]) => mockTestNotification(...args),
|
||||
fetchBackups: (...args: unknown[]) => mockFetchBackups(...args),
|
||||
createBackup: (...args: unknown[]) => mockCreateBackup(...args),
|
||||
fetchMemoryFiles: (...args: unknown[]) => mockFetchMemoryFiles(...args),
|
||||
fetchMemoryFile: (...args: unknown[]) => mockFetchMemoryFile(...args),
|
||||
saveMemoryFile: (...args: unknown[]) => mockSaveMemoryFile(...args),
|
||||
compactMemory: (...args: unknown[]) => mockCompactMemory(...args),
|
||||
fetchGlobalConcurrency: (...args: unknown[]) => mockFetchGlobalConcurrency(...args),
|
||||
updateGlobalConcurrency: (...args: unknown[]) => mockUpdateGlobalConcurrency(...args),
|
||||
fetchMemoryBackendStatus: (...args: unknown[]) => mockFetchMemoryBackendStatus(...args),
|
||||
testMemoryRetrieval: (...args: unknown[]) => mockTestMemoryRetrieval(...args),
|
||||
installQmd: (...args: unknown[]) => mockInstallQmd(...args),
|
||||
fetchGitRemotesDetailed: (...args: unknown[]) => mockFetchGitRemotesDetailed(...args),
|
||||
fetchDashboardHealth: (...args: unknown[]) => mockFetchDashboardHealth(...args),
|
||||
checkForUpdates: (...args: unknown[]) => mockCheckForUpdates(...args),
|
||||
fetchRemoteSettings: (...args: unknown[]) => mockFetchRemoteSettings(...args),
|
||||
updateRemoteSettings: (...args: unknown[]) => mockUpdateRemoteSettings(...args),
|
||||
fetchRemoteStatus: (...args: unknown[]) => mockFetchRemoteStatus(...args),
|
||||
installCloudflared: (...args: unknown[]) => mockInstallCloudflared(...args),
|
||||
startRemoteTunnel: (...args: unknown[]) => mockStartRemoteTunnel(...args),
|
||||
stopRemoteTunnel: (...args: unknown[]) => mockStopRemoteTunnel(...args),
|
||||
killExternalTunnel: (...args: unknown[]) => mockKillExternalTunnel(...args),
|
||||
regenerateRemotePersistentToken: (...args: unknown[]) => mockRegenerateRemotePersistentToken(...args),
|
||||
generateShortLivedRemoteToken: (...args: unknown[]) => mockGenerateShortLivedRemoteToken(...args),
|
||||
fetchRemoteQr: (...args: unknown[]) => mockFetchRemoteQr(...args),
|
||||
fetchRemoteUrl: (...args: unknown[]) => mockFetchRemoteUrl(...args),
|
||||
triggerMemoryDreams: (...args: unknown[]) => mockTriggerMemoryDreams(...args),
|
||||
});
|
||||
});
|
||||
|
||||
// Mock the hook
|
||||
const mockUseMemoryBackendStatus = vi.fn();
|
||||
|
||||
@@ -9,32 +9,34 @@ import { TaskDetailModal } from "../TaskDetailModal";
|
||||
import type { TaskDetail, Column, MergeResult, Task } from "@fusion/core";
|
||||
import { clearAuthToken } from "../../auth";
|
||||
|
||||
vi.mock("../../api", () => ({
|
||||
uploadAttachment: vi.fn(),
|
||||
deleteAttachment: vi.fn(),
|
||||
updateTask: vi.fn().mockResolvedValue({}),
|
||||
fetchTaskDetail: vi.fn().mockResolvedValue(makeTask()),
|
||||
fetchAgentLogs: vi.fn().mockResolvedValue([]),
|
||||
requestSpecRevision: vi.fn().mockResolvedValue({}),
|
||||
approvePlan: vi.fn().mockResolvedValue({}),
|
||||
rejectPlan: vi.fn().mockResolvedValue({}),
|
||||
duplicateTask: vi.fn().mockResolvedValue({}),
|
||||
refineTask: vi.fn().mockResolvedValue({}),
|
||||
addSteeringComment: vi.fn(),
|
||||
assignTask: vi.fn().mockResolvedValue({}),
|
||||
fetchAgents: vi.fn().mockResolvedValue([]),
|
||||
fetchAgent: vi.fn(),
|
||||
// TaskForm dependencies
|
||||
fetchModels: vi.fn().mockResolvedValue({ models: [], favoriteProviders: [] }),
|
||||
fetchSettings: vi.fn().mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {} }),
|
||||
fetchWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
refineText: vi.fn(),
|
||||
getRefineErrorMessage: vi.fn((err: any) => err?.message || "Failed to refine"),
|
||||
updateGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||
pauseTask: vi.fn().mockResolvedValue({}),
|
||||
unpauseTask: vi.fn().mockResolvedValue({}),
|
||||
fetchWorkflowResults: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
vi.mock("../../api", async (importOriginal) => {
|
||||
const { createDashboardApiMock } = await import("../../test/mockApi");
|
||||
return createDashboardApiMock(() => importOriginal<typeof import("../../api")>(), {
|
||||
uploadAttachment: vi.fn(),
|
||||
deleteAttachment: vi.fn(),
|
||||
updateTask: vi.fn().mockResolvedValue({}),
|
||||
fetchTaskDetail: vi.fn().mockResolvedValue(makeTask()),
|
||||
fetchAgentLogs: vi.fn().mockResolvedValue([]),
|
||||
requestSpecRevision: vi.fn().mockResolvedValue({}),
|
||||
approvePlan: vi.fn().mockResolvedValue({}),
|
||||
rejectPlan: vi.fn().mockResolvedValue({}),
|
||||
duplicateTask: vi.fn().mockResolvedValue({}),
|
||||
refineTask: vi.fn().mockResolvedValue({}),
|
||||
addSteeringComment: vi.fn(),
|
||||
assignTask: vi.fn().mockResolvedValue({}),
|
||||
fetchAgents: vi.fn().mockResolvedValue([]),
|
||||
fetchAgent: vi.fn(),
|
||||
fetchModels: vi.fn().mockResolvedValue({ models: [], favoriteProviders: [] }),
|
||||
fetchSettings: vi.fn().mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {} }),
|
||||
fetchWorkflowSteps: vi.fn().mockResolvedValue([]),
|
||||
refineText: vi.fn(),
|
||||
getRefineErrorMessage: vi.fn((err: any) => err?.message || "Failed to refine"),
|
||||
updateGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||
pauseTask: vi.fn().mockResolvedValue({}),
|
||||
unpauseTask: vi.fn().mockResolvedValue({}),
|
||||
fetchWorkflowResults: vi.fn().mockResolvedValue([]),
|
||||
});
|
||||
});
|
||||
|
||||
// Mock lucide-react icons used by TaskDetailModal, TaskForm, PrSection, CustomModelDropdown
|
||||
vi.mock("lucide-react", () => ({
|
||||
|
||||
@@ -22,19 +22,22 @@ import * as api from "../../api";
|
||||
import type { Task, Column } from "@fusion/core";
|
||||
|
||||
// Mock the api module
|
||||
vi.mock("../../api", () => ({
|
||||
fetchTasks: vi.fn().mockResolvedValue([]),
|
||||
createTask: vi.fn(),
|
||||
moveTask: vi.fn(),
|
||||
deleteTask: vi.fn(),
|
||||
mergeTask: vi.fn(),
|
||||
retryTask: vi.fn(),
|
||||
duplicateTask: vi.fn(),
|
||||
updateTask: vi.fn(),
|
||||
archiveTask: vi.fn(),
|
||||
unarchiveTask: vi.fn(),
|
||||
archiveAllDone: vi.fn(),
|
||||
}));
|
||||
vi.mock("../../api", async (importOriginal) => {
|
||||
const { createDashboardApiMock } = await import("../../test/mockApi");
|
||||
return createDashboardApiMock(() => importOriginal<typeof import("../../api")>(), {
|
||||
fetchTasks: vi.fn().mockResolvedValue([]),
|
||||
createTask: vi.fn(),
|
||||
moveTask: vi.fn(),
|
||||
deleteTask: vi.fn(),
|
||||
mergeTask: vi.fn(),
|
||||
retryTask: vi.fn(),
|
||||
duplicateTask: vi.fn(),
|
||||
updateTask: vi.fn(),
|
||||
archiveTask: vi.fn(),
|
||||
unarchiveTask: vi.fn(),
|
||||
archiveAllDone: vi.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
async function flushPromises(): Promise<void> {
|
||||
await Promise.resolve();
|
||||
|
||||
61
packages/dashboard/app/test/mockApi.ts
Normal file
61
packages/dashboard/app/test/mockApi.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Canonical dashboard app API mock helper.
|
||||
*
|
||||
* Add new `app/api.ts` or `app/api/legacy.ts` exports here first before
|
||||
* introducing ad-hoc per-test `vi.mock("../../api", …)` export lists.
|
||||
*/
|
||||
import { vi, type Mock } from "vitest";
|
||||
|
||||
type AnyFn = Mock;
|
||||
type AnyModule = Record<string, unknown>;
|
||||
|
||||
const fallbackFns = new Map<string, AnyFn>();
|
||||
|
||||
function getFallback(name: string): AnyFn {
|
||||
if (!fallbackFns.has(name)) {
|
||||
fallbackFns.set(name, vi.fn(async () => undefined));
|
||||
}
|
||||
return fallbackFns.get(name)!;
|
||||
}
|
||||
|
||||
export const dashboardApiMocks = {
|
||||
fetchTasks: vi.fn(async () => []),
|
||||
fetchSettings: vi.fn(async () => ({})),
|
||||
updateSettings: vi.fn(async () => ({})),
|
||||
fetchGlobalSettings: vi.fn(async () => ({})),
|
||||
fetchAuthStatus: vi.fn(async () => ({ providers: [] })),
|
||||
fetchModels: vi.fn(async () => ({ models: [], favoriteProviders: [], favoriteModels: [] })),
|
||||
fetchTaskDetail: vi.fn(),
|
||||
fetchUnreadCount: vi.fn(async () => ({ unreadCount: 0 })),
|
||||
} satisfies Record<string, AnyFn>;
|
||||
|
||||
export async function createDashboardApiMock(
|
||||
importActual: () => Promise<AnyModule>,
|
||||
overrides: Record<string, AnyFn> = {},
|
||||
): Promise<AnyModule> {
|
||||
const actual = await importActual();
|
||||
const mocked: AnyModule = { ...actual, ...dashboardApiMocks, ...overrides };
|
||||
|
||||
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 as AnyModule)[prop];
|
||||
if (typeof actualValue === "function" || actualValue === undefined) {
|
||||
const fn = getFallback(prop);
|
||||
(target as AnyModule)[prop] = fn;
|
||||
return fn;
|
||||
}
|
||||
return actualValue;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function resetDashboardApiMockState(): void {
|
||||
Object.values(dashboardApiMocks).forEach((fn) => fn.mockReset());
|
||||
for (const fn of fallbackFns.values()) fn.mockReset();
|
||||
}
|
||||
35
packages/dashboard/app/test/mockHelpers.test.ts
Normal file
35
packages/dashboard/app/test/mockHelpers.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createDashboardApiMock, dashboardApiMocks, resetDashboardApiMockState } from "./mockApi";
|
||||
import { createLucideMock } from "./mockLucide";
|
||||
|
||||
describe("dashboard test mock helpers", () => {
|
||||
it("creates stable fallback mocks for missing API function exports", async () => {
|
||||
const module = await createDashboardApiMock(async () => ({ known: () => "ok" }));
|
||||
const first = module.missingApi as (...args: unknown[]) => Promise<unknown>;
|
||||
const second = module.missingApi as (...args: unknown[]) => Promise<unknown>;
|
||||
|
||||
expect(first).toBe(second);
|
||||
await first("arg");
|
||||
expect(first).toHaveBeenCalledWith("arg");
|
||||
|
||||
resetDashboardApiMockState();
|
||||
expect(first).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("auto-generates missing lucide icons while preserving provided exports", async () => {
|
||||
const existing = () => "existing";
|
||||
const module = await createLucideMock(async () => ({ ExistingIcon: existing }));
|
||||
|
||||
expect(module.ExistingIcon).toBe(existing);
|
||||
const Generated = module.BrandNewIcon as (props: Record<string, unknown>) => any;
|
||||
const rendered = Generated({ className: "x" });
|
||||
expect(rendered.props["data-testid"]).toBe("icon-brand-new-icon");
|
||||
expect(rendered.props.className).toBe("x");
|
||||
});
|
||||
|
||||
it("keeps canonical dashboard api mocks resettable", () => {
|
||||
dashboardApiMocks.fetchSettings.mockResolvedValueOnce({ hello: "world" });
|
||||
resetDashboardApiMockState();
|
||||
expect(dashboardApiMocks.fetchSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
30
packages/dashboard/app/test/mockLucide.ts
Normal file
30
packages/dashboard/app/test/mockLucide.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Canonical lucide-react mock helper for dashboard app tests.
|
||||
*
|
||||
* Add newly used icons here first before creating per-suite lucide export lists.
|
||||
*/
|
||||
import React from "react";
|
||||
|
||||
type AnyModule = Record<string, unknown>;
|
||||
|
||||
function icon(name: string) {
|
||||
return function MockIcon(props: Record<string, unknown>) {
|
||||
return React.createElement("span", { "data-testid": `icon-${name}`, ...props });
|
||||
};
|
||||
}
|
||||
|
||||
export async function createLucideMock(importActual: () => Promise<AnyModule>): Promise<AnyModule> {
|
||||
const actual = await importActual();
|
||||
return new Proxy({ ...actual }, {
|
||||
get(target, prop, receiver) {
|
||||
if (typeof prop !== "string") return Reflect.get(target, prop, receiver);
|
||||
if (["then", "catch", "finally"].includes(prop)) return undefined;
|
||||
if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
|
||||
const generated = icon(
|
||||
prop.replace(/[A-Z]/g, (m, idx) => (idx === 0 ? m.toLowerCase() : `-${m.toLowerCase()}`)),
|
||||
);
|
||||
(target as AnyModule)[prop] = generated;
|
||||
return generated;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -13,10 +13,9 @@ import { createServer } from "../server.js";
|
||||
const mockInit = vi.fn().mockResolvedValue(undefined);
|
||||
const mockClose = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
vi.mock("@fusion/core", async () => {
|
||||
const actual = await vi.importActual<typeof import("@fusion/core")>("@fusion/core");
|
||||
return {
|
||||
...actual,
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createCoreMock } = await import("../test/mockCoreEngine.js");
|
||||
return createCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||
CentralCore: vi.fn().mockImplementation(() => ({
|
||||
init: mockInit,
|
||||
close: mockClose,
|
||||
@@ -42,7 +41,7 @@ vi.mock("@fusion/core", async () => {
|
||||
]),
|
||||
updateNode: vi.fn().mockResolvedValue(undefined),
|
||||
})),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
class MockStore extends EventEmitter {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { Request, Response } from "express";
|
||||
import { request } from "../test-request.js";
|
||||
import { createCoreMock, createEngineMock } from "../test/mockCoreEngine.js";
|
||||
|
||||
// ── SSE Test Helpers ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -111,7 +112,7 @@ const { mockCreateFnAgent, mockChatStreamManager, mockSendMessage, mockCancelGen
|
||||
});
|
||||
|
||||
// Mock @fusion/engine to prevent createFnAgent resolution
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
vi.mock("@fusion/engine", () => createEngineMock({
|
||||
createFnAgent: mockCreateFnAgent,
|
||||
}));
|
||||
|
||||
@@ -132,8 +133,9 @@ const mockAgentStoreInit = vi.fn().mockResolvedValue(undefined);
|
||||
const mockAgentStoreGetAgent = vi.fn();
|
||||
|
||||
// Mock ChatStore class for vi.mock
|
||||
vi.mock("@fusion/core", () => {
|
||||
return {
|
||||
vi.mock("@fusion/core", async (importOriginal) => createCoreMock(
|
||||
() => importOriginal<typeof import("@fusion/core")>(),
|
||||
{
|
||||
ChatStore: class MockChatStore extends EventEmitter {
|
||||
init = mockInit;
|
||||
createSession = mockCreateSession;
|
||||
@@ -151,8 +153,8 @@ vi.mock("@fusion/core", () => {
|
||||
init = mockAgentStoreInit;
|
||||
getAgent = mockAgentStoreGetAgent;
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
));
|
||||
|
||||
// Mock chat.js - must mock before importing server
|
||||
vi.mock("../chat.js", () => {
|
||||
|
||||
@@ -65,10 +65,9 @@ vi.mock("node:child_process", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@fusion/core", async () => {
|
||||
const actual = await vi.importActual<typeof import("@fusion/core")>("@fusion/core");
|
||||
return {
|
||||
...actual,
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createCoreMock } = await import("../test/mockCoreEngine.js");
|
||||
return createCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||
resolveGlobalDir: vi.fn().mockReturnValue("/tmp/fusion-test"),
|
||||
isGhAvailable: vi.fn(),
|
||||
isGhAuthenticated: vi.fn(),
|
||||
@@ -79,10 +78,12 @@ vi.mock("@fusion/core", async () => {
|
||||
listProjects: mockCentralListProjects,
|
||||
reconcileProjectStatuses: mockCentralReconcileProjectStatuses,
|
||||
})),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("@fusion/engine", () => ({
|
||||
vi.mock("@fusion/engine", async () => {
|
||||
const { createEngineMock } = await import("../test/mockCoreEngine.js");
|
||||
return createEngineMock({
|
||||
createFnAgent: vi.fn(async (options?: { onText?: (delta: string) => void }) => ({
|
||||
session: {
|
||||
state: {
|
||||
@@ -122,7 +123,8 @@ vi.mock("@fusion/engine", () => ({
|
||||
throw new Error("Reflection service unavailable in route tests");
|
||||
}
|
||||
},
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
import { AgentStore, Database, RoutineStore, isGhAvailable, isGhAuthenticated } from "@fusion/core";
|
||||
import { createFnAgent } from "@fusion/engine";
|
||||
|
||||
27
packages/dashboard/src/test/mockCoreEngine.ts
Normal file
27
packages/dashboard/src/test/mockCoreEngine.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import { vi } from "vitest";
|
||||
|
||||
type AnyModule = Record<string, unknown>;
|
||||
|
||||
export async function createCoreMock(
|
||||
importActual: () => Promise<AnyModule>,
|
||||
overrides: AnyModule = {},
|
||||
): Promise<AnyModule> {
|
||||
const actual = await importActual();
|
||||
return {
|
||||
...actual,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function createEngineMock(overrides: AnyModule = {}): AnyModule {
|
||||
return {
|
||||
createFnAgent: vi.fn(),
|
||||
promptWithFallback: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -5,11 +5,9 @@ import {
|
||||
createTaskDocumentWriteTool,
|
||||
} from "../agent-tools.js";
|
||||
|
||||
vi.mock("@fusion/core", async () => {
|
||||
const actual = await vi.importActual("@fusion/core");
|
||||
return {
|
||||
...actual,
|
||||
};
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createEngineCoreMock } = await import("../test/mockCore.js");
|
||||
return createEngineCoreMock(() => importOriginal<typeof import("@fusion/core")>());
|
||||
});
|
||||
|
||||
const TASK_ID = "FN-1272";
|
||||
|
||||
@@ -25,19 +25,18 @@ const mocks = vi.hoisted(() => ({
|
||||
notificationServiceStop: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@fusion/core", async () => {
|
||||
class MockAutomationStore {
|
||||
constructor(_cwd: string) {}
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createEngineCoreMock } = await import("../test/mockCore.js");
|
||||
return createEngineCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||
AutomationStore: class MockAutomationStore {
|
||||
constructor(_cwd: string) {}
|
||||
|
||||
init = mocks.automationStoreInit;
|
||||
}
|
||||
|
||||
return {
|
||||
AutomationStore: MockAutomationStore,
|
||||
init = mocks.automationStoreInit;
|
||||
},
|
||||
syncInsightExtractionAutomation: mocks.syncInsightExtractionAutomation,
|
||||
syncAutoSummarizeAutomation: mocks.syncAutoSummarizeAutomation,
|
||||
syncMemoryDreamsAutomation: mocks.syncMemoryDreamsAutomation,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("../cron-runner.js", () => {
|
||||
|
||||
@@ -29,12 +29,11 @@ vi.mock("../pi.js", () => ({
|
||||
promptWithFallback: vi.fn().mockReturnValue("mock-prompt"),
|
||||
}));
|
||||
|
||||
vi.mock("@fusion/core", async () => {
|
||||
const actual = await vi.importActual("@fusion/core");
|
||||
return {
|
||||
...actual,
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createEngineCoreMock } = await import("../test/mockCore.js");
|
||||
return createEngineCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||
resolveAgentPrompt: vi.fn().mockReturnValue(null),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
async function createTriageFixtureRoot(prefix: string): Promise<string> {
|
||||
|
||||
21
packages/engine/src/test/mockCore.ts
Normal file
21
packages/engine/src/test/mockCore.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Canonical @fusion/core mock helper for engine tests.
|
||||
*
|
||||
* Prefer extending this helper over suite-local full export lists.
|
||||
*/
|
||||
import type { Mock } from "vitest";
|
||||
|
||||
type AnyModule = Record<string, unknown>;
|
||||
|
||||
export async function createEngineCoreMock(
|
||||
importActual: () => Promise<AnyModule>,
|
||||
overrides: AnyModule = {},
|
||||
): Promise<AnyModule> {
|
||||
const actual = await importActual();
|
||||
return {
|
||||
...actual,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export type MockFn = Mock;
|
||||
Reference in New Issue
Block a user