@sanity/workflow-engine-test 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +531 -0
- package/README.md +66 -7
- package/dist/index.cjs +398 -5210
- package/dist/index.d.cts +185 -773
- package/dist/index.d.ts +185 -773
- package/dist/index.js +387 -5217
- package/package.json +6 -5
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,6 +13,60 @@ boilerplate.
|
|
|
13
13
|
npm install -D @sanity/workflow-engine-test
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## The bench
|
|
17
|
+
|
|
18
|
+
`createBench()` builds an isolated bench: the real engine, a fake in-memory
|
|
19
|
+
Sanity client, an all-access default actor, and a bench-owned clock. Every
|
|
20
|
+
wrapped verb injects the bench's client, tag, workflow resource, access
|
|
21
|
+
default, and clock, so a test reads as the workflow it exercises:
|
|
22
|
+
|
|
23
|
+
- **Runtime verbs** — `deployDefinitions`, `deleteDefinition`, `startInstance`,
|
|
24
|
+
`fireAction`, `editField`, `completeEffect` (and `completePendingEffect`,
|
|
25
|
+
addressed by effect name), `tick`, `evaluate`.
|
|
26
|
+
- **Instance-admin overrides** — `setStage`, `abortInstance`. Same injection,
|
|
27
|
+
so a forced move into a deadline-gated stage evaluates `$now` at bench time,
|
|
28
|
+
not wall-clock.
|
|
29
|
+
- **Reads and probes** — `getInstance`, `currentStage`, `activityStatus`,
|
|
30
|
+
`listPendingEffects`, `findPendingEffects`, `children`, `instancesForSubject`,
|
|
31
|
+
`instancesByStage`, `instancesForDocument`, `definitionsForDocument`, `query`,
|
|
32
|
+
plus the guard helpers below. Helpers sharing an engine verb's name take the
|
|
33
|
+
engine's own args objects and throw its typed errors — bench reads and engine
|
|
34
|
+
reads cannot drift.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const bench = createBench({now: '2026-01-01T00:00:00Z'})
|
|
38
|
+
await bench.deployDefinitions({definitions: [reviewWorkflow]})
|
|
39
|
+
|
|
40
|
+
const {instance} = await bench.startInstance({
|
|
41
|
+
definition: 'review-workflow',
|
|
42
|
+
initialFields: [subjectField('article-1')],
|
|
43
|
+
})
|
|
44
|
+
await bench.fireAction({instanceId: instance._id, activity: 'review', action: 'approve'})
|
|
45
|
+
expect(await bench.currentStage(instance._id)).toBe('approved')
|
|
46
|
+
|
|
47
|
+
// Admin overrides ride the same defaults:
|
|
48
|
+
await bench.abortInstance({instanceId: instance._id, reason: 'hard stop'})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`subjectField(docId)` and `releaseField(name)` build the runtime
|
|
52
|
+
initial-field entries for the bench's default resource, so tests never
|
|
53
|
+
hand-roll GDR URI strings. For denial paths, pass a restrictive
|
|
54
|
+
`currentUser` / `grants` (or a full `access`) at construction, or override
|
|
55
|
+
per call with the `actor` shortcut / an explicit `access`.
|
|
56
|
+
|
|
57
|
+
**Time is bench-owned.** `createBench({now})` starts frozen; `setNow` /
|
|
58
|
+
`advance` move it. Every wrapped verb evaluates `$now` and stamps history at
|
|
59
|
+
bench time, so deadline predicates are tested by advancing the clock and
|
|
60
|
+
ticking — never by waiting.
|
|
61
|
+
|
|
62
|
+
## Out-of-band engines
|
|
63
|
+
|
|
64
|
+
Some tests need an engine of their own — custom effect handlers, racing two
|
|
65
|
+
drainers, a custom clock. `createBenchEngine(bench, overrides?)`
|
|
66
|
+
builds one on the bench's client, inheriting its tag, workflow resource, and
|
|
67
|
+
clock (`overrides` win). The bench exposes `tag` and `workflowResource` for
|
|
68
|
+
raw `workflow.*` calls, and the default tag ships as the `BENCH_TAG` constant.
|
|
69
|
+
|
|
16
70
|
## Mutation guards
|
|
17
71
|
|
|
18
72
|
A workflow stage can deploy **mutation guards** — `temp.system.guard` documents
|
|
@@ -26,10 +80,15 @@ once the lake enforces them. There are two distinct things you test, and the
|
|
|
26
80
|
bench gives you a helper for each:
|
|
27
81
|
|
|
28
82
|
- **Write-time rejection (simulated)** — `bench.editDocument({documentId, patch})`
|
|
29
|
-
writes through the bench client
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
client it mints.
|
|
83
|
+
writes through the bench client (`patch` is a `{set?, unset?, insert?}` spec),
|
|
84
|
+
and a write a deployed guard denies throws `MutationGuardDeniedError` — the
|
|
85
|
+
rejection the lake is designed to apply to _any_ client once guard
|
|
86
|
+
enforcement ships; the bench reproduces it on the client it mints. Every
|
|
87
|
+
guard action is a real write: `action: 'delete'` deletes, `'publish'`
|
|
88
|
+
promotes the existing `drafts.<id>` draft onto the published id, and
|
|
89
|
+
`'unpublish'` retracts it — so a guard scoped to `actions: ['publish']` is
|
|
90
|
+
genuinely consulted. The helper resolves to the post-write document, or
|
|
91
|
+
`undefined` once a delete/unpublish removed it.
|
|
33
92
|
- **Read-time preflight** — `bench.activeGuardsForDocument(id)` answers "_would_
|
|
34
93
|
a write be denied right now?" _without_ writing. This is the check a UI uses
|
|
35
94
|
to disable a button or show a lock ahead of time. It's a pure read over the
|
|
@@ -46,9 +105,9 @@ const bench = createBench({documents: [{_id: 'doc-1', _type: 'article', body: 'b
|
|
|
46
105
|
const active = await bench.activeGuardsForDocument('doc-1')
|
|
47
106
|
|
|
48
107
|
// Rejection: a guard-violating write throws (bench-simulated lake contract).
|
|
49
|
-
await expect(
|
|
50
|
-
|
|
51
|
-
)
|
|
108
|
+
await expect(
|
|
109
|
+
bench.editDocument({documentId: 'doc-1', patch: {set: {body: 'edited'}}}),
|
|
110
|
+
).rejects.toThrow(MutationGuardDeniedError)
|
|
52
111
|
```
|
|
53
112
|
|
|
54
113
|
> To share one store across benches with the write seam intact, pass another
|