Skip to content
Back to skills

Add Integration Test

ASecurity

Use when a ticket asks for integration or end-to-end coverage across components — an API route hitting a database, a service-to-service call, a multi-step flow, file or database persistence, concurrent writers, crash recovery — rather than a single unit. Invoke for "test the endpoint end to end", "cover the checkout flow", "verify the migration + query together", or any acceptance criterion about persistence, concurrency or failure handling.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 5, 2026
ai-agentssqltestinggitapidatabase

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add tmj-90/gaffer --skill add-integration-test --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Add Integration Test?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Add Integration Test
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tmj-90-add-integration-test/badge)](https://www.skillsdirectory.com/skills/tmj-90-add-integration-test)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: add-integration-test
description: Use when a ticket asks for integration or end-to-end coverage across components — an API route hitting a database, a service-to-service call, a multi-step flow, file or database persistence, concurrent writers, crash recovery — rather than a single unit. Invoke for "test the endpoint end to end", "cover the checkout flow", "verify the migration + query together", or any acceptance criterion about persistence, concurrency or failure handling.
stack: []
area: testing
---

# Add an integration test

Integration tests exercise the real wiring a unit test mocks away: the route, the store,
the file system, the second process. In live runs, two builds shipped the same class of
defect past every review — concurrent writers colliding on temp-file names (HTTP 500),
unsynchronised read-modify-write losing updates, and a time-only stale-lock lease that lost
an acknowledged write when the holder was paused. Every one passed a happy-path test. For
any criterion about persistence, concurrency or failure, **the schedule is the test**.

## Procedure

1. **Read the lore and copy the harness.** Call `search_lore` for integration-test
   conventions (how the test DB, containers or temp dirs are provisioned, where these tests
   live, the integration command). Find an existing integration test and reuse its
   bootstrap: test client, setup/teardown, seeding. Do not stand up a new harness.
2. **Map each AC to an observable outcome at the boundary:** HTTP status + body, a row
   read back, a file's contents, an emitted event, an exit code. Name each test after the
   AC it proves. A test that only shows "the endpoint returns 200" does not prove "saves the
   note" — read the note back.
3. **Use real collaborators inside the boundary** (database, file system, queue, your own
   services); fake only true externals you do not own (third-party APIs) at their network
   edge. A narrow integration test covers one integration point; prefer several narrow ones
   to one sprawling journey.
4. **Cover the happy path, then the failure paths the AC implies:** invalid input,
   conflict, not-found, dependency unavailable.
5. **When an AC concerns persistence, concurrency or failure, run the real schedule** —
   real processes, real files in a temp dir, injected faults:
   - _Concurrent writers:_ fire 10–20 real operations at the same resource at once
     (`Promise.all` over real HTTP requests, or N spawned CLI processes) and repeat the
     burst several times — races are probabilistic. Assert: no 5xx or crash; the number of
     acknowledged writes equals what reads back (N increments → N); the store still
     parses; no stray temp or lock files.
   - _Durability:_ write, stop the process (clean, then `SIGKILL`), start a new one on the
     same data, read back. Every acknowledged write survives.
   - _Crash mid-operation:_ kill the process during a write loop; after restart the state
     is the old or new value, never partial.
   - _Paused holder:_ for locks, leases or leader election, `SIGSTOP` the holder past the
     timeout, let another writer take over, `SIGCONT` the first; its late write must be
     rejected (fencing token or version check) and no acknowledged write lost.
   - _Injected faults:_ read-only directory, corrupt or missing data file, closed port for
     a dependency, a transaction that throws halfway. Assert the documented error and an
     uncorrupted store.
6. **Isolate every run.** Fresh database or temp directory per test, free ports (bind to
   port 0), explicit teardown that also kills spawned processes. Poll for readiness with a
   deadline; never a fixed sleep.
7. **Prove the test bites.** Temporarily break the behaviour (skip the lock, write in place
   instead of temp-file-and-rename, drop the fsync or transaction), confirm the test fails,
   restore it and check `git diff`. A concurrency test that passes against the unguarded
   code is not testing concurrency — raise the parallelism or tighten the schedule.
   (An independent tester never edits implementation files and skips this step.)
8. **Run the repo's integration command** (the context packet's; it may differ from the
   unit `test` script) once when the tests are complete and once more after a fix — a race
   test repeats its burst inside the test, not by re-running the command. Then use the `record-evidence` skill: one
   `test_output` row per AC with the command, the summary, and the schedule exercised. The
   runner submits for review, not you. (An independent tester keeps the `black-box-test`
   budget — one run, one more after fixing its own test — and records `manual_note`s.)

## SQLite and file-backed stores

- **Fresh DB per test.** In-memory (`new Database(":memory:")`), foreign keys on, run the
  real migrations — through the repo's own test-DB helper if it has one. Never a
  module-level singleton shared across tests.
- **On-disk when the AC is about disk** (WAL, paths, durability, multi-process access):
  create it under `mkdtempSync(join(tmpdir(), "<area>-"))` in `beforeEach`, remove it in
  `afterEach`. Use the app's own open function so pragmas and migrations match production.
- Two connections or processes on one file is the only way to test `SQLITE_BUSY`,
  busy timeouts and lost updates; one connection serialises everything and proves nothing.

## Done when

- Each AC in scope has a named test asserting its own observable outcome.
- Every persistence/concurrency/failure AC has a test running the real schedule, and that
  test failed against a deliberately broken version.
- The repo's integration command passed in this session, with each concurrency test
  repeating its burst internally.

## Rules

- Mock only genuine externals; never the component under test.
- Match the repo's harness; do not install or stand up a new one.
- No retries, skips or longer sleeps to get green (the `fix-flaky-test` skill).
- Work on the delivery branch the runner prepared (the `create-branch` skill); never a
  protected branch.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…