Repository navigation
Developer Tests System
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) |
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:
-
Type loss. A
FULLTEXT KEYparsed as an ordinaryKEYstill produces the right number of indexes, so a count check passes while every full-text search silently returns nothing on upgraded installs. -
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. - 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.
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.
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.
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.
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?
php tests/config-not-load-bearing.php
13 assertions, needs nothing.
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.
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.
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.
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.
php tests/container-detection.php
12 assertions. Reads only: no database, no writes, nothing outside the repo.
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.
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.
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.
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.
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.
ZZSS-named rows, removed including on failure.
php tests/service-status-portal.php
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.
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 β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: Projects
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
- π Projects
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96