Testing Strategy
Testing Goals
The Universal Entity Platform has important invariants. Tests should protect those invariants.
Focus testing on:
- hierarchy correctness,
- path correctness,
- authorization scope,
- soft delete behavior,
- property validation,
- tag selection rules,
- group membership rules,
- relationship rules,
- transaction safety.
Test Types
Use multiple test levels.
Unit tests
Integration tests
Contract tests
End-to-end tests
Migration testsUnit Tests
Unit test pure functions.
Examples:
- path label generation,
- value validation,
- capability checks,
- DTO mappers,
- error mapping,
- template expansion logic.
Integration Tests
Integration tests should run against real PostgreSQL.
Do not mock ltree behavior with SQLite.
Test:
- create root entity,
- create child entity,
- get children,
- get descendants,
- get ancestors,
- get path,
- move subtree,
- reject cycle,
- reject out-of-scope access,
- soft-delete subtree,
- restore subtree.
Contract Tests
Contract tests should ensure frontend and backend agree on:
- request schemas,
- response schemas,
- error codes,
- DTO shapes.
If using Zod contracts, backend route handlers should parse inputs with the shared schema.
End-To-End Tests
E2E tests should cover key user workflows:
- create customer,
- create site under customer,
- create building under site,
- create asset under building,
- edit asset properties,
- assign tags,
- move asset,
- soft-delete asset,
- restore asset.
Migration Tests
Migration tests should verify:
- extensions are enabled,
- indexes exist,
- constraints exist,
- seed data loads,
- rollback strategy is understood.
Invariant Test Matrix
| Invariant | Test |
|---|---|
| Entity type must exist | creating entity with invalid type fails |
| Parent must exist | creating child under missing parent fails |
| Path must include parent path | child path equals parent path + label |
| No cycles | moving parent under descendant fails |
| Children are fast lookup | child query uses parent id |
| Descendants use ltree | subtree query returns expected nodes |
| Root scope applies | user cannot edit outside root |
| Soft delete cascades logically | subtree records get deleted_at |
| Relationships exclude containment | contains relationship rejected |
| Single-select tag enforced | second active tag in same group replaces or rejects |
| Sensitive property protected | plaintext secret value rejected |
Example Test Dataset
Customer A
├── Site A1
│ ├── Building A1-B1
│ │ ├── Asset A
│ │ └── Asset B
│ └── Building A1-B2
└── Site A2
Customer B
└── Site B1Use this dataset for hierarchy, root-scope, and move tests.
Reparent Tests
Test valid move:
Move Asset A from Building A1-B1 to Building A1-B2Expected:
- parent changes,
- path changes,
- descendants change if any,
- old parent children invalidated,
- new parent children invalidated,
- audit event emitted.
Test invalid move:
Move Building A1-B1 under Asset AExpected:
CYCLE_DETECTEDAuthorization Tests
Given:
User scoped to Customer AAllowed:
edit Site A1
edit Asset A
read Building A1-B2Denied:
read Customer B
edit Site B1
create asset under Customer B
move Asset A to Customer BSoft Delete Tests
Soft-delete Building A1-B1.
Expected deleted:
- Building A1-B1,
- Asset A,
- Asset B,
- their property values,
- tag assignments,
- relationships touching them.
Expected not deleted:
- Site A1,
- Building A1-B2,
- Customer A,
- tags themselves,
- property definitions.
Property Validation Tests
Test each value type.
Examples:
ip_address rejects invalid IP
integer rejects decimal
single_select rejects unknown option
secret_reference rejects plaintext
entity_reference rejects out-of-scope entityPerformance Tests
For large datasets, test:
- descendant query time,
- ancestor query time,
- child lookup time,
- move subtree time,
- soft-delete subtree time.
Use EXPLAIN ANALYZE to confirm indexes are used.
Testing Rule
Any bug involving hierarchy, authorization, soft delete, or sensitive data should result in a regression test.