Skip to content

Commit 2a4a56b

Browse files
committed
add some tests for new changes
1 parent b3212f3 commit 2a4a56b

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

apps/webapp/test/bulk-actions-api.e2e.full.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,79 @@ describe("Bulk actions API", () => {
199199
expect(response.status).toBe(500);
200200
await expect(response.json()).resolves.toEqual({ error: "Failed to create bulk action" });
201201
});
202+
203+
it("blocks a new replay once the concurrent-replay limit is reached", async () => {
204+
const server = getTestServer();
205+
const { apiKey, project, environment } = await seedTestEnvironment(server.prisma);
206+
207+
// Fill the per-environment concurrent-replay slots with fresh, in-flight replays.
208+
// The guard runs before the ClickHouse count, so this asserts cleanly without it.
209+
for (let i = 0; i < 3; i++) {
210+
await seedBulkAction(server.prisma, project, environment, {
211+
type: BulkActionType.REPLAY,
212+
status: BulkActionStatus.PENDING,
213+
});
214+
}
215+
216+
const response = await server.webapp.fetch("/api/v1/bulk-actions", {
217+
method: "POST",
218+
headers: authHeaders(apiKey),
219+
body: JSON.stringify({ action: "replay", filter: { status: "FAILED" } }),
220+
});
221+
222+
expect(response.status).toBe(429);
223+
// The cap is a semantic limit, not a transient rate limit, so the SDK must not retry it.
224+
expect(response.headers.get("x-should-retry")).toBe("false");
225+
const body = await response.json();
226+
expect(body.error).toContain("bulk replays at a time");
227+
});
228+
229+
it("does not count stale replays that have stopped making progress", async () => {
230+
const server = getTestServer();
231+
const { apiKey, project, environment } = await seedTestEnvironment(server.prisma);
232+
233+
for (let i = 0; i < 3; i++) {
234+
await seedBulkAction(server.prisma, project, environment, {
235+
type: BulkActionType.REPLAY,
236+
status: BulkActionStatus.PENDING,
237+
});
238+
}
239+
240+
// Backdate updatedAt past the in-flight window so these look like dead replays.
241+
// (updatedAt is @updatedAt, so it can only be set via raw SQL, not on create.)
242+
await server.prisma.$executeRawUnsafe(
243+
`UPDATE "BulkActionGroup" SET "updatedAt" = now() - interval '31 minutes' WHERE "environmentId" = $1`,
244+
environment.id
245+
);
246+
247+
const response = await server.webapp.fetch("/api/v1/bulk-actions", {
248+
method: "POST",
249+
headers: authHeaders(apiKey),
250+
body: JSON.stringify({ action: "replay", filter: { status: "FAILED" } }),
251+
});
252+
253+
// Stale replays don't hold a slot, so the guard lets the request through and it
254+
// reaches the count step, which fails (no ClickHouse in this suite) with a 500 rather
255+
// than being blocked by the concurrency guard's 429.
256+
expect(response.status).toBe(500);
257+
});
258+
259+
it("rejects create requests with more runIds than the allowed maximum", async () => {
260+
const server = getTestServer();
261+
const { apiKey } = await seedTestEnvironment(server.prisma);
262+
263+
const runIds = Array.from({ length: 501 }, (_, i) => `run_${i}`);
264+
265+
const response = await server.webapp.fetch("/api/v1/bulk-actions", {
266+
method: "POST",
267+
headers: authHeaders(apiKey),
268+
body: JSON.stringify({ action: "cancel", runIds }),
269+
});
270+
271+
expect(response.status).toBe(400);
272+
const body = await response.json();
273+
expect(body.error).toContain("Too many runIds");
274+
});
202275
});
203276

204277
function authHeaders(apiKey: string) {

0 commit comments

Comments
 (0)