FN-5792: fix GitHub auto-close reconciliation for soft-deleted tasks
Ensure GitHub issue auto-close reconciliation still processes soft-deleted and archived tracked tasks after restart. - Add pagination-aware reconcile scanning in core store with combined deleted+archived coverage and hasMore metadata. - Update dashboard GitHub tracking reconciler and route handling to consume paged reconcile batches safely. - Expand reconcile tests (core + dashboard) with restart/periodic sweep and source-issue scenarios to lock behavior. - Add release changeset and dashboard guide note for the reconciliation sweep behavior. Files changed: .changeset/fn-5792-github-tracking-sweep.md | 5 + docs/dashboard-guide.md | 1 + .../store-github-tracking-reconcile.test.ts | 45 ++++++++- packages/core/src/store.ts | 32 ++++-- .../github-source-issue-reconciler.test.ts | 22 ++-- ...ithub-tracking-periodic-reconcile-sweep.test.ts | 90 +++++++++++++++++ .../__tests__/github-tracking-reconciler.test.ts | 111 +++++---------------- .../dashboard/src/github-tracking-reconciler.ts | 19 ++-- .../dashboard/src/routes/register-git-github.ts | 57 +++++++++-- 9 files changed, 257 insertions(+), 125 deletions(-) Fusion-Task-Id: FN-5792 Fusion-Task-Lineage: 259f07cf-3470-47c7-b47a-86d212d44d52
This commit is contained in:
@@ -64,7 +64,7 @@ describe("TaskStore.listTasksForGithubTrackingReconcile", () => {
|
||||
const softDeletedWithoutTracking = await store.createTask({ description: "soft deleted no tracking" });
|
||||
await store.deleteTask(softDeletedWithoutTracking.id);
|
||||
|
||||
const tasks = await store.listTasksForGithubTrackingReconcile();
|
||||
const { tasks, hasMore } = await store.listTasksForGithubTrackingReconcile();
|
||||
const byId = new Map(tasks.map((task) => [task.id, task]));
|
||||
|
||||
expect(byId.has(softDeleted.id)).toBe(true);
|
||||
@@ -76,13 +76,52 @@ describe("TaskStore.listTasksForGithubTrackingReconcile", () => {
|
||||
|
||||
expect(byId.has(activeTracked.id)).toBe(false);
|
||||
expect(byId.has(softDeletedWithoutTracking.id)).toBe(false);
|
||||
expect(hasMore).toBe(false);
|
||||
});
|
||||
|
||||
it("returns empty results when nothing matches", async () => {
|
||||
const task = await store.createTask({ description: "no tracking" });
|
||||
await store.moveTask(task.id, "todo");
|
||||
|
||||
const tasks = await store.listTasksForGithubTrackingReconcile();
|
||||
expect(tasks).toEqual([]);
|
||||
const result = await store.listTasksForGithubTrackingReconcile();
|
||||
expect(result).toEqual({ tasks: [], hasMore: false });
|
||||
});
|
||||
|
||||
it("paginates across soft-deleted and archived tracked entries", async () => {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
const task = await store.createTask({ description: `deleted ${i}` });
|
||||
await store.updateGithubTracking(task.id, { enabled: true });
|
||||
await store.deleteTask(task.id);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
const task = await store.createTask({ description: `archived ${i}` });
|
||||
await store.updateGithubTracking(task.id, { enabled: true });
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
await store.moveTask(task.id, "in-review");
|
||||
await store.moveTask(task.id, "done");
|
||||
await store.archiveTask(task.id);
|
||||
}
|
||||
|
||||
const page1 = await store.listTasksForGithubTrackingReconcile({ offset: 0, limit: 2 });
|
||||
const page2 = await store.listTasksForGithubTrackingReconcile({ offset: 2, limit: 2 });
|
||||
const page3 = await store.listTasksForGithubTrackingReconcile({ offset: 4, limit: 2 });
|
||||
|
||||
expect(page1.tasks).toHaveLength(2);
|
||||
expect(page2.tasks).toHaveLength(2);
|
||||
expect(page3.tasks).toHaveLength(2);
|
||||
|
||||
const seen = new Set([...page1.tasks, ...page2.tasks, ...page3.tasks].map((task) => task.id));
|
||||
expect(seen.size).toBe(6);
|
||||
expect(page1.hasMore).toBe(true);
|
||||
expect(page2.hasMore).toBe(true);
|
||||
expect(page3.hasMore).toBe(false);
|
||||
|
||||
const activeTracked = await store.createTask({ description: "active tracked no reconcile" });
|
||||
await store.updateGithubTracking(activeTracked.id, { enabled: true });
|
||||
|
||||
const finalPage = await store.listTasksForGithubTrackingReconcile({ offset: 0, limit: 20 });
|
||||
expect(finalPage.tasks.some((task) => task.id === activeTracked.id)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user