108 lines
5.5 KiB
Markdown
108 lines
5.5 KiB
Markdown
## ADDED Requirements
|
|
|
|
### Requirement: Layer 0 dep-graph isolation
|
|
|
|
The `@suaveplan/testing-integration-oidc` package's `dependencies` MUST be exactly `@suaveplan/testing-integration` (`workspace:^`) and `testcontainers` (`catalog:integration`). No other `@suaveplan/*` runtime dep is permitted. `devDependencies` MAY include `@suaveplan/testing` for the package's own tests.
|
|
|
|
#### Scenario: Runtime closure clean
|
|
|
|
- **WHEN** `bunx turbo ls --filter=@suaveplan/testing-integration-oidc...` is run
|
|
- **THEN** the only `@suaveplan/*` package in the runtime closure is `@suaveplan/testing-integration`
|
|
|
|
### Requirement: Pluggable provider selection
|
|
|
|
The fixture SHALL accept a `provider: 'keycloak' | 'oauth2-mock-server'` config field. Both providers MUST satisfy the same `ServiceFixture<OidcConfig>` contract — only the underlying image, init sequence, and admin-API endpoints differ.
|
|
|
|
#### Scenario: Keycloak provider selected
|
|
|
|
- **WHEN** `start({ provider: 'keycloak', realm: 'test' })` is called
|
|
- **THEN** the fixture pulls `quay.io/keycloak/keycloak:26.0` AND seeds the `test` realm via the Keycloak admin REST API before resolving
|
|
|
|
#### Scenario: oauth2-mock-server provider selected
|
|
|
|
- **WHEN** `start({ provider: 'oauth2-mock-server' })` is called
|
|
- **THEN** the fixture pulls `ghcr.io/navikt/mock-oauth2-server:2` AND seeds clients/users via its JSON config-file mount before resolving
|
|
|
|
### Requirement: Image-pinning policy
|
|
|
|
Default images MUST be pinned to a minor tag (`quay.io/keycloak/keycloak:26.0`, `ghcr.io/navikt/mock-oauth2-server:2`). The `constants/index.ts` file MUST carry a `# TODO: replace with sha256:<digest>` comment for each image until the digest is recorded.
|
|
|
|
#### Scenario: Pinned tag
|
|
|
|
- **WHEN** the constants file is read
|
|
- **THEN** both image strings include an explicit minor tag AND a sha256 TODO line is present
|
|
|
|
### Requirement: Env-bridge passthrough
|
|
|
|
The fixture MUST consult `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_REDIRECT_URI`, and optional `OIDC_USERS_FILE`. When `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and `OIDC_REDIRECT_URI` are all present and non-empty, `start()` MUST skip container creation and return a handle whose config points at the live issuer.
|
|
|
|
#### Scenario: Live issuer present
|
|
|
|
- **WHEN** all four required env vars are set
|
|
- **THEN** `start()` returns without invoking `containerFactory` AND the returned config's `issuerUrl` equals `OIDC_ISSUER_URL`
|
|
|
|
#### Scenario: Partial env triggers container
|
|
|
|
- **WHEN** `OIDC_ISSUER_URL` is set but `OIDC_CLIENT_SECRET` is empty
|
|
- **THEN** `start()` falls back to container mode
|
|
|
|
### Requirement: Healthcheck
|
|
|
|
The fixture MUST poll `HTTP GET <issuerUrl>/.well-known/openid-configuration` and treat a `200` JSON response containing `issuer`, `authorization_endpoint`, `token_endpoint`, `jwks_uri` as ready. The default `healthTimeoutMs` MUST be `90000` (Keycloak slow-start). Callers MAY override.
|
|
|
|
#### Scenario: Healthcheck succeeds
|
|
|
|
- **WHEN** `/.well-known/openid-configuration` returns 200 with the four required fields
|
|
- **THEN** `start()` resolves
|
|
|
|
#### Scenario: Healthcheck times out
|
|
|
|
- **WHEN** the discovery endpoint never returns within `healthTimeoutMs`
|
|
- **THEN** `start()` rejects with code `INTEGRATION.OIDC.HEALTHCHECK_TIMEOUT`
|
|
|
|
### Requirement: Config shape
|
|
|
|
The exported `OidcConfig` interface MUST include `provider`, optional `realm`, optional `users: Array<{ username: string; password: string; email: string }>`, optional `clients: Array<{ id: string; secret: string; redirectUris: string[]; grantTypes: string[] }>`, and the resolved `issuerUrl`. A sibling `oidc-fixture.schemas.ts` MUST export a Zod schema (`OidcConfigSchema`) that validates the same shape.
|
|
|
|
#### Scenario: Schema parity
|
|
|
|
- **WHEN** `OidcConfigSchema.safeParse` is run against an `OidcConfig` value
|
|
- **THEN** the parse succeeds for valid configs AND fails with a `ZodError` for missing required fields
|
|
|
|
### Requirement: Error-code prefix
|
|
|
|
All errors thrown from the fixture MUST be subclasses of `TestingIntegrationError` carrying `{ code, cause?, context? }` where `code` matches `^INTEGRATION\.OIDC\.[A-Z_]+$`.
|
|
|
|
#### Scenario: Coded error
|
|
|
|
- **WHEN** any fixture method throws
|
|
- **THEN** the thrown value's `code` starts with `INTEGRATION.OIDC.`
|
|
|
|
### Requirement: Injectable fakes
|
|
|
|
The fixture constructor MUST accept `containerFactory?: () => GenericContainer` and `fetchFn?: typeof fetch`. Unit tests MUST be able to drive 100% Istanbul branch coverage without invoking Docker or the network.
|
|
|
|
#### Scenario: Fake injection
|
|
|
|
- **WHEN** the fixture is constructed with both fakes
|
|
- **THEN** real Docker / real `fetch` are never called during the test
|
|
|
|
### Requirement: Plugin scenarios
|
|
|
|
The fixture MUST support, end-to-end against a real container, the following flows used by `@suaveplan/oauth-client` and exercised in the plugin's smoke test:
|
|
|
|
#### Scenario: Authorization Code + PKCE returns valid tokens
|
|
|
|
- **WHEN** `@suaveplan/oauth-client` runs an Authorization Code + PKCE flow against the fixture's seeded client
|
|
- **THEN** the issuer returns an `id_token` AND `access_token` whose JWT signature verifies against the `jwks_uri`
|
|
|
|
#### Scenario: Client Credentials grant succeeds
|
|
|
|
- **WHEN** a `grant_type=client_credentials` request is made with the seeded client id and secret
|
|
- **THEN** the response is `200 OK` with an `access_token` JWT
|
|
|
|
#### Scenario: Revoked user introspection
|
|
|
|
- **WHEN** a seeded user is removed (Keycloak admin REST or oauth2-mock-server config reload) and a previously issued token is introspected
|
|
- **THEN** the introspection response has `active: false`
|