Skip to content

Contribution

How to add and change code and tests.


Adding a route

To add a new endpoint inside an existing feature (e.g. auth, admin, rbac, invitations):

  1. Router — In the feature's *.router.ts, add the route and wrap the handler with SharedHelpers.handleIfError(Controller.method).
  2. Controller — In the feature's *.controller.ts, add a static method that:
  3. Calls the feature validator to parse req.body / req.query
  4. Calls the feature service
  5. Sends the response with SharedHelpers.sendResponse(res, status, message, isSuccess, data)
  6. Validator — In the feature's *.validator.ts, add a Zod schema and parse function for the new input.
  7. Service — In the feature's *.service.ts, add the business logic; use models and shared helpers as needed; throw SharedErrors.* on failure.
  8. OpenAPI — If the route is part of the public API, register it in the feature's *.openapi.ts (see auth.openapi.ts, rbac.openapi.ts, invitations.openapi.ts and lib/openapi.ts).

Follow the existing patterns in that feature (e.g. emailPassword controller/service) and CONVENTIONS.md for response shape and errors.


Adding an auth method

To add a new auth strategy (e.g. magic link, social login):

  1. New folder under src/auth/ — e.g. src/auth/magicLink/ with:
  2. magicLink.router.ts — Routes (e.g. send-link, verify-token)
  3. magicLink.controller.ts — Handlers
  4. magicLink.service.ts — Business logic; integrate with Better Auth / session as needed
  5. magicLink.validator.ts — Request validation

  6. Strategy ID — In the new service, define a static AUTH_STRATEGY_ID (e.g. 'magic-link'). This is the URL segment under /api/auth/.

  7. Mount in auth router — In src/auth/auth.router.ts:

  8. import magicLinkRouter from './magicLink/magicLink.router';
  9. import MagicLinkService from './magicLink/magicLink.service';
  10. Add: authRouter.use('/' + MagicLinkService.AUTH_STRATEGY_ID, magicLinkRouter);
  11. If the strategy is optional per app, use AuthMiddleware.requireStrategyForApp(MagicLinkService.AUTH_STRATEGY_ID) on the router (see emailPassword.router.ts).

  12. Auth app config — If only some apps use this strategy, add or update a config in src/authApps/ (e.g. *.config.ts) so the auth app's strategies include the new ID.

  13. OpenAPI — Register the new paths in auth.openapi.ts (or a dedicated magicLink.openapi.ts if you split).

Use emailPassword, emailOtp, and smsOtp under src/auth/ as reference for structure and how they're mounted.


Adding tests

Integration tests are config-driven: each suite loads a JSON config and the runner executes HTTP and DB steps, then asserts responses.

Add a new suite

  1. Config file — Create tests/configs/<area>.<feature>.json (e.g. tests/configs/auth.magicLink.json). Structure:
  2. suiteId — Short name for the describe block
  3. flows — Reusable sequences of steps (HTTP and/or dbRead, dbWrite, dbInsert); each flow has flowId and steps
  4. tests — Each test has testId, request, expectedResponse, and optional preflowIds (flows to run first; captured values can be used in the test request via {{flow.flowId.key}})

  5. Suite file — Create tests/suites/<area>.<feature>.integration.test.ts:

  6. beforeAll(() => TestHelpers.beforeAll());
  7. afterAll(() => TestHelpers.afterAll());
  8. runConfigSuite('tests/configs/<area>.<feature>.json');

  9. Requestrequest has method, path, optional headers, body, query. Paths and body can use {{flow.flowId.key}} to inject values captured in a preflow.

  10. Expected responseexpectedResponse has status and optional body, headers. Top-level keys in body must match exactly. For data you can use __keys (list of keys that must exist and be non-empty strings) and __arrayKeys (keys that must be arrays); see tests/types/testConfig.types.ts.

  11. Flows — Steps can be type: 'http' (default), dbRead, dbWrite, dbInsert. Use capture to store response or document values for later steps (e.g. $body.data.token, $.code for a dbRead doc). Dates in dbInsert support __now and __now+MS.

See TESTS.md for config format details and examples in existing configs.


Test thresholds and coverage

Running tests

From repo root:

npm run test:integration --workspace=jadu-auth-be

From be/:

npm run test:integration

With coverage (enforced on pre-push when be/ changes):

npm run test:integration --workspace=jadu-auth-be -- --coverage

Thresholds

Coverage is enforced at 98% for lines, functions, branches, and statements (see vitest.config.integration.ts). The same run is used by the pre-push hook (Lefthook) when you push changes under be/.

Excluded from coverage

The following are excluded so that thresholds reflect meaningful code:

Pattern Reason
**/*.openapi.ts OpenAPI spec glue; no runtime logic
src/index.ts Entry point; exercised by integration tests via the app
src/**/*.config.ts Config objects; validated at load
src/**/*.types.ts Type definitions only
src/lib/jwt.ts Compatibility shim; production uses Better Auth
src/models/**/*.ts Data-access; exercised indirectly via services

To change thresholds or exclusions, edit be/vitest.config.integration.ts.