The retention purge job gated its whole body on activeRetention while every collection declares only postDeletion, and no app ever called registerRetentionPurgeJobs — retention was dead end to end (audit findings A2 + A3). The DSR soft delete now stamps a deletedAt tombstone on postDeletion collections (kept distinct from processingRestrictedAt so an Art. 18 restriction never feeds the purge), the job grace-purges tombstoned rows past postDeletion.duration with the declared action, core-cms injects the tombstone field + Payload task definitions, and bindAllProduction enqueues the first purge cycle at boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
62 lines
1.8 KiB
TypeScript
62 lines
1.8 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import type { Payload } from "payload";
|
|
import { buildRetentionPurgeTask } from "@/payload/retention-purge/task";
|
|
|
|
function makeFakePayload() {
|
|
const find = vi.fn().mockResolvedValue({ docs: [] });
|
|
const jobsQueue = vi.fn().mockResolvedValue({ id: "job-1" });
|
|
const payload = {
|
|
config: {
|
|
collections: [
|
|
{
|
|
slug: "users",
|
|
custom: {
|
|
retention: {
|
|
purgeSchedule: "daily",
|
|
postDeletion: {
|
|
duration: "P30D",
|
|
trigger: "after-deletion",
|
|
action: "hard-delete",
|
|
},
|
|
},
|
|
},
|
|
fields: [],
|
|
},
|
|
],
|
|
},
|
|
find,
|
|
update: vi.fn(),
|
|
delete: vi.fn(),
|
|
jobs: { queue: jobsQueue },
|
|
} as unknown as Payload;
|
|
return { payload, find, jobsQueue };
|
|
}
|
|
|
|
describe("buildRetentionPurgeTask (A3)", () => {
|
|
it("uses the retention-purge--<slug> task slug", () => {
|
|
expect(buildRetentionPurgeTask("users").slug).toBe(
|
|
"retention-purge--users",
|
|
);
|
|
});
|
|
|
|
it("runs the purge against req.payload and re-enqueues the next cycle", async () => {
|
|
const { payload, find, jobsQueue } = makeFakePayload();
|
|
const task = buildRetentionPurgeTask("users");
|
|
|
|
const result = await task.handler({ req: { payload } });
|
|
|
|
expect(result).toEqual({ output: {} });
|
|
// postDeletion branch queried the tombstone field
|
|
expect(find).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
collection: "users",
|
|
where: { deletedAt: expect.anything() },
|
|
}),
|
|
);
|
|
// self-re-enqueue went through the payload job queue
|
|
expect(jobsQueue).toHaveBeenCalledWith(
|
|
expect.objectContaining({ task: "retention-purge--users" }),
|
|
);
|
|
});
|
|
});
|