Skip to content
All projects

Shipwreck

A chaos-testing CLI that checks whether a web app really recovers from network faults.

Proxies frontend-to-backend traffic, injects controlled network faults and verifies that the client recovers. Its assertion engine catches duplicate non-idempotent requests caused by timeout-and-retry races.

  • Node.js
  • TypeScript
  • CLI
  • Fault injection

The problem

Most web apps are only ever tested on a healthy network. In production, requests time out, connections drop and clients retry, and those retries are where expensive bugs hide. A POST that timed out on the client may still have reached the server. If the client retries and the endpoint isn't idempotent, that's a second order or a second charge.

Fault-injection tools can make requests fail, but they usually stop there. They show that something broke, not whether the application handled it correctly.

Approach

Shipwreck is a command-line tool that sits between the frontend and the backend as a proxy. It forwards real traffic, injects controlled network faults and records what happens.

A request-fingerprinting and assertion engine then checks the behaviour automatically: did the client recover, and did a timeout-and-retry race send the same non-idempotent request twice? That moves fault-injection testing from detecting failures to verifying behaviour.

How it works

  1. Frontend

    The app under test, pointed at Shipwreck instead of the real API.

  2. Proxy and fault injection

    Forwards traffic and injects controlled network faults, such as timeouts.

  3. Backend

    Receives the traffic, including any retries the client sends.

  4. Request fingerprinting

    Matches repeated attempts of the same logical request.

  5. Assertion engine

    Flags duplicate non-idempotent requests and checks that the client recovered.

How a request moves through Shipwreck.

Key decisions

  1. A proxy, not an SDK

    Sitting on the network path means the app under test needs no code changes, and injected faults look like real network failures to both the client and the server.

  2. Assert behaviour, not just failure

    A test should fail when a retry duplicates a write, not merely when a request errors. The assertion engine checks what correct recovery looks like automatically, without hand-written test scripts.

  3. Fingerprint requests to spot duplicates

    Retries of one logical operation are matched by fingerprint, so a duplicate non-idempotent request is caught even when each attempt looks successful on its own.

Observability

Shipwreck shows request traces and retry behaviour in a command-line dashboard, so you can see which request was retried, when, and what the backend received.

Results

It turns resilience from an assumption into a test. The bug it targets is one I designed against in payment workflows at work: a timeout-and-retry race that turns one intended request into two.