Skip to content

Testing

Features do not ship without tests. The same entry points work locally and in CI.


  • Test-driven development: write or update tests alongside the code, not after. A PR that adds behavior without corresponding test coverage is incomplete.
  • Bug fixes start with a test: reproduce the bug in a test first, then fix the code. The test proves the fix and prevents regression.
  • Same entry point everywhere: make test-all runs locally and in CI — no divergence
  • Keep it simple: standard library testing package for Go, Playwright for e2e
  • No mocking the system under test: integration tests use real Postgres; e2e tests use the real stack with Zabbix, core, and all services

TierCommandWhat it testsDependencies
Unitmake test-unitGo packages in isolation — pure logic, no external depsGo toolchain
Integrationmake test-integrationGo service layer against Omniglass-owned schemas in PostgresGo + Postgres
E2Emake test-e2eFull stack from the outside — smoke checks then feature testsDocker, Bun, Playwright
Allmake test-allUnit + e2eAll of the above

Standard Go tests. No external dependencies. Run fast.

Terminal window
make test-unit

Test pure logic: validation, parsing, data transforms. If a function doesn’t touch the network or database, it belongs here.

Tests that require a running Postgres instance with the Omniglass schema. Guarded by a build tag. These validate SQL queries, constraint behavior, transaction logic, and service-layer operations against Omniglass-owned tables.

Terminal window
make test-integration
# or directly:
cd components/core && go test -tags=integration -count=1 ./...

Integration tests are gated by //go:build integration at the top of the test file. They are excluded from make test-unit and included only when explicitly requested. They do not require Zabbix server or any service other than Postgres.

Full-stack tests using Playwright. Start the entire Compose stack (Postgres, Zabbix server, Zabbix web, core, nodered, gateway), run tests against the live system, tear down.

Terminal window
make test-e2e
# optional: filter by test name
make test-e2e GREP="login"

E2e tests live in tests/e2e/specs/ and run in alphabetical order. Smoke checks are prefixed 00-* through 04-* so they run first:

PhaseSpecsWhat they verify
Smoke00-health-check through 04-vanillaStack boots, containers healthy, login works, each page renders, Node-RED loads
Featuressystem-types, systems-reconciliation, etc.CRUD lifecycles, API correctness, cross-service behavior

If smoke fails, there is no point running feature tests — the stack is broken.


  • Place _test.go files alongside the code they test
  • Use the standard testing package
  • No Go test frameworks (no testify, no ginkgo, no gomock)
  • Table-driven tests where appropriate
  • If you need a helper, write a plain function in _test.go
  • Use the //go:build integration build tag
  • Connect to a real Postgres instance via DATABASE_URL env var
  • Use testdb.Open(t) and testdb.Truncate(t, db) for shared setup
  • Each test starts with a clean slate — truncate at the start, not deferred
  • Browser tests use { page } fixture; API tests use { request } fixture
  • Smoke specs capture screenshots at key points for visual verification
  • Feature specs create test data, verify behavior, and clean up in finally blocks
  • All specs share the same Playwright config and stack lifecycle

CI calls the same Makefile targets:

- run: make test-unit
- run: make test-e2e

No CI-specific test scripts. No test-specific Docker images. If it passes locally with make test-all, it passes in CI.


  • No Go test frameworks: no testify, ginkgo, gomock. Standard library testing package only. (E2E tests use Playwright, which is the right tool for browser automation.)
  • No mocking Zabbix: integration tests talk to real Postgres; e2e tests talk to the real stack. Don’t mock the system you’re testing against.
  • No shipping without tests: a feature PR without tests is incomplete. A bug fix PR without a regression test is incomplete.
  • No coverage thresholds: coverage is a tool, not a target. Write meaningful tests.
  • No test-specific Docker images: e2e tests use the same images as production.
  • No conditional CI branches: the same run.sh and make targets run locally and in CI.