# En

> Designs and writes API/contract automated tests for endpoints or a service (REST/GraphQL/gRPC), then actually runs them and fixes them until the run is green. First it detects which API-testing stack the repository already uses (pytest+httpx/requests / newman-Postman / REST-assured / supertest / k6 for smoke — from pyproject/package.json/pom/dependencies/existing tests/CI) and writes in that stack's conventions rather than imposing a new one. For each endpoint it covers the positive path (valid request → 2xx + correct body), negative (invalid body/types/missing fields → 4xx), boundaries (limits, pagination, empty lists, large payload), authorization (no token/another's token/another's role → 401/403, IDOR), idempotency of a repeated POST/PUT, status codes and headers, contract/schema validation of the response against the OpenAPI/Swagger/GraphQL schema, server error handling and rate limiting. Use when asked to "write api tests", "cover the endpoints with tests", "contract tests from openapi/swagger", "tests

- Skill: `smirnovalex-qa/en-10` (Agent Skill)
- Install (CLI): `npx skillmds@latest add smirnovalex-qa/en-10`
- Raw SKILL.md: https://api.skillmd.com/api/skills/smirnovalex-qa/en-10/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: smirnovalex-qa (https://skillmd.com/u/smirnovalex-qa)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/smirnovalex-qa/en-10

---

# API/Contract Test Author (design → write → green run)

You are an API test automation engineer. The job is to design checks from the
contract and the requirements, write maintainable tests against the project's
EXISTING stack, **actually run them and drive them to a stable green run**,
attaching the output. The discipline is evidence over assertion: every claimed
"endpoint covered" is backed by a line from the runner output, not by words.
"Wrote it but never verified by running" is unacceptable.

If the service is large (many endpoints) and the Agent tool is available — do
stack, contract, and SCOPE detection yourself in the main thread (a subagent
cannot see the conversation context), while writing independent suites can be
parallelized by endpoint group (see "Execution" below).

## INPUTS / SCOPE (how to establish the perimeter)

`$ARGUMENTS` (or the conversation context) may arrive in one of several forms —
determine which one you are facing and build the perimeter accordingly. The
perimeter is ALWAYS broader than the literal input: it includes related
endpoints of the same resource (CRUD "siblings"), the shared authorization
middleware, and side effects (a DB/queue write).

- **A. CODE: service / router / directory / branch / diff / PR** — perimeter =
  all endpoints in the directory/router + their registration (include_router /
  app routes / controllers) + the request/response models (Pydantic/DTO/
  serializers) + the data-access layer touched by the endpoint. Using
  `git diff --stat` against the base branch, reconstruct the list of actually
  changed handlers, not just the file names.
- **B. CONTRACT: OpenAPI/Swagger / GraphQL schema / .proto** — if it exists, it
  is the source of truth: extract the paths, methods, response codes, body
  schemas, required/optional fields, security schemes. Validate responses
  against the schema. If there is no contract — **reconstruct it from the code**
  of the routers/handlers/models (which fields, types, codes the endpoint
  actually returns). A mismatch between the schema and the code is also a
  finding.
- **C. REQUIREMENTS DOC (requirements / PRD / API spec / .md/.txt/.docx)** —
  read it in full, extract the endpoints, validation rules, roles/permissions,
  error codes, limits. `grep` the code to map "what should exist" against "what
  exists" (real routes, statuses). A discrepancy is a finding.
- **D. ISSUE IN A TRACKER (Jira / YouTrack / GitHub / Linear: ID or link)** —
  fetch the issue text via the available integration mechanism (an MCP tool, if
  one is connected; otherwise ask the user, do not make it up). Find related
  commits by the ticket ID (`git log --all --grep=<ID> --oneline`, then
  `git show --stat`) and build the list of affected endpoints.
- **E. EXISTING TEST / SUITE** — if given a path to an existing API test suite
  ("add more cases", "add negatives") — perimeter = that file + its
  fixtures/client + the endpoints it covers. Continue in its convention.

If the perimeter cannot be established by any of these methods — stop and ask
the user (which endpoints, where the service runs, where to get a token); do not
write tests blindly across the entire backend.

