feat(FN-5124): add soft-delete QA boundary regression coverage

Adds a regression test for FN-5124 covering soft-delete QA boundary cases, targeting the engine reliability interaction suite. The single new test file exercises edge conditions in the soft-delete path to guard against future regressions.

Fusion-Task-Id: FN-5124

Fusion-Task-Lineage: 7166e252-9c98-4f51-956a-1402dae6f402
This commit is contained in:
Fusion (runfusion.ai)
2026-05-19 05:52:09 -07:00
committed by gsxdsm
parent 0cf65b4269
commit 15bb84f862
3 changed files with 101 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
describe("soft-delete QA boundary audit (FN-5124)", () => {
const harness = createTaskStoreTestHarness();
beforeEach(async () => {
await harness.beforeEach();
});
afterEach(async () => {
await harness.afterEach();
});
it("re-delete is deterministic: second delete fails not-found and does not emit duplicate task:deleted", async () => {
const store = harness.store();
const task = await store.createTask({ column: "todo", description: "redelete target" });
const deletedEvents: string[] = [];
store.on("task:deleted", (event) => deletedEvents.push(event.id));
await store.deleteTask(task.id);
const firstDeletedAt = ((store as any).db.prepare("SELECT deletedAt FROM tasks WHERE id = ?").get(task.id) as { deletedAt: string | null })
.deletedAt;
await expect(store.deleteTask(task.id)).rejects.toThrow(`Task ${task.id} not found`);
const secondDeletedAt = ((store as any).db.prepare("SELECT deletedAt FROM tasks WHERE id = ?").get(task.id) as { deletedAt: string | null })
.deletedAt;
expect(firstDeletedAt).toBeTruthy();
expect(secondDeletedAt).toBe(firstDeletedAt);
expect(deletedEvents).toEqual([task.id]);
});
it("preserves dependents by default and rewrites blockedBy when removeDependencyReferences is true", async () => {
const store = harness.store();
const parent = await store.createTask({ column: "todo", title: "parent", description: "parent task" });
const dependent = await store.createTask({ column: "todo", title: "dependent", description: "dependent task" });
await store.updateTask(dependent.id, { dependencies: [parent.id] });
await expect(store.deleteTask(parent.id)).rejects.toThrow(/depend/i);
await store.deleteTask(parent.id, { removeDependencyReferences: true });
const dependentAfter = await store.getTask(dependent.id);
expect(dependentAfter.dependencies).not.toContain(parent.id);
expect((store as any).findLiveDependents(parent.id)).toEqual([]);
});
it("archiving a soft-deleted task succeeds and hard-removes the row consistently", async () => {
const store = harness.store();
const doneTask = await store.createTask({ column: "done", title: "done task", description: "done task description" });
await store.deleteTask(doneTask.id);
const archived = await store.archiveTask(doneTask.id);
const liveRow = (store as any).db.prepare("SELECT id, deletedAt FROM tasks WHERE id = ?").get(doneTask.id) as
| { id: string; deletedAt: string | null }
| undefined;
expect(archived.column).toBe("archived");
expect(liveRow).toBeUndefined();
expect((store as any).archiveDb.get(doneTask.id)?.id).toBe(doneTask.id);
});
it.each(["todo", "in-progress", "in-review", "done", "triage"])(
"keeps ID reservation after soft-delete (%s)",
async (column) => {
const store = harness.store();
const task = await store.createTask({ column: column as any, title: `reserve-${column}`, description: `reserve ${column}` });
await store.deleteTask(task.id);
expect(() => (store as any).assertTaskIdAvailable(task.id)).toThrow(`Task ID already exists: ${task.id}`);
expect((store as any).taskIdExistsAnywhere(task.id)).toBe(true);
},
);
});

View File

@@ -677,6 +677,20 @@ test("createIsolatedHomeEnv: returns temp HOME/USERPROFILE pair without mutating
rmSync(isolatedHome, { recursive: true, force: true });
});
test("createIsolatedHomeEnv: preserves a stable COREPACK_HOME outside the isolated HOME", () => {
const baseEnv = {
PATH: process.env.PATH || "",
HOME: "/tmp/original-home",
};
const { env, isolatedHome } = createIsolatedHomeEnv(baseEnv);
assert.equal(env.HOME, isolatedHome);
assert.equal(env.COREPACK_HOME, path.join(baseEnv.HOME, ".cache", "node", "corepack"));
rmSync(isolatedHome, { recursive: true, force: true });
});
test("cleanupIsolatedHomePath: removes existing isolated HOME directory", () => {
const homePath = mkdtempSync(path.join(tmpdir(), "fusion-test-home-root-cleanup-"));
assert.equal(path.basename(homePath).startsWith("fusion-test-home-root-cleanup-"), true);

View File

@@ -766,10 +766,16 @@ export function createIsolatedHomeEnv(env = process.env) {
knownIsolatedHomeBasenames.add(path.basename(rawIsolatedHome));
knownIsolatedHomeBasenames.add(path.basename(isolatedHome));
const inheritedHome = env.HOME || env.USERPROFILE;
const corepackHome = env.COREPACK_HOME || (inheritedHome
? path.join(inheritedHome, ".cache", "node", "corepack")
: undefined);
const nextEnv = {
...env,
HOME: isolatedHome,
USERPROFILE: isolatedHome,
...(corepackHome ? { COREPACK_HOME: corepackHome } : {}),
};
if (process.platform === "win32") {