test(jetstream): cover bulk-create debounce and flush semantics
Some checks failed
CI / check (push) Failing after 11s
Some checks failed
CI / check (push) Failing after 11s
Uses @std/testing/time FakeTime to deterministically advance the 500ms debounce. Covers accumulation, timer restart on each push, the bulk-create verb + payload shape, and per-did buffer isolation with cleared state after flush.
This commit is contained in:
147
tests/jetstream_bulk_test.ts
Normal file
147
tests/jetstream_bulk_test.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { assertEquals } from "@std/assert";
|
||||
import { FakeTime } from "@std/testing/time";
|
||||
import {
|
||||
_resetBulkBuffersForTest,
|
||||
type BulkDeps,
|
||||
queueBulkCreate,
|
||||
} from "../src/jetstream/bulk.ts";
|
||||
import type { WebhookTarget } from "../src/jetstream/webhooks.ts";
|
||||
import type { WebhookVerb } from "../src/data/db.ts";
|
||||
|
||||
type DispatchCall = {
|
||||
webhooks: WebhookTarget[];
|
||||
payload: Record<string, unknown>;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const flushMicrotasks = async (): Promise<void> => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await Promise.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
const makeDeps = (
|
||||
webhooks: WebhookTarget[] = [],
|
||||
): {
|
||||
deps: BulkDeps;
|
||||
getSubsCalls: { did: string; verb: WebhookVerb }[];
|
||||
dispatchCalls: DispatchCall[];
|
||||
} => {
|
||||
const getSubsCalls: { did: string; verb: WebhookVerb }[] = [];
|
||||
const dispatchCalls: DispatchCall[] = [];
|
||||
const deps: BulkDeps = {
|
||||
getSubs: (did, verb) => {
|
||||
getSubsCalls.push({ did, verb });
|
||||
return webhooks;
|
||||
},
|
||||
dispatch: (w, payload, label) => {
|
||||
dispatchCalls.push({ webhooks: w, payload, label });
|
||||
return Promise.resolve();
|
||||
},
|
||||
debounceMs: 500,
|
||||
};
|
||||
return { deps, getSubsCalls, dispatchCalls };
|
||||
};
|
||||
|
||||
Deno.test("queueBulkCreate accumulates records and flushes once after debounce", async () => {
|
||||
const time = new FakeTime();
|
||||
try {
|
||||
_resetBulkBuffersForTest();
|
||||
const { deps, dispatchCalls } = makeDeps();
|
||||
|
||||
queueBulkCreate("did:plc:a", { rkey: "r1" }, deps);
|
||||
queueBulkCreate("did:plc:a", { rkey: "r2" }, deps);
|
||||
queueBulkCreate("did:plc:a", { rkey: "r3" }, deps);
|
||||
|
||||
assertEquals(dispatchCalls.length, 0);
|
||||
|
||||
await time.tickAsync(500);
|
||||
await flushMicrotasks();
|
||||
|
||||
assertEquals(dispatchCalls.length, 1);
|
||||
const payload = dispatchCalls[0].payload as {
|
||||
records: { rkey: string }[];
|
||||
};
|
||||
assertEquals(payload.records.map((r) => r.rkey), ["r1", "r2", "r3"]);
|
||||
} finally {
|
||||
_resetBulkBuffersForTest();
|
||||
time.restore();
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("queueBulkCreate restarts the timer on each push (debounce)", async () => {
|
||||
const time = new FakeTime();
|
||||
try {
|
||||
_resetBulkBuffersForTest();
|
||||
const { deps, dispatchCalls } = makeDeps();
|
||||
|
||||
queueBulkCreate("did:plc:a", { rkey: "r1" }, deps);
|
||||
await time.tickAsync(400);
|
||||
queueBulkCreate("did:plc:a", { rkey: "r2" }, deps);
|
||||
await time.tickAsync(400);
|
||||
|
||||
assertEquals(dispatchCalls.length, 0);
|
||||
|
||||
await time.tickAsync(100);
|
||||
await flushMicrotasks();
|
||||
|
||||
assertEquals(dispatchCalls.length, 1);
|
||||
} finally {
|
||||
_resetBulkBuffersForTest();
|
||||
time.restore();
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("flushBulkCreate uses bulk-create verb and the standard payload shape", async () => {
|
||||
const time = new FakeTime();
|
||||
try {
|
||||
_resetBulkBuffersForTest();
|
||||
const targets: WebhookTarget[] = [
|
||||
{ method: "POST", url: "https://hook.example/bulk" },
|
||||
];
|
||||
const { deps, getSubsCalls, dispatchCalls } = makeDeps(targets);
|
||||
|
||||
queueBulkCreate("did:plc:a", { rkey: "r1", title: "T" }, deps);
|
||||
await time.tickAsync(500);
|
||||
await flushMicrotasks();
|
||||
|
||||
assertEquals(getSubsCalls, [{ did: "did:plc:a", verb: "bulk-create" }]);
|
||||
assertEquals(dispatchCalls.length, 1);
|
||||
assertEquals(dispatchCalls[0].webhooks, targets);
|
||||
assertEquals(dispatchCalls[0].label, "bulk-create did:plc:a");
|
||||
assertEquals(dispatchCalls[0].payload, {
|
||||
event: "bulk-create",
|
||||
did: "did:plc:a",
|
||||
records: [{ rkey: "r1", title: "T" }],
|
||||
});
|
||||
} finally {
|
||||
_resetBulkBuffersForTest();
|
||||
time.restore();
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("bulkBuffers are cleared after flush and isolated per did", async () => {
|
||||
const time = new FakeTime();
|
||||
try {
|
||||
_resetBulkBuffersForTest();
|
||||
const { deps, dispatchCalls } = makeDeps();
|
||||
|
||||
queueBulkCreate("did:plc:a", { rkey: "a1" }, deps);
|
||||
queueBulkCreate("did:plc:b", { rkey: "b1" }, deps);
|
||||
await time.tickAsync(500);
|
||||
await flushMicrotasks();
|
||||
|
||||
assertEquals(dispatchCalls.length, 2);
|
||||
const dids = dispatchCalls.map((c) => (c.payload as { did: string }).did)
|
||||
.sort();
|
||||
assertEquals(dids, ["did:plc:a", "did:plc:b"]);
|
||||
|
||||
// Another flush window should not re-dispatch the same records.
|
||||
await time.tickAsync(500);
|
||||
await flushMicrotasks();
|
||||
assertEquals(dispatchCalls.length, 2);
|
||||
} finally {
|
||||
_resetBulkBuffersForTest();
|
||||
time.restore();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user