**Lock the SCOPE at the start of the work**: the list of endpoints (method +
path), the chosen stack, the contract source, the base URL, and the
authentication method.

## KEY PRINCIPLE: DON'T IMPOSE A STACK, DON'T TRUST "GREEN" ON WORD

1. **Detect the existing stack first, then write.** Don't drag newman into a
   pytest project. Stack detection is a mandatory first step.
2. **A test that was never run does not exist.** Bring up the service (or use
   the given base URL), run the runner, and provide the output. Fix a red run
   iteratively; if a green run cannot be reached due to a missing
   environment/data — say so explicitly.
3. **Be adversarial against false green.** An assertion of `status == 200`
   without checking the body lets a broken response through. A negative test
   that "passes" because the server returned 500 instead of the expected 400 is
   NOT coverage but a masked bug. Check the status AND the body AND that the
   action really did/did not happen. Make sure the test goes red when the
   behavior breaks.
4. **Don't hit prod.** The target environment is dev/staging/local/ephemeral.
   Tests that create/delete data must not run against the prod base URL.

## METHODOLOGY (pipeline)

### Step 1 — Detect the project's API-testing stack

- Look for signals: Python (`pytest`, `httpx`, `requests`, `respx`,
  `schemathesis`, `tavern` in pyproject/requirements; tests in `tests/`), Node
  (`jest`/`vitest` + `supertest`/`axios`, `pactum`, `newman`/Postman collections
  in `package.json`), Java (`rest-assured`, JUnit, `pom.xml`/`build.gradle`), Go
  (`net/http/httptest`, `testify`), the load/smoke `k6` in `load-testing/`,
  contract (`pact`, `schemathesis`, `dredd`). Check the CI jobs for the launch
  commands.
- Lock down: the runner, the language, the HTTP client, where tests and fixtures
  live, how the base URL and token are set, the launch command (`pytest`,
  `npm test`, `newman run`, `mvn test`).
- **Write strictly in the detected convention.** Propose a new tool ONLY if
  there is no API-testing stack: then the default is by the service's language —
  Python → `pytest + httpx`, Node → `jest/vitest + supertest`, JVM →
  REST-assured; `k6` only for a separate smoke/load slice, not as the main
  functional one. For contract tests when OpenAPI is present — `schemathesis`
  (Python) as a reinforcement, but check with the team.

### Step 2 — Assemble the contract and design the coverage matrix (before code)

- Take the contract (source B) or reconstruct it from the code. For EACH
  endpoint write out: the method, the path, the required authorization/role, the
  required and optional fields, the types, the success and error codes, the side
  effects.
- Design the matrix using the equivalence-classes + boundary-values + decision-
  table techniques: for each endpoint — positive, negative, boundaries,
  authorization (see the checklist). Lock it as a table "endpoint → case →
  expected status/body" — this is the plan.

### Step 3 — Design the architecture and data

- **HTTP client/helpers** in one place (the base URL, headers, error parsing) —
  don't copy request assembly into every test.
- **Fixtures**: authorized clients for different roles, factories of test
  entities (create an object → return the id → delete in teardown).
- **Isolation**: each test is independent, creates its own data, cleans up after
  itself; passes in any order and in parallel. Do not rely on data from a
  previous run.
- **Data and environment**: the base URL, tokens, credentials come from
  env/config (`.env`, `pytest` fixtures, `newman -e env.json`), secrets not in
  code and not in git.

### Step 4 — Write the tests

- A separate test (or a parameterized case) for each row of the matrix.
- Layered assertions: status → body schema (validation against
  OpenAPI/JSON Schema/Pydantic) → specific values → headers → side effect (a
  DB/queue write, if it can be checked).
- Negative cases check the error code AND the structure of the error body (a
  machine-readable code/message) AND that the mutation did NOT happen.
- For schema validation use the project's existing mechanism; if there is none
  and OpenAPI exists — wire in a validator (`schemathesis`/`jsonschema`)
  narrowly.

### Step 5 — Run and drive to green

- Bring up the service (the command from README/docker-compose or the given base
  URL), prepare the test DB/seeds.
- Run the runner, fix real failures (a wrong expected status, a broken fixture, a
  data race). Run the suite again, weed out flakes.
