DEV Community

Jeff
Jeff

Posted on Originally published at powerduck.com

Testing Bulk Endpoint Partial Failure

Quick answer: A bulk endpoint accepts many items in one request and can succeed on some while rejecting others, so the top-level status code does not tell you what happened. First pin down whether the endpoint is atomic (all items commit or all roll back) or non-atomic (each item is independent), then test the per-item result array across all-success, all-fail, and mixed cases, match every result back to its request item by identifier, and confirm the client lets the user retry only the failed entries without replaying the ones that already landed. Asserting on the single outer status is the mistake that hides a half-applied batch.

First pin down the contract

The two designs behave differently under failure and the tests follow from which one you have. An atomic endpoint treats the whole batch as one transaction: if any item is invalid it returns a single 4xx and nothing is written, so there is no partial state to reconcile. A non-atomic endpoint processes each item independently and returns a result per entry, so a batch can leave some resources created and others rejected. The documentation should state which model applies; if it does not, send a mixed batch and observe whether the valid items persist before you write the suite. Do not assume the presence of a bulk endpoint means a transaction.

The response shapes

The explicit form is 207 Multi-Status, whose body lists each entry with its own status, identifier, and error detail. Other APIs return 200 with a result array where every item carries a success flag, or 202 Accepted and process the batch asynchronously with a status endpoint to poll. In every non-atomic shape the meaningful result is the per-entry outcome, not the outer code. A 207 can contain both 201 and 409 entries, and a 200 can carry failed items, so a client that branches on the outer status alone will misread the batch.

The test matrix

Case Request Expected per-item result
All valid Clean items Every entry 2xx, all side effects present
All invalid Bad items Every entry 4xx, nothing written
Mixed Valid and bad items Success and failure entries both returned
Empty or single Zero or one item Handled, no crash or false result
Duplicate id Same identifier twice Conflict flagged, not silently merged
Over size limit More than the cap Rejected or bounded, never truncated silently
Partial then retry Re-send failed items Failed items fixed, landed items not duplicated

Matching results and retrying

Match each result to its request by the client-supplied identifier or the resource id, not by array position, because the server may reorder or drop entries. For the mixed case, assert the successful items exist and the rejected ones have no side effect, then check the UI renders the two groups separately so the user can act on the failures. A retry should send only the failed entries and rely on idempotency so the already-created items are not duplicated; re-sending the entire batch risks a second set of resources. In a desktop API workspace you can run the bulk request and the follow-up retry as an ordered scenario and inspect each sub-result. Powerduck supports this flow, keeping the per-item sequence deterministic rather than eyeballing a large response.

Honest limits

These tests prove the client handles per-item results and retries correctly, but they do not prove the real server's transaction boundaries, partial-commit behavior, database constraints, or how it processes a large batch under load. A local mock cannot reproduce an async worker that fails after accepting the job, race conditions between concurrent batches, or the latency of a queue, so confirm the production path with a genuine mixed batch and verify which items actually persisted. A passing run says nothing about whether the server stays consistent when a worker crashes mid-batch, and importing a schema does not deploy a backend.

Frequently asked questions

Does 207 mean the request failed? No. It means the response carries multiple independent statuses; read each entry rather than treating the whole call as an error.

How do I know if the endpoint is atomic? Send a mixed batch and check whether the valid items persist. If they do, it is non-atomic; if nothing is written, it is atomic.

What should I do after a partial failure? Retry only the failed items by id, confirm the landed items are not re-sent, and verify the retry does not create duplicates.

Can I trust result order? No. Match by identifier, since the server may process or return items in a different order.

The bottom line

Decide atomicity first, then drive the per-item result array across all-success, all-fail, mixed, duplicate, and retry cases, matching entries by identifier and asserting both the response and the side effects. Confirm the real server's partial-commit behavior separately. For more on this approach, see the API testing guide hub.

Top comments (0)