Skip to content

Developer Tests System

Ed Mozley edited this page Oct 4, 2026 · 2 revisions

πŸ§ͺ Developer Tests β€” System & infrastructure

Part of Developer Tests. Database verification, config.php, containers, clocks and the status portal.

πŸ”‘ Every test on this page exists because the fault was invisible on a developer's machine. A UTC bug does not show on a server whose clock is already UTC. An open_basedir bug does not show where open_basedir is unset. A config.php bug does not show on the one install whose config.php is the repository's. So these tests deliberately do not ask "does it work here" β€” they ask the structural question, or they recreate the hostile condition on purpose.

Test Needs
db-verify-indexes/run.php Nothing β€” this is the one CI runs
config-not-load-bearing.php Nothing
container-detection.php Nothing
utc-connection.php Database (temporary table only)
service-status-portal.php Database
cost-centres.php Database (rolled back)

tests/db-verify-indexes/run.php

What it tests

That the index backfill list understands FULLTEXT.

database/freeitsm.sql builds a new install; Database Verification upgrades an existing one. Indexes bridge the two through a generated mirror (includes/db_verify_indexes.php), so a grown install can be given an index it never received. That mirror used to record a single boolean per index β€” unique or not β€” which had nowhere to put a third kind.

Full-text search needs that third kind, and getting it wrong fails quietly:

  1. Type loss. A FULLTEXT KEY parsed as an ordinary KEY still produces the right number of indexes, so a count check passes while every full-text search silently returns nothing on upgraded installs.
  2. The truthy trap. The old third element was a bool read as $unique ? 'UNIQUE KEY' : 'KEY'. The new one is a string β€” and the string 'key' is truthy, so any code path still using that ternary would build a UNIQUE index over columns full of duplicates.
  3. Drift. The mirror is generated, so the generator and the drift self-check must agree about what an index "is". They share one parser precisely so they cannot disagree, and this suite pins that.

Run it

php tests/db-verify-indexes/run.php

32 assertions, needs nothing.

πŸ”‘ This is the only test currently wired into CI, via schema-drift.yml, which also runs php scripts/gen_db_verify_indexes.php --check.

If it fails

The CI job will have failed too. A drift failure means the generated mirror and freeitsm.sql disagree β€” regenerate the mirror rather than hand-editing it, and work out which of the two files is actually wrong before you do.


tests/config-not-load-bearing.php

What it tests

That config.php is not load-bearing.

πŸ”΄ A change once put dbConnectionOptions() into config.php and pointed eleven callers at it. But config.php is the operator's file: it ships as a template carrying a credentials path, so every install edits it once and keeps that copy, and the Docker image copies docker/config.php straight over the top. Upgrading therefore delivered the callers and left the definition behind β€” HTTP 500, empty body, every page in the product.

πŸ”‘ The rule: config.php is for values the operator chooses. Behaviour lives in includes/, which upgrades with the product.

How it works

⚠️ Why the ordinary suite could not catch it: the development machine's config.php is the repository's config.php. On the one install where that file is not customised, the function was present and everything worked.

So this test never asks "does it work here". It asks the structural question: does config.php declare any functions at all?

Run it

php tests/config-not-load-bearing.php

13 assertions, needs nothing.

If it fails

Something executable has been added to config.php. Move it into includes/ and have config.php call it. Remember docker/config.php is a different file with different contents β€” it defines DB_PASSWORD and other things a hand install must never have β€” so do not try to reconcile the two.


tests/container-detection.php

What it tests

storagePersistenceInContainer(), which gates the warning that tells an operator that docker compose up -d --build is about to destroy their uploads, unrecoverably. If it wrongly answers "no", the warning silently stops appearing for the only people it was ever written for.

πŸ”΄ The failure mode is invisible on a development machine. It only appears when open_basedir is set, which no developer here has, and its symptom is not an error but silence. That is exactly how the reported bug reached a user.

How it works

It drives PHP as a subprocess with the restriction switched on, rather than testing the function in-process β€” the condition cannot be created any other way.

⚠️ It also guards against the wrong fix. The reported warning goes away if you simply re-point the check at a path open_basedir allows β€” and that tests clean on any machine not running Docker, because there the right answer is false anyway. Case 5 fails if anybody does that.

Run it

php tests/container-detection.php

12 assertions. Reads only: no database, no writes, nothing outside the repo.

If it fails

If case 5 is the red one, read the warning above before "fixing" it β€” you are probably about to make the test pass and the feature stop working.


tests/utc-connection.php

What it tests

The database connection's clock.

MySQL evaluates NOW(), CURRENT_TIMESTAMP and CURDATE() in the connection's session time zone. That defaulted to SYSTEM β€” the database server's own clock β€” while FreeITSM stores every instant in UTC. So any INSERT that did not name a datetime column wrote a local wall clock through the column's DEFAULT CURRENT_TIMESTAMP, and the screen read it back as a UTC instant. A note came out the server's own offset into the future.

πŸ”΄ The fault is invisible on a server whose clock is already UTC, which is most of them and every sensible container. A suite that only ever runs on such a machine agrees with the bug.

So the assertions do not ask "is the value right". They ask "is the connection's clock UTC", and then prove the consequence on a scratch table.

Run it

php tests/utc-connection.php

Safe: writes only to a TEMPORARY table, which exists for this connection only and disappears when the script ends. Nothing in the real schema is touched.

If it fails

The connection is not being set to UTC. Every DEFAULT CURRENT_TIMESTAMP on the install is now writing local time into a column everything else reads as UTC β€” and on a UTC server you will not see it. Fix the connection setup, not the columns.


tests/service-status-portal.php

What it tests

Internal versus external incident updates. The point of this file is that an internal update never reaches the portal. Everything else is secondary, so each check is written from the side that must be refused as well as the side that must work.

How it works

⚠️ It puts the portal settings back exactly as it found them. They are global, and a test that leaves the portal publishing would be worse than no test. ZZSS-named rows, removed including on failure.

Run it

php tests/service-status-portal.php

If it fails

An internal update reaching the portal is a disclosure to every customer at once β€” internal updates are where engineers write frankly about what broke. Treat as blocking.

If the run dies part-way, check the portal settings by hand before doing anything else; the restore may not have run.

tests/cost-centres.php

3.1.0, #160. CostCentresService: codes kept as text and unique per company ignoring case, a parent in the same company and never a loop, delete refused with children, company scope (404 outside), and sync() - matched on code, all or nothing with every error listed, dry_run writing nothing, deactivate_missing. 38 checks inside one transaction that is always rolled back. See Cost centres β€” Developer Guide.

php tests/cost-centres.php

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally