MOTIR-3339Done
Integration test — the seam: permission refused before any upstream call, and no global row crosses it
The story-level vitest seam: the assembled route → client → upstream path, against the real Postgres, with motir-ai stubbed at the transport.
Four properties, each chosen because it fails silently rather than loudly:
- A caller without the view permission is refused AND no upstream call is made. Assert on the stub's call count, not only the status — a route that fetches then refuses passes a status assertion and has already built the payload.
- No
scope = 'global'row reaches the client, given an upstream response containing both. This guards the boundary from the core side as well as the motir-ai side, because either end can regress it alone. - Another project's lessons are unreachable — the project is resolved from the session, not taken from a parameter a caller can set.
- An upstream failure degrades: the section reports unavailable and the rest of the AI-planning settings payload is still returned.
Stub motir-ai at the HTTP boundary rather than mocking the client, so the client's own envelope and malformed-body handling are exercised.
Acceptance criteria
- All four assertions pass against the real test database.
- The permission assertion checks the upstream call count is zero, not just the response status.
- A mixed upstream fixture (global + tenant) proves nothing global crosses.
- The degradation case asserts the other settings groups still return.
- Only the changed files are run locally; CI runs the suite.
Context refs
motir-corelib/ai/motirAiClient.ts— the client under test, stubbed at transport.motir-corelib/permissions/resolve.ts— the guard.- MOTIR-3337 — the seam this exercises.
- MOTIR-3335 — the upstream contract the stub imitates.