CodePark/ questions / HTTP / API tests with supertest
START PRACTICINGPRACTICE
Backend question · Backend testing › HTTP / API tests with supertest

How would you test that POST /payments with an Idempotency-Key header is safe when a client retries concurrently?

it('charges once for concurrent retries with the same key', async () => {
  const send = () => request(server)
    .post('/payments')
    .set('Idempotency-Key', 'key-123')
    .send({ orderId, amount: 500 });

  const [a, b] = await Promise.all([send(), send()]);

  expect([a.status, b.status]).toContain(201);
  expect(await countPayments(orderId)).toBe(1);
  expect(gateway.charge).toHaveBeenCalledTimes(1);
});

Send two identical requests at the same time against a real database, then check that only one payment was made.

Why a real database? The protection usually depends on a unique constraint or a row lock on the key. A mocked repository can't reproduce a race: two requests running at the same moment.

Steps:

  • Send both requests at once with Promise.all.
  • Check there is exactly one payment row.
  • Check the stubbed payment gateway was charged once.
  • Check the responses match your design. Either both return the stored result, or one gets 409 while the first is still in progress.

Add sequential tests too:

  • A retry after success returns the same body.
  • The same key with a different payload is rejected.

One passing run doesn't prove there is no race, because races depend on timing. Repeat the concurrent test in a loop. Or add a hook that pauses the first request inside its transaction, so the second request always overlaps it.

  • Use a real database so constraints and locks are really tested.
  • Send concurrent requests with Promise.all.
  • Check for one row and one charge, plus the responses your design expects.
  • Also test a retry after success, and the same key with a different payload.
  • Force the overlap or repeat the test, because races depend on timing.