FN-6367: fix constructible CLI test mocks

Update CLI test mocks so Vitest-spied constructors remain new-able under current mock semantics.

- Add constructible mock wrappers for TaskStore and related CLI test constructor mocks.
- Replace arrow-function constructor mock implementations with function-based implementations.
- Bring task retry fixture data in line with the graph resume retry counter shape.

Files changed:
 .../cli/src/__tests__/experiment-finalize.test.ts   | 19 +++++++++++++++++--
 .../__tests__/extension-experiment-finalize.test.ts | 19 +++++++++++++++++--
 packages/cli/src/__tests__/plugin-dev.test.ts       | 19 +++++++++++++++++--
 packages/cli/src/__tests__/project-resolver.test.ts | 17 ++++++++++++++++-
 packages/cli/src/__tests__/task-plan.test.ts        | 17 ++++++++++++++++-
 packages/cli/src/__tests__/task-steer.test.ts       | 17 ++++++++++++++++-
 packages/cli/src/__tests__/update-cache.test.ts     | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/agent.test.ts   | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/backup.test.ts  | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/db.test.ts      | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/desktop.test.ts |  4 +++-
 packages/cli/src/commands/__tests__/init.test.ts    | 17 ++++++++++++++++-
 .../src/commands/__tests__/memory-backup.test.ts    | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/message.test.ts | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/node.test.ts    | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/plugin.test.ts  | 19 +++++++++++++++++--
 packages/cli/src/commands/__tests__/project.test.ts | 21 ++++++++++++++++++---
 packages/cli/src/commands/__tests__/serve.test.ts   | 17 ++++++++++++++++-
 .../src/commands/__tests__/settings-export.test.ts  | 17 ++++++++++++++++-
 .../src/commands/__tests__/settings-import.test.ts  | 17 ++++++++++++++++-
 .../cli/src/commands/__tests__/settings.test.ts     | 17 ++++++++++++++++-
 packages/cli/src/commands/__tests__/task.test.ts    |  2 ++
 22 files changed, 331 insertions(+), 27 deletions(-)

Fusion-Task-Id: FN-6367
Fusion-Task-Lineage: d900dfe0-e286-4175-a5ba-7c319e2527b0
This commit is contained in:
gsxdsm
2026-06-13 09:14:02 -07:00
parent 63f1e097a2
commit 550e9edd46
22 changed files with 331 additions and 27 deletions

View File

