docs(agents): per-feature + core-testing AGENTS.md for Plan 8 + Plan 9 conventions

Each per-feature AGENTS.md now reflects the post-Plan-9 layout:
entity/error paths, public-API split (./ui), use-case schemas, presenter
pattern, feature-scoped tRPC error map, and feature-specific
errors-to-codes table.

core-testing/AGENTS.md gains a Plan 9 test-patterns section documenting
R25 (output validation), R26 (router error mapping), R27/R28
(presenter shape) test obligations.

auth: documents real PayloadUsersRepository + AuthenticationService and
the deferred session methods.
media: documents the full Clean Architecture scaffold introduced in
Plan 8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 16:47:56 +02:00
parent 732fa63f69
commit edc98f8f9a
6 changed files with 652 additions and 538 deletions

View File

@@ -43,3 +43,50 @@ Combine with your app's TRPCProvider for components that need a tRPC client in t
## Adding a contract suite
See `docs/guides/tdd-workflow.md` §"Contract suite usage".
## Plan 9 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)
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 () => {
const badRepo = { getArticleBySlug: async () => ({ id: 1 }) }; // id should be string
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)
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 () => {
const caller = blogRouter.createCaller({});
const error = await caller.articleBySlug({ slug: "missing" }).catch((e) => e);
expect(error).toBeInstanceOf(TRPCError);
expect(error.code).toBe("NOT_FOUND");
});
```
Also assert `BAD_REQUEST` for at least one invalid-input call (exercises the `strict()` schema boundary).
### R27/R28 — 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
// NOT: expect(result.session).toBeDefined() — that's the use-case output, not the view
```
Identity presenters (`return value;`) skip this obligation — the view shape equals the use-case output shape.