test(FN-5128): complete Step 3 — add hydration soft-delete regressions
Fusion-Task-Id: FN-5128 Fusion-Task-Lineage: 1e8d4dd4-9712-496f-a64b-ae85198d93e8
This commit is contained in:
committed by
gsxdsm
parent
530a9c8fa4
commit
d9d3ea7fba
@@ -18,11 +18,12 @@ function makeProject(prefix: string): string {
|
||||
return dir;
|
||||
}
|
||||
|
||||
function insertTask(projectDir: string, id: string): void {
|
||||
function insertTask(projectDir: string, id: string, deletedAt: string | null = null): void {
|
||||
const db = new DatabaseSync(join(projectDir, ".fusion", "fusion.db"));
|
||||
const now = new Date().toISOString();
|
||||
db.prepare("INSERT OR REPLACE INTO tasks (id, description, \"column\", createdAt, updatedAt, dependencies) VALUES (?, ?, 'todo', ?, ?, '[]')")
|
||||
.run(id, id, now, now);
|
||||
db.prepare(
|
||||
"INSERT OR REPLACE INTO tasks (id, description, \"column\", createdAt, updatedAt, dependencies, deletedAt) VALUES (?, ?, 'todo', ?, ?, '[]', ?)",
|
||||
).run(id, id, now, now, deletedAt);
|
||||
db.close();
|
||||
}
|
||||
|
||||
@@ -99,6 +100,69 @@ describe("hydrateWorktreeDb", () => {
|
||||
expect(capped.tasksCopied).toBeLessThanOrEqual(50);
|
||||
}, 20_000);
|
||||
|
||||
it("excludes soft-deleted dependencies and their documents", async () => {
|
||||
const root = makeProject("h-soft-delete-");
|
||||
const worktree = makeProject("h-soft-delete-dst-");
|
||||
cleanup.push(root, worktree);
|
||||
|
||||
insertTask(root, "FN-A", null);
|
||||
insertTask(root, "FN-B", new Date().toISOString());
|
||||
insertDoc(root, "FN-A");
|
||||
insertDoc(root, "FN-B");
|
||||
|
||||
const depMap: Record<string, string[]> = { "FN-A": ["FN-B"], "FN-B": [] };
|
||||
const store = { getTask: vi.fn(async (id: string) => ({ id, dependencies: depMap[id] ?? [] })) };
|
||||
|
||||
const result = await hydrateWorktreeDb({ rootDir: root, worktreePath: worktree, taskId: "FN-A", store: store as any, logger: { warn: vi.fn() } });
|
||||
expect(result.degraded).toBe(false);
|
||||
expect(result.tasksCopied).toBe(1);
|
||||
expect(result.documentsCopied).toBe(1);
|
||||
|
||||
const db = new DatabaseSync(join(worktree, ".fusion", "fusion.db"));
|
||||
const softDeletedTask = db.prepare("SELECT id FROM tasks WHERE id='FN-B'").get();
|
||||
const softDeletedDoc = db.prepare("SELECT taskId FROM task_documents WHERE taskId='FN-B'").get();
|
||||
db.close();
|
||||
|
||||
expect(softDeletedTask).toBeUndefined();
|
||||
expect(softDeletedDoc).toBeUndefined();
|
||||
});
|
||||
|
||||
it("hydrates tasks with deletedAt IS NULL", async () => {
|
||||
const root = makeProject("h-null-deletedAt-");
|
||||
const worktree = makeProject("h-null-deletedAt-dst-");
|
||||
cleanup.push(root, worktree);
|
||||
|
||||
insertTask(root, "FN-1", null);
|
||||
insertDoc(root, "FN-1");
|
||||
const store = { getTask: vi.fn(async () => ({ id: "FN-1", dependencies: [] })) };
|
||||
|
||||
const result = await hydrateWorktreeDb({ rootDir: root, worktreePath: worktree, taskId: "FN-1", store: store as any, logger: { warn: vi.fn() } });
|
||||
expect(result.degraded).toBe(false);
|
||||
expect(result.tasksCopied).toBe(1);
|
||||
expect(result.documentsCopied).toBe(1);
|
||||
});
|
||||
|
||||
it("hydrates when source tasks schema has no deletedAt column", async () => {
|
||||
const root = makeProject("h-src-no-deletedAt-");
|
||||
const worktree = makeProject("h-src-no-deletedAt-dst-");
|
||||
cleanup.push(root, worktree);
|
||||
|
||||
insertTask(root, "FN-1", null);
|
||||
const srcDb = new DatabaseSync(join(root, ".fusion", "fusion.db"));
|
||||
srcDb.exec("DROP TRIGGER IF EXISTS tasks_fts_ai");
|
||||
srcDb.exec("DROP TRIGGER IF EXISTS tasks_fts_au");
|
||||
srcDb.exec("DROP TRIGGER IF EXISTS tasks_fts_ad");
|
||||
srcDb.exec("DROP INDEX IF EXISTS idx_tasks_deletedAt");
|
||||
srcDb.exec("ALTER TABLE tasks DROP COLUMN deletedAt");
|
||||
srcDb.close();
|
||||
|
||||
const store = { getTask: vi.fn(async () => ({ id: "FN-1", dependencies: [] })) };
|
||||
const result = await hydrateWorktreeDb({ rootDir: root, worktreePath: worktree, taskId: "FN-1", store: store as any, logger: { warn: vi.fn() } });
|
||||
|
||||
expect(result.degraded).toBe(false);
|
||||
expect(result.tasksCopied).toBe(1);
|
||||
});
|
||||
|
||||
it("handles schema drift by dropping missing destination columns", async () => {
|
||||
const root = makeProject("h-drift-");
|
||||
const worktree = makeProject("h-drift-dst-");
|
||||
|
||||
Reference in New Issue
Block a user