5.5 KiB
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.0AND seeds thetestrealm 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:2AND 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 invokingcontainerFactoryAND the returned config'sissuerUrlequalsOIDC_ISSUER_URL
Scenario: Partial env triggers container
- WHEN
OIDC_ISSUER_URLis set butOIDC_CLIENT_SECRETis 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-configurationreturns 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 codeINTEGRATION.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.safeParseis run against anOidcConfigvalue - THEN the parse succeeds for valid configs AND fails with a
ZodErrorfor 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
codestarts withINTEGRATION.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
fetchare 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-clientruns an Authorization Code + PKCE flow against the fixture's seeded client - THEN the issuer returns an
id_tokenANDaccess_tokenwhose JWT signature verifies against thejwks_uri
Scenario: Client Credentials grant succeeds
- WHEN a
grant_type=client_credentialsrequest is made with the seeded client id and secret - THEN the response is
200 OKwith anaccess_tokenJWT
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