@@ -3,6 +3,21 @@ import { writeFile, mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const previewPlan = vi.fn();
const finalize = vi.fn();
const init = vi.fn();
@@ -18,12 +33,12 @@ const mockErrors = vi.hoisted(() => ({
}));
vi.mock("@fusion/core", () => ({
TaskStore: vi.fn(() => ({ init, getExperimentSessionStore })),
TaskStore: makeConstructibleMock(() => ({ init, getExperimentSessionStore })),
}));
vi.mock("@fusion/engine", () => ({
defaultGitOps: vi.fn(() => ({})),
ExperimentFinalizeService: vi.fn(() => ({ previewPlan, finalize })),
ExperimentFinalizeService: makeConstructibleMock(() => ({ previewPlan, finalize })),
ExperimentFinalizeStateError: class extends Error { code = "state_error" as const; },
ExperimentFinalizeNoKeptRunsError: class extends Error { code = "no_kept_runs" as const; },
ExperimentFinalizePlanError: class extends Error { code = "plan_error" as const; },

View File

@@ -1,5 +1,20 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const previewPlanMock = vi.hoisted(() => vi.fn());
const finalizeMock = vi.hoisted(() => vi.fn());
@@ -18,7 +33,7 @@ const mockErrors = vi.hoisted(() => ({
}));
vi.mock("@fusion/core", () => ({
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: vi.fn().mockResolvedValue(undefined),
getExperimentSessionStore: vi.fn(() => ({})),
})),
@@ -43,7 +58,7 @@ vi.mock("@fusion/engine", () => ({
createFnAgent: vi.fn(),
fetchWebContent: vi.fn(),
defaultGitOps: vi.fn(() => ({})),
ExperimentFinalizeService: vi.fn(() => ({ previewPlan: previewPlanMock, finalize: finalizeMock })),
ExperimentFinalizeService: makeConstructibleMock(() => ({ previewPlan: previewPlanMock, finalize: finalizeMock })),
ExperimentFinalizeStateError: mockErrors.StateError,
ExperimentFinalizeNoKeptRunsError: mockErrors.NoKeptError,
ExperimentFinalizePlanError: mockErrors.PlanError,

View File

@@ -3,6 +3,21 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const pluginCommandMocks = vi.hoisted(() => {
const store = {
registerPlugin: vi.fn(async () => ({ id: "fusion-plugin-dev-test", enabled: true })),
@@ -16,8 +31,8 @@ const pluginCommandMocks = vi.hoisted(() => {
return {
store,
loader,
createPluginStore: vi.fn(async () => store),
createPluginLoader: vi.fn(async () => ({ store, loader })),
createPluginStore: makeConstructibleMock(async () => store),
createPluginLoader: makeConstructibleMock(async () => ({ store, loader })),
resolvePluginEntryFile: vi.fn(async (dir: string) => join(dir, "dist", "index.js")),
loadManifestFromPath: vi.fn(async () => ({
manifest: {

View File

@@ -2,6 +2,21 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { existsSync, statSync } from "node:fs";
import { TaskStore } from "@fusion/core";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const { mockIsValidSqliteDatabaseFile } = vi.hoisted(() => ({
mockIsValidSqliteDatabaseFile: vi.fn(),
}));
@@ -41,7 +56,7 @@ vi.mock("@fusion/core", async () => {
},
isValidSqliteDatabaseFile: (...args: Parameters<typeof mockIsValidSqliteDatabaseFile>) =>
mockIsValidSqliteDatabaseFile(...args),
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: vi.fn().mockResolvedValue(undefined),
listTasks: vi.fn().mockResolvedValue([]),
})),

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
// Mock node:readline/promises before importing
vi.mock("node:readline/promises", () => ({
createInterface: vi.fn(),
@@ -8,7 +23,7 @@ vi.mock("node:readline/promises", () => ({
// Mock @fusion/core before importing
vi.mock("@fusion/core", async (importOriginal) => ({
...(await importOriginal<typeof import("@fusion/core")>()),
TaskStore: vi.fn(),
TaskStore: makeConstructibleMock(),
COLUMNS: ["triage", "todo", "in-progress", "in-review", "done", "archived"],
COLUMN_LABELS: {
triage: "Triage",

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
// Mock node:readline/promises before importing
vi.mock("node:readline/promises", () => ({
createInterface: vi.fn(),
@@ -8,7 +23,7 @@ vi.mock("node:readline/promises", () => ({
// Mock @fusion/core before importing
vi.mock("@fusion/core", async (importOriginal) => ({
...(await importOriginal<typeof import("@fusion/core")>()),
TaskStore: vi.fn(),
TaskStore: makeConstructibleMock(),
COLUMNS: ["triage", "todo", "in-progress", "in-review", "done", "archived"],
COLUMN_LABELS: {
triage: "Triage",

View File

@@ -2,6 +2,21 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { readFileSync } from "node:fs";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const CLI_PACKAGE_VERSION = (
JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf-8")) as { version: string }
).version;
@@ -16,7 +31,7 @@ const { cacheDir, mockResolveGlobalDir } = vi.hoisted(() => {
vi.mock("@fusion/core", () => ({
resolveGlobalDir: mockResolveGlobalDir,
GlobalSettingsStore: vi.fn(),
GlobalSettingsStore: makeConstructibleMock(),
}));
const { getCachedUpdateStatus } = await import("../update-cache.js");

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
// ── Mock AgentStore ──────────────────────────────────────────────────
const mockGetAgent = vi.fn();
@@ -9,7 +24,7 @@ const mockInit = vi.fn().mockResolvedValue(undefined);
// AgentStore mock — vi.fn() with mockImplementation works with `new` in vitest.
// We return a plain object from the constructor which becomes the instance.
vi.mock("@fusion/core", () => ({
AgentStore: vi.fn().mockImplementation(() => ({
AgentStore: makeConstructibleMock(() => ({
init: mockInit,
getAgent: mockGetAgent,
updateAgentState: mockUpdateAgentState,

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const {
mockListBackups,
mockListBackupPairs,
@@ -20,7 +35,7 @@ const {
vi.mock("@fusion/core", () => ({
BackupManager: vi.fn(),
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: vi.fn().mockResolvedValue(undefined),
getSettings: mockGetSettings,
fusionDir: "/cwd/.fusion",

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
// Hoist mocks so they are evaluated before module imports
const { mockGetDatabase, mockVacuum, mockResolveProject } = vi.hoisted(() => ({
mockGetDatabase: vi.fn(),
@@ -8,7 +23,7 @@ const { mockGetDatabase, mockVacuum, mockResolveProject } = vi.hoisted(() => ({
}));
vi.mock("@fusion/core", () => ({
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: vi.fn(),
getDatabase: mockGetDatabase,
})),

View File

@@ -122,7 +122,9 @@ const mocks = vi.hoisted(() => {
server,
app,
spawn,
taskStoreCtor: vi.fn(() => store),
taskStoreCtor: vi.fn(function () {
return store;
}),
createServer: vi.fn(() => app),
};
});

View File

@@ -11,6 +11,21 @@ import { exec } from "node:child_process";
import { promisify } from "node:util";
import { GitRepositoryInitializationError } from "@fusion/core";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const execAsync = promisify(exec);
const mockCentralInit = vi.fn();
@@ -27,7 +42,7 @@ vi.mock("@fusion/core", async () => {
const actual = await vi.importActual<typeof import("@fusion/core")>("@fusion/core");
return {
...actual,
CentralCore: vi.fn().mockImplementation(() => ({
CentralCore: makeConstructibleMock(() => ({
init: mockCentralInit,
close: mockCentralClose,
getProjectByPath: mockGetProjectByPath,

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const {
mockListBackups,
mockRestoreBackup,
@@ -15,7 +30,7 @@ const {
}));
vi.mock("@fusion/core", () => ({
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: vi.fn().mockResolvedValue(undefined),
getSettings: mockGetSettings,
fusionDir: "/cwd/.fusion",

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
// ── Mock MessageStore ────────────────────────────────────────────────
const mockGetInbox = vi.fn();
@@ -17,7 +32,7 @@ vi.mock("@fusion/core", () => {
};
return {
createDatabase: vi.fn().mockReturnValue(mockDb),
MessageStore: vi.fn().mockImplementation(() => ({
MessageStore: makeConstructibleMock(() => ({
getInbox: mockGetInbox,
getOutbox: mockGetOutbox,
getMailbox: mockGetMailbox,

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const mockInit = vi.fn().mockResolvedValue(undefined);
const mockClose = vi.fn().mockResolvedValue(undefined);
const mockListNodes = vi.fn();
@@ -13,7 +28,7 @@ const mockQuestion = vi.fn();
const mockRlClose = vi.fn();
vi.mock("@fusion/core", () => ({
CentralCore: vi.fn().mockImplementation(() => ({
CentralCore: makeConstructibleMock(() => ({
init: mockInit,
close: mockClose,
listNodes: mockListNodes,

View File

@@ -3,6 +3,21 @@ import { dirname, join, resolve } from "node:path";
import { tmpdir } from "node:os";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const mocks = vi.hoisted(() => {
const pluginStoreInstances: Array<{
init: ReturnType<typeof vi.fn>;
@@ -15,9 +30,9 @@ const mocks = vi.hoisted(() => {
let loaderTaskStore: { getRootDir?: () => string } | undefined;
let loaderRootDir: string | undefined;
const PluginStore = vi.fn();
const PluginStore = makeConstructibleMock();
const PluginLoader = vi.fn();
const PluginLoader = makeConstructibleMock();
const setupDefaults = () => {
PluginStore.mockImplementation(() => {

View File

@@ -3,6 +3,21 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const mockListProjects = vi.fn();
const mockRegisterProject = vi.fn();
const mockEnsureProjectForPath = vi.fn(async (...args: unknown[]) => ({
@@ -29,7 +44,7 @@ const mockEnsureMemoryFileWithBackend = vi.fn();
// Mock @fusion/core
vi.mock("@fusion/core", () => ({
CentralCore: vi.fn().mockImplementation(() => ({
CentralCore: makeConstructibleMock(() => ({
init: mockInit.mockResolvedValue(undefined),
close: mockClose.mockResolvedValue(undefined),
listProjects: mockListProjects,
@@ -41,11 +56,11 @@ vi.mock("@fusion/core", () => ({
getProjectByPath: mockGetProjectByPath,
getProjectHealth: mockGetProjectHealth,
})),
GlobalSettingsStore: vi.fn().mockImplementation(() => ({
GlobalSettingsStore: makeConstructibleMock(() => ({
init: mockGlobalInit.mockResolvedValue(undefined),
getSettings: mockGetSettings,
})),
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: mockTaskStoreInit,
listTasks: mockTaskStoreListTasks,
})),

View File

@@ -4,6 +4,21 @@ import { mkdtempSync, rmSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const { mockSyncStartupModels, mockShouldUseHybridExecutor, mockHybridExecutorCtor, mockHybridExecutorInitialize, mockHybridExecutorShutdown } = vi.hoisted(() => ({
mockSyncStartupModels: vi.fn().mockResolvedValue(undefined),
mockShouldUseHybridExecutor: vi.fn().mockResolvedValue({ enabled: false, reason: "single-project-local-only" }),
@@ -590,7 +605,7 @@ vi.mock("@fusion/core", async (importOriginal) => {
storeToken: vi.fn().mockResolvedValue(undefined),
};
}),
GlobalSettingsStore: vi.fn().mockImplementation(function () {
GlobalSettingsStore: makeConstructibleMock(function () {
return {};
}),
resolveGlobalDir: vi.fn().mockReturnValue("/mock/global"),

View File

@@ -4,6 +4,21 @@ import { join, resolve } from "node:path";
import { TaskStore, exportSettings, generateExportFilename } from "@fusion/core";
import { resolveProject } from "../../project-context.js";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const mockStoreInit = vi.fn().mockResolvedValue(undefined);
vi.mock("node:fs/promises", () => ({
@@ -11,7 +26,7 @@ vi.mock("node:fs/promises", () => ({
}));
vi.mock("@fusion/core", () => ({
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: mockStoreInit,
})),
exportSettings: vi.fn(),

View File

@@ -3,6 +3,21 @@ import { existsSync } from "node:fs";
import { TaskStore, importSettings, readExportFile, validateImportData } from "@fusion/core";
import { resolveProject } from "../../project-context.js";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
const mockStoreInit = vi.fn().mockResolvedValue(undefined);
vi.mock("node:fs", () => ({
@@ -10,7 +25,7 @@ vi.mock("node:fs", () => ({
}));
vi.mock("@fusion/core", () => ({
TaskStore: vi.fn().mockImplementation(() => ({
TaskStore: makeConstructibleMock(() => ({
init: mockStoreInit,
})),
importSettings: vi.fn(),

View File

@@ -1,5 +1,20 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
function makeConstructibleMock<T extends (...args: any[]) => unknown>(impl?: T) {
const mock = vi.fn(function () {});
const originalMockImplementation = mock.mockImplementation.bind(mock);
const originalMockImplementationOnce = mock.mockImplementationOnce.bind(mock);
const wrap = (nextImpl: T) => function (this: unknown, ...args: Parameters<T>) {
return nextImpl(...args);
};
mock.mockImplementation = ((nextImpl: T) => originalMockImplementation(wrap(nextImpl))) as typeof mock.mockImplementation;
mock.mockImplementationOnce = ((nextImpl: T) => originalMockImplementationOnce(wrap(nextImpl))) as typeof mock.mockImplementationOnce;
if (impl) {
mock.mockImplementation(impl);
}
return mock;
}
vi.mock("@fusion/core", () => {
const DEFAULT_SETTINGS = {
maxConcurrent: 2,
@@ -23,7 +38,7 @@ vi.mock("@fusion/core", () => {
};
return {
GlobalSettingsStore: vi.fn(),
GlobalSettingsStore: makeConstructibleMock(),
DEFAULT_SETTINGS,
SUPPORTED_LOCALES: ["en", "zh-CN", "zh-TW", "fr", "es", "ko"],
resolveWorktrunkSettings: (globalValue: any, projectValue: any) => ({

View File

@@ -2460,6 +2460,7 @@ describe("runTaskRetry", () => {
reviewerContextRetryCount: 0,
reviewerFallbackRetryCount: 0,
completionHandoffLimboRecoveryCount: 0,
graphResumeRetryCount: 0,
mergeAuditBounceCount: 0,
mergeRetries: 0,
resumeLimboCount: 0,
@@ -2534,6 +2535,7 @@ describe("runTaskRetry", () => {
reviewerContextRetryCount: 0,
reviewerFallbackRetryCount: 0,
completionHandoffLimboRecoveryCount: 0,
graphResumeRetryCount: 0,
mergeAuditBounceCount: 0,
mergeRetries: 0,
resumeLimboCount: 0,