Anything addressed by its own id needs an API-level test through the router. Service tests can't see a route that was never registered — PATCH/DELETE /journal/:id once shipped fully implemented, fully unit-tested, and completely unreachable.
/seed-lots is currently in that state. Five routes (internal/api/api.go:181-186), five handlers (internal/api/seed_lots.go:84,102,116,129,156), and no internal/api/seed_lots_test.go — the only handler file without a sibling test.
Service coverage is good (internal/service/seed_lots_test.go, 519 lines), which is exactly what made journal feel covered too. Service tests cannot see an unregistered route, a wrong :param name, or a missing middleware.
This matters more than average for seed lots because they carry two invariants a broken route would silently violate:
remaining is derived, never stored (DESIGN.md) — a mis-wired route could return stale or wrong counts
lots are private to their owner and deliberately never shared along with a garden — a missing ACL check leaks purchases
Two smaller siblings in the same category
GET /gardens/:id/years (api.go:95) — no API test. Drives the season picker.
GET /capabilities (api.go:59,204) — no API test. It gates whether the Assistant tab renders at all (GardenEditorPage.tsx:393), and its documented purpose is the subtle distinction "report whether the runner BUILT, not whether it was configured". That distinction is untested, and #79 is about to make this endpoint dynamic — worth having a test before that lands, not after.
Shape
internal/api/journal_test.go is the model to copy — it walks the full lifecycle through the router, which is what caught the original bug.
CLAUDE.md, written after the journal incident:
> **Anything addressed by its own id needs an API-level test through the router.** Service tests can't see a route that was never registered — PATCH/DELETE `/journal/:id` once shipped fully implemented, fully unit-tested, and completely unreachable.
`/seed-lots` is currently in that state. Five routes (`internal/api/api.go:181-186`), five handlers (`internal/api/seed_lots.go:84,102,116,129,156`), and **no `internal/api/seed_lots_test.go`** — the only handler file without a sibling test.
Service coverage is good (`internal/service/seed_lots_test.go`, 519 lines), which is exactly what made journal feel covered too. Service tests cannot see an unregistered route, a wrong `:param` name, or a missing middleware.
This matters more than average for seed lots because they carry two invariants a broken route would silently violate:
- `remaining` is **derived**, never stored (DESIGN.md) — a mis-wired route could return stale or wrong counts
- lots are **private to their owner** and deliberately never shared along with a garden — a missing ACL check leaks purchases
## Two smaller siblings in the same category
- **`GET /gardens/:id/years`** (`api.go:95`) — no API test. Drives the season picker.
- **`GET /capabilities`** (`api.go:59,204`) — no API test. It gates whether the Assistant tab renders at all (`GardenEditorPage.tsx:393`), and its documented purpose is the subtle distinction "report whether the runner BUILT, not whether it was configured". That distinction is untested, and #79 is about to make this endpoint dynamic — worth having a test before that lands, not after.
## Shape
`internal/api/journal_test.go` is the model to copy — it walks the full lifecycle through the router, which is what caught the original bug.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
CLAUDE.md, written after the journal incident:
/seed-lotsis currently in that state. Five routes (internal/api/api.go:181-186), five handlers (internal/api/seed_lots.go:84,102,116,129,156), and nointernal/api/seed_lots_test.go— the only handler file without a sibling test.Service coverage is good (
internal/service/seed_lots_test.go, 519 lines), which is exactly what made journal feel covered too. Service tests cannot see an unregistered route, a wrong:paramname, or a missing middleware.This matters more than average for seed lots because they carry two invariants a broken route would silently violate:
remainingis derived, never stored (DESIGN.md) — a mis-wired route could return stale or wrong countsTwo smaller siblings in the same category
GET /gardens/:id/years(api.go:95) — no API test. Drives the season picker.GET /capabilities(api.go:59,204) — no API test. It gates whether the Assistant tab renders at all (GardenEditorPage.tsx:393), and its documented purpose is the subtle distinction "report whether the runner BUILT, not whether it was configured". That distinction is untested, and #79 is about to make this endpoint dynamic — worth having a test before that lands, not after.Shape
internal/api/journal_test.gois the model to copy — it walks the full lifecycle through the router, which is what caught the original bug.