test(concurrency): sync concurrency and provider capabilities tests with Phase 1.4 normalized error responses and FSM snapshot-first draw flow

This commit is contained in:
Ochenstarik 2026-08-18 01:56:26 +07:00
parent 50973b4f85
commit 867923186e
2 changed files with 10 additions and 11 deletions

View file

@ -43,6 +43,7 @@ async function createReadyGiveaway() {
}));
await GiveawayStore.updateParticipants(gw.id, participants);
await GiveawayStore.createAndLockSnapshot(gw.id, participants, DEFAULT_FILTER_RULES);
return gw;
}
@ -52,7 +53,7 @@ describe('Concurrency analysis', () => {
ProviderRegistry.useMockVk();
});
it('documents current double-draw race behavior (both requests may succeed)', async () => {
it('documents double-draw race protection (exactly one succeeds with 200, concurrent receives 409)', async () => {
const gw = await createReadyGiveaway();
const req1 = new NextRequest(`http://localhost/api/giveaways/${gw.id}/draw`, {
@ -69,10 +70,9 @@ describe('Concurrency analysis', () => {
drawPost(req2, { params: { id: gw.id } }),
]);
// This assertion captures the CURRENT behavior so the test passes today.
// If the race is fixed, the previous `it.failing` will start passing and
// this test should be updated to assert one failure.
expect([res1.status, res2.status]).toContain(200);
const statuses = [res1.status, res2.status];
expect(statuses).toContain(200);
expect(statuses).toContain(409);
});
it('should not corrupt giveaway state when snapshot and draw race', async () => {
@ -107,7 +107,7 @@ describe('Concurrency analysis', () => {
const gw = await createReadyGiveaway();
const refreshed = await GiveawayStore.getById(gw.id);
const eligible = refreshed!.participants.filter(p => p.eligible);
const snapshot = await GiveawayStore.createAndLockSnapshot(gw.id, eligible, DEFAULT_FILTER_RULES);
const snapshot = refreshed!.latestSnapshot!;
await GiveawayStore.saveDrawResult(gw.id, snapshot.id, {
drawId: 'draw-test',
giveawayId: gw.id,
@ -132,8 +132,6 @@ describe('Concurrency analysis', () => {
});
const res = await participantsPost(req, { params: { id: gw.id } });
// The route catches the FSM error and returns 500; the important thing is
// that the DRAWN giveaway is not silently overwritten.
expect(res.status).toBeGreaterThanOrEqual(400);
});
});

View file

@ -130,8 +130,8 @@ describe('Provider capabilities', () => {
const res = await participantsPost(req, { params: { id: gw.id } });
expect(res.status).toBe(400);
const data = await res.json();
expect(data.error).toMatch(/Unsupported filter rules/i);
expect(data.details.some((d: string) => d.includes('requireRepost'))).toBe(true);
const errorMessage = data.error?.message || data.error || '';
expect(errorMessage).toMatch(/repost/i);
});
it('participants route returns 400 when excludeAdmins is requested for VK', async () => {
@ -151,7 +151,8 @@ describe('Provider capabilities', () => {
const res = await participantsPost(req, { params: { id: gw.id } });
expect(res.status).toBe(400);
const data = await res.json();
expect(data.details.some((d: string) => d.includes('excludeAdmins'))).toBe(true);
const errorMessage = data.error?.message || data.error || '';
expect(errorMessage).toMatch(/admin/i);
});
it('participants route succeeds for supported VK rules', async () => {