@tailor-platform/sdk 1.58.0 → 1.60.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 +65 -0
- package/dist/{application-B59TaTk_.mjs → application-FnWOxBk7.mjs} +46 -27
- package/dist/application-FnWOxBk7.mjs.map +1 -0
- package/dist/application-VOdgMtOD.mjs +4 -0
- package/dist/{authconnection-TsdLYaLs.d.mts → authconnection-BIYzEh2p.d.mts} +2 -2
- package/dist/authconnection-D8SJGMpj.mjs.map +1 -1
- package/dist/cli/erd-viewer-assets/app.js +4 -4
- package/dist/cli/index.mjs +57 -13
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.mjs +4 -3
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/client-B-jRdlC_.mjs +4 -0
- package/dist/{client-62B-r3MN.mjs → client-W5P4NYYX.mjs} +117 -4
- package/dist/{client-62B-r3MN.mjs.map → client-W5P4NYYX.mjs.map} +1 -1
- package/dist/configure/index.d.mts +1 -1
- package/dist/configure/index.mjs +8 -8
- package/dist/configure/index.mjs.map +1 -1
- package/dist/{crashreport-CCGpLUlP.mjs → crashreport-D3DvAzdg.mjs} +3 -3
- package/dist/crashreport-D3DvAzdg.mjs.map +1 -0
- package/dist/{crashreport-CXD_Kjk-.mjs → crashreport-lnVTnbB5.mjs} +1 -1
- package/dist/file-B58Dm-2P.mjs.map +1 -1
- package/dist/{file-VTJbbOL3.d.mts → file-BzK8z3X-.d.mts} +2 -2
- package/dist/globals-ByrCoDip.mjs +109 -0
- package/dist/globals-ByrCoDip.mjs.map +1 -0
- package/dist/iconv-DreIffeM.mjs.map +1 -1
- package/dist/{iconv-Chu6Hit2.d.mts → iconv-kwrmd1U_.d.mts} +2 -2
- package/dist/{idp-Di9N4FSJ.d.mts → idp-BlBPtXJ-.d.mts} +2 -2
- package/dist/idp-Ch95ag8h.mjs.map +1 -1
- package/dist/{index-BWoHfE-i.d.mts → index-Cr6ufjZ5.d.mts} +10 -8
- package/dist/{index-DTSQthwF.d.mts → index-DRhMpdnA.d.mts} +7 -7
- package/dist/{job-CEAJLiGp.mjs → job-BpsFXPbi.mjs} +8 -17
- package/dist/job-BpsFXPbi.mjs.map +1 -0
- package/dist/mock-Dpu__UeJ.mjs +805 -0
- package/dist/mock-Dpu__UeJ.mjs.map +1 -0
- package/dist/registry-D0uB0OrK.mjs +178 -0
- package/dist/registry-D0uB0OrK.mjs.map +1 -0
- package/dist/{repl-editor-ihh8koiR.mjs → repl-editor-Y9QJDL0K.mjs} +3 -9
- package/dist/{repl-editor-ihh8koiR.mjs.map → repl-editor-Y9QJDL0K.mjs.map} +1 -1
- package/dist/runtime/authconnection.d.mts +1 -1
- package/dist/runtime/file.d.mts +1 -1
- package/dist/runtime/globals.d.mts +5 -5
- package/dist/runtime/iconv.d.mts +1 -1
- package/dist/runtime/idp.d.mts +1 -1
- package/dist/runtime/index.d.mts +7 -7
- package/dist/runtime/secretmanager.d.mts +1 -1
- package/dist/runtime/workflow.d.mts +1 -1
- package/dist/{runtime-BC-FbQkg.mjs → runtime-CrUa8Z2k.mjs} +238 -273
- package/dist/runtime-CrUa8Z2k.mjs.map +1 -0
- package/dist/secretmanager-B9h-U_8U.mjs.map +1 -1
- package/dist/{secretmanager-BhpDmxwT.d.mts → secretmanager-CKLB3wAQ.d.mts} +2 -2
- package/dist/utils/test/index.d.mts +5 -5
- package/dist/utils/test/index.mjs +7 -7
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/vitest/environment.mjs +3 -4
- package/dist/vitest/environment.mjs.map +1 -1
- package/dist/vitest/index.d.mts +167 -120
- package/dist/vitest/index.mjs +6 -6
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/setup.d.mts +1 -1
- package/dist/vitest/setup.mjs +4 -3
- package/dist/vitest/setup.mjs.map +1 -1
- package/dist/workflow--aPbA8Uq.mjs.map +1 -1
- package/dist/{workflow-dYYH7QFa.d.mts → workflow-CMamswkK.d.mts} +2 -2
- package/docs/cli/auth.md +40 -0
- package/docs/cli-reference.md +1 -0
- package/docs/configuration.md +1 -1
- package/docs/runtime.md +9 -12
- package/docs/services/auth.md +19 -1
- package/docs/testing.md +92 -85
- package/package.json +5 -5
- package/dist/application-B59TaTk_.mjs.map +0 -1
- package/dist/application-gO_pa5BO.mjs +0 -4
- package/dist/client-BWl3f1XS.mjs +0 -4
- package/dist/crashreport-CCGpLUlP.mjs.map +0 -1
- package/dist/job-CEAJLiGp.mjs.map +0 -1
- package/dist/mock-B6PI49C_.mjs +0 -844
- package/dist/mock-B6PI49C_.mjs.map +0 -1
- package/dist/runtime-BC-FbQkg.mjs.map +0 -1
- package/dist/test-env-key-CSnK4W1Y.mjs +0 -30
- package/dist/test-env-key-CSnK4W1Y.mjs.map +0 -1
package/docs/testing.md
CHANGED
|
@@ -19,11 +19,11 @@ Helpers under `@tailor-platform/sdk/test`:
|
|
|
19
19
|
|
|
20
20
|
- `unauthenticatedTailorUser` — default `user` value for resolver contexts
|
|
21
21
|
|
|
22
|
-
Platform API mocks under `@tailor-platform/sdk/vitest` (
|
|
22
|
+
Platform API mocks under `@tailor-platform/sdk/vitest` (for use with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below):
|
|
23
23
|
|
|
24
|
-
- `
|
|
25
|
-
- `
|
|
26
|
-
- `
|
|
24
|
+
- `mockTailordb` — TailorDB query stubs and call recording
|
|
25
|
+
- `mockWorkflow` — `tailor.workflow` job / wait / resolve mocks
|
|
26
|
+
- `mockSecretmanager`, `mockAuthconnection`, `mockIdp`, `mockFile`, `mockIconv` — corresponding platform API mocks
|
|
27
27
|
|
|
28
28
|
For tighter alignment with the production runtime — Node.js module blocking, Web-only globals, and platform API mocks — pair the resolver helpers with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta) below.
|
|
29
29
|
|
|
@@ -56,22 +56,41 @@ export default defineConfig({
|
|
|
56
56
|
|
|
57
57
|
1. **Node.js module blocking** — `import { randomBytes } from "node:crypto"` in production code throws an error with a suggestion for the Web Standard API alternative (`globalThis.crypto`). Test files (`*.test.ts`, `*.spec.ts`) are exempt.
|
|
58
58
|
2. **Node.js globals removal** — Only globals available in the platform runtime are kept (whitelist). `Buffer`, `global`, `setImmediate`, `__dirname`, `__filename`, `performance`, and others are removed.
|
|
59
|
-
3. **Platform API mocks** —
|
|
59
|
+
3. **Platform API mocks** — the platform error classes (`TailorErrors`, `TailorErrorMessage`, `TailorDBFileError`) and `tailor.context` are always available. The other namespaces (`tailordb.Client`, `tailor.workflow`, `tailor.secretmanager`, …) are mocked when you acquire the corresponding `mockX()` — see below.
|
|
60
60
|
|
|
61
|
-
###
|
|
61
|
+
### Acquiring mocks with `using`
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
Each mock controller (`mockTailordb`, `mockWorkflow`, `mockSecretmanager`, `mockAuthconnection`, `mockIdp`, `mockFile`, `mockIconv`) is a **factory function**. Acquire it inside a test with a [`using` declaration](https://github.com/tc39/proposal-explicit-resource-management) — its state is reset automatically when the test scope exits, so you no longer need `beforeEach(() => mock.reset())`:
|
|
64
64
|
|
|
65
65
|
```typescript
|
|
66
|
-
import {
|
|
66
|
+
import { mockTailordb } from "@tailor-platform/sdk/vitest";
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
});
|
|
68
|
+
test("...", () => {
|
|
69
|
+
using db = mockTailordb();
|
|
70
|
+
db.enqueueResult({ age: 30 });
|
|
71
|
+
// …
|
|
72
|
+
}); // reset automatically here
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The mock functions are also exposed directly (e.g. `db.queryObject`, `wf.triggerJobFunction`) so you can assert on them with `expect(...).toHaveBeenCalledWith(...)`.
|
|
76
|
+
|
|
77
|
+
> **Requirements:** `using` requires TypeScript ≥ 5.2 and a runtime that provides `Symbol.dispose` (Node ≥ 20.4 — the SDK already targets Node ≥ 22, and Vitest's transformer downlevels the syntax for you).
|
|
78
|
+
>
|
|
79
|
+
> **Acquire what you use:** a namespace is only mocked while you hold it with `using`, so code under test that calls a platform API (e.g. `tailor.workflow`, `tailordb.Client`) must run inside a test that has acquired the matching `mockX()`. The error classes and `tailor.context` are always present.
|
|
80
|
+
>
|
|
81
|
+
> **Seeded secrets survive:** secrets seeded from `tailor.config.ts` stay available across tests; a per-test `mockSecretmanager().setSecrets(...)` override applies only within that test.
|
|
82
|
+
|
|
83
|
+
### TailorDB Mock
|
|
84
|
+
|
|
85
|
+
Acquire `mockTailordb()` to install the mock `tailordb.Client`, configure responses, and assert on executed queries:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { mockTailordb } from "@tailor-platform/sdk/vitest";
|
|
71
89
|
|
|
72
90
|
test("resolver queries the database", async () => {
|
|
91
|
+
using db = mockTailordb();
|
|
73
92
|
// Order-based: stage rows for each upcoming query in one call
|
|
74
|
-
|
|
93
|
+
db.enqueueResults(
|
|
75
94
|
[], // BEGIN (empty result)
|
|
76
95
|
[{ age: 30 }], // SELECT (one row)
|
|
77
96
|
[], // COMMIT
|
|
@@ -80,8 +99,8 @@ test("resolver queries the database", async () => {
|
|
|
80
99
|
const result = await resolver.body({ input: { email: "test@example.com" } });
|
|
81
100
|
|
|
82
101
|
expect(result).toEqual({ oldAge: 30, newAge: 31 });
|
|
83
|
-
expect(
|
|
84
|
-
expect(
|
|
102
|
+
expect(db.executedQueries).toHaveLength(3);
|
|
103
|
+
expect(db.createdClients).toMatchObject([{ namespace: "tailordb" }]);
|
|
85
104
|
});
|
|
86
105
|
```
|
|
87
106
|
|
|
@@ -93,30 +112,28 @@ Three response modes:
|
|
|
93
112
|
|
|
94
113
|
```typescript
|
|
95
114
|
test("content-based mock", async () => {
|
|
96
|
-
|
|
115
|
+
using db = mockTailordb();
|
|
116
|
+
db.setQueryResolver((query) => {
|
|
97
117
|
if (query.includes("SELECT")) return [{ id: "1", name: "test" }];
|
|
98
118
|
return [];
|
|
99
119
|
});
|
|
100
120
|
|
|
101
121
|
const result = await resolver.body({ input: { userId: "1" } });
|
|
102
122
|
|
|
103
|
-
expect(
|
|
123
|
+
expect(db.executedQueries[0].query).toContain("SELECT");
|
|
104
124
|
});
|
|
105
125
|
```
|
|
106
126
|
|
|
107
127
|
### Workflow Mock
|
|
108
128
|
|
|
109
|
-
|
|
129
|
+
`.trigger()` runs the real job bodies locally out of the box (see [Running a full workflow locally](#running-a-full-workflow-locally)). Acquire `mockWorkflow()` when you want to override responses with `setJobHandler` / `enqueueResult` or assert on `triggeredJobs`:
|
|
110
130
|
|
|
111
131
|
```typescript
|
|
112
|
-
import {
|
|
113
|
-
|
|
114
|
-
beforeEach(() => {
|
|
115
|
-
workflowMock.reset();
|
|
116
|
-
});
|
|
132
|
+
import { mockWorkflow } from "@tailor-platform/sdk/vitest";
|
|
117
133
|
|
|
118
134
|
test("workflow triggers jobs", async () => {
|
|
119
|
-
|
|
135
|
+
using wf = mockWorkflow();
|
|
136
|
+
wf.setJobHandler((jobName, args) => {
|
|
120
137
|
if (jobName === "validate-order") return { valid: true };
|
|
121
138
|
if (jobName === "process-payment") return { txnId: "txn-1" };
|
|
122
139
|
return null;
|
|
@@ -124,52 +141,50 @@ test("workflow triggers jobs", async () => {
|
|
|
124
141
|
|
|
125
142
|
const result = await main({ input: { orderId: "o-1" } });
|
|
126
143
|
|
|
127
|
-
expect(
|
|
144
|
+
expect(wf.triggeredJobs).toEqual([
|
|
128
145
|
{ jobName: "validate-order", args: { orderId: "o-1" } },
|
|
129
146
|
{ jobName: "process-payment", args: { orderId: "o-1" } },
|
|
130
147
|
]);
|
|
131
148
|
});
|
|
132
149
|
```
|
|
133
150
|
|
|
134
|
-
`
|
|
151
|
+
`mockWorkflow()` also supports order-based responses:
|
|
135
152
|
|
|
136
153
|
```typescript
|
|
154
|
+
using wf = mockWorkflow();
|
|
155
|
+
|
|
137
156
|
// Single response for the next triggerJobFunction call
|
|
138
|
-
|
|
157
|
+
wf.enqueueResult({ valid: true });
|
|
139
158
|
|
|
140
159
|
// Multiple responses for subsequent calls (FIFO)
|
|
141
|
-
|
|
160
|
+
wf.enqueueResults({ valid: true }, { txnId: "txn-1" });
|
|
142
161
|
```
|
|
143
162
|
|
|
144
163
|
### SecretManager Mock
|
|
145
164
|
|
|
146
165
|
```typescript
|
|
147
|
-
import {
|
|
148
|
-
|
|
149
|
-
beforeEach(() => secretmanagerMock.reset());
|
|
166
|
+
import { mockSecretmanager } from "@tailor-platform/sdk/vitest";
|
|
150
167
|
|
|
151
168
|
test("reads secrets from vault", async () => {
|
|
152
|
-
|
|
169
|
+
using sm = mockSecretmanager();
|
|
170
|
+
sm.setSecrets({
|
|
153
171
|
"my-vault": { API_KEY: "sk-123", DB_PASS: "secret" },
|
|
154
172
|
});
|
|
155
173
|
|
|
156
174
|
const key = await tailor.secretmanager.getSecret("my-vault", "API_KEY");
|
|
157
175
|
expect(key).toBe("sk-123");
|
|
158
|
-
expect(
|
|
159
|
-
{ method: "getSecret", vault: "my-vault", name: "API_KEY" },
|
|
160
|
-
]);
|
|
176
|
+
expect(sm.calls).toEqual([{ method: "getSecret", vault: "my-vault", name: "API_KEY" }]);
|
|
161
177
|
});
|
|
162
178
|
```
|
|
163
179
|
|
|
164
180
|
### AuthConnection Mock
|
|
165
181
|
|
|
166
182
|
```typescript
|
|
167
|
-
import {
|
|
168
|
-
|
|
169
|
-
beforeEach(() => authconnectionMock.reset());
|
|
183
|
+
import { mockAuthconnection } from "@tailor-platform/sdk/vitest";
|
|
170
184
|
|
|
171
185
|
test("returns configured token", async () => {
|
|
172
|
-
|
|
186
|
+
using ac = mockAuthconnection();
|
|
187
|
+
ac.setTokens({
|
|
173
188
|
google: { access_token: "ya29.xxx", expires_in: 3600 },
|
|
174
189
|
});
|
|
175
190
|
|
|
@@ -183,12 +198,11 @@ When no token is configured for a connection, it returns `{ access_token: "mock-
|
|
|
183
198
|
### IDP Mock
|
|
184
199
|
|
|
185
200
|
```typescript
|
|
186
|
-
import {
|
|
187
|
-
|
|
188
|
-
beforeEach(() => idpMock.reset());
|
|
201
|
+
import { mockIdp } from "@tailor-platform/sdk/vitest";
|
|
189
202
|
|
|
190
203
|
test("resolver-based", async () => {
|
|
191
|
-
|
|
204
|
+
using idp = mockIdp();
|
|
205
|
+
idp.setResolver((method, args) => {
|
|
192
206
|
if (method === "user") return { id: "u-1", name: "alice", disabled: false };
|
|
193
207
|
return null; // falls back to defaults
|
|
194
208
|
});
|
|
@@ -199,31 +213,31 @@ test("resolver-based", async () => {
|
|
|
199
213
|
});
|
|
200
214
|
|
|
201
215
|
test("queue-based", async () => {
|
|
202
|
-
|
|
216
|
+
using idp = mockIdp();
|
|
217
|
+
idp.enqueueResult({ id: "u-1", name: "alice", disabled: false });
|
|
203
218
|
|
|
204
219
|
const client = new tailor.idp.Client({ namespace: "my-ns" });
|
|
205
220
|
const user = await client.user("u-1");
|
|
206
221
|
expect(user.name).toBe("alice");
|
|
207
|
-
expect(
|
|
222
|
+
expect(idp.calls).toMatchObject([{ method: "user", namespace: "my-ns" }]);
|
|
208
223
|
});
|
|
209
224
|
```
|
|
210
225
|
|
|
211
226
|
### File Mock
|
|
212
227
|
|
|
213
228
|
```typescript
|
|
214
|
-
import {
|
|
215
|
-
|
|
216
|
-
beforeEach(() => fileMock.reset());
|
|
229
|
+
import { mockFile } from "@tailor-platform/sdk/vitest";
|
|
217
230
|
|
|
218
231
|
test("mock file download", async () => {
|
|
219
|
-
|
|
232
|
+
using file = mockFile();
|
|
233
|
+
file.enqueueResult({
|
|
220
234
|
data: new Uint8Array([1, 2, 3]),
|
|
221
235
|
metadata: { contentType: "image/png", fileSize: 3, sha256sum: "abc", lastUploadedAt: "" },
|
|
222
236
|
});
|
|
223
237
|
|
|
224
238
|
const result = await tailordb.file.download("ns", "Doc", "attachment", "r-1");
|
|
225
239
|
expect(result.data).toEqual(new Uint8Array([1, 2, 3]));
|
|
226
|
-
expect(
|
|
240
|
+
expect(file.calls).toMatchObject([{ method: "download", recordId: "r-1" }]);
|
|
227
241
|
});
|
|
228
242
|
```
|
|
229
243
|
|
|
@@ -231,13 +245,14 @@ For `downloadStream`, enqueue a `FileDownloadStreamResponse` object with a `Read
|
|
|
231
245
|
|
|
232
246
|
```typescript
|
|
233
247
|
test("mock file download stream", async () => {
|
|
248
|
+
using file = mockFile();
|
|
234
249
|
const body = new ReadableStream({
|
|
235
250
|
start(controller) {
|
|
236
251
|
controller.enqueue(new Uint8Array([1, 2, 3]));
|
|
237
252
|
controller.close();
|
|
238
253
|
},
|
|
239
254
|
});
|
|
240
|
-
|
|
255
|
+
file.enqueueResult({
|
|
241
256
|
body,
|
|
242
257
|
metadata: { contentType: "image/png", fileSize: 3, sha256sum: "abc", lastUploadedAt: "" },
|
|
243
258
|
});
|
|
@@ -251,7 +266,8 @@ For the deprecated `openDownloadStream`, enqueue an iterable of `StreamValue` it
|
|
|
251
266
|
|
|
252
267
|
```typescript
|
|
253
268
|
test("mock file download stream (deprecated openDownloadStream)", async () => {
|
|
254
|
-
|
|
269
|
+
using file = mockFile();
|
|
270
|
+
file.enqueueResult([
|
|
255
271
|
{
|
|
256
272
|
type: "metadata",
|
|
257
273
|
metadata: { contentType: "image/png", fileSize: 3, sha256sum: "abc" },
|
|
@@ -271,19 +287,18 @@ test("mock file download stream (deprecated openDownloadStream)", async () => {
|
|
|
271
287
|
### Iconv Mock
|
|
272
288
|
|
|
273
289
|
```typescript
|
|
274
|
-
import {
|
|
275
|
-
|
|
276
|
-
beforeEach(() => iconvMock.reset());
|
|
290
|
+
import { mockIconv } from "@tailor-platform/sdk/vitest";
|
|
277
291
|
|
|
278
292
|
test("mock encoding conversion", () => {
|
|
279
|
-
|
|
293
|
+
using iconv = mockIconv();
|
|
294
|
+
iconv.setResolver((method, args) => {
|
|
280
295
|
if (method === "decode") return "decoded-text";
|
|
281
296
|
return null; // falls back to default empty string
|
|
282
297
|
});
|
|
283
298
|
|
|
284
299
|
const result = tailor.iconv.decode(new Uint8Array([0x48, 0x69]), "UTF-8");
|
|
285
300
|
expect(result).toBe("decoded-text");
|
|
286
|
-
expect(
|
|
301
|
+
expect(iconv.calls).toMatchObject([{ method: "decode" }]);
|
|
287
302
|
});
|
|
288
303
|
```
|
|
289
304
|
|
|
@@ -298,7 +313,7 @@ export default defineConfig({
|
|
|
298
313
|
});
|
|
299
314
|
```
|
|
300
315
|
|
|
301
|
-
This makes `tailor.secretmanager.getSecret("vault", "key")` return the values defined in your config. You can still override with `
|
|
316
|
+
This makes `tailor.secretmanager.getSecret("vault", "key")` return the values defined in your config. You can still override with `using sm = mockSecretmanager(); sm.setSecrets(...)` in individual tests: a per-test override applies only within that test, and the config-loaded secrets remain available to every other test.
|
|
302
317
|
|
|
303
318
|
### Per-Project Configuration
|
|
304
319
|
|
|
@@ -371,7 +386,7 @@ describe("add resolver", () => {
|
|
|
371
386
|
|
|
372
387
|
Stub the global `tailordb.Client` and queue raw query results in order. Best for resolvers that issue a short, predictable query sequence:
|
|
373
388
|
|
|
374
|
-
> If you are running with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta), `
|
|
389
|
+
> If you are running with the [`tailor-runtime` Vitest environment](#runtime-environment-emulation-beta), acquire `using db = mockTailordb()` to install and drive the mock `tailordb.Client` instead of `vi.stubGlobal()`.
|
|
375
390
|
|
|
376
391
|
```typescript
|
|
377
392
|
import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
|
|
@@ -519,25 +534,22 @@ describe("upsertUsers resolver", () => {
|
|
|
519
534
|
});
|
|
520
535
|
```
|
|
521
536
|
|
|
522
|
-
Reach for [`
|
|
537
|
+
Reach for [`mockTailordb`](#mocking-the-tailordb-client) instead when you want to drive the raw query sequence at the `tailordb.Client` level rather than at the Kysely layer.
|
|
523
538
|
|
|
524
539
|
#### Resolvers that resume a workflow
|
|
525
540
|
|
|
526
|
-
Resolvers that call `waitPoint.resolve(...)` delegate to `tailor.workflow.resolve` at runtime. With the `tailor-runtime` environment active, use `
|
|
541
|
+
Resolvers that call `waitPoint.resolve(...)` delegate to `tailor.workflow.resolve` at runtime. With the `tailor-runtime` environment active, use `mockWorkflow().setResolveHandler` to drive the user-supplied callback and inspect `resolveCalls`:
|
|
527
542
|
|
|
528
543
|
```typescript
|
|
529
544
|
import { unauthenticatedTailorUser } from "@tailor-platform/sdk/test";
|
|
530
|
-
import {
|
|
531
|
-
import {
|
|
545
|
+
import { mockWorkflow } from "@tailor-platform/sdk/vitest";
|
|
546
|
+
import { describe, expect, test } from "vitest";
|
|
532
547
|
import resolver from "./resolveApproval";
|
|
533
548
|
|
|
534
549
|
describe("resolveApproval resolver", () => {
|
|
535
|
-
beforeEach(() => {
|
|
536
|
-
workflowMock.reset();
|
|
537
|
-
});
|
|
538
|
-
|
|
539
550
|
test("resolves approval with approved=true", async () => {
|
|
540
|
-
|
|
551
|
+
using wf = mockWorkflow();
|
|
552
|
+
wf.setResolveHandler((_executionId, _key, callback) => {
|
|
541
553
|
const result = callback({ message: "Please approve order order-1", orderId: "order-1" });
|
|
542
554
|
expect(result).toEqual({ approved: true });
|
|
543
555
|
});
|
|
@@ -549,7 +561,7 @@ describe("resolveApproval resolver", () => {
|
|
|
549
561
|
});
|
|
550
562
|
|
|
551
563
|
expect(result).toEqual({ resolved: true });
|
|
552
|
-
expect(
|
|
564
|
+
expect(wf.resolveCalls).toEqual([{ executionId: "exec-1", key: "approval" }]);
|
|
553
565
|
});
|
|
554
566
|
});
|
|
555
567
|
```
|
|
@@ -660,32 +672,30 @@ describe("fulfillOrder", () => {
|
|
|
660
672
|
|
|
661
673
|
#### Jobs that wait on approval
|
|
662
674
|
|
|
663
|
-
`.wait()` calls delegate to `tailor.workflow.wait`. With the `tailor-runtime` environment active, use `
|
|
675
|
+
`.wait()` calls delegate to `tailor.workflow.wait`. With the `tailor-runtime` environment active, use `mockWorkflow().setWaitHandler` to drive each branch and inspect `waitCalls`:
|
|
664
676
|
|
|
665
677
|
```typescript
|
|
666
|
-
import {
|
|
667
|
-
import {
|
|
678
|
+
import { mockWorkflow } from "@tailor-platform/sdk/vitest";
|
|
679
|
+
import { describe, expect, test } from "vitest";
|
|
668
680
|
import { processWithApproval } from "./approval";
|
|
669
681
|
|
|
670
682
|
describe("processWithApproval", () => {
|
|
671
|
-
beforeEach(() => {
|
|
672
|
-
workflowMock.reset();
|
|
673
|
-
});
|
|
674
|
-
|
|
675
683
|
test("returns approved status when .wait() resolves positively", async () => {
|
|
676
|
-
|
|
684
|
+
using wf = mockWorkflow();
|
|
685
|
+
wf.setWaitHandler({ approved: true });
|
|
677
686
|
|
|
678
687
|
const result = await processWithApproval.body({ orderId: "order-1" }, { env: {} });
|
|
679
688
|
|
|
680
689
|
expect(result).toEqual({ orderId: "order-1", status: "approved" });
|
|
681
|
-
expect(
|
|
690
|
+
expect(wf.waitCalls[0]).toEqual({
|
|
682
691
|
key: "approval",
|
|
683
692
|
payload: { message: "Please approve order order-1", orderId: "order-1" },
|
|
684
693
|
});
|
|
685
694
|
});
|
|
686
695
|
|
|
687
696
|
test("returns rejected status when .wait() resolves negatively", async () => {
|
|
688
|
-
|
|
697
|
+
using wf = mockWorkflow();
|
|
698
|
+
wf.setWaitHandler({ approved: false });
|
|
689
699
|
|
|
690
700
|
const result = await processWithApproval.body({ orderId: "order-2" }, { env: {} });
|
|
691
701
|
|
|
@@ -698,19 +708,14 @@ describe("processWithApproval", () => {
|
|
|
698
708
|
|
|
699
709
|
#### Running a full workflow locally
|
|
700
710
|
|
|
701
|
-
To exercise the full chain with real job bodies, call `workflow.mainJob.trigger()
|
|
711
|
+
To exercise the full chain with real job bodies, just call `workflow.mainJob.trigger()` — no `mockWorkflow()` needed. Dependent jobs run their real `.body()` functions, and trigger args/results cross the same JSON boundary as the platform, so a non-serializable payload fails the test exactly as it would in production:
|
|
702
712
|
|
|
703
713
|
```typescript
|
|
704
|
-
import {
|
|
705
|
-
import { afterEach, describe, expect, test } from "vitest";
|
|
714
|
+
import { describe, expect, test } from "vitest";
|
|
706
715
|
import workflow from "./order-fulfillment";
|
|
707
716
|
|
|
708
717
|
describe("order-fulfillment workflow", () => {
|
|
709
|
-
afterEach(() => workflowMock.reset());
|
|
710
|
-
|
|
711
718
|
test("mainJob.trigger() executes all jobs", async () => {
|
|
712
|
-
workflowMock.setEnv({ PAYMENT_GATEWAY: "stripe" });
|
|
713
|
-
|
|
714
719
|
const result = await workflow.mainJob.trigger({ orderId: "order-3", amount: 300 });
|
|
715
720
|
|
|
716
721
|
expect(result).toMatchObject({ confirmed: true, paymentStatus: "completed" });
|
|
@@ -718,6 +723,8 @@ describe("order-fulfillment workflow", () => {
|
|
|
718
723
|
});
|
|
719
724
|
```
|
|
720
725
|
|
|
726
|
+
Acquire `mockWorkflow()` only when you need to override a dependent job with `wf.setJobHandler(...)` / `wf.enqueueResult(...)` (the rest still run their real bodies), control the env via `wf.setEnv(...)`, or assert on `wf.triggeredJobs`.
|
|
727
|
+
|
|
721
728
|
**Use when:** you want to verify orchestration end to end without the cost of a real deployment.
|
|
722
729
|
|
|
723
730
|
## End-to-End (E2E) Tests
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.60.0",
|
|
4
4
|
"description": "Tailor Platform SDK - The SDK to work with Tailor Platform",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
"ts-cron-validator": "1.1.5",
|
|
183
183
|
"tsx": "4.22.4",
|
|
184
184
|
"type-fest": "5.7.0",
|
|
185
|
-
"undici": "8.
|
|
185
|
+
"undici": "8.4.0",
|
|
186
186
|
"xdg-basedir": "5.1.0",
|
|
187
187
|
"zod": "4.4.3"
|
|
188
188
|
},
|
|
@@ -221,11 +221,11 @@
|
|
|
221
221
|
},
|
|
222
222
|
"scripts": {
|
|
223
223
|
"test": "vitest",
|
|
224
|
-
"test:unit": "vitest --project=unit",
|
|
224
|
+
"test:unit": "vitest --project=unit*",
|
|
225
225
|
"test:e2e": "vitest --project=e2e",
|
|
226
226
|
"test:coverage": "vitest --coverage",
|
|
227
|
-
"docs:check": "vitest run --project=unit src/cli/docs.test.ts",
|
|
228
|
-
"docs:update": "POLITTY_DOCS_UPDATE=true vitest run --project=unit src/cli/docs.test.ts",
|
|
227
|
+
"docs:check": "vitest run --project=unit* src/cli/docs.test.ts",
|
|
228
|
+
"docs:update": "POLITTY_DOCS_UPDATE=true vitest run --project=unit* src/cli/docs.test.ts",
|
|
229
229
|
"build": "tsdown",
|
|
230
230
|
"lint": "oxlint --type-aware .",
|
|
231
231
|
"check:public-api-jsdoc": "tsx scripts/check-public-api-jsdoc.ts",
|