feat(FN-4837): complete Step 2 — migrate store tests to shared harness

Fusion-Task-Id: FN-4837
Fusion-Task-Lineage: 93dda830-5658-4894-ad91-ac2c891d2436
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 22:05:52 -07:00
committed by gsxdsm
parent 743324f8ab
commit 139e25fcf3
6 changed files with 99 additions and 31 deletions

View File

@@ -1,15 +1,17 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll, vi } from "vitest";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { AgentStore } from "../agent-store.js";
import { TaskStore } from "../store.js";
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
describe("TaskStore Archive and Search", () => {
const harness = createTaskStoreTestHarness();
const harness = createSharedTaskStoreTestHarness();
let store: TaskStore;
beforeAll(harness.beforeAll);
beforeEach(async () => {
await harness.beforeEach();
store = harness.store();
@@ -19,6 +21,8 @@ describe("TaskStore Archive and Search", () => {
await harness.afterEach();
});
afterAll(harness.afterAll);
describe("archiveTask", () => {
it("archives a done task (moves done → archived)", async () => {
const task = await store.createTask({ description: "Test task" });
@@ -332,8 +336,8 @@ describe("TaskStore Archive and Search", () => {
describe("cleanupArchivedTasks", () => {
it("writes compact entry to archive DB with compact agent log", async () => {
// This test asserts the archive.db file exists on disk, which the
// in-memory beforeEach store can't satisfy. Swap to disk-backed.
await harness.reopenDiskBackedStore();
// shared in-memory store can't satisfy.
await harness.useIsolatedStore();
store = harness.store();
// Create and archive a task
@@ -751,7 +755,7 @@ describe("TaskStore Archive and Search", () => {
});
it("archiveTask clears stale linked agent assignments", async () => {
await harness.reopenDiskBackedStore();
await harness.useIsolatedStore();
store = harness.store();
const agentStore = new AgentStore({ rootDir: store.getFusionDir() });
@@ -783,7 +787,7 @@ describe("TaskStore Archive and Search", () => {
// store, but this test verifies disk persistence. Swap to a
// disk-backed store before doing any work so newStore (also
// disk-backed) can read what the first instance wrote.
await harness.reopenDiskBackedStore();
await harness.useIsolatedStore();
store = harness.store();
const task = await store.createTask({ description: "Survival test" });

View File

@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll, vi } from "vitest";
import { createTaskStoreTestHarness, makeTmpDir, mockedExecSync, mockedRunCommandAsync } from "./store-test-helpers.js";
import { createSharedTaskStoreTestHarness, makeTmpDir, mockedExecSync, mockedRunCommandAsync } from "./store-test-helpers.js";
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
import { join } from "node:path";
import { existsSync } from "node:fs";
@@ -13,11 +13,13 @@ import { buildResearchDocumentKey, type Task } from "../types.js";
import { setTaskCreatedHook } from "../task-creation-hooks.js";
describe("TaskStore", () => {
const harness = createTaskStoreTestHarness();
const harness = createSharedTaskStoreTestHarness();
let rootDir: string;
let globalDir: string;
let store: TaskStore;
beforeAll(harness.beforeAll);
beforeEach(async () => {
await harness.beforeEach();
rootDir = harness.rootDir();
@@ -29,6 +31,8 @@ describe("TaskStore", () => {
await harness.afterEach();
});
afterAll(harness.afterAll);
const createTestTask = () => harness.createTestTask();
const createTaskWithSteps = () => harness.createTaskWithSteps();
const deleteTaskDir = (taskId: string) => harness.deleteTaskDir(taskId);

View File

@@ -1,13 +1,16 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } from "vitest";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
import { TaskStore } from "../store.js";
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
describe("TaskStore", () => {
const harness = createTaskStoreTestHarness();
const harness = createSharedTaskStoreTestHarness();
beforeAll(harness.beforeAll);
beforeEach(harness.beforeEach);
afterEach(harness.afterEach);
afterAll(harness.afterAll);
describe("model settings", () => {
it("persists defaultProvider and defaultModelId via updateGlobalSettings", async () => {
@@ -1962,23 +1965,29 @@ describe("TaskStore", () => {
};
it("round-trips nested remoteAccess settings with both providers, token strategy, and lifecycle", async () => {
harness.store().close();
await harness.reopenDiskBackedStore();
await harness.useIsolatedStore();
let isolatedStore = harness.store();
await harness.store().updateGlobalSettings({ remoteAccess: baseRemoteAccess });
isolatedStore.close();
isolatedStore = new TaskStore(harness.rootDir(), harness.globalDir());
await isolatedStore.init();
const settings = await harness.store().getSettings();
await isolatedStore.updateGlobalSettings({ remoteAccess: baseRemoteAccess });
const settings = await isolatedStore.getSettings();
expect(settings.remoteAccess).toEqual(baseRemoteAccess);
const { project, global } = await harness.store().getSettingsByScope();
const { project, global } = await isolatedStore.getSettingsByScope();
expect((project as Record<string, unknown>).remoteAccess).toBeUndefined();
expect(global.remoteAccess).toEqual(baseRemoteAccess);
harness.store().close();
await harness.reopenDiskBackedStore();
isolatedStore.close();
const reloadedStore = new TaskStore(harness.rootDir(), harness.globalDir());
await reloadedStore.init();
const reloaded = await harness.store().getSettings();
const reloaded = await reloadedStore.getSettings();
expect(reloaded.remoteAccess).toEqual(baseRemoteAccess);
reloadedStore.close();
});
it("patching remoteAccess.providers.tailscale preserves providers.cloudflare", async () => {

View File

@@ -97,7 +97,14 @@ async function resetStoreFilesystem(rootDir: string, globalDir: string, store: T
await clearDirectoryContents(join(fusionDir, "tasks"));
await clearDirectoryContents(join(fusionDir, "task-documents"));
await clearDirectoryContents(join(fusionDir, "agent-logs"));
await clearDirectoryContents(join(globalDir, ".fusion-global-settings"));
try {
(store as any)._archiveDb?.close?.();
} catch {
// ignored
}
(store as any)._archiveDb = null;
await rm(join(fusionDir, "archive.db"), { force: true });
await clearDirectoryContents(globalDir);
const config = await (store as any).readConfig();
const content = (store as any).serializeConfigForDisk(config);
@@ -114,6 +121,16 @@ function resetTaskStorePrivateState(store: TaskStore): void {
(store as any).taskIdStateReconciled = false;
(store as any).taskIdIntegrityReport = (store as any).buildTaskIdIntegrityFallbackReport?.();
(store as any).lastTaskIdIntegrityLogSignature = null;
(store as any).distributedTaskIdAllocator = null;
(store as any)._archiveDb = null;
if ((store as any).agentLogFlushTimer) {
clearTimeout((store as any).agentLogFlushTimer);
(store as any).agentLogFlushTimer = null;
}
if (Array.isArray((store as any).agentLogBuffer)) {
(store as any).agentLogBuffer.length = 0;
}
(store as any).globalSettingsStore.cachedSettings = null;
}
export function createTaskStoreTestHarness() {
@@ -215,6 +232,17 @@ export function createSharedTaskStoreTestHarness() {
let isolatedStore: TaskStore | null = null;
let isolatedRootDir: string | null = null;
let isolatedGlobalDir: string | null = null;
let configRowSnapshot: {
nextId: number;
nextWorkflowStepId: number;
settings: string;
workflowSteps: string;
} = {
nextId: 1,
nextWorkflowStepId: 1,
settings: JSON.stringify(DEFAULT_PROJECT_SETTINGS),
workflowSteps: "[]",
};
let distributedStateSnapshot: Array<{
prefix: string;
nextSequence: number;
@@ -228,8 +256,14 @@ export function createSharedTaskStoreTestHarness() {
db.prepare("DELETE FROM config").run();
db.prepare(
`INSERT INTO config (id, nextId, nextWorkflowStepId, settings, workflowSteps, updatedAt)
VALUES (1, 1, 1, ?, '[]', ?)`,
).run(JSON.stringify(DEFAULT_PROJECT_SETTINGS), now);
VALUES (1, ?, ?, ?, ?, ?)`,
).run(
configRowSnapshot.nextId,
configRowSnapshot.nextWorkflowStepId,
configRowSnapshot.settings,
configRowSnapshot.workflowSteps,
now,
);
};
const resetDistributedState = (db: Database) => {
@@ -284,6 +318,13 @@ export function createSharedTaskStoreTestHarness() {
currentStore = sharedStore;
const db = (sharedStore as any).db as Database;
const configRow = db
.prepare("SELECT nextId, nextWorkflowStepId, settings, workflowSteps FROM config WHERE id = 1")
.get() as typeof configRowSnapshot | undefined;
if (configRow) {
configRowSnapshot = configRow;
}
distributedStateSnapshot = db
.prepare(
`SELECT prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt
@@ -306,10 +347,10 @@ export function createSharedTaskStoreTestHarness() {
resetConfigRow(db);
resetDistributedState(db);
});
await resetStoreFilesystem(rootDir, globalDir, sharedStore);
sharedStore.removeAllListeners();
resetTaskStorePrivateState(sharedStore);
(sharedStore as any).workflowStepsCache = null;
},
afterEach: async () => {
vi.useRealTimers();

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll, vi } from "vitest";
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
import { join } from "node:path";
@@ -8,14 +8,16 @@ import { AgentStore } from "../agent-store.js";
import { CentralDatabase } from "../central-db.js";
import { TaskStore, TaskHasDependentsError } from "../store.js";
import { buildResearchDocumentKey, type Task } from "../types.js";
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
describe("TaskStore", () => {
const harness = createTaskStoreTestHarness();
const harness = createSharedTaskStoreTestHarness();
let rootDir: string;
let globalDir: string;
let store: TaskStore;
beforeAll(harness.beforeAll);
beforeEach(async () => {
await harness.beforeEach();
rootDir = harness.rootDir();
@@ -27,6 +29,8 @@ describe("TaskStore", () => {
await harness.afterEach();
});
afterAll(harness.afterAll);
const createTestTask = () => harness.createTestTask();
const createTaskWithSteps = () => harness.createTaskWithSteps();
const deleteTaskDir = (taskId: string) => harness.deleteTaskDir(taskId);

View File

@@ -1,12 +1,14 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll, vi } from "vitest";
import { TaskStore } from "../store.js";
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
describe("TaskStore Workflow Steps", () => {
const harness = createTaskStoreTestHarness();
const harness = createSharedTaskStoreTestHarness();
let store: TaskStore;
beforeAll(harness.beforeAll);
beforeEach(async () => {
await harness.beforeEach();
store = harness.store();
@@ -16,6 +18,8 @@ describe("TaskStore Workflow Steps", () => {
await harness.afterEach();
});
afterAll(harness.afterAll);
describe("Workflow Steps", () => {
it("should create a workflow step with all fields", async () => {
const ws = await store.createWorkflowStep({
@@ -438,6 +442,8 @@ describe("TaskStore Workflow Steps", () => {
});
it("should delete a workflow step", async () => {
await harness.useIsolatedStore();
store = harness.store();
const ws = await store.createWorkflowStep({ name: "ToDelete", description: "Gone" });
await store.deleteWorkflowStep(ws.id);