AGENTS.md@packages/api-mock · git:20261020.8d47d7a · 2026-10-20 · sha256 747593d26fda8df0
AGENTS.md@packages/api-mock git:20261020.8d47d7aA
Immutable. This exact content never changes and is served at /api/v1/blob/747593d26fda8df0.
# Agent Instructions — `packages/api-mock`
`@zitadel/api-mock` stands in for the Zitadel API: MSW handlers for the typed
`@zitadel/api` Flow API and the platform API, and a standalone server built on
the same handlers. Storybook, the component, SDK and CLI tests use it in
process; the demos (`moon run api-mock:start`) and `apps/mock-zitadel` run the
server.
It is not published, but it builds to `dist/` (tsdown) like every package:
consumers resolve its export map (`@zitadel/source` points in-repo tooling at
the source) and depend on `api-mock:build`.
## The flow shape mirrors the real default flow
A mock flow that drifts from the server's makes every consumer prove the wrong
thing. The authority is
[`packages/config/defaults/default-login.json`](../config/defaults/default-login.json),
which the server embeds (`configdefaults.DefaultLoginFlowDefinition()`).
- Diff a step fixture against that JSON before changing it.
`src/fixtures/default-conformance.spec.ts` fails a fixture that misses a
declared action or grows one beyond its documented allowlist.
- Never invent a step shape for a test or a design: the flow definition
changes first.
- Sign-in is split: `identifier` → `password` → `done`, never a combined
email-and-password card.
- Credential fields are keyed by the schema pointer `x-auth-methods#password`
(`PASSWORD_FIELD`), never `"password"`, which the server refuses with
`req.invalid`.
- The steps `zitadel auth-method sso enable` adds (`register-sso`,
`sso-conflict`) follow
[`default-login.scaffold.json`](../../docs/design/idp/schemas/default-login.scaffold.json),
which the CLI's `applySsoToFlow` test keeps in step with the generator.
- The documented exceptions: `identifier` keeps a `recover` navigate (the
default flow has no recovery step yet); `passkey-upsell`/`passkey-setup`
stay for direct actor injection; `register-sso` serves
`given_name`/`family_name` and a `sign_in` navigate where the scaffold
collects `email` with one `submit`.
## Architecture
- The xstate machine (`src/flow-machine.ts`) picks the step. Its state names,
the `FlowStepName` union, are the wire step names; there is no mapping.
- Fixtures (`src/fixtures/login.ts`) return orval's `CreateFlow201`, which is
structurally the get and submit responses too, so one set serves all three
handlers (`src/handlers.ts`). Never declare shadow step, field or branding
types.
- Only `setupMock(worker)` and the test-only `./vitest-browser` harness may
use `msw/browser`: the same handlers must run in node.
## Adding a flow step
1. Add a state named as on the wire to `src/flow-machine.ts`, wire it into its
`SUBMIT` transition, and add the name to `FlowStepName`.
2. Add a fixture builder returning `CreateFlow201` to `src/fixtures/login.ts`
(or a sibling for other flows), wrapped with `wrap(...)`.
3. Add its `case` to `currentResponse()` in `src/handlers.ts`.
4. Walk the new branch through the typed orval client in `src/index.spec.ts`.
## Fixtures for error paths
Emails that trigger error paths (`REGISTRATION_ERRORS`, `LOGIN_ERRORS`,
`PASSKEY_UPSELL_ERRORS`) are in `src/lib/authn/authn-store.ts`; any other email
takes the happy path.
## The provider's return
- The `sso` submit answers `sso-redirect` and records the submission's
`return_target`. As on the engine (`internal/api/flow.go`), the target must
pass `parseRedirectTarget` (`src/redirect-target.ts`, a port of
`httputil.ParseRedirectTarget`) and be on the request origin, or the submit
is refused with `req.invalid`: the mock is publicly deployed.
- The refused and accepted targets are one list,
`internal/httputil/testdata/redirect_targets.json`, read by the Go tests and
`src/redirect-target.spec.ts`. A new way around the check goes there and
into both implementations.
- The return is `GET /__nextgen/idp/callback` (or `/idp/callback`), not a
submit, as on the engine (`internal/api/idp_callback.go`): its query
(`code`, `email`, `error`) decides the next step, and it answers with a
`303` to the recorded target. `returnFromProvider(...)` drives the same
transition in process.
## Branding
- `applyBranding(...)` overlays every response; clear it between tests with
`clearBranding()`. Its type stays open (`CreateFlow201Branding &
Record<string, unknown>`) so this package never depends on the components'
types.
- `createMockApp({ issuer })` (`src/server.ts`, the `./server` export) applies
`defaultDevBranding` on boot. `setupMockHandlers()` / `setupMock()` apply
nothing unless the caller does.
## Testing
- `pnpm test` runs both Vitest projects: `unit` (node, `*.spec.ts`) and
`browser` (Chromium, `*.browser.spec.ts`; needs
`pnpm exec playwright install`). `pnpm test:unit` is the fast lane.
- `src/index.spec.ts` is the handlers' contract: for a step-routing
regression, add its case there first.