Tests and tooling#
How this project verifies that something works β and what it cannot verify.
Tests#
tests/ β 314 files (find tests -name "*.php" | wc -l):
| Suite | Files | What is in it |
|---|---|---|
Unit/ |
75 | unit tests β Security/, Hydrators/, Filters/, Routers/, Cache/, Core/, UI/, Utils/ |
Integration/ |
15 | a pass across several layers |
Manual/ |
200 | manual scenarios and CLI harnesses |
Fixtures/, Support/ |
23 | data and helper classes |
Structural tests#
A category of its own: tests that read the sources and guard that nothing was forgotten. A forgotten guard in one new method breaks nothing β the hole simply stays there.
| Test | What it guards |
|---|---|
ApiPermissionCoverageTest |
that the permission guard is in every get*Data() |
PermissionResolverTest |
that the resolver's SQL contains no privilege groups |
FieldAccessCoverageTest |
that a field with a suspicious name is annotated or has an exception with a reason |
ConditionallyUpdatableContractTest |
that the named check method exists |
ExternalReadonlyCoverageTest |
that server-computed fields cannot be written from outside |
A tautological guard proves nothing
A check comparing a value with itself always passes. Verify every guard with a counter-example β that is, by showing when it does not pass.
A seed in a fixture is usually a sign of missing code
If a test has to add data by hand to pass, it usually means the application does not add it. Before adding a seed, check whether it should not be created in the code.
What tests will not catch#
This is the most important section of the chapter.
| Bug | Why it passes | What catches it |
|---|---|---|
the form cannot be submitted (mixed in onSuccess) |
building the form and the Api endpoint beneath it both pass | a POST or a live click-through |
setValidationScope([]) empties the values |
tests go through the Api with a hand-written payload | a live click-through |
| a CLI harness without a token returns emptiness | it returns emptiness without an error | checking that data really arrived |
| a snippet with the error message is not redrawn | the failure is silent | clicking through a browser |
A live click-through reveals what a harness cannot
A CLI run bypasses the layers that apply in a browser β forms, snippets, signals, JavaScript. Before declaring something done, click through it.
A CLI harness needs an internal token
Without one it runs as guest, gets Permission denied inside the bridge and returns
emptiness with no error. A test over that passes green and tests nothing. The fix:
ApiBridge::setInternalAuthToken($jwtManager->generateToken(β¦)) β in detail
Calling the API.
Equal Elasticsearch scores are no proof of ordering
When documents share a score, the order is decided by the index's internal order β and that can change with no code change at all. A test over such a list only proves the search returned something.
PHPStan#
phpstan.neon, level 9, plus 23 baseline files (ls phpstan-baseline*.neon).
A dead path in a baseline prevents the run ENTIRELY
PHPStan exits with an error and the analysis never happens β and because the output does not look like a finding, it is easily mistaken for "it passed". After renaming or deleting a file, go through the baselines.
The raw error count fluctuates even without code changes
Measured between 5,795 and 5,857 across runs. Accept differences by the list of findings, not by the number β otherwise you will be chasing noise.
The pre-commit hook#
.githooks/pre-commit (activate with git config core.hooksPath .githooks) blocks three
things:
- debug calls in PHP β
bdump(,dumpe(,dd(in added lines, :rootin component SCSS β tokens belong on the component's selector, not in the global scope (the allowlist exempts the token layers),- violations of the layer ratchet
Manager β Service β Mapper β Repository.
--no-verify bypasses all three
It exists for exceptions, not for haste. Whatever slips through --no-verify comes up
at code review β or in production a month later.
Building assets#
npm run build:admin # the administration's JS and CSS
npm run build:admin:css
npm run build:admin:js
npm run watch:admin:css
The front-end theme is built from its own folder β see Front-end themes.
npx sass is a different compiler version than sass
It produces a different output. Use the commands from package.json.
Skills#
skills/ β 19 procedures. They are not recommendations; they are the binding
description of how a particular thing is done in this project.
| Group | For |
|---|---|
code-standard/ |
conventions per layer (PHP, model, presenter, component, forms, signals, Latte, Core) |
cs-admin-* |
the administration β grid, form, picker, form audit |
cs-template-* |
templates and themes |
cs-translations |
translations |
cs-scss-structure |
SCSS |
cs-module-refactoring, cs-payment-gateway, cs-ui-audit |
end-to-end procedures |
Open the relevant skill before a task β not all of them
Every cs-conv-* asks for its own dependencies. The overview is in
Code conventions.
π΄ Production and development share a database#
Every write is live
There is no "let me try it on dev first". Run experiments in a transaction with a rollback, and before rewriting anything in bulk, count how many rows it affects.
The files, by contrast, are split across machines
The database is shared, the generated files (PDFs, images, feeds) are not. A missing PDF on the development machine therefore need not mean a generator bug β it may simply sit somewhere else.
Redis and files are not shared between local and the dev server
Clearing the cache locally does not mean the cache on the dev server is clear. Verify where it is supposed to work.
Where to look#
| I want | Where |
|---|---|
| the unit tests | tests/Unit/ |
| the manual harnesses | tests/Manual/ |
| the static analysis configuration | phpstan.neon + phpstan-baseline*.neon |
| the pre-commit checks | .githooks/pre-commit |
| the build commands | package.json, composer.json |
| the binding procedures | skills/ |
Follow-up chapters: Code conventions Β· Deployment checklist Β· Calling the API Β· RBAC in the API