4.1 KiB
Fixture & Preset Architecture
How the Seeder's JSON layers compose to create test data.
The Core Rule
Fixtures define what exists. Presets define how things relate.
Fixtures are independent, reusable building blocks. They never reference each other. The preset is the only layer that sees all the pieces and defines cross-cutting relationships between them.
Fixtures = Bricks
Each fixture type describes one category of entities:
| Fixture Type | What It Defines | Knows About |
|---|---|---|
| Organization | Name, domain | Nothing else |
| Roster | Users, groups, collections, permissions | Its own users (by email prefix) |
| Ciphers | Vault items (logins, cards, notes, etc.) | Nothing else |
Fixtures are stored under fixtures/organizations/, fixtures/rosters/, and fixtures/ciphers/. A roster knows about its own users (groups reference members by email prefix, collections reference groups and users) but has zero knowledge of which organization or ciphers it'll be paired with.
Presets = Assembly Instructions
A preset picks one of each fixture (or generates data inline) and defines the relationships between them. It's the entry point for the preset --name CLI command.
There are three composition modes:
- Fixture-based — pointers to existing JSON files. See
presets/qa/enterprise-basic.json. - Generation-based — inline counts + density parameters, no fixtures. See
presets/scale/md-balanced-sterling-cooper.json. - Hybrid — mix of fixture and generated data. See
presets/qa/families-basic.json.
The schemas are the source of truth for what fields are available: schemas/preset.schema.json, schemas/roster.schema.json, schemas/cipher.schema.json.
Cross-Cutting Relationships
The preset owns all relationships that cross fixture boundaries. Three assignment types are supported:
Collection Assignments (org-scoped)
collectionAssignments maps (cipher, collection) tuples — which ciphers belong in which collections. Collections are defined in the roster; ciphers are defined in the cipher fixture. When present, replaces the default round-robin cipher-to-collection assignment. Without it, CreateCiphersStep distributes ciphers across collections via round-robin.
Folder Assignments (user-scoped)
Folder declarations go in the roster — each user can optionally declare a folders array of named folders. For individual user presets (no roster), folder declarations use the preset-level folderNames array instead. Folder assignments go in the preset — folderAssignments maps (cipher, user, folder) tuples, mirroring the Cipher.Folders JSON column: {"USERID":"FOLDERID"}.
Favorite Assignments (user-scoped)
favoriteAssignments maps (cipher, user) tuples, mirroring Cipher.Favorites: {"USERID":true}.
See presets/qa/zero-knowledge-labs-enterprise.json for a working example with all three assignment types.
Why This Design
- Honest to the data model — assignment arrays mirror the actual DB relationships (
CollectionCipher,Cipher.FoldersJSON,Cipher.FavoritesJSON) - No mixed concerns — roster owns entities, cipher fixture owns vault items, preset owns relationships
- Reusable bricks — the same cipher fixture pairs with any roster and any assignment configuration
- Backward compatible — presets without assignment arrays continue to use round-robin collection distribution and have no folder/favorite data
The DB Analogy
| Fixture | Like This Table |
|---|---|
| Organization | Organization |
| Roster | User + Group + Collection + join tables |
| Ciphers | Cipher |
| Preset | The JOIN — picks tables and defines relationships |