An official developer portal of the Government of the Republic of Zambia · Test environmentVerify a certificate
Pamodzi Developer HubBuild, integrate and learn the e-Services framework

Testing and releasing a module

A module is not done when it works on your machine. It is done when the automated checks prove it is safe in every switch state, in both schema modes, for every ministry and in every platform role, and when the services it touches are tested end to end and documented. This lesson walks through each layer of tests and the release checklist.

The checks that run on every module

CheckCommandWhat it proves
Unit testsnpm testmodules/<id>/test/*.test.js and the core unit suite
Module contractpart of npm test (test/modules.contract.test.js)Every bundled module follows the rules, with no database
Boundariesnpm run check:modulesNo core → module or module → module require
MDA neutralitynpm run check:mdaNo ministry name, host or historical service id in core
Schemanpm run db:schema-diffModels and migrations agree
Migrationsnpm run db:check-migrationsDestructive steps carry the expand/contract marker
Integrationnpm run test:integrationReal behaviour against a throwaway database

The module contract test checks, for every bundled module: the manifest is valid; policy actions are prefixed and tagged; menu actions exist; every route names a policy action; every route answers 404 MODULE_DISABLED for every method and caller while the module is off, outside the ceiling or forced off; jobs are scheduled only while it is on; there are no forbidden requires; module migrations create only the module's tables; module-keys.json is current; and sample modules are excluded from production.

Integration tests and the harness

Integration tests in modules/<id>/test/integration/ run against a fresh database per test file. The harness switches modules for you:

const { setup, teardown } = require('../../../../test/integration/harness');

const h = await setup();                          // or setup({ modules: [manifest] }) for a test-only module
await h.enableModule('fisheries', { mdaId });     // on, assigned to that ministry (call again to add more)
await h.enableModule('fisheries');                // on for every ministry; { settings } sets installation settings
await h.disableModule('fisheries');               // off, rows kept

The minimum set of cases for every module:

  1. Off means 404. With the module off, its routes answer 404 MODULE_DISABLED.
  2. Assigned ministry is served. With the module assigned to ministry A, A's officer is served.
  3. Other ministry is refused. Ministry B's officer gets 404, and cannot see A's records.
  4. Citizens are refused on staff routes.

Both schema modes

Integration tests must pass twice:

npm run test:integration -- modules/fisheries/test/integration/fisheries.test.js
TEST_SCHEMA=migrations npm run test:integration -- modules/fisheries/test/integration/fisheries.test.js

The first run builds the schema from the models; the second builds it by running every migration. A test that passes only in one mode usually means a model and a migration disagree, or a migration is missing. Migrations must also be reversible: up, down and up again on a scratch database.

Always use your own local PostgreSQL with PostGIS (the team's convention is port 5433). Never point local tools or tests at a remote or shared database.

Money, statuses and permissions

Anything that changes money, a status or a permission needs an integration test that proves both the abuse case is refused and the legitimate path works. Think about:

  • double payment: the same invoice confirmed twice is applied once;
  • wrong amount: an amount that does not match the server's quote is refused;
  • replay: a repeated callback or request has no second effect;
  • race: two concurrent requests cannot both succeed;
  • atomicity: a payment hook that throws leaves the payment unpaid;
  • the money rule: a confirmation that arrives after the module was switched off is still finalised, and audited

    module.bypass.

Front-end tests

  • Back office. Each module ships a jest test: off shows "not enabled", on shows the page. The registry is checked

    against the backend's module-keys.json by moduleContract.test.js.

  • Portal. The module adds no page, section or home tile and follows the server switch. portalNeutrality.test.js

    checks every module against the allow-list.

  • Both apps must still build (npm run build:ci).

End-to-end tests and documentation

Every new or changed service ships with a Playwright journey in e2e/. It covers the full process: find the service, sign in, apply with form and documents, pay, officer processing through every step, the decision, and the issued documents, including download, seal and QR verification and the key fields.

  • It runs on both seed profiles: E2E_SEED_PROFILE=mihud (some modules on) and neutral (every module off).
  • It runs in federated topology (E2E_TOPOLOGY=federated) when the service can live on a node.
  • It uses the provider test doubles, never real payment, identity, SMS or signing services.
  • It captures screenshots of each step (portal and back office, desktop and 375 px for citizen steps) with synthetic

    data only.

  • npm run docs:services generates the service documentation (Markdown and DOCX from the standard template) into

    e2e/docs/services/<mda-code>/<service-key>/, and npm run status:services regenerates e2e/SERVICE-STATUS.md. Never edit the status list by hand.

A service is not done until its test passes and its documentation is generated and published to the central documentation share.

Release checklist

  • Personal data. If the module holds personal data: a DPIA note, pii.paths / pii.subjects,

    dsr.exportSections and anonymise.columns contributions, and a softDelete.kinds entry for deletable records.

  • Retention. A retention class for each table, agreed with the records officer.
  • Audit. Every state change writes a named audit event; every route names a policy action.
  • Money. Only through payment points, inside the caller's transaction, with atomicity and money-rule tests.
  • Translations. Portal texts in English, with ny, bem and toi requested.
  • Checks. Contract tests pass in all three apps; module-keys.json synced to both front ends.
  • Rollout. A switch-on plan: which MDAs, which settings, who approves.
  • Roles. It works as combined, hub and node.
  • Open questions written down with the safe default you assumed.

Check your understanding

  • List five things the module contract test checks for every bundled module.
  • Why must integration tests pass with and without TEST_SCHEMA=migrations?
  • Which four cases should every module's integration test cover?
  • Name four abuse cases a payment-related test should consider.
  • When must a module's e2e journey also run in federated topology?
  • Which command regenerates e2e/SERVICE-STATUS.md, and why is it never edited by hand?

Sign in to record your progress.

Previous: Switching modules on and off, and deploying them Go to the assessment