This file contains instructions for AI coding agents working in this repository.
oauth2-mock-server is a TypeScript library and CLI tool that provides a configurable OAuth2/OpenID Connect mock server for automated testing.
Tech stack: TypeScript (ultra-strict) · Node.js · ESM-only · Vitest · ESLint + Prettier
| File | Role |
|---|---|
src/index.ts |
Public barrel — all library exports originate here |
src/oauth2-mock-server.ts |
Binary shim — calls cli() from src/cli.ts with process.argv.slice(2); contains no logic |
src/cli.ts |
CLI logic — cli(args) parses argv and starts the server; also exports readJsonFromFile() and shift(); not part of the public API |
src/lib/http-server.ts |
HttpServer — restartable wrapper around node:http/node:https |
src/lib/jwk-store.ts |
JWKStore — manages JWK key pairs; uses jose for key generation/export |
src/lib/jwk-store.keys.ts |
JWK algorithm registry: supportedAlgs list and privateToPublicKeyTransformer; used by JWKStore and test helpers — not exported publicly |
src/lib/oauth2-issuer.ts |
OAuth2Issuer — holds the issuer URL + key store; signs JWTs with jose (SignJWT) |
src/lib/oauth2-service.ts |
OAuth2Service — HTTP request handler; owns all OAuth2/OIDC endpoint logic; composes OAuth2Issuer |
src/lib/oauth2-service.http.ts |
HTTP plumbing for OAuth2Service: body/query-param parsing, route matching — not exported publicly |
src/lib/oauth2-service.pkce.ts |
PKCE utilities: isValidPkceCodeVerifier, createPKCEVerifier, createPKCECodeChallenge — not exported publicly |
src/lib/oauth2-server.ts |
OAuth2Server — convenience façade; combines HttpServer + OAuth2Service + OAuth2Issuer |
src/lib/assertions.ts |
Internal assertion helpers: assertIsString, assertIsStringOrUndefined, assertIsAddressInfo, assertIsPlainObject — not exported publicly |
src/lib/types.ts |
Public types and interfaces (exported via barrel) |
src/lib/types-internals.ts |
Internal types (JWKWithKid, AugmentedRequest, RouteHandler, InternalEvents, supportedPkceAlgorithms) — not re-exported |
Key dependency: jose (async, Promise-based API) is used for all JWK generation, JWT signing, and key import/export.
- Build output:
dist/*.mjs(ESM only, generated bytsdown— never edit manually)
Every change must pass all three steps below. Do not mark a task done until all three succeed.
npm run lintThis runs tsc --noEmit (type-check) then eslint with zero warnings allowed. Fix all reported issues before proceeding.
npx vitest --run --coverage --reporter=verbose --coverage.reporter=text--run: non-interactive single pass--reporter=verbose: prints each test name with pass/fail — easy to scan for failures--coverage.reporter=text: prints a coverage table to stdout — no browser needed
All tests must pass. Coverage must not decrease from the baseline reported on master.
Current baseline (master):
| File | % Stmts | % Branch | % Funcs |
|---|---|---|---|
| All files | 96.24 | 94.31 | 96.7 |
| src | 87.8 | 89.65 | 75 |
| src/lib | 97.98 | 95.05 | 100 |
npm run testis equivalent but also triggers lint, which is slower when iterating. Use thenpx vitest ...command during development and runnpm run testas the final gate.
Review whether the change requires documentation updates:
README.md— update when the public API surface, usage examples, CLI options, or any user-visible behaviour changes.AGENTS.md— update when project structure, module map, test conventions, tooling, or agent-facing rules change.
If neither file needs updating, explicitly confirm that before marking the task done.
-
noUncheckedIndexedAccess:arr[i]andrecord[key]are typedT | undefined. Always guard before use. -
exactOptionalPropertyTypes:{ x?: T }does not acceptx: undefined— omit the property instead. -
isolatedDeclarations: exported function/type signatures must be fully explicit — no inferred return types on public exports. -
import type: useimport typefor any import used only as a type. ESLint (@typescript-eslint/consistent-type-imports) enforces this. -
node:prefix: always use thenode:protocol for built-in imports (e.g.,import { randomUUID } from 'node:crypto'). -
Import order (enforced by
eslint-plugin-import-x):- Node.js built-ins (
node:*) - External packages
- Internal / relative paths
Separate each group with a blank line.
- Node.js built-ins (
- Boring over clever. Write straightforward, easy-to-read code. Avoid one-liners, clever tricks, or expressions that require a second read to understand. If it needs a comment to explain what it does, rewrite it instead.
- Short functions. Each function should do one thing. If a function is growing long or handles multiple concerns, split it.
- No duplication. Shared logic belongs in a shared helper. Refactoring to eliminate duplication is expected and encouraged — it is part of the job, not optional cleanup.
- Refactoring is in scope. Both source and test code may be refactored when it improves clarity or removes duplication. Test refactoring does not require special approval as long as no passing test is deleted or weakened (see Test integrity).
- Readability over brevity. Prefer explicit variable names and intermediate variables over chained expressions. The reader should be able to follow the logic without tracing execution mentally.
- Copy the copyright header from any existing
src/lib/*.tsfile. - Add a
/** @module lib/your-module */JSDoc comment below the header. - Add JSDoc to every exported symbol (
jsdoc/require-jsdocis enforced for public exports). - If the symbol is part of the public API, re-export it from
src/index.ts.
Tests are the behavioural contract of this library and are more critical than the implementation code itself. The rules below are strictly enforced.
- Every code change must be accompanied by unit tests that cover the new or modified behaviour. A PR without tests for its changes will not be accepted.
- All code branches must be covered. When a branch genuinely cannot be reached in tests (e.g. a defensive assertion against an impossible runtime state), you must explicitly tell the user which branch is uncovered, why it cannot be tested, and what the underlying reason is. Do not silently leave branches uncovered.
- Modifying existing passing tests is strictly forbidden without the explicit agreement of the user. If you believe a test must be changed, stop, explain clearly which test you want to modify and exactly why, and wait for approval before touching it.
- Test files live in
test/and mirror thesrc/lib/path (e.g.,src/lib/foo.ts→test/foo.test.ts). - Reuse shared utilities — do not reinvent them:
test/lib/test_helpers.ts:verifyTokenWithKey,createMockRequesttest/lib/cli-fake-runner.ts:exec(args)— runscli()with captured stdout/stderr; use in CLI teststest/keys/index.ts:getParsedKey(filename)loader for the JSON key fixture files in the same directory (test-rs256-key.json,test-es256-key.json,test-eddsa-key.json)
- The Vitest setup file (
test/lib/_vitest-setup.ts) promotes unhandled rejections to thrown errors — async tests must handle all promise rejections. - Do not modify
test/integration/— it tests the published package and runs separately.
Never modify these generated/external directories:
dist/— build outputcoverage/— test coverage outputnode_modules/test/integration/