Skip to content

Developer Tests Assets

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

πŸ§ͺ Developer Tests β€” Assets & CMDB

Part of Developer Tests. Eleven 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 eleven 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)

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:

  1. Reconciliation. The same file twice must update, never duplicate, and an ambiguous match must refuse rather than guess.
  2. Preview. Reports exactly what a live run would do, and writes nothing.
  3. The holding area. A bad row is kept, with its reason and its source line, so somebody can see what to correct.
  4. 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_seen keeps 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

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally