Skip to content

Testing and delivery

EverDuet treats tests as the shipping contract, but “run everything constantly” is not the strategy. The goal is fast local confidence and a complete release gate with bounded memory.

The feedback pyramid

text
while editing       bun run test:changed
                      focused related tests + owning typechecks

before PR           bun run test:ci
                      lint + prod/test types + coverage units + builds

on Coolify PR       isolated ephemeral PostgreSQL + Redis
                      same verification + migrations + BullMQ canary

after merge         production health + affected behavior

Each layer answers a different question. A unit test cannot prove a migration applies to PostgreSQL; a production smoke test should not be the first place a pure domain bug is discovered.

Unit tests stay deterministic

Web and admin Jest suites run at most two workers. Coverage is serial because multiple instrumented workers can create large memory peaks on a small builder. Admin tests default to Node and opt into jsdom only for components.

ts
/** @jest-environment node */

test('claims a pending invitation once', async () => {
  // exercise domain behavior without mounting a browser
})

Separating behavior tests from TypeScript validation keeps the ordinary unit loop quick while the release gate still requires both.

Typecheck production and tests separately

The repository has two explicit contracts:

  • production source compiles under strict TypeScript;
  • Jest helpers, mocks, Playwright fixtures, and E2E configuration compile too.
bash
bun run typecheck
bun run typecheck:test

This catches a common blind spot: tests can pass at runtime while their mocks have drifted from production types, especially when transforms skip full type analysis.

Changed-file selection is code

scripts/test-changed-plan.mjs maps file boundaries to validation steps and has its own unit tests.

Examples:

ChangeSelected work
Web componentrelated Jest tests + web production/test types
packages/corepackage units + core typecheck + dependent app types
Prisma schemaclient generation + DB integration prerequisites
Playwright configE2E typecheck + credential-boundary notice
Root quality configcomplete unit and typecheck gates

The selection logic is deterministic and reviewable, not a collection of shell guesses.

Contract tests protect the public boundary

The API client is generated from OpenAPI. Tests verify that:

  • operation IDs match;
  • every schema reference resolves;
  • request values validate before transport;
  • success and error responses validate at runtime;
  • generated source is current.

This makes the public API an architectural exercise with an enforceable contract rather than a second undocumented route style.

Database integration tests use real PostgreSQL

Prisma mocks are useful for domain branching, but cannot prove triggers, partial uniqueness, transaction locking, or SQL backfills. The integration tier runs against disposable PostgreSQL and is selected for schema and relationship-integrity changes.

The Coolify PR gate

Every trusted same-repository PR receives an isolated Compose stack:

text
check container
  ├── lint
  ├── production + test typechecks
  ├── unit/package/script tests
  ├── web + admin + worker + docs builds
  ├── Prisma migrations on ephemeral PostgreSQL
  └── BullMQ canary on ephemeral Redis

PostgreSQL and Redis use temporary storage. No production secrets or domains enter the preview stack.

Why GitHub Actions are manual fallbacks

The project is hosted on its own Coolify machine. Running the exact container release gate there avoids paid runner usage and reduces “CI passed in a different environment” drift. Focused GitHub Actions remain available for manually dispatched production smoke or database operations.

A useful test asks about risk

Before adding a test, name the failure it prevents:

  • pure rule regression: unit test;
  • component state/copy regression: component test;
  • route contract regression: route test;
  • database concurrency/invariant: PostgreSQL integration test;
  • auth and browser integration: focused E2E;
  • container/release ordering: Compose health gate.

This keeps the suite fast and reliable instead of duplicating every assertion at every layer.

Next: operations on Coolify.

Built as a living architecture notebook for a personal project.