Repeatable Znode test data: stable keys, readback, and no-op reruns
Build fixtures that converge on an explicit desired state without duplicating records or overwriting values owned by administrators.
In this guide
The fixture that changes on every run
A developer creates test accounts, products, and prices to reproduce a checkout bug. Another developer runs the script and gets duplicates. A third run repairs some associations but resets a setting someone changed in Admin. The fixture has become another source of environment drift.
A repeatable seed should converge on a declared state. It needs stable business keys, clear ownership, exact readback, and a second run that makes no changes. Recording returned IDs is useful during execution, but environment-specific numeric IDs should not be the identity of the fixture.
Define ownership before mutation
Use a small fictional namespace for owned accounts, products, and price lists. Resolve those records by their stable codes. Refuse collisions where an existing record with the same key clearly belongs to another fixture or has incompatible identity. Avoid temporary usernames that resemble real staff or clients.
Decide which fields the script owns and which administrators may edit. Missing-only defaults are different from enforced configuration. If the API replaces an entire collection or attribute set, load and preserve the required existing values before writing. Verify the exact replacement scope instead of assuming that a narrow-looking request behaves like a patch.
Reconcile one owned fixture record
ensureRecord implements stable-key lookup, duplicate rejection, create readback, owned-field correction, and final verification. The FixturePort interface keeps the selected Znode resource's transport contract separate from this convergence logic.
Map findExact, create, and updateOwnedFields to one supported endpoint. Preserve unowned fields when that endpoint replaces a collection. On an uncertain create, the function reads back the same identity and propagates the failure when a record is still absent. Serialize concurrent seed runs or rely on an enforced unique key.
fixtures/reconcile-owned-record.ts typescript
export type OwnedRecord = Readonly<{ key: string; label: string }>;
export interface FixturePort {
findExact(key: string): Promise<readonly OwnedRecord[]>;
create(record: OwnedRecord): Promise<void>;
updateOwnedFields(record: OwnedRecord): Promise<void>;
}
export async function ensureRecord(
desired: OwnedRecord, port: FixturePort
): Promise<{ outcome: "existing" | "created" | "updated"; record: OwnedRecord }> {
if (!/^[A-Z0-9-]{1,60}$/.test(desired.key) || desired.label.length > 200)
throw new Error("Invalid fixture.");
const read = async () => {
const rows = await port.findExact(desired.key);
if (rows.length > 1 || rows.some(row => row.key !== desired.key))
throw new Error("Fixture identity conflict.");
return rows[0];
};
let current = await read();
let outcome: "existing" | "created" | "updated" = "existing";
if (!current) {
try { await port.create(desired); }
catch (failure) {
current = await read(); // Reconcile a commit followed by a lost response.
if (!current) throw failure;
}
current = await read();
if (!current) throw new Error("Created fixture is awaiting readback.");
outcome = "created";
}
if (current.label !== desired.label) {
await port.updateOwnedFields(desired);
outcome = "updated";
}
const verified = await read();
if (!verified || verified.label !== desired.label)
throw new Error("Fixture readback differs from desired state.");
return { outcome, record: verified };
}
// Adapter writes only the fields owned by this fixture. Preserve other fields.
// Serialize seed runs or enforce stable-key uniqueness in the target contract.
Provision in dependency order
An account price fixture needs its store, account, product, price list, and association in a valid sequence. A storefront product fixture also needs the publication steps required by the target environment. A successful create response does not demonstrate that the product is visible or purchasable.
Model these dependencies explicitly and report where execution stopped. A rerun should resume from observed state rather than starting over blindly. Keep publication and theme activation deliberate so a routine data check does not unexpectedly expose content or change another store's appearance.
Make the second run a real test
After the first run, read every owned record and relationship. Run the same fixture again and count actual mutations, not merely printed 'unchanged' messages. A valid no-op result means the script called no create or update operation unnecessarily and the readback still matches the desired state.
Then interrupt a run between dependencies, simulate a write that commits before returning an error, and manually edit an unowned setting. Confirm that recovery is bounded and the administrator's value survives. Use distinct prices and product characteristics so accidental cross-associations can be recognized on the storefront, not just in an API response.
- Reject duplicate keys instead of normalizing them away.
- Report IDs as resolved evidence, not portable fixture definitions.
- Separate component fixtures from end-to-end purchase coverage.
Treat fixture changes like application changes
Version fixture definitions and explain why a desired value changed. Retiring an old owned record needs an explicit cleanup policy; deletion should not be an incidental side effect of a missing row in a file. Preserve useful failure reports without including authentication material.
Reliable test data reduces the cost of every later investigation. A developer can reproduce the starting state, verify what exists, and rerun safely after a partial failure. That is a stronger foundation than a large seed script whose only success signal is that it reached its final line.
References and further reading
Bring your next engineering question.
Improve implementation and release quality
Independent guidance from GCG. Znode is a trademark of its owner. Examples use fictional data and are not official platform documentation. Suggest a correction.