docs(adr): rename ADR-012 — drop Lazar; update title + content + cross-refs
- Rename docs/decisions/adr-012-lazar-conformance.md → adr-012-feature-conventions.md - Strip "Lazar", "Plan 8/9/10/11", "refactor-logs" refs from all ADRs, architecture docs, HTML explainers, and feature/core AGENTS.md files - Update all incoming links in docs/, packages/*/AGENTS.md, HTML explainers Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,30 +44,31 @@ Combine with your app's TRPCProvider for components that need a tRPC client in t
|
||||
|
||||
See `docs/guides/tdd-workflow.md` §"Contract suite usage".
|
||||
|
||||
## Plan 9 test patterns
|
||||
## Test patterns
|
||||
|
||||
These test obligations apply to every feature package. The examples below show the minimal shape — adapt to the feature's actual types.
|
||||
|
||||
### R25 — Output validation (use case)
|
||||
### Output validation (use case)
|
||||
|
||||
Every non-void use case must have a test that injects a mock returning malformed data and asserts the use case rejects with a `ZodError`. This proves `xOutputSchema.parse(result)` is actually called.
|
||||
|
||||
```typescript
|
||||
it("throws ZodError when repository returns malformed data (R25)", async () => {
|
||||
it("throws ZodError when repository returns malformed data", async () => {
|
||||
const badRepo = { getArticleBySlug: async () => ({ id: 1 }) }; // id should be string
|
||||
await expect(getArticleBySlugUseCase(badRepo as any)({ slug: "x" }))
|
||||
.rejects.toBeInstanceOf(ZodError);
|
||||
await expect(
|
||||
getArticleBySlugUseCase(badRepo as any)({ slug: "x" }),
|
||||
).rejects.toBeInstanceOf(ZodError);
|
||||
});
|
||||
```
|
||||
|
||||
Void use cases (`signOut`, `deleteMedia`) are exempt — they have no `xOutputSchema`.
|
||||
|
||||
### R26 — Router error mapping (tRPC)
|
||||
### Router error mapping (tRPC)
|
||||
|
||||
Each feature's `router.test.ts` must assert the correct `TRPCError.code` for at least one mapped domain error, using `xRouter.createCaller({})`.
|
||||
|
||||
```typescript
|
||||
it("returns NOT_FOUND when article is missing (R26)", async () => {
|
||||
it("returns NOT_FOUND when article is missing", async () => {
|
||||
const caller = blogRouter.createCaller({});
|
||||
const error = await caller.articleBySlug({ slug: "missing" }).catch((e) => e);
|
||||
expect(error).toBeInstanceOf(TRPCError);
|
||||
@@ -77,15 +78,18 @@ it("returns NOT_FOUND when article is missing (R26)", async () => {
|
||||
|
||||
Also assert `BAD_REQUEST` for at least one invalid-input call (exercises the `strict()` schema boundary).
|
||||
|
||||
### R27/R28 — Presenter shape (controller tests)
|
||||
### Presenter shape (controller tests)
|
||||
|
||||
When a controller's presenter reshapes the use-case output (e.g., `signInController` extracts `cookie` from `{ session, cookie }`), the controller test must assert against the **view shape**, not the use-case output shape.
|
||||
|
||||
```typescript
|
||||
// signInController: presenter returns value.cookie (a Cookie object)
|
||||
const result = await signInController(mockUseCase)({ username: "u", password: "p" });
|
||||
expect(result.name).toBe(SESSION_COOKIE); // Cookie.name
|
||||
expect(result.value).toBeDefined(); // Cookie.value
|
||||
const result = await signInController(mockUseCase)({
|
||||
username: "u",
|
||||
password: "p",
|
||||
});
|
||||
expect(result.name).toBe(SESSION_COOKIE); // Cookie.name
|
||||
expect(result.value).toBeDefined(); // Cookie.value
|
||||
// NOT: expect(result.session).toBeDefined() — that's the use-case output, not the view
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user