Skip to main content

H. API Development & Testing

Wasteology builds and consumes a growing number of HTTP APIs — the WDP customer API, CieTrade, QuickBooks, HubSpot, Prefect, n8n, and internal service endpoints — but has no standard for how those APIs are exercised, shared, or regression-tested. This chapter sets that standard: Bruno as the company API client, collections stored in-repo as version-controlled files, and API contract checks that run in CI like every other test.


H.1 — Current State

API testing today is ad hoc and split across tools. Two Postman collections are already committed as exported JSON — quickbooks-etl/qbo-api.postman_collection.json (20 requests) and prefect-azure-infrastructure/cietrade-api.postman_collection.json (13 requests) — plus a get_postman_tokens.py helper and Postman references in docs/hubspot-setup.md. At least one developer has moved to Bruno. There is no declared standard, no CI execution of any collection, and no rule for where collections live or how secrets are handled.

The committed Postman JSON is a tell: someone already felt the pull to get collections into the repo. But those files are lossy exports — the canonical collection still lives in Postman's cloud, so the in-repo copy drifts the moment anyone edits in the app. That is the exact "second source of truth outside git" anti-pattern the rest of this package works to eliminate.


Standardize on Bruno as the Wasteology API client. Bruno stores each request as a plain-text .bru file in a folder you commit to the repo — no cloud account, no export step, no drift.

FactorBrunoPostman
Source of truth.bru files in the API's repo — human-readable, diffable, PR-reviewableCloud workspace; committed JSON is a drifting export
Cost / accessOpen-source, free, offline; no per-seat licensing; works for sandboxed contractorsFree tier limits collaborators/runs; team sync is paid
CIbru CLI (@usebruno/cli) runs collections headlessly → contract tests as a pipeline stepNewman exists, but the canonical collection lives off-repo
MigrationImports Postman collections directly
Templating{{var}} variables + per-environment .bru files{{var}} + cloud environments
Where Postman is strongerRicher UI, mock servers, monitors, larger ecosystem

This is not a preference call — it is the choice consistent with every other standard in this package: the source of truth lives in git, next to the code, reviewed via PR, and runnable in CI. Bruno gives API collections the same treatment as lat check, pytest, and docs-as-code.

PO decision point

Bruno is the recommended and (per this package's decision register) selected default. It is a reversible choice — collections are portable text, and Bruno imports/exports Postman format — so if a future need favors Postman's mock-server/monitor features for a specific product, that product can be documented as an exception rather than reversing the company default.


H.3 — In-Repo Collection Layout

Every repo that owns or consumes an API keeps its Bruno collection in that repo, versioned alongside the code it exercises.

<api-repo>/
└── bruno/
├── bruno.json # collection manifest (name, version, type)
├── environments/
│ ├── dev.bru # variable NAMES + non-secret defaults
│ └── prod.bru # variable NAMES only (values injected at run time)
├── Reference Data/ # folders → subdirectories
│ ├── Get Company Info.bru
│ └── ...
└── Write Operations/
└── ...

Rules:

  • One collection per API, in the API's repo. The collection is reviewed in the same PR as the code that changes the endpoint.
  • No secrets in .bru files or environment files. Environment files hold variable names with placeholders (token: {{QBO_ACCESS_TOKEN}}); real values come from the developer's local environment, Key Vault, or CI pipeline variables — never committed. This mirrors the secrets discipline in E — Access Governance and D — Agentic Workflows.
  • Collections are documentation. A well-named Bruno collection is the fastest way for a new developer (or contractor) to understand an API's surface — treat it as a first-class deliverable, not a scratchpad.
PO decision point

Directory name — bruno/ (tool-named, unambiguous) vs api-tests/ (tool-agnostic). Recommended: bruno/, since the tool is now a standard and the name signals what opens it. Revisit only if the tool ever changes.


H.4 — CI Contract Checks

Run the Bruno collection in CI so a breaking API change fails the build, the same way a failing unit test does.

# azure-pipelines.yml — API contract check stage
- script: |
npm install -g @usebruno/cli
bru run bruno --env dev --bail
displayName: "API contract checks (Bruno)"
env:
QBO_ACCESS_TOKEN: $(QBO_ACCESS_TOKEN) # injected secret, never in the .bru files
  • Point contract checks at a dev or sandbox environment, never production (consistent with the contractor sandbox model in F and least-privilege in E).
  • Use Bruno assertions/tests in the .bru files (status code, response shape) so bru run is a real gate, not just a smoke ping.
  • This makes API health a mechanical check — the same philosophy as lat check and pytest elsewhere in the standard.
PO decision point

Whether contract checks are blocking (fail the PR) or advisory (report only) at first. Recommended: advisory for the first sprint while collections gain assertions, then flip to blocking once each API's collection has meaningful tests.


H.5 — Migrating from Postman

The two existing Postman collections are the reference migration. A reusable converter lives at docs/strategy/standardization/tools/postman_to_bruno.py (Postman v2.1 JSON → Bruno .bru directory, stdlib-only).

Per collection:

  1. Run the converter: python tools/postman_to_bruno.py <collection>.postman_collection.json <api-repo>/bruno.
  2. Review the generated .bru files in a PR; confirm folders, methods, headers, query params, and auth converted correctly.
  3. Move any secret variable values out of the collection into CI pipeline variables / Key Vault; the converter placeholder-izes anything that looks like a token.
  4. Add the bru run CI stage (H.4).
  5. Retire the Postman cloud copy and delete the committed .postman_collection.json export. Retire get_postman_tokens.py (its token flow becomes environment/CI variables).

In flight: qbo-api (quickbooks-etl) and cietrade-api (prefect-azure-infrastructure) are being converted as the first two migrations and the pattern for the rest. Both repos are GitHub-hosted, so the bruno/ directories are committed via their normal GitHub PR flow.

PO decision point

Timing of the Postman retirement. Recommended: run Bruno and Postman in parallel for one sprint per API (validate the converted collection against the live API), then delete the Postman export and cancel unused Postman seats.


H.6 — Where This Fits — "I'm Building a New API"

API testing is one stop on the new-API path, and the wg-navigator agent routes a developer through all of them:

  1. Scaffold the repo from wg-template (A).
  2. Build the API; add a bruno/ collection alongside the code (this chapter).
  3. Wire the bru run contract check into CI (A branch policies + this chapter).
  4. Request any DB/RBAC access the API needs (E).
  5. Publish the API reference to the internal docs site — or the CRM site if it is vendor-facing (C).
  6. If a contractor is building it, apply the external-team runbook and sandbox model (F).

This is exactly the kind of cross-cutting, "which processes apply to my scenario" question the navigator agent exists to answer — API development touches six of the eight standards at once.


Summary

DecisionRecommendationStatus
API clientBruno (git-native .bru, free, CLI in CI)Selected default
Collection locationbruno/ directory in each API's repoAdopt
SecretsNever in .bru/env files; CI variables + Key VaultAdopt immediately
CIbru run contract check per API repoAdvisory first sprint → blocking
Migrationtools/postman_to_bruno.py; convert, validate, retire Postmanqbo-api + cietrade-api in flight