Files
server/util/Seeder/Seeds/docs/architecture.md
2026-03-30 15:17:54 +02:00

68 lines
4.1 KiB
Markdown

# 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
1. **Honest to the data model** — assignment arrays mirror the actual DB relationships (`CollectionCipher`, `Cipher.Folders` JSON, `Cipher.Favorites` JSON)
2. **No mixed concerns** — roster owns entities, cipher fixture owns vault items, preset owns relationships
3. **Reusable bricks** — the same cipher fixture pairs with any roster and any assignment configuration
4. **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 |