@sanity/workflow-engine-test 0.9.0 → 0.10.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 +494 -0
- package/README.md +58 -7
- package/dist/index.cjs +5527 -4719
- package/dist/index.d.cts +228 -90
- package/dist/index.d.ts +228 -90
- package/dist/index.js +5512 -4728
- package/package.json +6 -5
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import type { AbortInstanceArgs } from "@sanity/workflow-engine";
|
|
1
2
|
import { ActivityStatus } from "@sanity/workflow-engine";
|
|
2
3
|
import { Actor } from "@sanity/workflow-engine";
|
|
4
|
+
import { CandidateDocument } from "@sanity/workflow-engine";
|
|
5
|
+
import { Clock } from "@sanity/workflow-engine";
|
|
3
6
|
import type { CompleteEffectArgs } from "@sanity/workflow-engine";
|
|
7
|
+
import { CreateEngineArgs } from "@sanity/workflow-engine";
|
|
8
|
+
import type { DeclaredExecutionContext } from "@sanity/workflow-engine";
|
|
4
9
|
import { DeployDefinitionsArgs } from "@sanity/workflow-engine";
|
|
5
10
|
import { DeployDefinitionsResult } from "@sanity/workflow-engine";
|
|
11
|
+
import { DeployedDefinition } from "@sanity/workflow-engine";
|
|
6
12
|
import type { EditFieldArgs } from "@sanity/workflow-engine";
|
|
13
|
+
import { Engine } from "@sanity/workflow-engine";
|
|
7
14
|
import type { EvaluateArgs } from "@sanity/workflow-engine";
|
|
8
15
|
import type { FireActionArgs } from "@sanity/workflow-engine";
|
|
9
16
|
import { GdrUri } from "@sanity/workflow-engine";
|
|
@@ -14,23 +21,31 @@ import { MutationGuardDoc } from "@sanity/workflow-engine";
|
|
|
14
21
|
import type { OperationArgs } from "@sanity/workflow-engine";
|
|
15
22
|
import { OperationResult } from "@sanity/workflow-engine";
|
|
16
23
|
import { PendingEffect } from "@sanity/workflow-engine";
|
|
24
|
+
import type { SetStageArgs } from "@sanity/workflow-engine";
|
|
17
25
|
import type { StartInstanceArgs } from "@sanity/workflow-engine";
|
|
18
26
|
import type { WorkflowAccess } from "@sanity/workflow-engine";
|
|
27
|
+
import { WorkflowDefinitionInput } from "@sanity/workflow-engine";
|
|
19
28
|
import { WorkflowEvaluation } from "@sanity/workflow-engine";
|
|
20
29
|
import { WorkflowInstance } from "@sanity/workflow-engine";
|
|
21
30
|
import type { WorkflowResource } from "@sanity/workflow-engine";
|
|
22
31
|
|
|
23
32
|
declare type Action = ReleaseAction | VersionAction;
|
|
24
33
|
|
|
34
|
+
/** Options for `action()`. Only `tag` is meaningful to the fake. */
|
|
35
|
+
declare interface ActionOptions {
|
|
36
|
+
/** Request tag, recorded on the {@link CallRecord} (`null` = explicit suppression). */
|
|
37
|
+
tag?: string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
declare interface ActionResult {
|
|
26
41
|
transactionId: string;
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
/**
|
|
30
|
-
* The bench's default
|
|
31
|
-
*
|
|
32
|
-
* engine
|
|
33
|
-
*
|
|
45
|
+
* The bench's default identity — wildcard user + wildcard grants,
|
|
46
|
+
* registered as the default token on the bench's fake client so the
|
|
47
|
+
* engine token-resolves it (`/users/me` + the ACL path) exactly like
|
|
48
|
+
* production.
|
|
34
49
|
*
|
|
35
50
|
* Production code never sees this; pass `createBench({ access })` to
|
|
36
51
|
* test denial paths.
|
|
@@ -55,56 +70,70 @@ export declare type Bench = BenchState &
|
|
|
55
70
|
QueryHelpers &
|
|
56
71
|
ComposedHelpers;
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/** Shortcut for `access.actor`. */
|
|
61
|
-
actor?: Actor;
|
|
62
|
-
};
|
|
73
|
+
/** Default engine-scope partition tag for benches that don't pass one. */
|
|
74
|
+
export declare const BENCH_TAG = "bench";
|
|
63
75
|
|
|
64
|
-
declare type
|
|
65
|
-
access?: WorkflowAccess;
|
|
66
|
-
/** Shortcut for `access.actor`. */
|
|
67
|
-
actor?: Actor;
|
|
68
|
-
/** Shortcut for `access.grants`. */
|
|
69
|
-
grants?: Grant[];
|
|
70
|
-
};
|
|
76
|
+
declare type BenchAbortInstanceArgs = BenchArgs<AbortInstanceArgs>;
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
/**
|
|
79
|
+
* A bench verb's args: the engine verb's args plus the bench's per-call
|
|
80
|
+
* identity shortcuts — the composed pair runs the verb through a
|
|
81
|
+
* token-scoped sibling client.
|
|
82
|
+
*/
|
|
83
|
+
declare type BenchArgs<T> = T & {
|
|
73
84
|
access?: WorkflowAccess;
|
|
74
85
|
/** Shortcut for `access.actor`. */
|
|
75
86
|
actor?: Actor;
|
|
76
|
-
/** Shortcut for `access.grants`. */
|
|
77
|
-
grants?: Grant[];
|
|
78
87
|
};
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/** Shortcut for `access.actor`. */
|
|
83
|
-
actor?: Actor;
|
|
89
|
+
/** {@link BenchArgs} for the verbs that also take the `grants` shortcut. */
|
|
90
|
+
declare type BenchArgsWithGrants<T> = BenchArgs<T> & {
|
|
84
91
|
/** Shortcut for `access.grants`. */
|
|
85
92
|
grants?: Grant[];
|
|
86
93
|
};
|
|
87
94
|
|
|
88
|
-
declare type
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
declare type BenchCompleteEffectArgs = BenchArgs<CompleteEffectArgs>;
|
|
96
|
+
|
|
97
|
+
declare type BenchEditFieldArgs = BenchArgsWithGrants<EditFieldArgs>;
|
|
98
|
+
|
|
99
|
+
declare type BenchEvaluateArgs = BenchArgsWithGrants<EvaluateArgs>;
|
|
100
|
+
|
|
101
|
+
declare type BenchFireActionArgs = BenchArgsWithGrants<FireActionArgs>;
|
|
102
|
+
|
|
103
|
+
declare type BenchSetStageArgs = BenchArgs<SetStageArgs>;
|
|
104
|
+
|
|
105
|
+
declare type BenchStartInstanceArgs = BenchArgs<StartInstanceArgs>;
|
|
93
106
|
|
|
94
107
|
/** Base bench state, distinct from the engine/read/guard/query helper groups. */
|
|
95
108
|
declare interface BenchState {
|
|
96
109
|
/** The fake Sanity client. Passed automatically by bench wrappers. */
|
|
97
110
|
client: TestClient;
|
|
111
|
+
/** The engine-scope partition tag every wrapped call runs under. */
|
|
112
|
+
tag: string;
|
|
113
|
+
/** The workflow resource every wrapped call targets. */
|
|
114
|
+
workflowResource: WorkflowResource;
|
|
115
|
+
/** The declared execution context stamped on every wrapped call's history
|
|
116
|
+
* entries (default `{kind: 'test'}`) — inherited by {@link createBenchEngine}. */
|
|
117
|
+
executionContext: DeclaredExecutionContext;
|
|
98
118
|
/**
|
|
99
|
-
* The
|
|
100
|
-
*
|
|
101
|
-
* `ALL_ACCESS`.
|
|
119
|
+
* The bench's default identity (actor + grants) — registered as the
|
|
120
|
+
* default token on the fake client. Defaults to `ALL_ACCESS`.
|
|
102
121
|
*/
|
|
103
122
|
access: WorkflowAccess;
|
|
104
123
|
/** Convenience: `access.actor`. */
|
|
105
124
|
currentUser: Actor;
|
|
106
125
|
/** Convenience: `access.grants ?? []`. */
|
|
107
126
|
grants: Grant[];
|
|
127
|
+
/**
|
|
128
|
+
* A token-bound sibling client acting as `access` — the bench's ONE
|
|
129
|
+
* identity-registration seam. Registers the pair on the fake client's
|
|
130
|
+
* family-shared registry (idempotent, cached per identity; omitted
|
|
131
|
+
* grants default to wildcard) and returns the sibling whose
|
|
132
|
+
* `/users/me` + ACL path serve it — the engine token-resolves it like
|
|
133
|
+
* production. Bind it to {@link createBenchEngine} to act as another
|
|
134
|
+
* user end-to-end; never hand-roll `addIdentity` + `withConfig`.
|
|
135
|
+
*/
|
|
136
|
+
clientFor: (access: WorkflowAccess) => TestClient;
|
|
108
137
|
/** Add documents to the store after construction. */
|
|
109
138
|
seedDocuments: (documents: StoreDocument[]) => void;
|
|
110
139
|
/**
|
|
@@ -129,11 +158,7 @@ declare interface BenchState {
|
|
|
129
158
|
advance: (ms: number) => void;
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
declare type BenchTickArgs =
|
|
133
|
-
access?: WorkflowAccess;
|
|
134
|
-
/** Shortcut for `access.actor`. */
|
|
135
|
-
actor?: Actor;
|
|
136
|
-
};
|
|
161
|
+
declare type BenchTickArgs = BenchArgs<OperationArgs>;
|
|
137
162
|
|
|
138
163
|
/**
|
|
139
164
|
* One recorded invocation of a client method — the spy layer behind
|
|
@@ -151,6 +176,8 @@ declare interface CallRecord {
|
|
|
151
176
|
result?: unknown;
|
|
152
177
|
/** Thrown or rejected value, set if the call failed. */
|
|
153
178
|
error?: unknown;
|
|
179
|
+
/** Request tag from the call's options, when one was passed (`null` = explicit suppression). */
|
|
180
|
+
tag?: string | null;
|
|
154
181
|
projectId: string;
|
|
155
182
|
dataset: string;
|
|
156
183
|
}
|
|
@@ -177,14 +204,28 @@ declare interface ComposedHelpers {
|
|
|
177
204
|
*/
|
|
178
205
|
export declare function createBench(options?: CreateBenchOptions): Bench;
|
|
179
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Build an out-of-band {@link Engine} on a bench's client, inheriting the
|
|
209
|
+
* bench's tag, workflow resource, and clock (so `setNow` / `advance` govern
|
|
210
|
+
* the engine's `$now` too). Use this when a test needs an engine of its own —
|
|
211
|
+
* custom effect handlers, racing two drainers — rather than the wrappers the
|
|
212
|
+
* bench exposes. `overrides` win over every inherited value.
|
|
213
|
+
*/
|
|
214
|
+
export declare function createBenchEngine(
|
|
215
|
+
bench: EngineScope & {
|
|
216
|
+
now: Clock;
|
|
217
|
+
},
|
|
218
|
+
overrides?: Partial<CreateEngineArgs>,
|
|
219
|
+
): Engine;
|
|
220
|
+
|
|
180
221
|
export declare interface CreateBenchOptions {
|
|
181
222
|
/** Subject documents seeded into the store. */
|
|
182
223
|
documents?: StoreDocument[];
|
|
183
224
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* test denial paths.
|
|
225
|
+
* The bench's default identity (actor + grants), registered as the
|
|
226
|
+
* default token on the fake client — the engine token-resolves it like
|
|
227
|
+
* production. Defaults to `ALL_ACCESS` — wildcard user + wildcard
|
|
228
|
+
* grants. Pass a restrictive value to test denial paths.
|
|
188
229
|
*
|
|
189
230
|
* Mutually exclusive with the `currentUser` / `grants` shortcuts
|
|
190
231
|
* (which compose into the same shape internally).
|
|
@@ -195,28 +236,7 @@ export declare interface CreateBenchOptions {
|
|
|
195
236
|
/** Shortcut for `access.grants`. Composed with `currentUser`. */
|
|
196
237
|
grants?: Grant[];
|
|
197
238
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* When set, the bench's underlying TestClient:
|
|
201
|
-
* - Exposes `client.request({ url })` serving `/users/me` + the
|
|
202
|
-
* configured ACL path, so the engine can resolve `WorkflowAccess`
|
|
203
|
-
* from "the token" exactly like production.
|
|
204
|
-
* - Rejects writes the grants don't permit with a Sanity-style
|
|
205
|
-
* `WriteAccessDeniedError` — the same 403 a real lake would
|
|
206
|
-
* return.
|
|
207
|
-
*
|
|
208
|
-
* Use this to demonstrate what would happen if the engine's
|
|
209
|
-
* soft-gate were bypassed (or disagreed with) — the lake is still
|
|
210
|
-
* authoritative. The bench's `access` field is the engine-side
|
|
211
|
-
* override; `accessControl` is the lake-side enforcement. They
|
|
212
|
-
* usually carry the same `actor` + `grants` but can be made to
|
|
213
|
-
* disagree to test edge cases.
|
|
214
|
-
*
|
|
215
|
-
* Mutually exclusive with passing a pre-built `client`.
|
|
216
|
-
*/
|
|
217
|
-
accessControl?: TestClientAccessControl;
|
|
218
|
-
/**
|
|
219
|
-
* Engine-scope environment partition. Defaults to `"bench"`. Pass
|
|
239
|
+
* Engine-scope environment partition. Defaults to {@link BENCH_TAG}. Pass
|
|
220
240
|
* different tags to two benches sharing a `client` to assert partition
|
|
221
241
|
* isolation; pass the same tag to assert visibility through a shared
|
|
222
242
|
* partition.
|
|
@@ -246,6 +266,12 @@ export declare interface CreateBenchOptions {
|
|
|
246
266
|
* start on the wall clock (`advance` / `setNow` can freeze it later).
|
|
247
267
|
*/
|
|
248
268
|
now?: string;
|
|
269
|
+
/**
|
|
270
|
+
* Declared execution context stamped (with the inferred runtime) on every
|
|
271
|
+
* history entry the bench's engine calls append. Defaults to
|
|
272
|
+
* `{ kind: "test" }`; override to exercise provenance rendering.
|
|
273
|
+
*/
|
|
274
|
+
executionContext?: DeclaredExecutionContext;
|
|
249
275
|
}
|
|
250
276
|
|
|
251
277
|
declare interface CurrentUser {
|
|
@@ -278,10 +304,33 @@ declare interface DocumentBody {
|
|
|
278
304
|
|
|
279
305
|
declare type DocumentValuePermission = "create" | "read" | "update";
|
|
280
306
|
|
|
281
|
-
/**
|
|
307
|
+
/**
|
|
308
|
+
* How {@link GuardHelpers.editDocument} mutates the document. Each present
|
|
309
|
+
* key becomes one patch op, applied in `set` → `unset` → `insert` order in a
|
|
310
|
+
* single commit; the shapes mirror the client's patch builder.
|
|
311
|
+
*/
|
|
312
|
+
export declare type EditDocumentPatch = {
|
|
313
|
+
set?: Record<string, unknown>;
|
|
314
|
+
unset?: string[];
|
|
315
|
+
insert?: {
|
|
316
|
+
position: "before" | "after" | "replace";
|
|
317
|
+
anchor: string;
|
|
318
|
+
items: unknown[];
|
|
319
|
+
};
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/** The bench-owned engine call context injected into every wrapped verb. */
|
|
323
|
+
declare type EngineScope = {
|
|
324
|
+
client: TestClient;
|
|
325
|
+
tag: string;
|
|
326
|
+
workflowResource: WorkflowResource;
|
|
327
|
+
executionContext: DeclaredExecutionContext;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/** Engine-call wrappers that inject the bench-owned client + tag + identity. */
|
|
282
331
|
declare type EngineWrappers = {
|
|
283
|
-
deployDefinitions: (
|
|
284
|
-
args: DeployDefinitionsArgs
|
|
332
|
+
deployDefinitions: <T extends WorkflowDefinitionInput<T>>(
|
|
333
|
+
args: DeployDefinitionsArgs<T>,
|
|
285
334
|
) => Promise<DeployDefinitionsResult>;
|
|
286
335
|
startInstance: (args: BenchStartInstanceArgs) => Promise<OperationResult>;
|
|
287
336
|
fireAction: (args: BenchFireActionArgs) => Promise<OperationResult>;
|
|
@@ -289,6 +338,10 @@ declare type EngineWrappers = {
|
|
|
289
338
|
completeEffect: (args: BenchCompleteEffectArgs) => Promise<OperationResult>;
|
|
290
339
|
tick: (args: BenchTickArgs) => Promise<OperationResult>;
|
|
291
340
|
evaluate: (args: BenchEvaluateArgs) => Promise<WorkflowEvaluation>;
|
|
341
|
+
/** Admin override — bypass filters/transitions and force the stage. */
|
|
342
|
+
setStage: (args: BenchSetStageArgs) => Promise<OperationResult>;
|
|
343
|
+
/** Admin override — hard-stop an in-flight instance where it stands. */
|
|
344
|
+
abortInstance: (args: BenchAbortInstanceArgs) => Promise<OperationResult>;
|
|
292
345
|
guardsForInstance: (instanceId: string) => Promise<MutationGuardDoc[]>;
|
|
293
346
|
guardsForDefinition: (definition: string) => Promise<MutationGuardDoc[]>;
|
|
294
347
|
};
|
|
@@ -369,8 +422,12 @@ declare interface FetchOptions {
|
|
|
369
422
|
* raw response. Ignored when `filterResponse` is not `false`.
|
|
370
423
|
*/
|
|
371
424
|
returnQuery?: boolean;
|
|
372
|
-
/**
|
|
373
|
-
|
|
425
|
+
/**
|
|
426
|
+
* Request tag, recorded on the {@link CallRecord} for assertions.
|
|
427
|
+
* `null` mirrors `@sanity/client`'s explicit-suppression form and is
|
|
428
|
+
* recorded as-is. No other behavior is simulated.
|
|
429
|
+
*/
|
|
430
|
+
tag?: string | null;
|
|
374
431
|
/** Accepted for signature parity; not simulated. */
|
|
375
432
|
signal?: AbortSignal;
|
|
376
433
|
}
|
|
@@ -397,11 +454,13 @@ declare type GuardHelpers = {
|
|
|
397
454
|
* client gates the write against deployed guards (incl. `identity()`) and
|
|
398
455
|
* throws `MutationGuardDeniedError` on denial — the lake's intended
|
|
399
456
|
* rejection, simulated by the bench. `as` routes the write through a
|
|
400
|
-
* token-scoped sibling so `identity()` resolves to that actor.
|
|
457
|
+
* token-scoped sibling so `identity()` resolves to that actor. A `patch`
|
|
458
|
+
* ({@link EditDocumentPatch}) is required for edits and rejected for
|
|
459
|
+
* `action: 'delete'` — the combination is contradictory input.
|
|
401
460
|
*/
|
|
402
461
|
editDocument: (opts: {
|
|
403
462
|
documentId: string;
|
|
404
|
-
patch
|
|
463
|
+
patch?: EditDocumentPatch;
|
|
405
464
|
action?: MutationGuardAction;
|
|
406
465
|
as?: Actor;
|
|
407
466
|
}) => Promise<StoreDocument>;
|
|
@@ -423,6 +482,8 @@ declare interface MutationLogEntry {
|
|
|
423
482
|
projectId: string;
|
|
424
483
|
dataset: string;
|
|
425
484
|
mutations: TxnMutation[];
|
|
485
|
+
/** Request tag the commit carried, when one was passed (`null` = explicit suppression). */
|
|
486
|
+
tag?: string | null;
|
|
426
487
|
}
|
|
427
488
|
|
|
428
489
|
/** The operation recorded per affected document, matching `@sanity/client`. */
|
|
@@ -450,8 +511,21 @@ declare interface MutationOptions {
|
|
|
450
511
|
visibility?: "sync" | "async" | "deferred";
|
|
451
512
|
/** Accepted for signature parity; not simulated. */
|
|
452
513
|
autoGenerateArrayKeys?: boolean;
|
|
514
|
+
/**
|
|
515
|
+
* Request tag, recorded on the {@link CallRecord} and the
|
|
516
|
+
* {@link MutationLogEntry} for assertions. `null` mirrors
|
|
517
|
+
* `@sanity/client`'s explicit-suppression form and is recorded as-is.
|
|
518
|
+
* No other behavior is simulated.
|
|
519
|
+
*/
|
|
520
|
+
tag?: string | null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** Options for `PatchHandle.commit()`. Only `tag` is meaningful to the fake. */
|
|
524
|
+
declare interface PatchCommitOptions {
|
|
525
|
+
/** Request tag, recorded on the mutation log entry (`null` = explicit suppression). */
|
|
526
|
+
tag?: string | null;
|
|
453
527
|
/** Accepted for signature parity; not simulated. */
|
|
454
|
-
|
|
528
|
+
visibility?: "sync" | "async" | "deferred";
|
|
455
529
|
}
|
|
456
530
|
|
|
457
531
|
declare interface PatchHandle {
|
|
@@ -466,7 +540,7 @@ declare interface PatchHandle {
|
|
|
466
540
|
items: unknown[],
|
|
467
541
|
) => PatchHandle;
|
|
468
542
|
ifRevisionId: (rev: string) => PatchHandle;
|
|
469
|
-
commit: () => Promise<SanityDocument>;
|
|
543
|
+
commit: (options?: PatchCommitOptions) => Promise<SanityDocument>;
|
|
470
544
|
/** Test-only introspection — not on real Patch */
|
|
471
545
|
toJSON: () => {
|
|
472
546
|
id: string;
|
|
@@ -560,9 +634,9 @@ declare type ReadHelpers = {
|
|
|
560
634
|
* Spawned children of `parentInstanceId`. If `activity` is provided,
|
|
561
635
|
* only children spawned by that specific activity on the parent.
|
|
562
636
|
*
|
|
563
|
-
* Walks `history.spawned` entries on the parent —
|
|
564
|
-
*
|
|
565
|
-
*
|
|
637
|
+
* Walks `history.spawned` entries on the parent — the durable record
|
|
638
|
+
* (the workflow-scope registry tracks the same children, keyed for
|
|
639
|
+
* adoption rather than enumeration).
|
|
566
640
|
*
|
|
567
641
|
* Returns children ordered by `startedAt` ascending.
|
|
568
642
|
*/
|
|
@@ -597,6 +671,15 @@ declare type ReadHelpers = {
|
|
|
597
671
|
* non-reactive content forwarder needs. Sorted by `startedAt` ascending.
|
|
598
672
|
*/
|
|
599
673
|
instancesForDocument: (document: GdrUri) => Promise<WorkflowInstance[]>;
|
|
674
|
+
/**
|
|
675
|
+
* The startable half of {@link ReadHelpers.instancesForDocument}: the
|
|
676
|
+
* latest deployed version of every definition applicable to the LOADED
|
|
677
|
+
* candidate document — startable, a required subject entry accepts its
|
|
678
|
+
* `_type`, `applicableWhen` passes. All matches, name ascending.
|
|
679
|
+
*/
|
|
680
|
+
definitionsForDocument: (
|
|
681
|
+
document: CandidateDocument,
|
|
682
|
+
) => Promise<DeployedDefinition[]>;
|
|
600
683
|
/** Snapshot of all documents in the store — for inspecting the world. */
|
|
601
684
|
snapshot: () => readonly StoreDocument[];
|
|
602
685
|
};
|
|
@@ -782,22 +865,18 @@ declare interface TestClient {
|
|
|
782
865
|
options?: FetchOptions,
|
|
783
866
|
) => Promise<(T | null)[]>;
|
|
784
867
|
/**
|
|
785
|
-
* Sanity-style HTTP request hook. Present iff the
|
|
786
|
-
* created with `accessControl
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
* Test cases that want to exercise the engine's token-resolution
|
|
792
|
-
* path should configure `accessControl`; cases that want bench-style
|
|
793
|
-
* always-allow should leave it off and pass `access` through the
|
|
794
|
-
* bench.
|
|
868
|
+
* Sanity-style HTTP request hook. Present iff the handle has an
|
|
869
|
+
* acting identity — created with `accessControl`, or bound to a
|
|
870
|
+
* registered token (`identities` + `token`). The engine's access
|
|
871
|
+
* resolver pokes `/users/me` and the configured ACL path through
|
|
872
|
+
* this — without it the engine throws on any verb that needs an
|
|
873
|
+
* actor.
|
|
795
874
|
*/
|
|
796
875
|
request?: <T>(opts: {
|
|
797
876
|
url?: string;
|
|
798
877
|
uri?: string;
|
|
799
878
|
signal?: AbortSignal;
|
|
800
|
-
tag?: string;
|
|
879
|
+
tag?: string | null;
|
|
801
880
|
}) => Promise<T>;
|
|
802
881
|
create: <T extends SanityDocumentLike>(
|
|
803
882
|
doc: T,
|
|
@@ -835,7 +914,10 @@ declare interface TestClient {
|
|
|
835
914
|
* Returns a synthetic `transactionId`. Multi-action arrays are applied
|
|
836
915
|
* atomically — any failure rolls back the batch.
|
|
837
916
|
*/
|
|
838
|
-
action: (
|
|
917
|
+
action: (
|
|
918
|
+
action: Action | Action[],
|
|
919
|
+
options?: ActionOptions,
|
|
920
|
+
) => Promise<ActionResult>;
|
|
839
921
|
/** Helpers mirroring `@sanity/client`'s `client.releases.*` surface. */
|
|
840
922
|
releases: ReleasesHandle;
|
|
841
923
|
/** Helper for `sanity.action.document.version.create`. */
|
|
@@ -849,11 +931,15 @@ declare interface TestClient {
|
|
|
849
931
|
releaseId: string;
|
|
850
932
|
publishedId: string;
|
|
851
933
|
}) => Promise<ActionResult>;
|
|
852
|
-
/**
|
|
934
|
+
/**
|
|
935
|
+
* Current `(projectId, dataset)` and active perspective. `token` is
|
|
936
|
+
* present iff the handle is bound to a registered identity.
|
|
937
|
+
*/
|
|
853
938
|
config: () => {
|
|
854
939
|
projectId: string;
|
|
855
940
|
dataset: string;
|
|
856
941
|
perspective: Perspective;
|
|
942
|
+
token?: string;
|
|
857
943
|
};
|
|
858
944
|
/**
|
|
859
945
|
* Return a new client handle pointing at a different `(projectId, dataset)`
|
|
@@ -870,8 +956,18 @@ declare interface TestClient {
|
|
|
870
956
|
* Rebind the acting identity (and its grants) on a sibling that shares
|
|
871
957
|
* the same store — the test analogue of a token-scoped client. Lets two
|
|
872
958
|
* actors write the same dataset, e.g. for `identity()`-based guards.
|
|
959
|
+
* Mutually exclusive with `token`.
|
|
873
960
|
*/
|
|
874
961
|
accessControl: TestClientAccessControl;
|
|
962
|
+
/**
|
|
963
|
+
* Bind the sibling to a token registered in the family's identity
|
|
964
|
+
* registry (`createTestClient({ identities })` / `addIdentity`): its
|
|
965
|
+
* `/users/me` serves that identity's user, its ACL path serves that
|
|
966
|
+
* identity's grants, and reads/writes are gated through them — the
|
|
967
|
+
* token determines identity, exactly like production. Unknown tokens
|
|
968
|
+
* throw. Mutually exclusive with `accessControl`.
|
|
969
|
+
*/
|
|
970
|
+
token: string;
|
|
875
971
|
}>,
|
|
876
972
|
) => TestClient;
|
|
877
973
|
/** Replace the current dataset with the given documents. */
|
|
@@ -888,9 +984,21 @@ declare interface TestClient {
|
|
|
888
984
|
* Simulate `count` concurrent edits to `id` in the current dataset: the
|
|
889
985
|
* next `count` revision-guarded mutations (those passing `ifRevisionId`)
|
|
890
986
|
* to that document throw a 409-shaped conflict, as if another client
|
|
891
|
-
* committed between the caller's read and write.
|
|
892
|
-
*
|
|
893
|
-
*
|
|
987
|
+
* committed between the caller's read and write. Mutations that don't
|
|
988
|
+
* pass `ifRevisionId` are unaffected.
|
|
989
|
+
*
|
|
990
|
+
* Reach for this when the code under test reads a document's `_rev` and
|
|
991
|
+
* commits with `ifRevisionId` internally — so you can't stage the race
|
|
992
|
+
* by mutating the document first (the code would just re-read the new
|
|
993
|
+
* revision and its commit would match). The counter is consumed one per
|
|
994
|
+
* guarded commit, so `count` lets a test drive a recover-after-N-losses
|
|
995
|
+
* path or exhaust a retry budget, deterministically and without relying
|
|
996
|
+
* on promise interleaving.
|
|
997
|
+
*
|
|
998
|
+
* Note: this forces the conflict *outcome* — it short-circuits the
|
|
999
|
+
* revision comparison rather than changing the stored `_rev`. The code
|
|
1000
|
+
* under test sees the identical 409 error a real lost race produces; the
|
|
1001
|
+
* stored revision is left untouched.
|
|
894
1002
|
*/
|
|
895
1003
|
simulateConcurrentEdit: (id: string, count?: number) => void;
|
|
896
1004
|
/** List `(projectId, dataset)` pairs known in this registry. */
|
|
@@ -908,6 +1016,16 @@ declare interface TestClient {
|
|
|
908
1016
|
stub: (name: string, handler: unknown) => void;
|
|
909
1017
|
/** The currently-registered stubs, keyed by member name. */
|
|
910
1018
|
stubs: () => Record<string, unknown>;
|
|
1019
|
+
/**
|
|
1020
|
+
* Register (or upsert) a token→identity entry in the family's shared
|
|
1021
|
+
* registry. Siblings already bound to `token` resolve lazily, so a
|
|
1022
|
+
* re-registration takes effect on them immediately. Registering an
|
|
1023
|
+
* identity does not bind it — pass `token` at `createTestClient` or
|
|
1024
|
+
* `withConfig({ token })` to act as it.
|
|
1025
|
+
*/
|
|
1026
|
+
addIdentity: (token: string, identity: TestClientIdentity) => void;
|
|
1027
|
+
/** Snapshot of the registered token→identity entries. */
|
|
1028
|
+
identities: () => Record<string, TestClientIdentity>;
|
|
911
1029
|
/**
|
|
912
1030
|
* Every mutation committed through this client's registry, flattened
|
|
913
1031
|
* and in order — `create` / `createOrReplace` / `createIfNotExists` /
|
|
@@ -952,6 +1070,20 @@ declare interface TestClientAccessControl {
|
|
|
952
1070
|
aclPath?: string;
|
|
953
1071
|
}
|
|
954
1072
|
|
|
1073
|
+
/**
|
|
1074
|
+
* One entry in the token→identity registry (`createTestClient({ identities })`).
|
|
1075
|
+
* A handle bound to the entry's token behaves exactly like one configured
|
|
1076
|
+
* with `accessControl: { currentUser: user, grants }` — `/users/me` serves
|
|
1077
|
+
* `user`, the default ACL path serves `grants`, and reads/writes are gated
|
|
1078
|
+
* through them.
|
|
1079
|
+
*/
|
|
1080
|
+
declare interface TestClientIdentity {
|
|
1081
|
+
/** What `/users/me` returns for clients bound to this token. */
|
|
1082
|
+
user: CurrentUser;
|
|
1083
|
+
/** Grants served at the ACL path and enforced on reads/writes. */
|
|
1084
|
+
grants: Grant_2[];
|
|
1085
|
+
}
|
|
1086
|
+
|
|
955
1087
|
/**
|
|
956
1088
|
* In-memory document store backing a TestClient. Closure-scoped — created
|
|
957
1089
|
* fresh per test, no global state.
|
|
@@ -1007,6 +1139,12 @@ declare interface TxnCommitOptions {
|
|
|
1007
1139
|
returnFirst?: boolean;
|
|
1008
1140
|
/** Override the synthetic transaction id (mirrors `client.transaction(undefined, { transactionId })`). */
|
|
1009
1141
|
transactionId?: string;
|
|
1142
|
+
/**
|
|
1143
|
+
* Request tag, recorded on the mutation log entry for assertions.
|
|
1144
|
+
* `null` mirrors `@sanity/client`'s explicit-suppression form and is
|
|
1145
|
+
* recorded as-is. No other behavior is simulated.
|
|
1146
|
+
*/
|
|
1147
|
+
tag?: string | null;
|
|
1010
1148
|
}
|
|
1011
1149
|
|
|
1012
1150
|
declare type TxnCommitResult =
|