Unit testing
Applies to all code in this repo. Every new exported function, HTTP
handler, or Vue component with logic beyond pure markup needs a test
before the PR is considered done — not added later.
Orchestrator is Python, packages and frontend server are Go — see
ADR-0008. Don't assume
one language's conventions apply to both.
Python (orchestrator/)
- pytest, parametrized tests (
@pytest.mark.parametrize) as the Python
equivalent of table-driven cases — co-located as test_*.py or under
tests/ mirroring the source layout.
- Test behavior, not implementation — assert on outputs/side effects, not
on internal call sequences, unless the sequence itself is the contract
(e.g. "Reviewer must run before execution").
- External calls (Alpaca, BigQuery, Firestore, Agent Registry, Interactions
API) go behind a small interface/protocol at the point of use, with
fakes for tests — never hit real external services in unit tests.
- Pydantic models: test validation failures explicitly (e.g. an
out-of-enum
risk_tolerance), not just the happy path — this is the
layer that's supposed to catch schema-invalid data before it reaches
Firestore.
- Minimum per function: one golden-path case, one error-path case. Add
edge cases (empty input, boundary values) where the domain has them —
e.g. zero-quantity trades, expired skill TTL.
- For the Reviewer/Critic and HITL gate specifically: test the rejection
paths as thoroughly as the approval path. This is the governance
surface — undertested rejection logic defeats the point of the demo.
Go (pkg/ and frontend/server/)
- Table-driven tests, co-located as
*_test.go next to the code under
test.
- Test behavior, not implementation — assert on outputs/side effects, not
on internal call sequences.
- External calls go behind small interfaces at the point of use, with
fakes for tests — never hit real external services in unit tests.
- Minimum per function: one golden-path case, one error-path case.
TypeScript / Vue (frontend/)
- Vitest + Testing Library. Test user-visible behavior (what renders,
what a click does), not component internals or implementation details.
- Every component that renders conditionally (e.g. the approval card's
approve/edit/reject states) needs a test per state.
What NOT to do
- Don't write a test just to move a coverage number — see
code-coverage/SKILL.md.
- Don't mock what you don't own carelessly — if a fake for an external
API drifts from its real behavior, the test suite gives false
confidence. Keep fakes minimal and reviewed when the real API's
contract changes.
- Don't test framework code (e.g. that a Pydantic field assigns
correctly, or that a Go struct's field assignment works) — test your
logic.
1---2name: unit-testing3description: Testing conventions for this repo — table-driven Go tests for pkg and frontend server, pytest conventions for the orchestrator, fakes for external calls, coverage expectations for golden and error paths. Use when writing or reviewing any new function, handler, or component with logic beyond pure markup.4---56# Unit testing78Applies to all code in this repo. Every new exported function, HTTP9handler, or Vue component with logic beyond pure markup needs a test10before the PR is considered done — not added later.1112Orchestrator is Python, packages and frontend server are Go — see13[ADR-0008](../../../docs/adr/0008-python-for-orchestrator.md). Don't assume14one language's conventions apply to both.1516## Python (`orchestrator/`)1718- pytest, parametrized tests (`@pytest.mark.parametrize`) as the Python19 equivalent of table-driven cases — co-located as `test_*.py` or under20 `tests/` mirroring the source layout.21- Test behavior, not implementation — assert on outputs/side effects, not22 on internal call sequences, unless the sequence itself is the contract23 (e.g. "Reviewer must run before execution").24- External calls (Alpaca, BigQuery, Firestore, Agent Registry, Interactions25 API) go behind a small interface/protocol at the point of use, with26 fakes for tests — never hit real external services in unit tests.27- Pydantic models: test validation failures explicitly (e.g. an28 out-of-enum `risk_tolerance`), not just the happy path — this is the29 layer that's supposed to catch schema-invalid data before it reaches30 Firestore.31- Minimum per function: one golden-path case, one error-path case. Add32 edge cases (empty input, boundary values) where the domain has them —33 e.g. zero-quantity trades, expired skill TTL.34- For the Reviewer/Critic and HITL gate specifically: test the *rejection*35 paths as thoroughly as the approval path. This is the governance36 surface — undertested rejection logic defeats the point of the demo.3738## Go (`pkg/` and `frontend/server/`)3940- Table-driven tests, co-located as `*_test.go` next to the code under41 test.42- Test behavior, not implementation — assert on outputs/side effects, not43 on internal call sequences.44- External calls go behind small interfaces at the point of use, with45 fakes for tests — never hit real external services in unit tests.46- Minimum per function: one golden-path case, one error-path case.4748## TypeScript / Vue (`frontend/`)4950- Vitest + Testing Library. Test user-visible behavior (what renders,51 what a click does), not component internals or implementation details.52- Every component that renders conditionally (e.g. the approval card's53 approve/edit/reject states) needs a test per state.5455## What NOT to do5657- Don't write a test just to move a coverage number — see58 `code-coverage/SKILL.md`.59- Don't mock what you don't own carelessly — if a fake for an external60 API drifts from its real behavior, the test suite gives false61 confidence. Keep fakes minimal and reviewed when the real API's62 contract changes.63- Don't test framework code (e.g. that a Pydantic field assigns64 correctly, or that a Go struct's field assignment works) — test your65 logic.66