Unit test Sume SDK retries with a fake fetch

createSumeClient takes a fetch option for tests. Feed it a 503 then a 200 to prove the SDK retries a GET and sends one x-api-key, with no network.

5 min readSume
All posts

Short answer

createSumeClient accepts a fetch option that the docs describe as a seam for instrumentation, retries or tests. Pass a function that returns a 503 first and a 200 second, call any generated operation, and you can assert that the client made two attempts and sent the x-api-key header, without a server or a key that spends credits.

What the client does around your fetch

The SDK wraps whatever fetch you give it. The wrapper retries 408, 429 and 5xx responses and transport failures, up to maxRetries, which defaults to 2. A GET or HEAD is always safe to replay. A POST is replayed only when it carries an Idempotency-Key header, because replaying a run create without one would start and charge for a second run.

createSumeClient options and retry rules, per the SDK docs and source (read 2026-10-03)
ItemValue
fetchDefaults to globalThis.fetch; a test seam
maxRetries2
timeout10 minutes per request; 0 disables
Retried statuses408, 429, 5xx, plus transport errors
GET and HEADAlways retried
POSTRetried only with an Idempotency-Key header

A fake fetch test

The wrapper hands your fake a Request, so you can read its headers directly. The sample fails once and then succeeds. Because the first retry waits roughly half a second plus or minus 20 percent, the script takes about that long; keep that in mind for suites with many such cases.

Generated operations do not throw on API errors. They resolve with data, error and response, so the assertion reads error rather than catching an exception. The wait helpers are different and throw on timeout or a refused create.

import { createSumeClient, listFormats } from "@sume-com/sdk";

const keys: (string | null)[] = [];
const fake = async (req: Request) => {
  keys.push(req.headers.get("x-api-key"));
  if (keys.length === 1) return new Response("{}", { status: 503 });
  return new Response(JSON.stringify({ data: [] }), {
    status: 200,
    headers: { "content-type": "application/json" },
  });
};

const client = createSumeClient({ apiKey: "test-key", fetch: fake });
const { error } = await listFormats({ client });
console.log(keys.length, keys[0], error);

Cases that pay for themselves

Three more fakes cover most of the policy. First, return 400 and assert one attempt: client errors are not retried. Second, send a POST without an Idempotency-Key, return 503, and assert one attempt, which proves your code never replays an unkeyed paid request. Third, send the same POST with a key and assert that both attempts carry the identical header value.

If you wrap fetch for logging, remember the credential rule. The API rejects a request carrying both Authorization and x-api-key with 401 unauthorized, so a wrapper that adds Authorization on top of the client's own header breaks every call. A fake that records all headers makes that mistake visible in a unit test.

Limits of the seam

A fake fetch tests your wiring and the SDK's policy, not the Sume service. Keep fixtures aligned with the documented error shape, which carries error.code, error.message and error.request_id, and keep a small live smoke test on a read-only route for the real contract.

Running in CI

The whole file runs offline, so it can live in your normal unit test job without secrets. Use a throwaway string for apiKey, since the fake never contacts the service. If you also keep a live smoke test, gate it behind an environment variable and read the real key from that variable, so the offline tests pass on forks and local checkouts that have no key.

To keep timing deterministic in larger suites, you can set maxRetries to 1 for most cases and reserve the default of 2 for the one test that checks the policy. Each retry adds a real delay, because the SDK sleeps between attempts.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume