Developer Tests Assets - edmozley/freeitsm GitHub Wiki
๐งช Developer Tests โ Assets & CMDB
Part of Developer Tests. Seventeen 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 (label designs from 4.8.0), the CMDB's typed properties, the Proxmox and Cloud Director syncs, and holders from Intune.
Almost all touch the database (asset-label-designs.php only from its part 2). 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-type-layout.php |
ZZ Layout |
Database (rolled back) |
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 |
cmdb-relationship-direction.php |
ZZ class, objects, relationship type, analyst | Database + HTTP |
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) |
asset-label-designs.php |
test company 987654 |
Nothing; part 2 the database |
intune-primary-user.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-type-layout.php
What it tests
Per-type layouts (#200) and the storage rule they rest on: no row means shown. A type nobody configured shows everything; saving the default layout stores no rows; a tab added in a later release still appears on a type saved before it existed; an unknown stored key is ignored; normalise() can never hide identity or move Key info; every preset names real keys; deleting a type takes its layout with it. See Asset type layouts - Developer Guide.
How it works
Drives AssetTypeLayout directly against a ZZ Layout type, inside one transaction that is always rolled back.
Run it
php tests/asset-type-layout.php
If it fails
A failure in stores no rows or still appears means an upgrade would change installs nobody configured - fix that before anything else.
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-relationship-direction.php
CMDB relationships read the right way round wherever they are put into words (5.1.0, write-up). Makes a host, a VM and an app linked runs on / hosts, then checks what the AI summary is told (cmdbObjectSummaryMessage(): source first, forward verb, never "hosts this", no "What depends on this object") and calls the real api/network-mapper/get_related_objects.php with a signed-in session (the VM incoming, labelled runs on). 8 checks; everything it makes is removed in finally.
FREEITSM_TEST_DATABASE=<throwaway db> FREEITSM_TEST_URL=<that install's URL> php tests/cmdb-relationship-direction.php
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
tests/asset-label-designs.php
4.8.0, #229. Label designs, sizes, fonts and the label designer's saves. 45 checks.
- Part 1 (no database): every A4 sheet fits its page at Avery's positions; a size out of range is refused; every ready-made layout keeps every item inside the label on every size, with and without a logo; a font is recognised by its first bytes (a PNG renamed
.ttf, a WOFF named.woff2and a.ttccollection are refused). - Part 1b (no database): what the designer may save -
assetLabelSanitiseItems()drops unknown item types and properties, holds every number to a range, falls back on any choice outside its list, keeps only offered fields, makes every picture the settings logo, replaces odd or repeated ids, keeps at most 100 items; a designed label prints its own items while a template design still follows the settings. - Part 2 (
FREEITSM_TEST_DATABASE): on test company987654, which no install uses - Standard is made once and is the default, one default per company, a stale version saves nothing, an unknown layout is refused, another company's font falls back to Noto Sans. Its rows are deleted at the end.
php tests/asset-label-designs.php
FREEITSM_TEST_DATABASE=<throwaway db> php tests/asset-label-designs.php
The drawing and the PDF are checked in a browser - see QR asset labels - Developer Guide ยง6b-6c.
tests/intune-primary-user.php
4.8.0, PR #236 by vsc55. intuneAssignPrimaryUsers() gives an unheld asset to its Intune device's primary user, matched by Entra object id. 10 checks pinning every way it could pick the wrong person or overwrite a person's decision: an existing holder kept, a leaver skipped, two devices naming two people skipped, an object id nobody imported skipped, another company's person skipped - and the assignment itself, stored with a NULL actor (it fails without #2690, the fix for actor id 0).
Runs in a transaction that is always rolled back, with asset.assigned workflows paused for the run and switched back on in finally - they dispatch on their own connection, outside the transaction. The function reads every Intune device on the install, so real unheld assets are assigned too, inside the same transaction, and rolled back with it.
FREEITSM_TEST_DATABASE=<throwaway db> php tests/intune-primary-user.php