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:
committed by
gsxdsm
parent
743324f8ab
commit
139e25fcf3
@@ -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" });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user