Files
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

70 lines
2.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { runBulkAction } from "../../src/lib/bulk";
/** A deferred promise whose resolution the test controls. */
function deferred() {
let resolve!: () => void;
const promise = new Promise<void>((res) => {
resolve = res;
});
return { promise, resolve };
}
describe("runBulkAction", () => {
it("runs every id and reports no failures on success", async () => {
const seen: string[] = [];
const result = await runBulkAction(["a", "b", "c"], async (id) => {
seen.push(id);
});
expect(seen.toSorted()).toEqual(["a", "b", "c"]);
expect(result.failedIds).toEqual([]);
});
it("collects failed ids in input order without aborting the rest", async () => {
const seen: string[] = [];
const result = await runBulkAction(["a", "b", "c", "d"], async (id) => {
seen.push(id);
if (id === "c" || id === "a") throw new Error(`boom ${id}`);
});
expect(seen.toSorted()).toEqual(["a", "b", "c", "d"]);
expect(result.failedIds).toEqual(["a", "c"]);
});
it("never exceeds the concurrency limit", async () => {
const limit = 2;
let inFlight = 0;
let maxInFlight = 0;
const gates = new Map<string, ReturnType<typeof deferred>>();
const ids = ["a", "b", "c", "d", "e"];
for (const id of ids) gates.set(id, deferred());
const run = runBulkAction(
ids,
async (id) => {
inFlight++;
maxInFlight = Math.max(maxInFlight, inFlight);
await gates.get(id)!.promise;
inFlight--;
},
limit,
);
// Release the gates one at a time so the queue has to refill.
for (const id of ids) {
// Let the queue start whatever it can before releasing the next gate.
await new Promise((resolve) => setTimeout(resolve, 0));
gates.get(id)!.resolve();
}
const result = await run;
expect(maxInFlight).toBeLessThanOrEqual(limit);
expect(result.failedIds).toEqual([]);
});
it("handles an empty id list", async () => {
const result = await runBulkAction([], () => Promise.resolve());
expect(result.failedIds).toEqual([]);
});
});