Testing
make test is go test ./... -count=1; make lint is go vet ./... — and CI
runs both plus govulncheck before any image is built. The suite lives beside
the code it protects, in layers:
| Layer | Where | What it pins |
|---|---|---|
| Domain unit | internal/domain/**/*_test.go |
entities, Role.Has wildcard precedence, settings document round-trips, audit entry shape |
| Application unit | internal/application/**/*_test.go |
use cases with fake repos: money posting/void/transfer/undated input, fee apply/revert, scope resolution, settings validation |
| Repository | internal/infrastructure/sqlite/*_test.go |
round-trips against file-backed SQLite with the production pragmas: pagination, sorting, list filters, per-prefix numbering, salary/audit/enrollment list pages |
| Boot | boot_test.go |
the graded plugin lifecycle (below) |
| Migration | migration_test.go |
the baseline is exactly the two embedded files in order, and schema_migrations reflects them |
| Template | internal/interfaces/templates/parse_test.go |
every page under web/templates parses and executes against shaped data |
| Handler | internal/interfaces/http/*_test.go |
per-handler behaviour: auth middleware, RBAC catalogue coverage of route gates, payment/role/student scope, movement detail |
| E2E | tests/ |
the full HTTP stack, in process |
The e2e suite
tests/ boots the complete stack exactly like cmd/server/main.go wires it
— same repos, services, middleware order, template funcMap, CSRF middleware and
route registration — against a fresh SQLite database per test, and drives real
HTTP through the router with a cookie jar. It cannot import cmd/server (a
main package), so the wiring is deliberately copied; the cost is that wiring
changes must be mirrored, the benefit is that what is tested is what ships.
The flows it walks: auth and password change, user/teacher/parent CRUD, cash boxes, ledger (read-only — the page has no add controls), receipts, transfers, attendance and marks entry, audit, guardians, grade-subject assignment, year expenses, academic-year promotion, enrollment fee inheritance, student pay (full, partial, multi-currency, multi-line, custom expenses), payment edit and void, bulk assignment, salary pay, description types, first-login setup, currency-disable guard, nav highlighting — plus two meta-tests that enumerate every protected GET route authenticated (expect 200) and unauthenticated (expect redirect to login).
The phase2* files are the scope gauntlet: a parent sees only guardian-linked
children, a teacher sees only their class roster, a roles save with a bad scope
is refused, a payment write at scope own is filtered before pagination, and
the roles matrix shows no drift rows.
What the boot tests protect
internal/infrastructure/sqlite/boot_test.go runs bootPlugins over a
throwaway database with a fake plugin and proves the graded lifecycle that the
architecture promises:
| Case | Proven behaviour |
|---|---|
core plugin, Init fails |
boot returns an error — a refusal to start, no recorded degrade |
non-core plugin, Init fails |
boot succeeds with one recorded Failure{plugin, stage:init} — the host hides the feature and keeps serving |
| core plugin, clean migrate + init | boots with no error and no failures |
cmd/server/main_test.go complements it with a fake provisioner proving
provisionAccess hands the store exactly rbacdomain.DefaultRoles() and
nothing else — the stand-in for a future access plugin.
What the drift test protects
internal/domain/rbac/drift_test.go maintains a hand-written, literal list of
the resources and action triples the routes_*.go files actually gate, and
asserts it bidirectionally against both Catalogue() and the Resource*
constants. Add a route gate without updating the catalogue (or vice versa) and
the test names the drift. The http package has the matching half
(rbac_catalogue_test.go): every Require argument must be a catalogue
resource. This is what makes the 22-resource table in
RBAC a checked fact rather than a description.
The plugin conformance kit — where it is
The settings-contract conformance kit (nxtest in nx-common: the rule that a
plugin's Fields(), its struct fields and its Put keys agree) is not
referenced anywhere in this repository — it lives in nx-common and runs
against the plugins themselves, not against the host. From this repo's side,
the guarantees are the boot tests above plus the e2e coverage of the register's
surfaces (pay, void, ledger, boxes, close, transfers). The contract it enforces
is documented in settings contract.
Rendered from simple-lms/testing.md · OKF bundle · regenerate with npm run render