Skip to main content

tdd

PitLog test patterns and the anti-rationalization table. The Red-Green-Refactor cycle itself is already mandated in AGENTS.md — this skill encodes only the project-specific patterns (dependency injection over database fixtures, invariant tests, the exact-answer fixture race) and the excuses for skipping the cycle. TRIGGER when writing or modifying code in packages/ or apps/api/src/. DO NOT trigger for documentation, YAML config, CI workflow files, or styling-only changes.

Jump to install

Source facts

Repository
weaties/pitlog
Last source activity
August 19, 2026 at 15:32
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
tdd
description
PitLog test patterns and the anti-rationalization table. The Red-Green-Refactor cycle itself is already mandated in AGENTS.md — this skill encodes only the project-specific patterns (dependency injection over database fixtures, invariant tests, the exact-answer fixture race) and the excuses for skipping the cycle. TRIGGER when writing or modifying code in packages/ or apps/api/src/. DO NOT trigger for documentation, YAML config, CI workflow files, or styling-only changes.
# TDD — PitLog patterns AGENTS.md already mandates: **failing test → implement → green → lint.** This skill encodes only the bits that aren't obvious from reading the existing tests. ## Patterns **Unit tests never touch a database or a browser.** Inject the dependency instead. The tenancy gates take a `MembershipResolver` precisely so the whole role matrix can be tested with a plain object: ```ts function resolverFor(memberships: Partial<Record<string, Role>>): MembershipResolver { return async (userId, teamId) => { /* … */ } } ``` The real query is exercised once, by the browser smoke test. Do not stand up Postgres to test a rule about roles. **API route tests drive Hono in-process** — no port, no network: ```ts const res = await app.request(`/teams/${TEAM}/thing`) expect(res.status).toBe(403) ``` **Test the invariant, not the instance.** `packages/db/src/schema.test.ts` asserts that *every* domain table carries `team_id`, enumerated from the schema module, rather than listing the tables that do. A new table cannot quietly skip the rule. When you add a cross-cutting requirement, write the test that way. **The fixture race is the planner's test bed** (SPEC §7). Its numbers are chosen so the correct answer is exact — 6 stints × 75 min + 5 stops × 6 min = 8 h to the second, 3 drivers × 2 stints = perfectly equal seat time. Assert **equality** on seat time and stint boundaries, not tolerance. A planner that is "close" on fairness is wrong. Import it from `@pitlog/domain`; it has no database dependency: ```ts import { EIGHT_HOUR_RACE, KNOWN_GOOD_SOLUTION } from '@pitlog/domain' ``` **Determinism is a test requirement.** `generateFixtureLaps()` uses a seeded Mulberry32 PRNG, not `Math.random()`, and there is a test asserting two calls are identical. Any new fixture data follows the same rule — a fixture that differs per run cannot be a known-good answer. **Browser tests are thin.** `tests/e2e/` proves the app boots, login works, and the shell renders. Behavioural depth belongs in Vitest, which runs in under a second. ## What must be TDD'd Logic, always: the stint planner, the burn-rate model, the sync merge, the rule engine, expense splitting, the role gates, anything that computes a number a human will act on at 3am. Exempt: scaffolding, config files, CI workflows, Tailwind classes, route wiring that only calls into tested code. ## Don't rationalize skipping the cycle The cycle only helps if you don't talk yourself out of it. | Rationalization | Rebuttal | |---|---| | "This change is too small to need a test." | Size predicts neither breakage nor regression. The failing test is what proves the change does what you think. | | "I'll write the test after I see it work." | Test-after rationalizes whatever the code already does, bugs included. | | "It's just a route handler — I'll eyeball it." | Route handlers are the easiest place to ship a silent 500 or a missing `team_id` filter. `app.request()` costs you three lines. | | "Testing this properly needs a database." | Then the dependency is in the wrong place. Inject it. That is why `MembershipResolver` exists. | | "Tests pass, so it's correct." | Passing tests are evidence, not proof. Confirm the test would actually fail without your change — if you didn't see it red, you don't know. | | "The planner is close enough on fairness." | The fixture race has an exact answer on purpose. "Close" means a driver gets short-changed on seat time at a real race. | | "I'll use `Math.random()` in the fixture, it's only test data." | Then the known-good solution isn't known. Seed the PRNG. | | "Lint is noisy here, I'll skip the gate." | There is no pre-existing-error allowlist in this repo and there must never be one. Zero, always. | | "I'll add the `team_id` filter in a follow-up." | That follow-up is a data leak across a tenancy boundary. Do it now. | ## Running things ```bash make test # vitest, sub-second make e2e # playwright; needs make up && make migrate && make seed first make check # typecheck + lint + test — the pre-PR gate npx vitest packages/domain # watch one package while iterating ``` There is **no pre-existing-error allowlist** in this repo, deliberately (see AGENTS.md → Decisions). If lint or typecheck is red, fix it.
View on GitHub