@tailor-platform/sdk 2.0.0-next.5 → 2.0.0-next.7
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 +85 -0
- package/dist/{application-Bf8wxOi6.mjs → application-GzW98_Xr.mjs} +377 -1418
- package/dist/application-GzW98_Xr.mjs.map +1 -0
- package/dist/application-OM0taSPn.mjs +3 -0
- package/dist/cli/commands/deploy/deploy.d.mts +8 -3
- package/dist/cli/commands/generate/options.d.mts +0 -1
- package/dist/cli/commands/generate/service.d.mts +1 -1
- package/dist/cli/commands/staticwebsite/deploy.d.mts +19 -0
- package/dist/cli/commands/workspace/create.d.mts +3 -0
- package/dist/cli/commands/workspace/list.d.mts +1 -0
- package/dist/cli/commands/workspace/transform.d.mts +2 -0
- package/dist/cli/index.mjs +781 -1553
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +7 -3
- package/dist/cli/lib.mjs +49 -3
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/shared/config-loader.d.mts +1 -1
- package/dist/cli/shared/readonly-guard.d.mts +25 -0
- package/dist/cli/shared/tailordb-namespaces.d.mts +37 -0
- package/dist/cli/shared/workspace-name.d.mts +1 -0
- package/dist/completion/zsh-worker.zsh +15 -303
- package/dist/configure/config/types.d.mts +5 -1
- package/dist/configure/index.mjs +20 -19
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/auth/types.d.mts +18 -23
- package/dist/configure/services/executor/executor.d.mts +2 -13
- package/dist/configure/services/executor/operation.d.mts +8 -3
- package/dist/configure/services/tailordb/schema.d.mts +1 -7
- package/dist/configure/services/tailordb/types.d.mts +1 -1
- package/dist/configure/services/workflow/execution-policy.d.mts +2 -2
- package/dist/configure/services/workflow/job.d.mts +10 -10
- package/dist/configure/services/workflow/workflow.d.mts +6 -4
- package/dist/configure/types/type.d.mts +1 -9
- package/dist/{crashreport-D3RjuSYb.mjs → crashreport-0ioNRZ4K.mjs} +1 -1
- package/dist/{crashreport-Cg-pqRJV.mjs → crashreport-CxHne11E.mjs} +4 -3
- package/dist/{crashreport-Cg-pqRJV.mjs.map → crashreport-CxHne11E.mjs.map} +1 -1
- package/dist/errors-DfnF4ibq.mjs +100 -0
- package/dist/errors-DfnF4ibq.mjs.map +1 -0
- package/dist/{globals-BjhJJ2dN.mjs → globals-CBZ0egXT.mjs} +13 -10
- package/dist/globals-CBZ0egXT.mjs.map +1 -0
- package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
- package/dist/logger-BwS4ppwO.mjs.map +1 -0
- package/dist/parser/service/tailordb/type-source.d.mts +10 -0
- package/dist/{register-ts-hook-BhzVkej0.mjs → register-ts-hook-DvEs6YsL.mjs} +2571 -1636
- package/dist/register-ts-hook-DvEs6YsL.mjs.map +1 -0
- package/dist/{registry-BSbq6SPo.mjs → registry-i7EdJ-D5.mjs} +10 -10
- package/dist/registry-i7EdJ-D5.mjs.map +1 -0
- package/dist/runtime/field-parse.d.mts +10 -0
- package/dist/runtime/globals.d.mts +3 -3
- package/dist/runtime/index.mjs +1 -1
- package/dist/runtime/workflow.d.mts +18 -33
- package/dist/runtime/workflow.mjs +1 -1
- package/dist/{schema-iCd1ugGp.mjs → schema--xYWRGfe.mjs} +42 -24
- package/dist/schema--xYWRGfe.mjs.map +1 -0
- package/dist/{secret-file-aoNXJmKa.mjs → secret-file-C3J4Gy8r.mjs} +2 -27
- package/dist/secret-file-C3J4Gy8r.mjs.map +1 -0
- package/dist/{secretmanager-Bd45j7an.mjs → secretmanager-IY4UvinW.mjs} +42 -9
- package/dist/secretmanager-IY4UvinW.mjs.map +1 -0
- package/dist/service-BJAQ70e5.mjs +3 -0
- package/dist/{service-DeZeYa06.mjs → service-DaeJlBoH.mjs} +3 -3
- package/dist/{service-DeZeYa06.mjs.map → service-DaeJlBoH.mjs.map} +1 -1
- package/dist/{service-CzZEVrp9.mjs → service-Dmxa2I4i.mjs} +64 -28
- package/dist/service-Dmxa2I4i.mjs.map +1 -0
- package/dist/service_pb-BoLM0X4K.mjs +167 -0
- package/dist/service_pb-BoLM0X4K.mjs.map +1 -0
- package/dist/{service_pb-DZkfjB_6.mjs → service_pb-r1T7kpJc.mjs} +1 -1
- package/dist/types/executor.generated.d.mts +60 -42
- package/dist/user-agent-Bgsszb5I.mjs +28 -0
- package/dist/user-agent-Bgsszb5I.mjs.map +1 -0
- package/dist/utils/test/index.mjs +11 -11
- package/dist/utils/test/index.mjs.map +1 -1
- package/dist/utils/test/mock.d.mts +6 -6
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +8 -8
- package/dist/vitest/index.mjs +613 -227
- package/dist/vitest/index.mjs.map +1 -1
- package/dist/vitest/mock.d.mts +8 -8
- package/dist/vitest/mocks/aigateway.d.mts +14 -4
- package/dist/vitest/mocks/authconnection.d.mts +14 -4
- package/dist/vitest/mocks/file.d.mts +23 -6
- package/dist/vitest/mocks/iconv.d.mts +28 -4
- package/dist/vitest/mocks/idp.d.mts +25 -9
- package/dist/vitest/mocks/secretmanager.d.mts +13 -4
- package/dist/vitest/mocks/tailordb.d.mts +48 -7
- package/dist/vitest/mocks/workflow.d.mts +59 -33
- package/dist/vitest/setup.mjs +2 -2
- package/dist/vitest/workflow-local.d.mts +2 -2
- package/dist/workflow-CsBvRE3g.mjs +34 -0
- package/dist/workflow-CsBvRE3g.mjs.map +1 -0
- package/dist/{service_pb-Bk7dL-yT.mjs → workspace_resource_pb-Db3fv68L.mjs} +47 -165
- package/dist/workspace_resource_pb-Db3fv68L.mjs.map +1 -0
- package/docs/cli/application.md +63 -15
- package/docs/cli/function.md +2 -2
- package/docs/cli/tailordb.md +8 -140
- package/docs/cli/workspace.md +6 -4
- package/docs/cli-reference.md +11 -8
- package/docs/configuration.md +2 -0
- package/docs/github-actions.md +8 -0
- package/docs/migration/v2.md +169 -11
- package/docs/runtime.md +5 -6
- package/docs/services/auth.md +7 -7
- package/docs/services/executor.md +5 -0
- package/docs/services/tailordb.md +44 -0
- package/docs/services/workflow.md +43 -43
- package/docs/testing.md +112 -96
- package/package.json +2 -5
- package/dist/application-Bf8wxOi6.mjs.map +0 -1
- package/dist/application-CUkjzfij.mjs +0 -3
- package/dist/cli/erd-viewer-assets/app.js +0 -1424
- package/dist/cli/erd-viewer-assets/index.html +0 -77
- package/dist/cli/erd-viewer-assets/serve.json +0 -13
- package/dist/cli/erd-viewer-assets/styles.css +0 -1036
- package/dist/configure/types/field-runtime.d.mts +0 -16
- package/dist/errors-118wUDRr.mjs +0 -42
- package/dist/errors-118wUDRr.mjs.map +0 -1
- package/dist/globals-BjhJJ2dN.mjs.map +0 -1
- package/dist/logger-BEiZZ3qT.mjs.map +0 -1
- package/dist/register-ts-hook-BhzVkej0.mjs.map +0 -1
- package/dist/registry-BSbq6SPo.mjs.map +0 -1
- package/dist/resource_pb-BRv7AMXK.mjs +0 -49
- package/dist/resource_pb-BRv7AMXK.mjs.map +0 -1
- package/dist/schema-iCd1ugGp.mjs.map +0 -1
- package/dist/secret-file-aoNXJmKa.mjs.map +0 -1
- package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
- package/dist/service-BT0GPd01.mjs +0 -3
- package/dist/service-CzZEVrp9.mjs.map +0 -1
- package/dist/service_pb-Bk7dL-yT.mjs.map +0 -1
- package/dist/workflow-GNuSnsTx.mjs +0 -22
- package/dist/workflow-GNuSnsTx.mjs.map +0 -1
package/dist/vitest/index.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { i as getRegisteredJob, t as
|
|
1
|
+
import { i as getRegisteredJob, t as START_DEFAULT } from "../registry-i7EdJ-D5.mjs";
|
|
2
2
|
import { a as writeWorkflowTestEnv, n as clearWorkflowTestEnv, r as readWorkflowTestEnv, t as buildJobContext } from "../test-env-key-D7UkZp99.mjs";
|
|
3
3
|
import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
|
|
4
4
|
import { t as platformSerialize } from "../platform-serialize-RoRtBS0v.mjs";
|
|
5
|
-
import { i as withDispose, n as tailorRoot, r as tailordbRoot, t as mockSecretmanager } from "../secretmanager-
|
|
5
|
+
import { i as withDispose, n as tailorRoot, r as tailordbRoot, t as mockSecretmanager } from "../secretmanager-IY4UvinW.mjs";
|
|
6
6
|
import { builtinModules } from "node:module";
|
|
7
|
+
import { isEqual } from "es-toolkit";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { dirname, isAbsolute, matchesGlob, relative, resolve } from "node:path";
|
|
9
10
|
import { ColumnNode, InsertQueryNode, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler, PrimitiveValueListNode, ReferenceNode, UpdateQueryNode, ValueListNode, ValueNode, ValuesNode } from "kysely";
|
|
@@ -297,27 +298,31 @@ function createEnvironmentPlugin(options) {
|
|
|
297
298
|
//#region src/vitest/mocks/aigateway.ts
|
|
298
299
|
/**
|
|
299
300
|
* Acquire a disposable mock for `tailor.aigateway`. Restored on dispose.
|
|
301
|
+
* @param options - Initial AI Gateway URL fixtures
|
|
300
302
|
* @returns Disposable AI Gateway mock control object
|
|
301
303
|
* @example
|
|
302
304
|
* ```typescript
|
|
303
305
|
* import { mockAigateway } from "@tailor-platform/sdk/vitest";
|
|
304
306
|
*
|
|
305
307
|
* test("resolves an AI Gateway URL", async () => {
|
|
306
|
-
* using aigateway = mockAigateway(
|
|
307
|
-
*
|
|
308
|
+
* using aigateway = mockAigateway({
|
|
309
|
+
* urls: { "my-aigateway": "https://my-aigateway.example.com" },
|
|
310
|
+
* });
|
|
311
|
+
* aigateway.setUrl("my-aigateway", "https://replacement.example.com");
|
|
308
312
|
* // …
|
|
309
313
|
* });
|
|
310
314
|
* ```
|
|
311
315
|
*/
|
|
312
|
-
function mockAigateway() {
|
|
316
|
+
function mockAigateway(options = {}) {
|
|
313
317
|
const root = tailorRoot();
|
|
314
318
|
const prev = root.aigateway;
|
|
315
|
-
let urls = {};
|
|
316
|
-
|
|
319
|
+
let urls = { ...options.urls };
|
|
320
|
+
async function defaultGet(name) {
|
|
317
321
|
const url = urls[name];
|
|
318
322
|
if (url === void 0) throw new Error(`No AI Gateway registered for "${name}". Acquire mockAigateway() and call setUrls(...).`);
|
|
319
323
|
return { url };
|
|
320
|
-
}
|
|
324
|
+
}
|
|
325
|
+
const get = vi.fn(defaultGet);
|
|
321
326
|
root.aigateway = { get };
|
|
322
327
|
return withDispose({
|
|
323
328
|
/** The `get` `vi.fn`. */
|
|
@@ -325,12 +330,22 @@ function mockAigateway() {
|
|
|
325
330
|
setUrls(value) {
|
|
326
331
|
urls = value;
|
|
327
332
|
},
|
|
333
|
+
setUrl(name, url) {
|
|
334
|
+
urls = {
|
|
335
|
+
...urls,
|
|
336
|
+
[name]: url
|
|
337
|
+
};
|
|
338
|
+
},
|
|
328
339
|
get calls() {
|
|
329
340
|
return get.mock.calls.map(([name]) => ({ name }));
|
|
330
341
|
},
|
|
342
|
+
clear() {
|
|
343
|
+
get.mockClear();
|
|
344
|
+
},
|
|
331
345
|
reset() {
|
|
332
346
|
urls = {};
|
|
333
|
-
get.
|
|
347
|
+
get.mockReset();
|
|
348
|
+
get.mockImplementation(defaultGet);
|
|
334
349
|
}
|
|
335
350
|
}, () => {
|
|
336
351
|
root.aigateway = prev;
|
|
@@ -341,23 +356,30 @@ function mockAigateway() {
|
|
|
341
356
|
//#region src/vitest/mocks/authconnection.ts
|
|
342
357
|
/**
|
|
343
358
|
* Acquire a disposable mock for `tailor.authconnection`. Restored on dispose.
|
|
359
|
+
* @param options - Initial token fixtures and fallback behavior
|
|
344
360
|
* @returns Disposable AuthConnection mock control object
|
|
345
361
|
* @example
|
|
346
362
|
* ```typescript
|
|
347
363
|
* import { mockAuthconnection } from "@tailor-platform/sdk/vitest";
|
|
348
364
|
*
|
|
349
365
|
* test("returns configured token", async () => {
|
|
350
|
-
* using ac = mockAuthconnection();
|
|
351
|
-
* ac.
|
|
366
|
+
* using ac = mockAuthconnection({ tokens: { google: { access_token: "ya29.xxx" } } });
|
|
367
|
+
* ac.setToken("google", { access_token: "replacement" });
|
|
352
368
|
* // …
|
|
353
369
|
* });
|
|
354
370
|
* ```
|
|
355
371
|
*/
|
|
356
|
-
function mockAuthconnection() {
|
|
372
|
+
function mockAuthconnection(options = {}) {
|
|
357
373
|
const root = tailorRoot();
|
|
358
374
|
const prev = root.authconnection;
|
|
359
|
-
let tokens = {};
|
|
360
|
-
|
|
375
|
+
let tokens = { ...options.tokens };
|
|
376
|
+
async function defaultGetConnectionToken(connectionName) {
|
|
377
|
+
const token = tokens[connectionName];
|
|
378
|
+
if (token) return token;
|
|
379
|
+
if (options.onUnhandled === "error") throw new Error(`No AuthConnection token configured for "${connectionName}"`);
|
|
380
|
+
return { access_token: "mock-token" };
|
|
381
|
+
}
|
|
382
|
+
const getConnectionToken = vi.fn(defaultGetConnectionToken);
|
|
361
383
|
root.authconnection = { getConnectionToken };
|
|
362
384
|
return withDispose({
|
|
363
385
|
/** The `getConnectionToken` `vi.fn`. */
|
|
@@ -365,12 +387,22 @@ function mockAuthconnection() {
|
|
|
365
387
|
setTokens(value) {
|
|
366
388
|
tokens = value;
|
|
367
389
|
},
|
|
390
|
+
setToken(connectionName, token) {
|
|
391
|
+
tokens = {
|
|
392
|
+
...tokens,
|
|
393
|
+
[connectionName]: token
|
|
394
|
+
};
|
|
395
|
+
},
|
|
368
396
|
get calls() {
|
|
369
397
|
return getConnectionToken.mock.calls.map(([connectionName]) => ({ connectionName }));
|
|
370
398
|
},
|
|
399
|
+
clear() {
|
|
400
|
+
getConnectionToken.mockClear();
|
|
401
|
+
},
|
|
371
402
|
reset() {
|
|
372
403
|
tokens = {};
|
|
373
|
-
getConnectionToken.
|
|
404
|
+
getConnectionToken.mockReset();
|
|
405
|
+
getConnectionToken.mockImplementation(defaultGetConnectionToken);
|
|
374
406
|
}
|
|
375
407
|
}, () => {
|
|
376
408
|
root.authconnection = prev;
|
|
@@ -379,6 +411,15 @@ function mockAuthconnection() {
|
|
|
379
411
|
|
|
380
412
|
//#endregion
|
|
381
413
|
//#region src/vitest/mocks/file.ts
|
|
414
|
+
const FILE_METHODS = [
|
|
415
|
+
"upload",
|
|
416
|
+
"download",
|
|
417
|
+
"downloadAsBase64",
|
|
418
|
+
"delete",
|
|
419
|
+
"getMetadata",
|
|
420
|
+
"downloadStream",
|
|
421
|
+
"uploadStream"
|
|
422
|
+
];
|
|
382
423
|
const FILE_DEFAULTS = {
|
|
383
424
|
upload: { metadata: {
|
|
384
425
|
fileSize: 0,
|
|
@@ -402,7 +443,6 @@ const FILE_DEFAULTS = {
|
|
|
402
443
|
lastUploadedAt: ""
|
|
403
444
|
}
|
|
404
445
|
},
|
|
405
|
-
delete: void 0,
|
|
406
446
|
getMetadata: {
|
|
407
447
|
contentType: "",
|
|
408
448
|
fileSize: 0,
|
|
@@ -417,6 +457,7 @@ const FILE_DEFAULTS = {
|
|
|
417
457
|
};
|
|
418
458
|
/**
|
|
419
459
|
* Acquire a disposable mock for `tailordb.file`. Restored on dispose.
|
|
460
|
+
* @param options - Controls behavior for calls without a configured result
|
|
420
461
|
* @returns Disposable File mock control object
|
|
421
462
|
* @example
|
|
422
463
|
* ```typescript
|
|
@@ -424,74 +465,102 @@ const FILE_DEFAULTS = {
|
|
|
424
465
|
*
|
|
425
466
|
* test("mock file download", async () => {
|
|
426
467
|
* using file = mockFile();
|
|
427
|
-
* file.
|
|
468
|
+
* file.download.mockResolvedValue({ data: new Uint8Array(), metadata: { ... } });
|
|
428
469
|
* // …
|
|
429
470
|
* });
|
|
430
471
|
* ```
|
|
431
472
|
*/
|
|
432
|
-
function mockFile() {
|
|
473
|
+
function mockFile(options = {}) {
|
|
433
474
|
const root = tailordbRoot();
|
|
434
475
|
const prev = root.file;
|
|
476
|
+
const { onUnhandled = "fallback" } = options;
|
|
435
477
|
const queue = [];
|
|
436
|
-
let resolver = () => null;
|
|
437
478
|
const calls = [];
|
|
479
|
+
let resolver = () => null;
|
|
438
480
|
function handle(method, namespace, typeName, fieldName, recordId) {
|
|
439
|
-
|
|
481
|
+
if (queue.length > 0) return queue.shift();
|
|
482
|
+
const resolved = resolver(method, {
|
|
440
483
|
method,
|
|
441
484
|
namespace,
|
|
442
485
|
typeName,
|
|
443
486
|
fieldName,
|
|
444
487
|
recordId
|
|
445
|
-
};
|
|
446
|
-
calls.push(call);
|
|
447
|
-
if (queue.length > 0) return queue.shift();
|
|
448
|
-
const resolved = resolver(method, call);
|
|
488
|
+
});
|
|
449
489
|
if (resolved != null) return resolved;
|
|
490
|
+
if (onUnhandled === "error") throw new Error(`No File mock configured for "${method}"`);
|
|
450
491
|
const fallback = FILE_DEFAULTS[method];
|
|
451
492
|
return fallback === void 0 ? void 0 : structuredClone(fallback);
|
|
452
493
|
}
|
|
494
|
+
const upload = vi.fn(async (...args) => {
|
|
495
|
+
const [namespace, typeName, fieldName, recordId] = args;
|
|
496
|
+
return handle("upload", namespace, typeName, fieldName, recordId);
|
|
497
|
+
});
|
|
498
|
+
const download = vi.fn(async (...args) => handle("download", ...args));
|
|
499
|
+
const downloadAsBase64 = vi.fn(async (...args) => handle("downloadAsBase64", ...args));
|
|
500
|
+
const deleteFile = vi.fn(async (...args) => {
|
|
501
|
+
handle("delete", ...args);
|
|
502
|
+
});
|
|
503
|
+
const getMetadata = vi.fn(async (...args) => handle("getMetadata", ...args));
|
|
504
|
+
const downloadStream = vi.fn(async (...args) => {
|
|
505
|
+
const resolved = handle("downloadStream", ...args);
|
|
506
|
+
if (resolved != null) return resolved;
|
|
507
|
+
return {
|
|
508
|
+
body: new ReadableStream({ start(controller) {
|
|
509
|
+
controller.close();
|
|
510
|
+
} }),
|
|
511
|
+
metadata: {
|
|
512
|
+
contentType: "",
|
|
513
|
+
fileSize: 0,
|
|
514
|
+
sha256sum: "",
|
|
515
|
+
lastUploadedAt: ""
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
});
|
|
519
|
+
const uploadStream = vi.fn(async (...args) => {
|
|
520
|
+
const [namespace, typeName, fieldName, recordId] = args;
|
|
521
|
+
return handle("uploadStream", namespace, typeName, fieldName, recordId);
|
|
522
|
+
});
|
|
523
|
+
const mocks = {
|
|
524
|
+
upload,
|
|
525
|
+
download,
|
|
526
|
+
downloadAsBase64,
|
|
527
|
+
delete: deleteFile,
|
|
528
|
+
getMetadata,
|
|
529
|
+
downloadStream,
|
|
530
|
+
uploadStream
|
|
531
|
+
};
|
|
532
|
+
function track(method, operation) {
|
|
533
|
+
return function(...args) {
|
|
534
|
+
calls.push({
|
|
535
|
+
method,
|
|
536
|
+
namespace: args[0],
|
|
537
|
+
typeName: args[1],
|
|
538
|
+
fieldName: args[2],
|
|
539
|
+
recordId: args[3]
|
|
540
|
+
});
|
|
541
|
+
return operation.apply(this, args);
|
|
542
|
+
};
|
|
543
|
+
}
|
|
453
544
|
root.file = {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return handle("downloadAsBase64", namespace, typeName, fieldName, recordId);
|
|
462
|
-
},
|
|
463
|
-
async delete(namespace, typeName, fieldName, recordId) {
|
|
464
|
-
handle("delete", namespace, typeName, fieldName, recordId);
|
|
465
|
-
},
|
|
466
|
-
async getMetadata(namespace, typeName, fieldName, recordId) {
|
|
467
|
-
return handle("getMetadata", namespace, typeName, fieldName, recordId);
|
|
468
|
-
},
|
|
469
|
-
async downloadStream(namespace, typeName, fieldName, recordId) {
|
|
470
|
-
const resolved = handle("downloadStream", namespace, typeName, fieldName, recordId);
|
|
471
|
-
if (resolved != null) return resolved;
|
|
472
|
-
return {
|
|
473
|
-
body: new ReadableStream({ start(c) {
|
|
474
|
-
c.close();
|
|
475
|
-
} }),
|
|
476
|
-
metadata: {
|
|
477
|
-
contentType: "",
|
|
478
|
-
fileSize: 0,
|
|
479
|
-
sha256sum: "",
|
|
480
|
-
lastUploadedAt: ""
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
},
|
|
484
|
-
async uploadStream(namespace, typeName, fieldName, recordId) {
|
|
485
|
-
return handle("uploadStream", namespace, typeName, fieldName, recordId);
|
|
486
|
-
}
|
|
545
|
+
upload: track("upload", upload),
|
|
546
|
+
download: track("download", download),
|
|
547
|
+
downloadAsBase64: track("downloadAsBase64", downloadAsBase64),
|
|
548
|
+
delete: track("delete", deleteFile),
|
|
549
|
+
getMetadata: track("getMetadata", getMetadata),
|
|
550
|
+
downloadStream: track("downloadStream", downloadStream),
|
|
551
|
+
uploadStream: track("uploadStream", uploadStream)
|
|
487
552
|
};
|
|
553
|
+
function allMocks() {
|
|
554
|
+
return FILE_METHODS.map((method) => mocks[method]);
|
|
555
|
+
}
|
|
488
556
|
return withDispose({
|
|
557
|
+
...mocks,
|
|
489
558
|
setResolver(value) {
|
|
490
559
|
resolver = value;
|
|
491
560
|
},
|
|
492
561
|
/**
|
|
493
|
-
* Enqueue a single result for the next `tailordb.file` call
|
|
494
|
-
*
|
|
562
|
+
* Enqueue a single result for the next `tailordb.file` call.
|
|
563
|
+
* The queue is shared across all methods and namespaces.
|
|
495
564
|
* @param result - Result to return from the next file call
|
|
496
565
|
*/
|
|
497
566
|
enqueueResult(result) {
|
|
@@ -499,6 +568,7 @@ function mockFile() {
|
|
|
499
568
|
},
|
|
500
569
|
/**
|
|
501
570
|
* Enqueue results for multiple subsequent `tailordb.file` calls.
|
|
571
|
+
* The queue is shared across all methods and namespaces.
|
|
502
572
|
* @param results - Results to enqueue, one per upcoming call
|
|
503
573
|
*/
|
|
504
574
|
enqueueResults(...results) {
|
|
@@ -507,10 +577,15 @@ function mockFile() {
|
|
|
507
577
|
get calls() {
|
|
508
578
|
return calls;
|
|
509
579
|
},
|
|
580
|
+
clear() {
|
|
581
|
+
calls.length = 0;
|
|
582
|
+
for (const mock of allMocks()) mock.mockClear();
|
|
583
|
+
},
|
|
510
584
|
reset() {
|
|
511
585
|
queue.length = 0;
|
|
512
|
-
resolver = () => null;
|
|
513
586
|
calls.length = 0;
|
|
587
|
+
resolver = () => null;
|
|
588
|
+
for (const mock of allMocks()) mock.mockReset();
|
|
514
589
|
}
|
|
515
590
|
}, () => {
|
|
516
591
|
root.file = prev;
|
|
@@ -529,11 +604,11 @@ function defaultIconvResult(method, args) {
|
|
|
529
604
|
case "decode": return "";
|
|
530
605
|
case "encode": return isUtf8(args[1]) ? "" : /* @__PURE__ */ new Uint8Array();
|
|
531
606
|
case "encodings": return [];
|
|
532
|
-
default: return;
|
|
533
607
|
}
|
|
534
608
|
}
|
|
535
609
|
/**
|
|
536
610
|
* Acquire a disposable mock for `tailor.iconv`. Restored on dispose.
|
|
611
|
+
* @param options - Fallback behavior for unconfigured operations
|
|
537
612
|
* @returns Disposable Iconv mock control object
|
|
538
613
|
* @example
|
|
539
614
|
* ```typescript
|
|
@@ -541,27 +616,66 @@ function defaultIconvResult(method, args) {
|
|
|
541
616
|
*
|
|
542
617
|
* test("mock encoding conversion", () => {
|
|
543
618
|
* using iconv = mockIconv();
|
|
544
|
-
* iconv.
|
|
619
|
+
* iconv.decode.mockReturnValue("decoded-text");
|
|
545
620
|
* // …
|
|
546
621
|
* });
|
|
547
622
|
* ```
|
|
548
623
|
*/
|
|
549
|
-
function mockIconv() {
|
|
624
|
+
function mockIconv(options = {}) {
|
|
550
625
|
const root = tailorRoot();
|
|
551
626
|
const prev = root.iconv;
|
|
552
627
|
let resolver = null;
|
|
553
628
|
const calls = [];
|
|
554
|
-
function
|
|
555
|
-
calls.push({
|
|
556
|
-
method,
|
|
557
|
-
args: [...args]
|
|
558
|
-
});
|
|
629
|
+
function resolve(method, args) {
|
|
559
630
|
if (resolver) {
|
|
560
631
|
const result = resolver(method, args);
|
|
561
632
|
if (result != null) return result;
|
|
562
633
|
}
|
|
634
|
+
if (options.onUnhandled === "error") throw new Error(`No Iconv mock behavior configured for "${method}"`);
|
|
563
635
|
return defaultIconvResult(method, args);
|
|
564
636
|
}
|
|
637
|
+
function defaultConvert(input, fromEncoding, toEncoding) {
|
|
638
|
+
return resolve("convert", [
|
|
639
|
+
input,
|
|
640
|
+
fromEncoding,
|
|
641
|
+
toEncoding
|
|
642
|
+
]);
|
|
643
|
+
}
|
|
644
|
+
function defaultConvertBuffer(input, fromEncoding, toEncoding) {
|
|
645
|
+
return resolve("convertBuffer", [
|
|
646
|
+
input,
|
|
647
|
+
fromEncoding,
|
|
648
|
+
toEncoding
|
|
649
|
+
]);
|
|
650
|
+
}
|
|
651
|
+
function defaultDecode(input, encoding) {
|
|
652
|
+
return resolve("decode", [input, encoding]);
|
|
653
|
+
}
|
|
654
|
+
function defaultEncode(input, encoding) {
|
|
655
|
+
return resolve("encode", [input, encoding]);
|
|
656
|
+
}
|
|
657
|
+
function defaultEncodings() {
|
|
658
|
+
return resolve("encodings", []);
|
|
659
|
+
}
|
|
660
|
+
const convert = vi.fn(defaultConvert);
|
|
661
|
+
const convertBuffer = vi.fn(defaultConvertBuffer);
|
|
662
|
+
const decode = vi.fn(defaultDecode);
|
|
663
|
+
const encode = vi.fn(defaultEncode);
|
|
664
|
+
const encodings = vi.fn(defaultEncodings);
|
|
665
|
+
function track(method, operation) {
|
|
666
|
+
return function(...args) {
|
|
667
|
+
calls.push({
|
|
668
|
+
method,
|
|
669
|
+
args: [...args]
|
|
670
|
+
});
|
|
671
|
+
return operation.apply(this, args);
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
const trackedConvert = track("convert", convert);
|
|
675
|
+
const trackedConvertBuffer = track("convertBuffer", convertBuffer);
|
|
676
|
+
const trackedDecode = track("decode", decode);
|
|
677
|
+
const trackedEncode = track("encode", encode);
|
|
678
|
+
const trackedEncodings = track("encodings", encodings);
|
|
565
679
|
class MockIconv {
|
|
566
680
|
#fromEncoding;
|
|
567
681
|
#toEncoding;
|
|
@@ -570,39 +684,56 @@ function mockIconv() {
|
|
|
570
684
|
this.#toEncoding = toEncoding;
|
|
571
685
|
}
|
|
572
686
|
convert(input) {
|
|
573
|
-
return
|
|
574
|
-
input,
|
|
575
|
-
this.#fromEncoding,
|
|
576
|
-
this.#toEncoding
|
|
577
|
-
]);
|
|
687
|
+
return trackedConvert.call(this, input, this.#fromEncoding, this.#toEncoding);
|
|
578
688
|
}
|
|
579
689
|
}
|
|
580
690
|
root.iconv = {
|
|
581
|
-
convert:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
convertBuffer: (buf, from, to) => handle("convertBuffer", [
|
|
587
|
-
buf,
|
|
588
|
-
from,
|
|
589
|
-
to
|
|
590
|
-
]),
|
|
591
|
-
decode: (buf, encoding) => handle("decode", [buf, encoding]),
|
|
592
|
-
encode: (str, encoding) => handle("encode", [str, encoding]),
|
|
593
|
-
encodings: () => handle("encodings", []),
|
|
691
|
+
convert: trackedConvert,
|
|
692
|
+
convertBuffer: trackedConvertBuffer,
|
|
693
|
+
decode: trackedDecode,
|
|
694
|
+
encode: trackedEncode,
|
|
695
|
+
encodings: trackedEncodings,
|
|
594
696
|
Iconv: MockIconv
|
|
595
697
|
};
|
|
698
|
+
function clear() {
|
|
699
|
+
calls.length = 0;
|
|
700
|
+
convert.mockClear();
|
|
701
|
+
convertBuffer.mockClear();
|
|
702
|
+
decode.mockClear();
|
|
703
|
+
encode.mockClear();
|
|
704
|
+
encodings.mockClear();
|
|
705
|
+
}
|
|
596
706
|
return withDispose({
|
|
707
|
+
/** The `convert` `vi.fn`. */
|
|
708
|
+
convert,
|
|
709
|
+
/** The `convertBuffer` `vi.fn`. */
|
|
710
|
+
convertBuffer,
|
|
711
|
+
/** The `decode` `vi.fn`. */
|
|
712
|
+
decode,
|
|
713
|
+
/** The `encode` `vi.fn`. */
|
|
714
|
+
encode,
|
|
715
|
+
/** The `encodings` `vi.fn`. */
|
|
716
|
+
encodings,
|
|
597
717
|
setResolver(value) {
|
|
598
718
|
resolver = value;
|
|
599
719
|
},
|
|
600
720
|
get calls() {
|
|
601
721
|
return calls;
|
|
602
722
|
},
|
|
723
|
+
clear,
|
|
603
724
|
reset() {
|
|
604
725
|
resolver = null;
|
|
605
726
|
calls.length = 0;
|
|
727
|
+
convert.mockReset();
|
|
728
|
+
convert.mockImplementation(defaultConvert);
|
|
729
|
+
convertBuffer.mockReset();
|
|
730
|
+
convertBuffer.mockImplementation(defaultConvertBuffer);
|
|
731
|
+
decode.mockReset();
|
|
732
|
+
decode.mockImplementation(defaultDecode);
|
|
733
|
+
encode.mockReset();
|
|
734
|
+
encode.mockImplementation(defaultEncode);
|
|
735
|
+
encodings.mockReset();
|
|
736
|
+
encodings.mockImplementation(defaultEncodings);
|
|
606
737
|
}
|
|
607
738
|
}, () => {
|
|
608
739
|
root.iconv = prev;
|
|
@@ -611,99 +742,131 @@ function mockIconv() {
|
|
|
611
742
|
|
|
612
743
|
//#endregion
|
|
613
744
|
//#region src/vitest/mocks/idp.ts
|
|
745
|
+
const IDP_METHODS = [
|
|
746
|
+
"users",
|
|
747
|
+
"user",
|
|
748
|
+
"userByName",
|
|
749
|
+
"createUser",
|
|
750
|
+
"updateUser",
|
|
751
|
+
"deleteUser",
|
|
752
|
+
"sendPasswordResetEmail",
|
|
753
|
+
"unenrollMfa"
|
|
754
|
+
];
|
|
755
|
+
const IDP_USER_DEFAULT = {
|
|
756
|
+
id: "mock-id",
|
|
757
|
+
name: "mock-user",
|
|
758
|
+
disabled: false,
|
|
759
|
+
mfaEnrolled: false,
|
|
760
|
+
mfaFactorIds: []
|
|
761
|
+
};
|
|
614
762
|
const IDP_DEFAULTS = {
|
|
615
763
|
users: {
|
|
616
764
|
users: [],
|
|
617
765
|
nextPageToken: null,
|
|
618
766
|
totalCount: 0
|
|
619
767
|
},
|
|
620
|
-
user:
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
mfaEnrolled: false,
|
|
625
|
-
mfaFactorIds: []
|
|
626
|
-
},
|
|
627
|
-
userByName: {
|
|
628
|
-
id: "mock-id",
|
|
629
|
-
name: "mock-user",
|
|
630
|
-
disabled: false,
|
|
631
|
-
mfaEnrolled: false,
|
|
632
|
-
mfaFactorIds: []
|
|
633
|
-
},
|
|
634
|
-
createUser: {
|
|
635
|
-
id: "mock-id",
|
|
636
|
-
name: "mock-user",
|
|
637
|
-
disabled: false,
|
|
638
|
-
mfaEnrolled: false,
|
|
639
|
-
mfaFactorIds: []
|
|
640
|
-
},
|
|
641
|
-
updateUser: {
|
|
642
|
-
id: "mock-id",
|
|
643
|
-
name: "mock-user",
|
|
644
|
-
disabled: false,
|
|
645
|
-
mfaEnrolled: false,
|
|
646
|
-
mfaFactorIds: []
|
|
647
|
-
},
|
|
768
|
+
user: IDP_USER_DEFAULT,
|
|
769
|
+
userByName: IDP_USER_DEFAULT,
|
|
770
|
+
createUser: IDP_USER_DEFAULT,
|
|
771
|
+
updateUser: IDP_USER_DEFAULT,
|
|
648
772
|
deleteUser: true,
|
|
649
773
|
sendPasswordResetEmail: true,
|
|
650
774
|
unenrollMfa: true
|
|
651
775
|
};
|
|
652
776
|
/**
|
|
653
777
|
* Acquire a disposable mock for `tailor.idp`. Restored on dispose.
|
|
778
|
+
* @param options - Controls behavior for calls without a configured result
|
|
654
779
|
* @returns Disposable IDP mock control object
|
|
655
780
|
* @example
|
|
656
781
|
* ```typescript
|
|
657
782
|
* import { mockIdp } from "@tailor-platform/sdk/vitest";
|
|
658
783
|
*
|
|
659
|
-
* test("
|
|
784
|
+
* test("returns a user", async () => {
|
|
660
785
|
* using idp = mockIdp();
|
|
661
|
-
* idp.
|
|
662
|
-
*
|
|
663
|
-
*
|
|
786
|
+
* idp.namespace("my-idp").user.mockResolvedValue({
|
|
787
|
+
* id: "u-1",
|
|
788
|
+
* name: "alice",
|
|
789
|
+
* disabled: false,
|
|
790
|
+
* mfaEnrolled: false,
|
|
791
|
+
* mfaFactorIds: [],
|
|
792
|
+
* });
|
|
664
793
|
* // …
|
|
665
794
|
* });
|
|
666
795
|
* ```
|
|
667
796
|
*/
|
|
668
|
-
function mockIdp() {
|
|
797
|
+
function mockIdp(options = {}) {
|
|
669
798
|
const root = tailorRoot();
|
|
670
799
|
const prev = root.idp;
|
|
800
|
+
const { onUnhandled = "fallback" } = options;
|
|
671
801
|
const queue = [];
|
|
672
|
-
let resolver = () => null;
|
|
673
802
|
const calls = [];
|
|
803
|
+
let resolver = () => null;
|
|
804
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
805
|
+
function compatibilityArgs(method, args) {
|
|
806
|
+
return method === "users" && args.length === 0 ? [void 0] : args;
|
|
807
|
+
}
|
|
674
808
|
function handle(method, args, namespace) {
|
|
675
|
-
calls.push({
|
|
676
|
-
method,
|
|
677
|
-
args,
|
|
678
|
-
namespace
|
|
679
|
-
});
|
|
680
809
|
if (queue.length > 0) return queue.shift();
|
|
681
|
-
const resolved = resolver(method, args, namespace);
|
|
810
|
+
const resolved = resolver(method, compatibilityArgs(method, args), namespace);
|
|
682
811
|
if (resolved != null) return resolved;
|
|
683
|
-
|
|
684
|
-
return
|
|
812
|
+
if (onUnhandled === "error") throw new Error(`No IdP mock configured for "${namespace}.${method}"`);
|
|
813
|
+
return structuredClone(IDP_DEFAULTS[method]);
|
|
685
814
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
815
|
+
function createNamespaceMocks(namespace) {
|
|
816
|
+
return {
|
|
817
|
+
users: vi.fn(async (...args) => handle("users", args, namespace)),
|
|
818
|
+
user: vi.fn(async (...args) => handle("user", args, namespace)),
|
|
819
|
+
userByName: vi.fn(async (...args) => handle("userByName", args, namespace)),
|
|
820
|
+
createUser: vi.fn(async (...args) => handle("createUser", args, namespace)),
|
|
821
|
+
updateUser: vi.fn(async (...args) => handle("updateUser", args, namespace)),
|
|
822
|
+
deleteUser: vi.fn(async (...args) => handle("deleteUser", args, namespace)),
|
|
823
|
+
sendPasswordResetEmail: vi.fn(async (...args) => handle("sendPasswordResetEmail", args, namespace)),
|
|
824
|
+
unenrollMfa: vi.fn(async (...args) => handle("unenrollMfa", args, namespace))
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function namespace(name) {
|
|
828
|
+
const existing = namespaces.get(name);
|
|
829
|
+
if (existing) return existing;
|
|
830
|
+
const mocks = createNamespaceMocks(name);
|
|
831
|
+
namespaces.set(name, mocks);
|
|
832
|
+
return mocks;
|
|
833
|
+
}
|
|
834
|
+
function track(method, mock, namespaceName) {
|
|
835
|
+
return function(...args) {
|
|
836
|
+
calls.push({
|
|
837
|
+
method,
|
|
838
|
+
args: [...compatibilityArgs(method, args)],
|
|
839
|
+
namespace: namespaceName
|
|
840
|
+
});
|
|
841
|
+
return mock.apply(this, args);
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const defaultClient = function(config) {
|
|
845
|
+
const mocks = namespace(config.namespace);
|
|
846
|
+
this.users = track("users", mocks.users, config.namespace);
|
|
847
|
+
this.user = track("user", mocks.user, config.namespace);
|
|
848
|
+
this.userByName = track("userByName", mocks.userByName, config.namespace);
|
|
849
|
+
this.createUser = track("createUser", mocks.createUser, config.namespace);
|
|
850
|
+
this.updateUser = track("updateUser", mocks.updateUser, config.namespace);
|
|
851
|
+
this.deleteUser = track("deleteUser", mocks.deleteUser, config.namespace);
|
|
852
|
+
this.sendPasswordResetEmail = track("sendPasswordResetEmail", mocks.sendPasswordResetEmail, config.namespace);
|
|
853
|
+
this.unenrollMfa = track("unenrollMfa", mocks.unenrollMfa, config.namespace);
|
|
854
|
+
};
|
|
855
|
+
const Client = vi.fn(defaultClient);
|
|
697
856
|
root.idp = { Client };
|
|
857
|
+
function allMocks() {
|
|
858
|
+
return [...namespaces.values()].flatMap((mocks) => IDP_METHODS.map((method) => mocks[method]));
|
|
859
|
+
}
|
|
698
860
|
return withDispose({
|
|
699
861
|
/** The mock IDP `Client` constructor (`vi.fn`). */
|
|
700
862
|
Client,
|
|
863
|
+
namespace,
|
|
701
864
|
setResolver(value) {
|
|
702
865
|
resolver = value;
|
|
703
866
|
},
|
|
704
867
|
/**
|
|
705
|
-
* Enqueue a single result for the next IDP call
|
|
706
|
-
*
|
|
868
|
+
* Enqueue a single result for the next IDP call.
|
|
869
|
+
* The queue is shared across all methods and namespaces.
|
|
707
870
|
* @param result - Result to return from the next IDP call
|
|
708
871
|
*/
|
|
709
872
|
enqueueResult(result) {
|
|
@@ -711,6 +874,7 @@ function mockIdp() {
|
|
|
711
874
|
},
|
|
712
875
|
/**
|
|
713
876
|
* Enqueue results for multiple subsequent IDP calls.
|
|
877
|
+
* The queue is shared across all methods and namespaces.
|
|
714
878
|
* @param results - Results to enqueue, one per upcoming call
|
|
715
879
|
*/
|
|
716
880
|
enqueueResults(...results) {
|
|
@@ -719,10 +883,18 @@ function mockIdp() {
|
|
|
719
883
|
get calls() {
|
|
720
884
|
return calls;
|
|
721
885
|
},
|
|
886
|
+
clear() {
|
|
887
|
+
calls.length = 0;
|
|
888
|
+
Client.mockClear();
|
|
889
|
+
for (const mock of allMocks()) mock.mockClear();
|
|
890
|
+
},
|
|
722
891
|
reset() {
|
|
723
892
|
queue.length = 0;
|
|
724
|
-
resolver = () => null;
|
|
725
893
|
calls.length = 0;
|
|
894
|
+
resolver = () => null;
|
|
895
|
+
Client.mockReset();
|
|
896
|
+
Client.mockImplementation(defaultClient);
|
|
897
|
+
for (const mock of allMocks()) mock.mockReset();
|
|
726
898
|
}
|
|
727
899
|
}, () => {
|
|
728
900
|
root.idp = prev;
|
|
@@ -731,6 +903,15 @@ function mockIdp() {
|
|
|
731
903
|
|
|
732
904
|
//#endregion
|
|
733
905
|
//#region src/vitest/mocks/tailordb.ts
|
|
906
|
+
function testRegex(regex, value) {
|
|
907
|
+
const lastIndex = regex.lastIndex;
|
|
908
|
+
regex.lastIndex = 0;
|
|
909
|
+
try {
|
|
910
|
+
return regex.test(value);
|
|
911
|
+
} finally {
|
|
912
|
+
regex.lastIndex = lastIndex;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
734
915
|
var MockQueryResult = class {
|
|
735
916
|
command;
|
|
736
917
|
rowCount;
|
|
@@ -746,27 +927,62 @@ var MockQueryResult = class {
|
|
|
746
927
|
* `tailordb.Client` whose `queryObject` is a shared `vi.fn()` (so query
|
|
747
928
|
* responses can be staged before the client is constructed). Restored on
|
|
748
929
|
* dispose.
|
|
930
|
+
* @param options - Query fallback behavior
|
|
749
931
|
* @returns Disposable TailorDB mock control object
|
|
750
932
|
* @example
|
|
751
933
|
* ```typescript
|
|
752
934
|
* import { mockTailordb } from "@tailor-platform/sdk/vitest";
|
|
753
935
|
*
|
|
754
|
-
* test("
|
|
936
|
+
* test("query-based", async () => {
|
|
755
937
|
* using db = mockTailordb();
|
|
756
|
-
* db.
|
|
938
|
+
* db.onQuery({ sql: /FROM users/, params: ["u-1"] }).returnsRows([{ age: 30 }]);
|
|
757
939
|
* // …
|
|
758
|
-
* expect(db.queryObject).
|
|
940
|
+
* expect(db.queryObject).toHaveBeenCalled();
|
|
759
941
|
* expect(db.Client).toHaveBeenCalledWith({ namespace: "tailordb" });
|
|
760
942
|
* });
|
|
761
943
|
* ```
|
|
762
944
|
*/
|
|
763
|
-
function mockTailordb() {
|
|
945
|
+
function mockTailordb(options = {}) {
|
|
764
946
|
const root = tailordbRoot();
|
|
765
947
|
const prevClient = root.Client;
|
|
766
|
-
const
|
|
767
|
-
|
|
948
|
+
const rules = [];
|
|
949
|
+
let queryResolver;
|
|
950
|
+
function matchesQuery(matcher, query, params) {
|
|
951
|
+
if (typeof matcher === "function") return matcher(query, params);
|
|
952
|
+
if (typeof matcher === "string") return query === matcher;
|
|
953
|
+
if (matcher instanceof RegExp) return testRegex(matcher, query);
|
|
954
|
+
let sqlMatches;
|
|
955
|
+
if (typeof matcher.sql === "string") sqlMatches = query === matcher.sql;
|
|
956
|
+
else sqlMatches = testRegex(matcher.sql, query);
|
|
957
|
+
if (!sqlMatches || matcher.params === void 0) return sqlMatches;
|
|
958
|
+
if (typeof matcher.params === "function") return matcher.params(params);
|
|
959
|
+
const expectedParams = matcher.params;
|
|
960
|
+
return params.length === expectedParams.length && params.every((value, index) => isEqual(value, expectedParams[index]));
|
|
961
|
+
}
|
|
962
|
+
function queryResponse(response) {
|
|
963
|
+
if (response.type === "error") throw response.error;
|
|
964
|
+
return new MockQueryResult(response.rows);
|
|
965
|
+
}
|
|
966
|
+
async function defaultQuery(query, params = []) {
|
|
967
|
+
for (let i = rules.length - 1; i >= 0; i -= 1) {
|
|
968
|
+
const rule = rules[i];
|
|
969
|
+
if (!rule || !matchesQuery(rule.matcher, query, params)) continue;
|
|
970
|
+
const once = rule.once.shift();
|
|
971
|
+
if (once) return queryResponse(once);
|
|
972
|
+
if (rule.fallback) return queryResponse(rule.fallback);
|
|
973
|
+
}
|
|
974
|
+
if (queryResolver) return new MockQueryResult(queryResolver(query, params) ?? []);
|
|
975
|
+
if (options.onUnhandled === "error") throw new Error(`No TailorDB query behavior matched: ${query}`);
|
|
976
|
+
return new MockQueryResult([]);
|
|
977
|
+
}
|
|
978
|
+
const queryObject = vi.fn(defaultQuery);
|
|
979
|
+
const defaultConnect = async () => {};
|
|
980
|
+
const connect = vi.fn(defaultConnect);
|
|
768
981
|
const createdClients = [];
|
|
769
|
-
|
|
982
|
+
function enqueueRowsList(rowsList) {
|
|
983
|
+
for (const rows of rowsList) queryObject.mockImplementationOnce(async () => new MockQueryResult(rows));
|
|
984
|
+
}
|
|
985
|
+
const defaultClient = function(config) {
|
|
770
986
|
const record = {
|
|
771
987
|
namespace: config?.namespace,
|
|
772
988
|
ended: false
|
|
@@ -786,7 +1002,8 @@ function mockTailordb() {
|
|
|
786
1002
|
queryObject
|
|
787
1003
|
};
|
|
788
1004
|
};
|
|
789
|
-
}
|
|
1005
|
+
};
|
|
1006
|
+
const Client = vi.fn(defaultClient);
|
|
790
1007
|
root.Client = Client;
|
|
791
1008
|
return withDispose({
|
|
792
1009
|
/** The mock `tailordb.Client` constructor (`vi.fn`). */
|
|
@@ -798,7 +1015,53 @@ function mockTailordb() {
|
|
|
798
1015
|
* @param resolver - Function that returns rows for a given query and params
|
|
799
1016
|
*/
|
|
800
1017
|
setQueryResolver(resolver) {
|
|
801
|
-
|
|
1018
|
+
queryResolver = resolver;
|
|
1019
|
+
queryObject.mockImplementation(defaultQuery);
|
|
1020
|
+
},
|
|
1021
|
+
/**
|
|
1022
|
+
* Configure responses for queries matching SQL text, parameters, or a predicate.
|
|
1023
|
+
* More recently registered matchers take precedence.
|
|
1024
|
+
* Do not combine matchers with a direct `queryObject.mockImplementation()` override.
|
|
1025
|
+
* @param matcher - Query matcher
|
|
1026
|
+
* @returns Chainable query behavior
|
|
1027
|
+
*/
|
|
1028
|
+
onQuery(matcher) {
|
|
1029
|
+
const rule = {
|
|
1030
|
+
matcher,
|
|
1031
|
+
once: []
|
|
1032
|
+
};
|
|
1033
|
+
rules.push(rule);
|
|
1034
|
+
const behavior = {
|
|
1035
|
+
returnsRows(rows) {
|
|
1036
|
+
rule.fallback = {
|
|
1037
|
+
type: "rows",
|
|
1038
|
+
rows
|
|
1039
|
+
};
|
|
1040
|
+
return behavior;
|
|
1041
|
+
},
|
|
1042
|
+
returnsRowsOnce(rows) {
|
|
1043
|
+
rule.once.push({
|
|
1044
|
+
type: "rows",
|
|
1045
|
+
rows
|
|
1046
|
+
});
|
|
1047
|
+
return behavior;
|
|
1048
|
+
},
|
|
1049
|
+
rejects(error) {
|
|
1050
|
+
rule.fallback = {
|
|
1051
|
+
type: "error",
|
|
1052
|
+
error
|
|
1053
|
+
};
|
|
1054
|
+
return behavior;
|
|
1055
|
+
},
|
|
1056
|
+
rejectsOnce(error) {
|
|
1057
|
+
rule.once.push({
|
|
1058
|
+
type: "error",
|
|
1059
|
+
error
|
|
1060
|
+
});
|
|
1061
|
+
return behavior;
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
return behavior;
|
|
802
1065
|
},
|
|
803
1066
|
/**
|
|
804
1067
|
* Enqueue rows for the next `queryObject` call (FIFO; takes priority over
|
|
@@ -806,14 +1069,21 @@ function mockTailordb() {
|
|
|
806
1069
|
* @param rows - Row objects to return from the next `queryObject` call
|
|
807
1070
|
*/
|
|
808
1071
|
enqueueResult(...rows) {
|
|
809
|
-
|
|
1072
|
+
enqueueRowsList([rows]);
|
|
810
1073
|
},
|
|
811
1074
|
/**
|
|
812
1075
|
* Enqueue rows for multiple subsequent `queryObject` calls (FIFO).
|
|
813
1076
|
* @param rowsList - Rows arrays, one per upcoming query
|
|
814
1077
|
*/
|
|
815
1078
|
enqueueResults(...rowsList) {
|
|
816
|
-
|
|
1079
|
+
enqueueRowsList(rowsList);
|
|
1080
|
+
},
|
|
1081
|
+
/**
|
|
1082
|
+
* Enqueue row arrays for subsequent queries whose exact order is under test.
|
|
1083
|
+
* @param rowsList - Rows arrays, one per upcoming query
|
|
1084
|
+
*/
|
|
1085
|
+
enqueueRows(...rowsList) {
|
|
1086
|
+
enqueueRowsList(rowsList);
|
|
817
1087
|
},
|
|
818
1088
|
/**
|
|
819
1089
|
* All queries executed via `queryObject`, in order, derived from the vi.fn
|
|
@@ -833,13 +1103,24 @@ function mockTailordb() {
|
|
|
833
1103
|
get createdClients() {
|
|
834
1104
|
return createdClients;
|
|
835
1105
|
},
|
|
1106
|
+
/** Clear recorded calls while preserving configured query behavior. */
|
|
1107
|
+
clear() {
|
|
1108
|
+
queryObject.mockClear();
|
|
1109
|
+
connect.mockClear();
|
|
1110
|
+
Client.mockClear();
|
|
1111
|
+
createdClients.length = 0;
|
|
1112
|
+
},
|
|
836
1113
|
/** Reset query responses and recorded calls (keeps the mock installed). */
|
|
837
1114
|
reset() {
|
|
838
1115
|
queryObject.mockReset();
|
|
839
|
-
queryObject.mockImplementation(
|
|
840
|
-
connect.
|
|
841
|
-
|
|
1116
|
+
queryObject.mockImplementation(defaultQuery);
|
|
1117
|
+
connect.mockReset();
|
|
1118
|
+
connect.mockImplementation(defaultConnect);
|
|
1119
|
+
Client.mockReset();
|
|
1120
|
+
Client.mockImplementation(defaultClient);
|
|
842
1121
|
createdClients.length = 0;
|
|
1122
|
+
rules.length = 0;
|
|
1123
|
+
queryResolver = void 0;
|
|
843
1124
|
}
|
|
844
1125
|
}, () => {
|
|
845
1126
|
root.Client = prevClient;
|
|
@@ -848,6 +1129,30 @@ function mockTailordb() {
|
|
|
848
1129
|
|
|
849
1130
|
//#endregion
|
|
850
1131
|
//#region src/vitest/mocks/workflow.ts
|
|
1132
|
+
const originalProcedures = /* @__PURE__ */ new WeakMap();
|
|
1133
|
+
function replaceProcedure(target, key, current) {
|
|
1134
|
+
const original = originalProcedures.get(current) ?? current;
|
|
1135
|
+
const defaultImplementation = function(...args) {
|
|
1136
|
+
return Reflect.apply(original, this, args);
|
|
1137
|
+
};
|
|
1138
|
+
const mock = vi.fn(defaultImplementation);
|
|
1139
|
+
originalProcedures.set(mock, original);
|
|
1140
|
+
const record = target;
|
|
1141
|
+
record[key] = mock;
|
|
1142
|
+
return {
|
|
1143
|
+
mock,
|
|
1144
|
+
scoped: {
|
|
1145
|
+
mockClear: () => mock.mockClear(),
|
|
1146
|
+
mockReset: () => mock.mockReset(),
|
|
1147
|
+
restore: () => {
|
|
1148
|
+
if (record[key] === mock) record[key] = current;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
function replaceStart(definition) {
|
|
1154
|
+
return replaceProcedure(definition, "start", definition.start);
|
|
1155
|
+
}
|
|
851
1156
|
/**
|
|
852
1157
|
* Acquire a disposable mock for workflow operations (`tailor.workflow`).
|
|
853
1158
|
* Restored on dispose.
|
|
@@ -856,36 +1161,45 @@ function mockTailordb() {
|
|
|
856
1161
|
* ```typescript
|
|
857
1162
|
* import { mockWorkflow } from "@tailor-platform/sdk/vitest";
|
|
858
1163
|
*
|
|
859
|
-
* test("job
|
|
1164
|
+
* test("job start", async () => {
|
|
860
1165
|
* using wf = mockWorkflow();
|
|
861
|
-
* wf.
|
|
862
|
-
*
|
|
863
|
-
*
|
|
1166
|
+
* const job = wf.job(validateOrder);
|
|
1167
|
+
* job.mockResolvedValue({ valid: true });
|
|
1168
|
+
* await runWorkflowUnderTest();
|
|
1169
|
+
* expect(job).toHaveBeenCalled();
|
|
864
1170
|
* });
|
|
865
1171
|
* ```
|
|
866
1172
|
*/
|
|
867
1173
|
function mockWorkflow() {
|
|
868
1174
|
const root = tailorRoot();
|
|
869
1175
|
const prev = root.workflow;
|
|
870
|
-
const
|
|
1176
|
+
const prevEnv = readWorkflowTestEnv();
|
|
1177
|
+
const jobSpies = /* @__PURE__ */ new Map();
|
|
1178
|
+
const workflowSpies = /* @__PURE__ */ new Map();
|
|
1179
|
+
const waitPointMocks = /* @__PURE__ */ new Map();
|
|
1180
|
+
const scopedMocks = /* @__PURE__ */ new Set();
|
|
1181
|
+
const defaultStartJob = (jobName, _args, _options) => {
|
|
871
1182
|
throw new Error(`No workflow job mock for "${jobName}". Call mockWorkflow().setJobHandler(...) or enqueueResult(...), or use runWorkflowLocally() for local workflow execution.`);
|
|
872
1183
|
};
|
|
873
|
-
const
|
|
874
|
-
return
|
|
1184
|
+
const defaultStartWorkflow = async (_workflowName, _args, _options) => {
|
|
1185
|
+
return START_DEFAULT;
|
|
875
1186
|
};
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
const
|
|
879
|
-
const
|
|
1187
|
+
const defaultResumeWorkflowExecution = async (executionId) => executionId;
|
|
1188
|
+
const startJobFunction = vi.fn(defaultStartJob);
|
|
1189
|
+
const startWorkflow = vi.fn(defaultStartWorkflow);
|
|
1190
|
+
const resumeWorkflowExecution = vi.fn(defaultResumeWorkflowExecution);
|
|
880
1191
|
const wait = vi.fn((_key, _payload) => null);
|
|
881
1192
|
const resolve = vi.fn(async (_executionId, _key, _callback) => {});
|
|
1193
|
+
const jobFunctionShim = (...call) => {
|
|
1194
|
+
const out = call.length >= 3 ? startJobFunction(call[0], platformSerialize(call[1]), call[2]) : startJobFunction(call[0], platformSerialize(call[1]));
|
|
1195
|
+
return out instanceof Promise ? out.then((v) => platformSerialize(v)) : platformSerialize(out);
|
|
1196
|
+
};
|
|
1197
|
+
const workflowShim = (...call) => call.length >= 3 ? startWorkflow(call[0], platformSerialize(call[1]), call[2]) : startWorkflow(call[0], platformSerialize(call[1]));
|
|
1198
|
+
const resumeShim = (executionId) => resumeWorkflowExecution(executionId);
|
|
882
1199
|
root.workflow = {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
},
|
|
887
|
-
triggerWorkflow: (...call) => call.length >= 3 ? triggerWorkflow(call[0], platformSerialize(call[1]), call[2]) : triggerWorkflow(call[0], platformSerialize(call[1])),
|
|
888
|
-
resumeWorkflow: (executionId) => resumeWorkflow(executionId),
|
|
1200
|
+
startJobFunction: jobFunctionShim,
|
|
1201
|
+
startWorkflow: workflowShim,
|
|
1202
|
+
resumeWorkflowExecution: resumeShim,
|
|
889
1203
|
wait: (key, payload) => wait(key, platformSerialize(payload)),
|
|
890
1204
|
resolve: (executionId, key, callback) => resolve(executionId, key, (payload) => {
|
|
891
1205
|
const out = callback(payload);
|
|
@@ -893,64 +1207,122 @@ function mockWorkflow() {
|
|
|
893
1207
|
})
|
|
894
1208
|
};
|
|
895
1209
|
return withDispose({
|
|
896
|
-
/** The `
|
|
897
|
-
|
|
898
|
-
/** The `
|
|
899
|
-
|
|
900
|
-
/** The `
|
|
901
|
-
|
|
1210
|
+
/** The `startJobFunction` `vi.fn`. */
|
|
1211
|
+
startJobFunction,
|
|
1212
|
+
/** The `startWorkflow` `vi.fn`. */
|
|
1213
|
+
startWorkflow,
|
|
1214
|
+
/** The `resumeWorkflowExecution` `vi.fn`. */
|
|
1215
|
+
resumeWorkflowExecution,
|
|
902
1216
|
/** The `wait` `vi.fn`. */
|
|
903
1217
|
wait,
|
|
904
1218
|
/** The `resolve` `vi.fn`. */
|
|
905
1219
|
resolve,
|
|
906
1220
|
/**
|
|
1221
|
+
* Get a stable, typed mock for a workflow job's `start` method.
|
|
1222
|
+
* The real start behavior is used until an implementation or result is configured.
|
|
1223
|
+
* @param definition - Workflow job definition to mock
|
|
1224
|
+
* @returns Typed `start` mock for the definition
|
|
1225
|
+
*/
|
|
1226
|
+
job(definition) {
|
|
1227
|
+
const existing = jobSpies.get(definition);
|
|
1228
|
+
if (existing) return existing;
|
|
1229
|
+
const { mock, scoped } = replaceStart(definition);
|
|
1230
|
+
scopedMocks.add(scoped);
|
|
1231
|
+
jobSpies.set(definition, mock);
|
|
1232
|
+
return mock;
|
|
1233
|
+
},
|
|
1234
|
+
/**
|
|
1235
|
+
* Get a stable, typed mock for a workflow definition's `start` method.
|
|
1236
|
+
* The real start behavior is used until an implementation or result is configured.
|
|
1237
|
+
* @param definition - Workflow definition to mock
|
|
1238
|
+
* @returns Typed `start` mock for the definition
|
|
1239
|
+
*/
|
|
1240
|
+
workflow(definition) {
|
|
1241
|
+
const existing = workflowSpies.get(definition);
|
|
1242
|
+
if (existing) return existing;
|
|
1243
|
+
const { mock, scoped } = replaceStart(definition);
|
|
1244
|
+
workflowSpies.set(definition, mock);
|
|
1245
|
+
scopedMocks.add(scoped);
|
|
1246
|
+
return mock;
|
|
1247
|
+
},
|
|
1248
|
+
/**
|
|
1249
|
+
* Get stable, typed mocks for a wait point's `wait` and `resolve` methods.
|
|
1250
|
+
* @param definition - Wait point definition to mock
|
|
1251
|
+
* @returns Typed wait point mock control object
|
|
1252
|
+
*/
|
|
1253
|
+
waitPoint(definition) {
|
|
1254
|
+
const existing = waitPointMocks.get(definition);
|
|
1255
|
+
if (existing) return existing;
|
|
1256
|
+
const waitReplacement = replaceProcedure(definition, "wait", definition.wait);
|
|
1257
|
+
const resolveReplacement = replaceProcedure(definition, "resolve", definition.resolve);
|
|
1258
|
+
const waitSpy = waitReplacement.mock;
|
|
1259
|
+
const resolveSpy = resolveReplacement.mock;
|
|
1260
|
+
scopedMocks.add(waitReplacement.scoped);
|
|
1261
|
+
scopedMocks.add(resolveReplacement.scoped);
|
|
1262
|
+
const waitPointMock = {
|
|
1263
|
+
wait: waitSpy,
|
|
1264
|
+
resolve: resolveSpy,
|
|
1265
|
+
/**
|
|
1266
|
+
* Invoke the next and subsequent resolve callbacks with a wait payload.
|
|
1267
|
+
* @param payload - Payload originally supplied to the wait point
|
|
1268
|
+
*/
|
|
1269
|
+
setResolvePayload(payload) {
|
|
1270
|
+
resolveSpy.mockImplementation(async (_executionId, callback) => {
|
|
1271
|
+
platformSerialize(await callback(platformSerialize(payload)));
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
waitPointMocks.set(definition, waitPointMock);
|
|
1276
|
+
return waitPointMock;
|
|
1277
|
+
},
|
|
1278
|
+
/**
|
|
907
1279
|
* Set a fallback job handler. Called when the enqueue queue is empty.
|
|
908
1280
|
* @param handler - Function returning a result for a job name, args, and options
|
|
909
1281
|
*/
|
|
910
1282
|
setJobHandler(handler) {
|
|
911
|
-
|
|
1283
|
+
startJobFunction.mockImplementation((name, args, options) => handler(name, args, options));
|
|
912
1284
|
},
|
|
913
1285
|
/**
|
|
914
|
-
* Enqueue a single result for the next `
|
|
1286
|
+
* Enqueue a single result for the next `startJobFunction` call (FIFO;
|
|
915
1287
|
* takes priority over `setJobHandler`).
|
|
916
1288
|
* @param result - Result to return from the next call
|
|
917
1289
|
*/
|
|
918
1290
|
enqueueResult(result) {
|
|
919
|
-
|
|
1291
|
+
startJobFunction.mockImplementationOnce(() => result);
|
|
920
1292
|
},
|
|
921
1293
|
/**
|
|
922
|
-
* Enqueue results for multiple subsequent `
|
|
1294
|
+
* Enqueue results for multiple subsequent `startJobFunction` calls (FIFO).
|
|
923
1295
|
* @param results - Results to enqueue, one per upcoming call
|
|
924
1296
|
*/
|
|
925
1297
|
enqueueResults(...results) {
|
|
926
|
-
for (const result of results)
|
|
1298
|
+
for (const result of results) startJobFunction.mockImplementationOnce(() => result);
|
|
927
1299
|
},
|
|
928
1300
|
/**
|
|
929
|
-
* All jobs
|
|
930
|
-
* @returns
|
|
1301
|
+
* All jobs started via `startJobFunction`, in order.
|
|
1302
|
+
* @returns Started jobs array
|
|
931
1303
|
*/
|
|
932
|
-
get
|
|
933
|
-
return
|
|
1304
|
+
get startedJobs() {
|
|
1305
|
+
return startJobFunction.mock.calls.map(([jobName, args, options]) => ({
|
|
934
1306
|
jobName,
|
|
935
1307
|
args,
|
|
936
1308
|
...options !== void 0 && { options }
|
|
937
1309
|
}));
|
|
938
1310
|
},
|
|
939
1311
|
/**
|
|
940
|
-
* Configure what `
|
|
1312
|
+
* Configure what `startWorkflow` returns. Pass a string (same id every
|
|
941
1313
|
* call) or `(name, args, options) => string`. Default: a placeholder UUID.
|
|
942
1314
|
* @param handler - Static execution ID or a function returning one
|
|
943
1315
|
*/
|
|
944
|
-
|
|
945
|
-
|
|
1316
|
+
setStartHandler(handler) {
|
|
1317
|
+
startWorkflow.mockImplementation(typeof handler === "function" ? async (name, args, options) => handler(name, args, options) : async () => handler);
|
|
946
1318
|
},
|
|
947
1319
|
/**
|
|
948
|
-
* Configure what `
|
|
949
|
-
* call) or `(executionId) => string`. Default: echoes the input executionId.
|
|
1320
|
+
* Configure what `resumeWorkflowExecution` returns. Pass a string (same id
|
|
1321
|
+
* every call) or `(executionId) => string`. Default: echoes the input executionId.
|
|
950
1322
|
* @param handler - Static execution ID or a function returning one
|
|
951
1323
|
*/
|
|
952
1324
|
setResumeHandler(handler) {
|
|
953
|
-
|
|
1325
|
+
resumeWorkflowExecution.mockImplementation(typeof handler === "function" ? async (executionId) => handler(executionId) : async () => handler);
|
|
954
1326
|
},
|
|
955
1327
|
/**
|
|
956
1328
|
* Configure what `wait` returns. Pass `(key, payload) => unknown` or any
|
|
@@ -961,7 +1333,7 @@ function mockWorkflow() {
|
|
|
961
1333
|
wait.mockImplementation(typeof handler === "function" ? (key, payload) => handler(key, payload) : () => handler);
|
|
962
1334
|
}),
|
|
963
1335
|
/**
|
|
964
|
-
* Set the `env` passed to job bodies invoked via `createWorkflowJob().
|
|
1336
|
+
* Set the `env` passed to job bodies invoked via `createWorkflowJob().start()`.
|
|
965
1337
|
* Cleared on dispose / reset.
|
|
966
1338
|
* @param env - Env passed to job bodies.
|
|
967
1339
|
*/
|
|
@@ -998,29 +1370,41 @@ function mockWorkflow() {
|
|
|
998
1370
|
key
|
|
999
1371
|
}));
|
|
1000
1372
|
},
|
|
1373
|
+
/** Clear recorded calls while preserving configured responses. */
|
|
1374
|
+
clear() {
|
|
1375
|
+
startJobFunction.mockClear();
|
|
1376
|
+
startWorkflow.mockClear();
|
|
1377
|
+
resumeWorkflowExecution.mockClear();
|
|
1378
|
+
wait.mockClear();
|
|
1379
|
+
resolve.mockClear();
|
|
1380
|
+
for (const mock of scopedMocks) mock.mockClear();
|
|
1381
|
+
},
|
|
1001
1382
|
/** Reset all workflow responses and recorded calls (keeps the mock installed). */
|
|
1002
1383
|
reset() {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1384
|
+
startJobFunction.mockReset();
|
|
1385
|
+
startJobFunction.mockImplementation(defaultStartJob);
|
|
1386
|
+
startWorkflow.mockReset();
|
|
1387
|
+
startWorkflow.mockImplementation(defaultStartWorkflow);
|
|
1388
|
+
resumeWorkflowExecution.mockReset();
|
|
1389
|
+
resumeWorkflowExecution.mockImplementation(defaultResumeWorkflowExecution);
|
|
1009
1390
|
wait.mockReset();
|
|
1010
1391
|
wait.mockImplementation(() => null);
|
|
1011
1392
|
resolve.mockReset();
|
|
1012
1393
|
resolve.mockImplementation(async () => {});
|
|
1394
|
+
for (const mock of scopedMocks) mock.mockReset();
|
|
1013
1395
|
clearWorkflowTestEnv();
|
|
1014
1396
|
}
|
|
1015
1397
|
}, () => {
|
|
1398
|
+
for (const mock of scopedMocks) mock.restore();
|
|
1016
1399
|
root.workflow = prev;
|
|
1017
|
-
|
|
1400
|
+
if (prevEnv !== void 0) writeWorkflowTestEnv(prevEnv);
|
|
1401
|
+
else clearWorkflowTestEnv();
|
|
1018
1402
|
});
|
|
1019
1403
|
}
|
|
1020
1404
|
|
|
1021
1405
|
//#endregion
|
|
1022
1406
|
//#region src/vitest/workflow-local.ts
|
|
1023
|
-
var
|
|
1407
|
+
var PendingStart = class {
|
|
1024
1408
|
jobName;
|
|
1025
1409
|
args;
|
|
1026
1410
|
constructor(jobName, args) {
|
|
@@ -1037,7 +1421,7 @@ async function runWorkflowLocally(workflow, args, options) {
|
|
|
1037
1421
|
if (options?.env !== void 0) writeWorkflowTestEnv({ ...options.env });
|
|
1038
1422
|
root.tailor = {
|
|
1039
1423
|
...previousTailor,
|
|
1040
|
-
workflow: createLocalWorkflowRuntime(previousTailor?.workflow, runner.
|
|
1424
|
+
workflow: createLocalWorkflowRuntime(previousTailor?.workflow, runner.startJobFunction)
|
|
1041
1425
|
};
|
|
1042
1426
|
try {
|
|
1043
1427
|
return await runner.runJob(workflow.mainJob.name, args);
|
|
@@ -1050,19 +1434,19 @@ async function runWorkflowLocally(workflow, args, options) {
|
|
|
1050
1434
|
}
|
|
1051
1435
|
function createLocalJobRunner() {
|
|
1052
1436
|
let activeExecution;
|
|
1053
|
-
const
|
|
1054
|
-
if (!activeExecution) throw new Error(`Cannot
|
|
1437
|
+
const startJobFunction = (jobName, args) => {
|
|
1438
|
+
if (!activeExecution) throw new Error(`Cannot start workflow job "${jobName}" outside runWorkflowLocally() job execution.`);
|
|
1055
1439
|
if (activeExecution.pending) throw activeExecution.pending;
|
|
1056
1440
|
const serializedArgs = platformSerialize(args);
|
|
1057
1441
|
const index = activeExecution.cursor;
|
|
1058
1442
|
activeExecution.cursor += 1;
|
|
1059
1443
|
const cached = activeExecution.records[index];
|
|
1060
1444
|
if (cached) {
|
|
1061
|
-
|
|
1445
|
+
assertSameStart(cached, jobName, serializedArgs);
|
|
1062
1446
|
if (cached.status === "rejected") throw cached.error;
|
|
1063
1447
|
return platformSerialize(cached.result);
|
|
1064
1448
|
}
|
|
1065
|
-
const pending = new
|
|
1449
|
+
const pending = new PendingStart(jobName, serializedArgs);
|
|
1066
1450
|
activeExecution.pending = pending;
|
|
1067
1451
|
throw pending;
|
|
1068
1452
|
};
|
|
@@ -1080,15 +1464,15 @@ function createLocalJobRunner() {
|
|
|
1080
1464
|
try {
|
|
1081
1465
|
const out = await body(platformSerialize(args), buildJobContext());
|
|
1082
1466
|
if (execution.pending) {
|
|
1083
|
-
await
|
|
1467
|
+
await settlePendingStart(records, execution.pending, runJob);
|
|
1084
1468
|
continue;
|
|
1085
1469
|
}
|
|
1086
|
-
if (execution.cursor !== records.length) throw new Error(`Workflow job
|
|
1470
|
+
if (execution.cursor !== records.length) throw new Error(`Workflow job start sequence changed while replaying "${name}". Expected ${records.length} start(s), but replay reached ${execution.cursor}.`);
|
|
1087
1471
|
return platformSerialize(out);
|
|
1088
1472
|
} catch (cause) {
|
|
1089
|
-
const pending = cause instanceof
|
|
1473
|
+
const pending = cause instanceof PendingStart ? cause : execution.pending;
|
|
1090
1474
|
if (pending) {
|
|
1091
|
-
await
|
|
1475
|
+
await settlePendingStart(records, pending, runJob);
|
|
1092
1476
|
continue;
|
|
1093
1477
|
}
|
|
1094
1478
|
throw cause;
|
|
@@ -1099,10 +1483,10 @@ function createLocalJobRunner() {
|
|
|
1099
1483
|
};
|
|
1100
1484
|
return {
|
|
1101
1485
|
runJob,
|
|
1102
|
-
|
|
1486
|
+
startJobFunction
|
|
1103
1487
|
};
|
|
1104
1488
|
}
|
|
1105
|
-
async function
|
|
1489
|
+
async function settlePendingStart(records, pending, runJob) {
|
|
1106
1490
|
try {
|
|
1107
1491
|
records.push({
|
|
1108
1492
|
jobName: pending.jobName,
|
|
@@ -1119,22 +1503,24 @@ async function settlePendingTrigger(records, pending, runJob) {
|
|
|
1119
1503
|
});
|
|
1120
1504
|
}
|
|
1121
1505
|
}
|
|
1122
|
-
function
|
|
1506
|
+
function assertSameStart(record, jobName, args) {
|
|
1123
1507
|
if (record.jobName === jobName && JSON.stringify(record.args) === JSON.stringify(args)) return;
|
|
1124
|
-
throw new Error(`Workflow job
|
|
1508
|
+
throw new Error(`Workflow job start sequence changed while replaying. Expected ${record.jobName}(${JSON.stringify(record.args)}), but got ${jobName}(${JSON.stringify(args)}).`);
|
|
1125
1509
|
}
|
|
1126
|
-
function createLocalWorkflowRuntime(previous,
|
|
1510
|
+
function createLocalWorkflowRuntime(previous, startJobFunction) {
|
|
1511
|
+
const startWorkflow = async (name, args, options) => {
|
|
1512
|
+
if (previous) return await previous.startWorkflow(name, args, options);
|
|
1513
|
+
platformSerialize(args);
|
|
1514
|
+
return START_DEFAULT;
|
|
1515
|
+
};
|
|
1516
|
+
const resumeWorkflowExecution = async (executionId) => {
|
|
1517
|
+
if (previous) return await previous.resumeWorkflowExecution(executionId);
|
|
1518
|
+
return executionId;
|
|
1519
|
+
};
|
|
1127
1520
|
return {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
platformSerialize(args);
|
|
1132
|
-
return TRIGGER_DEFAULT;
|
|
1133
|
-
},
|
|
1134
|
-
resumeWorkflow: async (executionId) => {
|
|
1135
|
-
if (previous) return await previous.resumeWorkflow(executionId);
|
|
1136
|
-
return executionId;
|
|
1137
|
-
},
|
|
1521
|
+
startJobFunction,
|
|
1522
|
+
startWorkflow,
|
|
1523
|
+
resumeWorkflowExecution,
|
|
1138
1524
|
wait: (key, payload) => {
|
|
1139
1525
|
if (previous) return previous.wait(key, payload);
|
|
1140
1526
|
throw new Error(`No wait handler for "${key}". Acquire mockWorkflow() and call setWaitHandler(...).`);
|