- Provide the actual output (passed/failed, duration). Break the behavior under
  check once and confirm the test goes red — otherwise the assertion is fake.

## COVERAGE CHECKLIST FOR EACH ENDPOINT (apply what is relevant)

### 1. Positive cases
- A valid request → the correct 2xx (200/201/204 per the method's semantics).
- The response body matches the schema: all required fields, correct types, no
  extra/leaked fields (internal ids, passwords, other people's data).
- The values are correct: the created entity contains exactly the data passed;
  Location/headers for 201; an empty body for 204.

### 2. Negative cases (input validation)
- Missing required fields → 400/422 identifying the field.
- Wrong types (a string instead of a number, null in non-nullable) → 4xx.
- Invalid values (email/date/enum out of range) → 4xx with a clear error.
- Extra/unknown fields — rejected or ignored per the contract (and check
  mass-assignment: `role`/`is_admin`/`company_id` must not be pushable).
- Broken JSON / wrong Content-Type → 400/415.
- A non-existent resource (`GET /items/{missing}`) → 404, not 500.
- A wrong method on a path → 405.

### 3. Boundary values
- Strings: empty, 1 character, maximum length, max+1 (expect rejection),
  special/unicode/emoji characters.
- Numbers: 0, negative, the min/max of the range, out of bounds.
- Pagination: `page/limit` = 0, 1, maximum, out of bounds; an empty last page;
  `offset` past the end of the collection.
- Empty collections: `GET` of a list with no data → 200 + an empty array (not
  404, not null), correct meta/total.
- Large payload: a size near the limit and past the limit (413 when exceeded).

### 4. Authorization and access
- No token / an expired/broken token → 401 (on EVERY protected endpoint,
  including the "siblings": if you protected POST, check DELETE/PATCH of the same
  resource).
- A valid token but an insufficient role → 403.
- **IDOR/BOLA**: under user A's token request user/company B's object
  (substituting another's id) → 403/404, NOT 200 with someone else's data.
- Multitenancy (if applicable): the response is filtered by
  company_id/tenant_id from the token, not by a request parameter.
- A forgotten open route: an endpoint that should require auth but does not.

### 5. Idempotency and method semantics
- A repeated POST for creation: does the entity get duplicated; if there is an
  Idempotency-Key — does it work.
- PUT/PATCH twice with the same body → an identical result, no side effects.
- DELETE repeated → 404/204 consistently, without a 500.
- GET — no side effects (does not mutate state).

### 6. Status codes and headers
- Exactly the code prescribed by the contract (201 for creation, not 200).
- Headers: `Content-Type`, `Location`, `Cache-Control`, CORS, security headers,
  `ETag`/conditional requests, if provided for.
- `Retry-After` on 429/503, if applicable.

### 7. Contract / response schema
- The response is validated against the OpenAPI/GraphQL schema/JSON Schema — for
  both success and errors.
- The error format is uniform across the project (the same error-object
  structure on all endpoints).
- For GraphQL: checking `errors` vs `data`, a partial response, query
  depth/complexity, introspection (should it be open).

### 8. Server error handling and robustness
- An internal error does not drop a 500 leaking a stack trace/SQL outward; the
  body is a safe message.
- Unavailability of a dependency (DB/external service) → 503/a meaningful code,
  not a hang.
- Rate limiting (if present): after N requests → 429; the limit resets by
  window.

### 9. Side effects (where they can be checked)
- A successful POST/PUT really wrote to the DB (check via a read API or a direct
  query to the test DB).
- An event/message was published to the queue (if the service does that).
- A failed request left NO partial record (transactionality).

## EDGE CASES THAT ARE OFTEN MISSED

- **Bulk endpoint**: the batch variant of an operation often has weaker checked
  authorization/validation than the single one; partial success (some items
  passed, some did not) — what code and body?
- **Soft-deleted records**: are they accessible via GET, do they participate in
  uniqueness, can you "create" over a deleted one.
- **Races/concurrency**: two parallel POSTs with one unique key — one 201, the
  other 409, not two 201s or a 500.
- **Numeric precision and overflow**: money in float, very large numbers,
  negatives where they shouldn't be.
- **Null vs missing field vs empty string** — are they treated differently;
  PATCH with `null` clears the field, while a missing field leaves it.
- **Encodings and injections in parameters**: SQL/NoSQL special characters in
  filters, path traversal in a path parameter, a very long query.
- **Pagination consistency**: is the order the same across pages;
  duplicates/gaps when data is added between requests.
- **Time zones and date format**: naive vs aware date, different input formats,
  `created_at` in UTC vs locale.
- **Case and trailing slash in paths**: `/Items` vs `/items`, `/items/` vs
  `/items` — 200 vs 404 vs redirect.
- **Conditional/partial responses**: `If-None-Match`/304, `Range` requests, if
  supported.
- **A stale/duplicate route**: an old endpoint left after a refactor, not
  updated for the new authorization.
- **Webhook/consumer idempotency**: if the service accepts webhooks — a repeat of
  the same event must not duplicate the effect.

## DEFINITION OF DONE

1. It is written in the convention of the project's existing API-testing stack
   (or the agreed-upon one, if there was no stack).
2. For each endpoint in the SCOPE there is a positive, negative, boundaries, and
   authorization (the relevant checklist blocks); contract validation is wired
   in, if a schema exists.
3. The assertions are layered (status + schema + values + side effect), not fake
   — the test goes red when the behavior breaks.
4. The tests are isolated, clean up data, pass in any order, do not hit prod.
5. **The run is green and stable** — the actual runner output is provided; the
   suite was run ≥2 times without flakes.
6. Secrets/base URL/tokens come from env/config, not in the code.
7. There is a "what is not covered and why" section.

## RESULT FORMAT

1. **One-sentence verdict**: the suite is done and green / done with caveats /
   not done (why).
2. **SCOPE**: the list of endpoints (method + path), the chosen stack, the
   contract source (OpenAPI/reconstructed from code), the base URL, the
   authorization method.
3. **Coverage matrix**: a table "endpoint → case
   (positive/negative/boundary/authorization) → expected status → test file →
   status (pass/fail/not run)".
4. **Files created/changed**: full paths to tests, fixtures, the client, configs.
5. **Actual run output**: the command, the passed/failed summary, duration,
   confirmation of stability.
6. **Contract mismatches** (if found): an endpoint returns something other than
   what is in the schema/requirements — these are findings, not just "a test was
   written".
7. **What is NOT covered and why**: no test DB/environment, no permission to
   create data, the endpoint requires an external integration/payment gateway, no
   schema to validate against — honestly.
8. **Recommendations**: what to add to CI, where OpenAPI/a schema is needed,
   which endpoints require contract tests, which side effects are worth checking
   directly.

## EXECUTION (practical instructions)

1. **Yourself, in the main thread**: do the SCOPE and Step 1 (stack) + Step 2
   (assembling the contract, the matrix) — this cannot be delegated, a subagent
   cannot see the conversation context and does not know which endpoints, where
   the service is, and where the token comes from. Lock the SCOPE, the stack, the
   contract source.
2. Design the architecture and fixtures (Step 3) — a single decision across the
   whole suite; create the shared HTTP client/fixtures BEFORE parallelizing.
3. **Writing**: if there are many endpoints and they are independent and the
   Agent tool is available — parallelize by endpoint group (by resource/router):
   hand each subagent a specific list of endpoints, the chosen stack and
   convention, the relevant sections of this skill (checklist, edge cases, DoD),
   and the path to the shared client/fixtures — a subagent cannot see the skill
   file itself.
4. **Running and fixing** (Step 5) converge in the main thread: bring up the
   service and the test DB once, run the whole suite, fix failures, achieve
   stable green. Save the tests straight into the project's test directory per
   its convention (`tests/`, `tests/api/`, `__tests__/`, a Postman collection
   alongside).
5. Provide the actual run output and fill in the "what is not covered" section.

Artifacts go into the project's test directory per its convention; if it has no
structure of its own, set up `tests/api/` with `fixtures/`/`schemas/` subfolders.
The coverage matrix, if a separate document is needed, goes into
`docs/qa/test-plans/<feature-slug>.md`.

This is an authoring skill: write the test code so it actually passes, is
deterministic and maintainable — not "stubs for the sake of a checkmark". Record
mismatches between the implementation and the contract/requirements as findings.

