Integration Testing (Live TerminusDB)
The integration suite in backend/tests/integration-live/** exercises route → service → real TerminusDB with the real Fastify server and the full production schema. It has its own Vitest config (backend/vitest.integration.config.ts) and is excluded from the default npm run test:run, which still passes with no services available.
Why It Exists
Mocked unit tests cannot catch entire classes of regression that only appear against a live database:
- GraphQL field/schema mismatches — e.g.
Person(id:)does not match subclass documents (Faculty/...); the correct form isPerson(filter: { _id }). A mocked client happily returns whatever the test stubs. - TerminusDB Set-ordering behavior —
Setfields come back unordered; code that assumes insertion order passes against mocks and fails live. - Schema drift — queries or writes referencing fields that no longer exist in
pbs-schema.json.
The suite loads the full production schema and runs the real workflow/approval flows end to end, so these break loudly before deploy instead of in production.
Running Locally
From backend/:
npm run test:integration:db-up # disposable terminusdb/terminusdb-server:v12.0.5 on 127.0.0.1:6364
npm run test:integration # creates pbs_itest DB, loads full pbs-schema.json, runs suites
npm run test:integration:db-down # removes the container (all test data discarded)
The database container is throwaway — every run starts from a clean pbs_itest database and teardown drops it.
Safety Guards (Do Not Weaken)
The suite is engineered so it cannot touch the production pbs-terminusdb container:
- Loopback-only target.
tests/integration-live/env.ts(assertSafeTestTarget) refuses to run unlessTERMINUSDB_URLis exactly the loopback test instance (http://127.0.0.1:6364by default — never port 6363) andTERMINUSDB_DBstarts withpbs_itest. - Triple-gated test auth. Authentication uses an
x-test-userheader bypass that requiresNODE_ENV=testandTEST_AUTH_BYPASS=1andbuildServer({ testAuth: true }). It is inert in production;tests/unit/plugins/testAuthBypass.test.tsasserts every gate. - No real side-channel services. Redis/BullMQ are
vi.mocked (setup.ts); enqueued jobs are captured inqueueCapture.tsso tests can assert on notifications without sending anything. Firebase is absent (the plugin skips init underNODE_ENV=test); email templates fall back to embedded defaults.
Layout
backend/tests/integration-live/
├── env.ts # assertSafeTestTarget — loopback + pbs_itest guard
├── global-setup.ts # idempotent DB lifecycle: delete → create pbs_itest →
│ # load FULL src/schema/pbs-schema.json via
│ # /api/document?graph_type=schema; teardown drops the DB
├── setup.ts # per-suite mocks (Redis/BullMQ)
├── queueCapture.ts # captured queue jobs for notification assertions
├── helpers.ts # fixtures and server/query helpers (below)
├── workflows/ # award-nomination, fellowship, thesis, independent-research,
│ # major-plan flow suites + authz.test.ts
├── admin/ migrations/ sync/ # additional suites
Key helpers (helpers.ts)
buildTestServer()— boots the realbuildServerexported frombackend/src/index.ts(plugins + routes + boot registrations, nolisten). Health routes register before plugins on purpose —/api/healthmust stay reachable without auth (it is the deploy gate).TestFixtures— typed,test_-prefixed persons/groups created via the Document API, with auto-cleanup including the workflow-artifact cascade (WorkflowInstance / StageInstance / StageApproval).asUser(actor)— builds thex-test-userheaders for the triple-gated bypass, so suites can act as a specific student, advisor, or committee member.gql(query, variables),getDocument/replaceDocument,getWorkflowStateForEntity(entityUri)— direct query and workflow-state assertion helpers.testId(type)/fullUri/shortId— id construction utilities (terminusdb:///data/prefix handling).
A typical workflow suite creates fixtures, submits an entity through the real bridge endpoint as a student, approves it as the resolved approver, and asserts both the unified StageApproval state and the captured notification jobs.
CI: This Suite Gates Production Deploys
The integration-tests job in .github/workflows/ci.yml runs npm run test:integration against a terminusdb/terminusdb-server:v12.0.5 service container (runner port 6364). The deploy job — which is what ships main to production — depends on integration-tests (alongside build, security-audit, and ferpa-compliance). A failing integration test blocks the deploy.
Practical implications:
- If you change
pbs-schema.json, a query, or a workflow flow, run the suite locally before pushing tomain—maindeploys automatically on green CI. - New approval flows should ship with a flow suite under
workflows/(copy an existing one); these caught real bugs on day one, including thePerson(id:)subclass mismatch. - Never point the suite at port 6363 or weaken
assertSafeTestTargetto "make it run" — fix the environment instead.
When to Add an Integration Test (vs a Unit Test)
Use a live integration test when the behavior under test depends on what TerminusDB actually does:
- A new or changed GraphQL query/filter shape (especially anything involving abstract types like
Person,Setfields, or_idfiltering) - A new approval/workflow flow or stage-completion hook — assert end state via
getWorkflowStateForEntity - Authorization on routes that read or mutate real documents (see
workflows/authz.test.tsfor the pattern) - Document writes where field preservation matters (partial updates, schema-added fields)
Keep using mocked unit tests for pure logic: status mapping, validation, formatting, and error branches. Integration tests are slower (full schema load per run), so test the database-shaped behavior there and the combinatorics in unit tests.