Developer Tests Assets - edmozley/freeitsm GitHub Wiki
๐งช Developer Tests โ Assets & CMDB
Part of Developer Tests. Fourteen suites covering the asset estate: importing it, extending it with custom fields, what the inventory agent reports, recognising a renamed machine, numbering and labelling it, and the CMDB's typed properties, and the Proxmox and Cloud Director syncs.
All fourteen touch the database. The older suites create only rows with their own
prefix and sweep them before and after, including on failure, because the
services they drive open their own transactions. The three added in 3.1.0 can
do better: the reconciliation and tag services join a transaction the caller
already holds, so those suites run inside one that is always rolled back, and
the labels suite only reads. A sweep is only ever by the suite's own prefix โ
never a broad pattern like COMP% that could match a real machine.
| Test | Prefix it uses | Needs |
|---|---|---|
asset-import.php |
zzimp |
Database |
asset-custom-fields.php |
zz_af_ |
Database |
asset-physical-disks.php |
ZZPD |
Database + local HTTP |
asset-disk-hiding.php |
ZZDH |
Database + local HTTP |
asset-last-seen-repair.php |
ZZLS |
Database |
cmdb-typed-fields.php |
zz_parity |
Database |
contract-assets.php |
ZZCA |
Database |
asset-reconciliation.php |
ZZREC- |
Database (rolled back) |
asset-tag-numbering.php |
ZZTAG- |
Database (rolled back) |
asset-labels.php |
โ | Database (read-only) |
hypervisor-sync.php |
ZZHV |
Database (rolled back) |
leases.php |
ZZLEASE |
Database (rolled back) |
contract-customer-party.php |
ZZ |
Database (rolled back) |
rfp-companies.php |
ZZ |
Database (rolled back) |
tests/asset-import.php
What it tests
Not "does it read a CSV" โ it obviously does โ but the four things that make an import safe to leave running unattended on a schedule:
- Reconciliation. The same file twice must update, never duplicate, and an ambiguous match must refuse rather than guess.
- Preview. Reports exactly what a live run would do, and writes nothing.
- The holding area. A bad row is kept, with its reason and its source line, so somebody can see what to correct.
- Rows that vanish. Handled by an explicit policy, never a default guess.
How it works
Writes a real CSV to the system temp directory and runs the import service
against it, then runs it again to test reconciliation. Sweeps zzimp-prefixed
rows out of asset_import_runs, asset_import_run_entries, assets,
asset_history and the field-set tables before and after.
Run it
php tests/asset-import.php
If it fails
- Reconciliation โ a repeat import is now duplicating assets. This is the one that silently doubles a customer's estate overnight, so treat it as blocking.
- Preview wrote something โ the preview path has picked up a write. Anyone using preview to check a risky file is now being changed by the check.
- The holding area lost the source line โ a rejected row is no longer traceable to the line it came from, which makes a failed import unfixable.
tests/asset-custom-fields.php
What it tests
The scenario the feature was built for, end to end, exactly as it was described:
"We buy 10 TVs for our meeting rooms and on day 1 my manager says record make, model and size. Then 6 months later he wants to pilot making SOME of them smart โ can I add IP address, MAC address and Netflix enabled to SOME of the TVs and leave the others?"
So the test buys ten televisions, records three fields on all of them, then six months later adds three more fields to three of them, and asserts the other seven are untouched โ no rows, no fields, nothing to fill in.
How it works
Drives AssetFieldsService directly. errOf() captures ServiceError messages
so refusals can be asserted by their text. It never edits an existing field set,
only zz_af_-prefixed ones.
Run it
php tests/asset-custom-fields.php
If it fails
The assertion about the other seven is the important one. If adding fields to a subset touches assets outside that subset, the feature has become "add these fields to everything", which is the opposite of what it is for.
tests/asset-physical-disks.php
What it tests
Physical disks reported by the inventory agent. The agent has sent
disks.physical โ model, serial, size, media type, interface โ since its
first version, and the ingest endpoint read only disks.logical and dropped
the rest on the floor.
How it works
๐ It drives the real ingest endpoint over HTTP, with a real agent-shaped payload, auth header and JSON body โ because that is how the agent reaches it. A direct include would skip half of what can go wrong. It then reads the data back the way the asset screen does, not the way the writer wrote it.
Creates an asset and an API key, both prefixed ZZPD, and removes them plus
whatever rows the endpoint wrote.
Run it
php tests/asset-physical-disks.php
Needs the app reachable at http://localhost/freeitsm-app, or set
FREEITSM_BASE_URL.
If it fails
If every assertion fails at once, check the base URL first โ the test is probably not reaching the app at all. A single field missing means the ingest endpoint has stopped reading that part of the agent's payload.
tests/asset-disk-hiding.php
What it tests
Hiding a physical drive you do not care about.
๐ด The test that matters is "a hidden drive stays hidden after the agent reports
again". asset_physical_disks is cleared and rewritten on every run and the
row ids are reissued, so any implementation that remembers a disk by id, or by
a column on its row, passes every other test here and then fails silently, hours
later, on a customer's scheduled task.
How it works
By genuinely re-posting to the real ingest endpoint, not by simulating one. That is the only way to exercise the clear-and-rewrite the agent actually causes.
Run it
php tests/asset-disk-hiding.php
Needs the app reachable over HTTP, as above.
If it fails
If only the "stays hidden after the agent reports again" assertion is red, something is identifying a disk by a value that does not survive a re-report. A hidden disk must be remembered by something stable โ its serial or model โ not by its row.
tests/asset-last-seen-repair.php
What it tests
The repair that db_verify.php runs on upgrade. Creating an asset by hand used
to stamp last_seen as well as first_seen, as though something had reported
it. Once last_seen went on screen, every television and SIM card in every
install started reading "21 days ago" in amber โ and had been inflating the
Watchtower "not seen" count all along.
It pins down five things:
- the repair fires on a hand-added asset that never reported
- ๐ด it never touches an asset an agent has reported
- the date is not destroyed, only de-duplicated โ
first_seenkeeps it - it is idempotent, so it needs no run-once flag
- the preview counts the same rows the repair updates โ the two are maintained by hand in different files and will drift the first time somebody edits one of them
Run it
php tests/asset-last-seen-repair.php
If it fails
- "does not touch a reported asset" โ the repair has become too broad and is rewriting real agent data. Blocking.
- Preview/repair disagree โ exactly the drift the last assertion exists to catch. Whoever edited one file must edit the other; the preview is what an admin reads before agreeing to the change.
tests/cmdb-typed-fields.php
What it tests
A parity test for the CMDB's property write path after the typed-field engine was extracted. Three things must be identical to before the extraction:
- every type stores in the right column
- every validation still fires
- every error message is byte-identical โ they are the REST API's published error bodies, so a reworded message is a breaking API change
How it works
Creates only zz_parity-prefixed classes and objects, and sweeps before and
after. โ ๏ธ It never edits an existing class โ doing so would rewrite the real
Criticality option list on your database.
Run it
php tests/cmdb-typed-fields.php
If it fails
A wrong column means data is being written where nothing will read it. A changed error message means an API consumer that matched on the old text has broken โ change it back, or treat it as a deliberate API change and document it.
tests/contract-assets.php
What it tests
Assets covered by a contract โ every query in includes/contract_assets.php
against the real database.
๐ It checks the guards from the attacker's side as well as the happy path.
A scoped list is not a gate: a list that only shows you your own records says
nothing about what happens when someone asks directly for a record that is not
theirs. So the gate is tested separately, with a refuses() helper that asserts
a call throws.
Run it
php tests/contract-assets.php
If it fails
A refuses() assertion going red means a guard has stopped refusing โ someone
can attach or read an asset across a boundary. A happy-path failure with the
guards still green usually means a query changed shape; read the label for which.
tests/asset-reconciliation.php
3.1.0, from Sandy's PR #164. How an incoming device is matched to an existing asset โ serial before hostname โ and the Intune link rules. 24 checks inside one transaction that is always rolled back: possible here because createDiscoveredAsset() and the tag service join a transaction the caller already holds. Covers placeholder serials, a renamed machine found by serial, the ambiguity guard, the laptop refresh (a new serial under an old name is a new asset), rename collisions, company isolation, and Intune (a moved asset keeps its link; an unlinked device links by serial with no stub). See Asset reconciliation โ Developer Guide.
php tests/asset-reconciliation.php
tests/asset-tag-numbering.php
3.1.0. AssetTagsService: formats, the counter, the one lock, forward-only, scope, and a failed create giving its number back. 29 checks. Settings come from AssetTagsService::withSettings(), never the live rows; the writes are in one rolled-back transaction. See Asset tag numbering โ Developer Guide.
php tests/asset-tag-numbering.php
tests/asset-labels.php
3.1.0. Label fields and their translated names, the asset tag always printed first, QR error correction for a logo, the sheet sizes, and the label URL built on publicBaseUrl(). 20 checks, read-only: it changes no setting, because a test that rewrote the install's public address would leave every emailed link broken if it crashed half-way.
php tests/asset-labels.php
tests/hypervisor-sync.php
3.1.0, from Andrew's PR #167. The Proxmox VE and VMware Cloud Director syncs, run for real against a stand-in of each API answering through HypervisorHttp::$testTransport, so no server is needed. 48 checks in one transaction that is always rolled back, most of them about what gets deleted: a Proxmox node whose qemu list failed keeps its VMs, an offline node keeps its VMs, the safety guard, Director read past its own page cap, a failed page removes nothing, edge gateways only after every page. Also https only, the secret stored encrypted and never returned, both Proxmox logins, IPs only from the VM's own NICs, Director's logout, and an unreachable server named as such. With the PR's original deletion rules put back, 7 checks fail. See Proxmox and Cloud Director โ Developer Guide.
php tests/hypervisor-sync.php
tests/leases.php
3.5.0. Assets โ Leases (includes/leases.php), 47 checks in one transaction that is always rolled back. It covers:
- a lease starts with one schedule;
- dates and costs are validated;
- the asset's lease dates follow the schedule, and a schedule change moves every active asset except one with its own dates;
- typing a date on the asset becomes its own date, and typing the schedule's date back clears it;
- outcomes (a closed one stamps today and leaves the dates alone);
- one active lease per asset;
- Due back windows (overdue always included);
- the calendar "Lease notice" entries, on and off;
- history on the asset;
- every company rule, as an analyst who can reach one company: another company's lease, schedule and asset rows are not found, and its contract and cost centre are refused;
- delete, after which the assets keep their dates.
See Leases โ Developer Guide.
php tests/leases.php
tests/contract-customer-party.php
3.5.0. Contracts belong to a company (includes/contract_party.php, ContractsService): ownership decides visibility, the list follows the switcher, numbers are unique per company, and links (assets, projects, domains, Warbot) stay within one company. This applies to all-access actors too. 58 checks, rolled back. See Customer contracts โ Developer Guide.
php tests/contract-customer-party.php
tests/rfp-companies.php
3.5.0. RFPs belong to a company (includes/rfp_scope.php): rfpGate() on every RFP Builder endpoint, rfpCanView() on the five streaming ones, the list follows the switcher, and a contract stays in the same company. It fails if an endpoint is added without the gate. 25 checks, rolled back.
php tests/rfp-companies.php