mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 21:07:40 +01:00
test: eliminate decision sweep timing races
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
cffe8efd85
commit
2c6e14dffb
1 file changed
+14
-9
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
activityLog,
|
||||
@@ -84,6 +84,11 @@ describePg("decisionService", () => {
|
||||
options: [{ id: "yes", label: "Yes", effects: [{ type: "comment_on_issue", targetIssueId, staleness, bodyMarkdown: "hello" }] }],
|
||||
...extra,
|
||||
});
|
||||
const expireDecisions = async (...ids: string[]) => {
|
||||
await db.update(decisions)
|
||||
.set({ expiresAt: new Date(Date.now() - 1_000) })
|
||||
.where(inArray(decisions.id, ids));
|
||||
};
|
||||
|
||||
it("returns the existing decision for concurrent idempotent creates", async () => {
|
||||
const input = {
|
||||
@@ -440,27 +445,27 @@ describePg("decisionService", () => {
|
||||
|
||||
it("bounds expiration work to the configured batch size", async () => {
|
||||
process.env.PAPERCLIP_DECISIONS_SWEEP_BATCH_SIZE = "1";
|
||||
await createCommentDecision("lenient", { idempotencyKey: "batch-1", expiresAt: new Date(Date.now() + 5) });
|
||||
await createCommentDecision("lenient", { idempotencyKey: "batch-2", expiresAt: new Date(Date.now() + 5) });
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
const first = await createCommentDecision("lenient", { idempotencyKey: "batch-1" });
|
||||
const second = await createCommentDecision("lenient", { idempotencyKey: "batch-2" });
|
||||
await expireDecisions(first.id, second.id);
|
||||
expect((await service().sweepExpired()).expired).toBe(1);
|
||||
expect((await service().sweepExpired()).expired).toBe(1);
|
||||
});
|
||||
|
||||
it("falls back to the default sweep batch size for invalid configuration", async () => {
|
||||
process.env.PAPERCLIP_DECISIONS_SWEEP_BATCH_SIZE = "not-a-number";
|
||||
await createCommentDecision("lenient", { idempotencyKey: "invalid-batch-1", expiresAt: new Date(Date.now() + 5) });
|
||||
await createCommentDecision("lenient", { idempotencyKey: "invalid-batch-2", expiresAt: new Date(Date.now() + 5) });
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
const first = await createCommentDecision("lenient", { idempotencyKey: "invalid-batch-1" });
|
||||
const second = await createCommentDecision("lenient", { idempotencyKey: "invalid-batch-2" });
|
||||
await expireDecisions(first.id, second.id);
|
||||
|
||||
await expect(service().sweepExpired()).resolves.toMatchObject({ expired: 2 });
|
||||
});
|
||||
|
||||
it("expires TTL and target-gone decisions and wakes the origin agent", async () => {
|
||||
const ttl = await createCommentDecision("lenient", { expiresAt: new Date(Date.now() + 5) });
|
||||
const ttl = await createCommentDecision("lenient");
|
||||
const gone = await createCommentDecision("strict", { idempotencyKey: "gone" });
|
||||
await db.update(issues).set({ status: "cancelled" }).where(eq(issues.id, targetIssueId));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
await expireDecisions(ttl.id);
|
||||
expect((await service().sweepExpired()).expired).toBe(2);
|
||||
const rows = await db.select().from(decisions);
|
||||
expect(rows.find((row) => row.id === ttl.id)?.metadata).toMatchObject({ expiredReason: "ttl" });
|
||||
|
||||
Reference in new issue
Block a user