@tailor-platform/sdk 1.76.2 → 1.78.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 +26 -0
- package/dist/{application-CqjCcZ2n.mjs → application-CUxVA7Fs.mjs} +8 -8
- package/dist/application-CUxVA7Fs.mjs.map +1 -0
- package/dist/application-Dp_-ioiP.mjs +3 -0
- package/dist/cli/commands/deploy/deploy.d.mts +8 -3
- package/dist/cli/commands/show.d.mts +9 -3
- 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 +69 -22
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +2 -2
- package/dist/cli/lib.mjs +2 -2
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/cli/shared/workspace-name.d.mts +1 -0
- package/dist/cli/skills.mjs +1 -1
- package/dist/completion/zsh-worker.zsh +18 -1
- package/dist/configure/index.mjs +198 -4
- package/dist/configure/index.mjs.map +1 -1
- package/dist/configure/services/idp/index.d.mts +1 -4
- package/dist/{crashreport-DZDr-TvH.mjs → crashreport-BNWUM6Kf.mjs} +2 -2
- package/dist/{crashreport-DZDr-TvH.mjs.map → crashreport-BNWUM6Kf.mjs.map} +1 -1
- package/dist/{crashreport-np4-_DX2.mjs → crashreport-BafWvjjX.mjs} +1 -1
- package/dist/errors-DVFS0AZk.mjs +100 -0
- package/dist/errors-DVFS0AZk.mjs.map +1 -0
- package/dist/{globals-z6zYMjlr.mjs → globals-CvizbGLY.mjs} +2 -2
- package/dist/{globals-z6zYMjlr.mjs.map → globals-CvizbGLY.mjs.map} +1 -1
- package/dist/{logger-BEiZZ3qT.mjs → logger-BwS4ppwO.mjs} +3 -3
- package/dist/logger-BwS4ppwO.mjs.map +1 -0
- package/dist/{registry-CTgoVWc_.mjs → registry-CYBRvfaU.mjs} +2 -2
- package/dist/{registry-CTgoVWc_.mjs.map → registry-CYBRvfaU.mjs.map} +1 -1
- package/dist/{runtime-DlklyuHN.mjs → runtime-BD2vYJFT.mjs} +1169 -539
- package/dist/runtime-BD2vYJFT.mjs.map +1 -0
- package/dist/{schema-DbTR1c6M.mjs → schema-U6DxoZ8D.mjs} +203 -3
- package/dist/schema-U6DxoZ8D.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-CghpdV-9.mjs +3 -0
- package/dist/{service-DBFZQloR.mjs → service-Cp0I7ZFe.mjs} +3 -3
- package/dist/{service-DBFZQloR.mjs.map → service-Cp0I7ZFe.mjs.map} +1 -1
- package/dist/{service-BCRJ-3NV.mjs → service-D12iQGcS.mjs} +2 -2
- package/dist/{service-BCRJ-3NV.mjs.map → service-D12iQGcS.mjs.map} +1 -1
- package/dist/{test-env-key-IPapa1KJ.mjs → test-env-key-DuZycyWM.mjs} +2 -2
- package/dist/{test-env-key-IPapa1KJ.mjs.map → test-env-key-DuZycyWM.mjs.map} +1 -1
- package/dist/types/auth.generated.d.mts +9 -36
- package/dist/types/executor.generated.d.mts +19 -59
- package/dist/types/idp.generated.d.mts +518 -517
- package/dist/types/resolver.generated.d.mts +6 -2
- package/dist/types/tailordb.generated.d.mts +8 -7
- package/dist/utils/test/index.mjs +1 -1
- package/dist/vitest/environment.mjs +1 -1
- package/dist/vitest/index.d.mts +8 -8
- package/dist/vitest/index.mjs +600 -186
- 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 +24 -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 +41 -9
- package/dist/vitest/setup.mjs +2 -2
- package/docs/cli/application.md +58 -11
- package/docs/cli/setup.md +1 -1
- package/docs/cli/workspace.md +6 -4
- package/docs/cli-reference.md +12 -8
- package/docs/runtime.md +3 -4
- package/docs/services/aigateway.md +2 -0
- package/docs/services/tailordb.md +42 -0
- package/docs/services/workflow.md +1 -1
- package/docs/testing.md +103 -89
- package/package.json +7 -7
- package/dist/application-6zsDLGKv.mjs +0 -3
- package/dist/application-CqjCcZ2n.mjs.map +0 -1
- package/dist/errors-Dtf2WPaW.mjs +0 -42
- package/dist/errors-Dtf2WPaW.mjs.map +0 -1
- package/dist/field-runtime-D4QWRiB1.mjs +0 -203
- package/dist/field-runtime-D4QWRiB1.mjs.map +0 -1
- package/dist/logger-BEiZZ3qT.mjs.map +0 -1
- package/dist/runtime-DlklyuHN.mjs.map +0 -1
- package/dist/schema-DbTR1c6M.mjs.map +0 -1
- package/dist/secretmanager-Bd45j7an.mjs.map +0 -1
- package/dist/service-CPMlIg3X.mjs +0 -3
- package/dist/types-BzJ7A_fn.mjs +0 -199
- package/dist/types-BzJ7A_fn.mjs.map +0 -1
- package/dist/types-CG9H7HuE.mjs +0 -4
package/dist/vitest/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { a as getRegisteredWorkflow, i as getRegisteredJob, t as TRIGGER_DEFAULT, u as platformSerialize } from "../registry-
|
|
2
|
-
import {
|
|
1
|
+
import { a as getRegisteredWorkflow, i as getRegisteredJob, t as TRIGGER_DEFAULT, u as platformSerialize } from "../registry-CYBRvfaU.mjs";
|
|
2
|
+
import { a as writeWorkflowTestEnv, i as readWorkflowTestEnv, n as buildJobContext, r as clearWorkflowTestEnv } from "../test-env-key-DuZycyWM.mjs";
|
|
3
3
|
import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
|
|
4
|
-
import { i as withDispose, n as tailorRoot, r as tailordbRoot, t as mockSecretmanager } from "../secretmanager-
|
|
4
|
+
import { i as withDispose, n as tailorRoot, r as tailordbRoot, t as mockSecretmanager } from "../secretmanager-IY4UvinW.mjs";
|
|
5
5
|
import { builtinModules } from "node:module";
|
|
6
|
+
import { isEqual } from "es-toolkit";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { dirname, isAbsolute, matchesGlob, relative, resolve } from "node:path";
|
|
8
9
|
import { ColumnNode, InsertQueryNode, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler, PrimitiveValueListNode, ReferenceNode, UpdateQueryNode, ValueListNode, ValueNode, ValuesNode } from "kysely";
|
|
@@ -296,27 +297,31 @@ function createEnvironmentPlugin(options) {
|
|
|
296
297
|
//#region src/vitest/mocks/aigateway.ts
|
|
297
298
|
/**
|
|
298
299
|
* Acquire a disposable mock for `tailor.aigateway`. Restored on dispose.
|
|
300
|
+
* @param options - Initial AI Gateway URL fixtures
|
|
299
301
|
* @returns Disposable AI Gateway mock control object
|
|
300
302
|
* @example
|
|
301
303
|
* ```typescript
|
|
302
304
|
* import { mockAigateway } from "@tailor-platform/sdk/vitest";
|
|
303
305
|
*
|
|
304
306
|
* test("resolves an AI Gateway URL", async () => {
|
|
305
|
-
* using aigateway = mockAigateway(
|
|
306
|
-
*
|
|
307
|
+
* using aigateway = mockAigateway({
|
|
308
|
+
* urls: { "my-aigateway": "https://my-aigateway.example.com" },
|
|
309
|
+
* });
|
|
310
|
+
* aigateway.setUrl("my-aigateway", "https://replacement.example.com");
|
|
307
311
|
* // …
|
|
308
312
|
* });
|
|
309
313
|
* ```
|
|
310
314
|
*/
|
|
311
|
-
function mockAigateway() {
|
|
315
|
+
function mockAigateway(options = {}) {
|
|
312
316
|
const root = tailorRoot();
|
|
313
317
|
const prev = root.aigateway;
|
|
314
|
-
let urls = {};
|
|
315
|
-
|
|
318
|
+
let urls = { ...options.urls };
|
|
319
|
+
async function defaultGet(name) {
|
|
316
320
|
const url = urls[name];
|
|
317
321
|
if (url === void 0) throw new Error(`No AI Gateway registered for "${name}". Acquire mockAigateway() and call setUrls(...).`);
|
|
318
322
|
return { url };
|
|
319
|
-
}
|
|
323
|
+
}
|
|
324
|
+
const get = vi.fn(defaultGet);
|
|
320
325
|
root.aigateway = { get };
|
|
321
326
|
return withDispose({
|
|
322
327
|
/** The `get` `vi.fn`. */
|
|
@@ -324,12 +329,22 @@ function mockAigateway() {
|
|
|
324
329
|
setUrls(value) {
|
|
325
330
|
urls = value;
|
|
326
331
|
},
|
|
332
|
+
setUrl(name, url) {
|
|
333
|
+
urls = {
|
|
334
|
+
...urls,
|
|
335
|
+
[name]: url
|
|
336
|
+
};
|
|
337
|
+
},
|
|
327
338
|
get calls() {
|
|
328
339
|
return get.mock.calls.map(([name]) => ({ name }));
|
|
329
340
|
},
|
|
341
|
+
clear() {
|
|
342
|
+
get.mockClear();
|
|
343
|
+
},
|
|
330
344
|
reset() {
|
|
331
345
|
urls = {};
|
|
332
|
-
get.
|
|
346
|
+
get.mockReset();
|
|
347
|
+
get.mockImplementation(defaultGet);
|
|
333
348
|
}
|
|
334
349
|
}, () => {
|
|
335
350
|
root.aigateway = prev;
|
|
@@ -340,23 +355,30 @@ function mockAigateway() {
|
|
|
340
355
|
//#region src/vitest/mocks/authconnection.ts
|
|
341
356
|
/**
|
|
342
357
|
* Acquire a disposable mock for `tailor.authconnection`. Restored on dispose.
|
|
358
|
+
* @param options - Initial token fixtures and fallback behavior
|
|
343
359
|
* @returns Disposable AuthConnection mock control object
|
|
344
360
|
* @example
|
|
345
361
|
* ```typescript
|
|
346
362
|
* import { mockAuthconnection } from "@tailor-platform/sdk/vitest";
|
|
347
363
|
*
|
|
348
364
|
* test("returns configured token", async () => {
|
|
349
|
-
* using ac = mockAuthconnection();
|
|
350
|
-
* ac.
|
|
365
|
+
* using ac = mockAuthconnection({ tokens: { google: { access_token: "ya29.xxx" } } });
|
|
366
|
+
* ac.setToken("google", { access_token: "replacement" });
|
|
351
367
|
* // …
|
|
352
368
|
* });
|
|
353
369
|
* ```
|
|
354
370
|
*/
|
|
355
|
-
function mockAuthconnection() {
|
|
371
|
+
function mockAuthconnection(options = {}) {
|
|
356
372
|
const root = tailorRoot();
|
|
357
373
|
const prev = root.authconnection;
|
|
358
|
-
let tokens = {};
|
|
359
|
-
|
|
374
|
+
let tokens = { ...options.tokens };
|
|
375
|
+
async function defaultGetConnectionToken(connectionName) {
|
|
376
|
+
const token = tokens[connectionName];
|
|
377
|
+
if (token) return token;
|
|
378
|
+
if (options.onUnhandled === "error") throw new Error(`No AuthConnection token configured for "${connectionName}"`);
|
|
379
|
+
return { access_token: "mock-token" };
|
|
380
|
+
}
|
|
381
|
+
const getConnectionToken = vi.fn(defaultGetConnectionToken);
|
|
360
382
|
root.authconnection = { getConnectionToken };
|
|
361
383
|
return withDispose({
|
|
362
384
|
/** The `getConnectionToken` `vi.fn`. */
|
|
@@ -364,12 +386,22 @@ function mockAuthconnection() {
|
|
|
364
386
|
setTokens(value) {
|
|
365
387
|
tokens = value;
|
|
366
388
|
},
|
|
389
|
+
setToken(connectionName, token) {
|
|
390
|
+
tokens = {
|
|
391
|
+
...tokens,
|
|
392
|
+
[connectionName]: token
|
|
393
|
+
};
|
|
394
|
+
},
|
|
367
395
|
get calls() {
|
|
368
396
|
return getConnectionToken.mock.calls.map(([connectionName]) => ({ connectionName }));
|
|
369
397
|
},
|
|
398
|
+
clear() {
|
|
399
|
+
getConnectionToken.mockClear();
|
|
400
|
+
},
|
|
370
401
|
reset() {
|
|
371
402
|
tokens = {};
|
|
372
|
-
getConnectionToken.
|
|
403
|
+
getConnectionToken.mockReset();
|
|
404
|
+
getConnectionToken.mockImplementation(defaultGetConnectionToken);
|
|
373
405
|
}
|
|
374
406
|
}, () => {
|
|
375
407
|
root.authconnection = prev;
|
|
@@ -378,6 +410,16 @@ function mockAuthconnection() {
|
|
|
378
410
|
|
|
379
411
|
//#endregion
|
|
380
412
|
//#region src/vitest/mocks/file.ts
|
|
413
|
+
const FILE_METHODS = [
|
|
414
|
+
"upload",
|
|
415
|
+
"download",
|
|
416
|
+
"downloadAsBase64",
|
|
417
|
+
"delete",
|
|
418
|
+
"getMetadata",
|
|
419
|
+
"openDownloadStream",
|
|
420
|
+
"downloadStream",
|
|
421
|
+
"uploadStream"
|
|
422
|
+
];
|
|
381
423
|
const FILE_DEFAULTS = {
|
|
382
424
|
upload: { metadata: {
|
|
383
425
|
fileSize: 0,
|
|
@@ -401,7 +443,6 @@ const FILE_DEFAULTS = {
|
|
|
401
443
|
lastUploadedAt: ""
|
|
402
444
|
}
|
|
403
445
|
},
|
|
404
|
-
delete: void 0,
|
|
405
446
|
getMetadata: {
|
|
406
447
|
contentType: "",
|
|
407
448
|
fileSize: 0,
|
|
@@ -414,27 +455,59 @@ const FILE_DEFAULTS = {
|
|
|
414
455
|
sha256sum: ""
|
|
415
456
|
} }
|
|
416
457
|
};
|
|
458
|
+
function wrapFileIterator(inner, closeSource) {
|
|
459
|
+
let closePromise;
|
|
460
|
+
const closeSourceOnce = () => closePromise ??= Promise.resolve(closeSource());
|
|
461
|
+
const close = async () => {
|
|
462
|
+
try {
|
|
463
|
+
await inner.return?.();
|
|
464
|
+
} finally {
|
|
465
|
+
await closeSourceOnce();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const stream = {
|
|
469
|
+
async next() {
|
|
470
|
+
const result = await inner.next();
|
|
471
|
+
if (!result.done) assertStreamValue(result.value);
|
|
472
|
+
return result.done ? {
|
|
473
|
+
done: true,
|
|
474
|
+
value: void 0
|
|
475
|
+
} : result;
|
|
476
|
+
},
|
|
477
|
+
close,
|
|
478
|
+
async return(value) {
|
|
479
|
+
try {
|
|
480
|
+
return inner.return ? await inner.return(value) : {
|
|
481
|
+
done: true,
|
|
482
|
+
value
|
|
483
|
+
};
|
|
484
|
+
} finally {
|
|
485
|
+
await closeSourceOnce();
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
async throw(error) {
|
|
489
|
+
try {
|
|
490
|
+
if (inner.throw) return await inner.throw(error);
|
|
491
|
+
throw error;
|
|
492
|
+
} finally {
|
|
493
|
+
await closeSourceOnce();
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
[Symbol.asyncIterator]() {
|
|
497
|
+
return stream;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
return stream;
|
|
501
|
+
}
|
|
417
502
|
function toFileStream(value) {
|
|
418
|
-
if (value !== null && typeof value === "object" && Symbol.asyncIterator in value && typeof value.close === "function")
|
|
503
|
+
if (value !== null && typeof value === "object" && Symbol.asyncIterator in value && typeof value.close === "function") {
|
|
504
|
+
const source = value;
|
|
505
|
+
return wrapFileIterator(source[Symbol.asyncIterator](), () => source.close());
|
|
506
|
+
}
|
|
419
507
|
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) throw new TypeError("openDownloadStream expects an iterable of StreamValue items (e.g. [{ type: \"chunk\", data, position }, { type: \"complete\" }]); got raw bytes. Wrap the bytes in a structured chunk first.");
|
|
420
508
|
if (value !== null && typeof value === "object" && (Symbol.iterator in value || Symbol.asyncIterator in value)) {
|
|
421
509
|
const source = value;
|
|
422
|
-
|
|
423
|
-
const stream = {
|
|
424
|
-
async next() {
|
|
425
|
-
const r = await inner.next();
|
|
426
|
-
if (!r.done) assertStreamValue(r.value);
|
|
427
|
-
return r.done ? {
|
|
428
|
-
done: true,
|
|
429
|
-
value: void 0
|
|
430
|
-
} : r;
|
|
431
|
-
},
|
|
432
|
-
async close() {},
|
|
433
|
-
[Symbol.asyncIterator]() {
|
|
434
|
-
return stream;
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
return stream;
|
|
510
|
+
return wrapFileIterator(Symbol.asyncIterator in source ? source[Symbol.asyncIterator]() : source[Symbol.iterator](), () => {});
|
|
438
511
|
}
|
|
439
512
|
const empty = {
|
|
440
513
|
async next() {
|
|
@@ -450,14 +523,15 @@ function toFileStream(value) {
|
|
|
450
523
|
};
|
|
451
524
|
return empty;
|
|
452
525
|
}
|
|
453
|
-
function assertStreamValue(
|
|
454
|
-
if (
|
|
455
|
-
if (
|
|
456
|
-
const type =
|
|
526
|
+
function assertStreamValue(value) {
|
|
527
|
+
if (value === null || typeof value !== "object") throw new TypeError(`openDownloadStream expected a StreamValue item ({ type: "metadata" | "chunk" | "complete", ... }); got ${typeof value === "object" ? "null" : typeof value}.`);
|
|
528
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) throw new TypeError("openDownloadStream expected a StreamValue item, got raw bytes. Wrap the bytes in a structured chunk first (e.g. { type: \"chunk\", data, position }).");
|
|
529
|
+
const type = value.type;
|
|
457
530
|
if (type !== "metadata" && type !== "chunk" && type !== "complete") throw new TypeError(`openDownloadStream expected a StreamValue item with type "metadata" | "chunk" | "complete"; got ${JSON.stringify(type)}.`);
|
|
458
531
|
}
|
|
459
532
|
/**
|
|
460
533
|
* Acquire a disposable mock for `tailordb.file`. Restored on dispose.
|
|
534
|
+
* @param options - Controls behavior for calls without a configured result
|
|
461
535
|
* @returns Disposable File mock control object
|
|
462
536
|
* @example
|
|
463
537
|
* ```typescript
|
|
@@ -465,77 +539,105 @@ function assertStreamValue(v) {
|
|
|
465
539
|
*
|
|
466
540
|
* test("mock file download", async () => {
|
|
467
541
|
* using file = mockFile();
|
|
468
|
-
* file.
|
|
542
|
+
* file.download.mockResolvedValue({ data: new Uint8Array(), metadata: { ... } });
|
|
469
543
|
* // …
|
|
470
544
|
* });
|
|
471
545
|
* ```
|
|
472
546
|
*/
|
|
473
|
-
function mockFile() {
|
|
547
|
+
function mockFile(options = {}) {
|
|
474
548
|
const root = tailordbRoot();
|
|
475
549
|
const prev = root.file;
|
|
550
|
+
const { onUnhandled = "fallback" } = options;
|
|
476
551
|
const queue = [];
|
|
477
|
-
let resolver = () => null;
|
|
478
552
|
const calls = [];
|
|
553
|
+
let resolver = () => null;
|
|
479
554
|
function handle(method, namespace, typeName, fieldName, recordId) {
|
|
480
|
-
|
|
555
|
+
if (queue.length > 0) return queue.shift();
|
|
556
|
+
const resolved = resolver(method, {
|
|
481
557
|
method,
|
|
482
558
|
namespace,
|
|
483
559
|
typeName,
|
|
484
560
|
fieldName,
|
|
485
561
|
recordId
|
|
486
|
-
};
|
|
487
|
-
calls.push(call);
|
|
488
|
-
if (queue.length > 0) return queue.shift();
|
|
489
|
-
const resolved = resolver(method, call);
|
|
562
|
+
});
|
|
490
563
|
if (resolved != null) return resolved;
|
|
564
|
+
if (onUnhandled === "error") throw new Error(`No File mock configured for "${method}"`);
|
|
491
565
|
const fallback = FILE_DEFAULTS[method];
|
|
492
566
|
return fallback === void 0 ? void 0 : structuredClone(fallback);
|
|
493
567
|
}
|
|
568
|
+
const upload = vi.fn(async (...args) => {
|
|
569
|
+
const [namespace, typeName, fieldName, recordId] = args;
|
|
570
|
+
return handle("upload", namespace, typeName, fieldName, recordId);
|
|
571
|
+
});
|
|
572
|
+
const download = vi.fn(async (...args) => handle("download", ...args));
|
|
573
|
+
const downloadAsBase64 = vi.fn(async (...args) => handle("downloadAsBase64", ...args));
|
|
574
|
+
const deleteFile = vi.fn(async (...args) => {
|
|
575
|
+
handle("delete", ...args);
|
|
576
|
+
});
|
|
577
|
+
const getMetadata = vi.fn(async (...args) => handle("getMetadata", ...args));
|
|
578
|
+
const openDownloadStream = vi.fn(async (...args) => toFileStream(handle("openDownloadStream", ...args)));
|
|
579
|
+
const downloadStream = vi.fn(async (...args) => {
|
|
580
|
+
const resolved = handle("downloadStream", ...args);
|
|
581
|
+
if (resolved != null) return resolved;
|
|
582
|
+
return {
|
|
583
|
+
body: new ReadableStream({ start(controller) {
|
|
584
|
+
controller.close();
|
|
585
|
+
} }),
|
|
586
|
+
metadata: {
|
|
587
|
+
contentType: "",
|
|
588
|
+
fileSize: 0,
|
|
589
|
+
sha256sum: "",
|
|
590
|
+
lastUploadedAt: ""
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
});
|
|
594
|
+
const uploadStream = vi.fn(async (...args) => {
|
|
595
|
+
const [namespace, typeName, fieldName, recordId] = args;
|
|
596
|
+
return handle("uploadStream", namespace, typeName, fieldName, recordId);
|
|
597
|
+
});
|
|
598
|
+
const mocks = {
|
|
599
|
+
upload,
|
|
600
|
+
download,
|
|
601
|
+
downloadAsBase64,
|
|
602
|
+
delete: deleteFile,
|
|
603
|
+
getMetadata,
|
|
604
|
+
openDownloadStream,
|
|
605
|
+
downloadStream,
|
|
606
|
+
uploadStream
|
|
607
|
+
};
|
|
608
|
+
function track(method, operation) {
|
|
609
|
+
return function(...args) {
|
|
610
|
+
calls.push({
|
|
611
|
+
method,
|
|
612
|
+
namespace: args[0],
|
|
613
|
+
typeName: args[1],
|
|
614
|
+
fieldName: args[2],
|
|
615
|
+
recordId: args[3]
|
|
616
|
+
});
|
|
617
|
+
return operation.apply(this, args);
|
|
618
|
+
};
|
|
619
|
+
}
|
|
494
620
|
root.file = {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
},
|
|
504
|
-
async delete(namespace, typeName, fieldName, recordId) {
|
|
505
|
-
handle("delete", namespace, typeName, fieldName, recordId);
|
|
506
|
-
},
|
|
507
|
-
async getMetadata(namespace, typeName, fieldName, recordId) {
|
|
508
|
-
return handle("getMetadata", namespace, typeName, fieldName, recordId);
|
|
509
|
-
},
|
|
510
|
-
async openDownloadStream(namespace, typeName, fieldName, recordId) {
|
|
511
|
-
return toFileStream(handle("openDownloadStream", namespace, typeName, fieldName, recordId));
|
|
512
|
-
},
|
|
513
|
-
async downloadStream(namespace, typeName, fieldName, recordId) {
|
|
514
|
-
const resolved = handle("downloadStream", namespace, typeName, fieldName, recordId);
|
|
515
|
-
if (resolved != null) return resolved;
|
|
516
|
-
return {
|
|
517
|
-
body: new ReadableStream({ start(c) {
|
|
518
|
-
c.close();
|
|
519
|
-
} }),
|
|
520
|
-
metadata: {
|
|
521
|
-
contentType: "",
|
|
522
|
-
fileSize: 0,
|
|
523
|
-
sha256sum: "",
|
|
524
|
-
lastUploadedAt: ""
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
},
|
|
528
|
-
async uploadStream(namespace, typeName, fieldName, recordId) {
|
|
529
|
-
return handle("uploadStream", namespace, typeName, fieldName, recordId);
|
|
530
|
-
}
|
|
621
|
+
upload: track("upload", upload),
|
|
622
|
+
download: track("download", download),
|
|
623
|
+
downloadAsBase64: track("downloadAsBase64", downloadAsBase64),
|
|
624
|
+
delete: track("delete", deleteFile),
|
|
625
|
+
getMetadata: track("getMetadata", getMetadata),
|
|
626
|
+
openDownloadStream: track("openDownloadStream", openDownloadStream),
|
|
627
|
+
downloadStream: track("downloadStream", downloadStream),
|
|
628
|
+
uploadStream: track("uploadStream", uploadStream)
|
|
531
629
|
};
|
|
630
|
+
function allMocks() {
|
|
631
|
+
return FILE_METHODS.map((method) => mocks[method]);
|
|
632
|
+
}
|
|
532
633
|
return withDispose({
|
|
634
|
+
...mocks,
|
|
533
635
|
setResolver(value) {
|
|
534
636
|
resolver = value;
|
|
535
637
|
},
|
|
536
638
|
/**
|
|
537
|
-
* Enqueue a single result for the next `tailordb.file` call
|
|
538
|
-
*
|
|
639
|
+
* Enqueue a single result for the next `tailordb.file` call.
|
|
640
|
+
* The queue is shared across all methods and namespaces.
|
|
539
641
|
* @param result - Result to return from the next file call
|
|
540
642
|
*/
|
|
541
643
|
enqueueResult(result) {
|
|
@@ -543,6 +645,7 @@ function mockFile() {
|
|
|
543
645
|
},
|
|
544
646
|
/**
|
|
545
647
|
* Enqueue results for multiple subsequent `tailordb.file` calls.
|
|
648
|
+
* The queue is shared across all methods and namespaces.
|
|
546
649
|
* @param results - Results to enqueue, one per upcoming call
|
|
547
650
|
*/
|
|
548
651
|
enqueueResults(...results) {
|
|
@@ -551,10 +654,15 @@ function mockFile() {
|
|
|
551
654
|
get calls() {
|
|
552
655
|
return calls;
|
|
553
656
|
},
|
|
657
|
+
clear() {
|
|
658
|
+
calls.length = 0;
|
|
659
|
+
for (const mock of allMocks()) mock.mockClear();
|
|
660
|
+
},
|
|
554
661
|
reset() {
|
|
555
662
|
queue.length = 0;
|
|
556
|
-
resolver = () => null;
|
|
557
663
|
calls.length = 0;
|
|
664
|
+
resolver = () => null;
|
|
665
|
+
for (const mock of allMocks()) mock.mockReset();
|
|
558
666
|
}
|
|
559
667
|
}, () => {
|
|
560
668
|
root.file = prev;
|
|
@@ -573,11 +681,11 @@ function defaultIconvResult(method, args) {
|
|
|
573
681
|
case "decode": return "";
|
|
574
682
|
case "encode": return isUtf8(args[1]) ? "" : /* @__PURE__ */ new Uint8Array();
|
|
575
683
|
case "encodings": return [];
|
|
576
|
-
default: return;
|
|
577
684
|
}
|
|
578
685
|
}
|
|
579
686
|
/**
|
|
580
687
|
* Acquire a disposable mock for `tailor.iconv`. Restored on dispose.
|
|
688
|
+
* @param options - Fallback behavior for unconfigured operations
|
|
581
689
|
* @returns Disposable Iconv mock control object
|
|
582
690
|
* @example
|
|
583
691
|
* ```typescript
|
|
@@ -585,27 +693,66 @@ function defaultIconvResult(method, args) {
|
|
|
585
693
|
*
|
|
586
694
|
* test("mock encoding conversion", () => {
|
|
587
695
|
* using iconv = mockIconv();
|
|
588
|
-
* iconv.
|
|
696
|
+
* iconv.decode.mockReturnValue("decoded-text");
|
|
589
697
|
* // …
|
|
590
698
|
* });
|
|
591
699
|
* ```
|
|
592
700
|
*/
|
|
593
|
-
function mockIconv() {
|
|
701
|
+
function mockIconv(options = {}) {
|
|
594
702
|
const root = tailorRoot();
|
|
595
703
|
const prev = root.iconv;
|
|
596
704
|
let resolver = null;
|
|
597
705
|
const calls = [];
|
|
598
|
-
function
|
|
599
|
-
calls.push({
|
|
600
|
-
method,
|
|
601
|
-
args: [...args]
|
|
602
|
-
});
|
|
706
|
+
function resolve(method, args) {
|
|
603
707
|
if (resolver) {
|
|
604
708
|
const result = resolver(method, args);
|
|
605
709
|
if (result != null) return result;
|
|
606
710
|
}
|
|
711
|
+
if (options.onUnhandled === "error") throw new Error(`No Iconv mock behavior configured for "${method}"`);
|
|
607
712
|
return defaultIconvResult(method, args);
|
|
608
713
|
}
|
|
714
|
+
function defaultConvert(input, fromEncoding, toEncoding) {
|
|
715
|
+
return resolve("convert", [
|
|
716
|
+
input,
|
|
717
|
+
fromEncoding,
|
|
718
|
+
toEncoding
|
|
719
|
+
]);
|
|
720
|
+
}
|
|
721
|
+
function defaultConvertBuffer(input, fromEncoding, toEncoding) {
|
|
722
|
+
return resolve("convertBuffer", [
|
|
723
|
+
input,
|
|
724
|
+
fromEncoding,
|
|
725
|
+
toEncoding
|
|
726
|
+
]);
|
|
727
|
+
}
|
|
728
|
+
function defaultDecode(input, encoding) {
|
|
729
|
+
return resolve("decode", [input, encoding]);
|
|
730
|
+
}
|
|
731
|
+
function defaultEncode(input, encoding) {
|
|
732
|
+
return resolve("encode", [input, encoding]);
|
|
733
|
+
}
|
|
734
|
+
function defaultEncodings() {
|
|
735
|
+
return resolve("encodings", []);
|
|
736
|
+
}
|
|
737
|
+
const convert = vi.fn(defaultConvert);
|
|
738
|
+
const convertBuffer = vi.fn(defaultConvertBuffer);
|
|
739
|
+
const decode = vi.fn(defaultDecode);
|
|
740
|
+
const encode = vi.fn(defaultEncode);
|
|
741
|
+
const encodings = vi.fn(defaultEncodings);
|
|
742
|
+
function track(method, operation) {
|
|
743
|
+
return function(...args) {
|
|
744
|
+
calls.push({
|
|
745
|
+
method,
|
|
746
|
+
args: [...args]
|
|
747
|
+
});
|
|
748
|
+
return operation.apply(this, args);
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
const trackedConvert = track("convert", convert);
|
|
752
|
+
const trackedConvertBuffer = track("convertBuffer", convertBuffer);
|
|
753
|
+
const trackedDecode = track("decode", decode);
|
|
754
|
+
const trackedEncode = track("encode", encode);
|
|
755
|
+
const trackedEncodings = track("encodings", encodings);
|
|
609
756
|
class MockIconv {
|
|
610
757
|
#fromEncoding;
|
|
611
758
|
#toEncoding;
|
|
@@ -614,39 +761,56 @@ function mockIconv() {
|
|
|
614
761
|
this.#toEncoding = toEncoding;
|
|
615
762
|
}
|
|
616
763
|
convert(input) {
|
|
617
|
-
return
|
|
618
|
-
input,
|
|
619
|
-
this.#fromEncoding,
|
|
620
|
-
this.#toEncoding
|
|
621
|
-
]);
|
|
764
|
+
return trackedConvert.call(this, input, this.#fromEncoding, this.#toEncoding);
|
|
622
765
|
}
|
|
623
766
|
}
|
|
624
767
|
root.iconv = {
|
|
625
|
-
convert:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
convertBuffer: (buf, from, to) => handle("convertBuffer", [
|
|
631
|
-
buf,
|
|
632
|
-
from,
|
|
633
|
-
to
|
|
634
|
-
]),
|
|
635
|
-
decode: (buf, encoding) => handle("decode", [buf, encoding]),
|
|
636
|
-
encode: (str, encoding) => handle("encode", [str, encoding]),
|
|
637
|
-
encodings: () => handle("encodings", []),
|
|
768
|
+
convert: trackedConvert,
|
|
769
|
+
convertBuffer: trackedConvertBuffer,
|
|
770
|
+
decode: trackedDecode,
|
|
771
|
+
encode: trackedEncode,
|
|
772
|
+
encodings: trackedEncodings,
|
|
638
773
|
Iconv: MockIconv
|
|
639
774
|
};
|
|
775
|
+
function clear() {
|
|
776
|
+
calls.length = 0;
|
|
777
|
+
convert.mockClear();
|
|
778
|
+
convertBuffer.mockClear();
|
|
779
|
+
decode.mockClear();
|
|
780
|
+
encode.mockClear();
|
|
781
|
+
encodings.mockClear();
|
|
782
|
+
}
|
|
640
783
|
return withDispose({
|
|
784
|
+
/** The `convert` `vi.fn`. */
|
|
785
|
+
convert,
|
|
786
|
+
/** The `convertBuffer` `vi.fn`. */
|
|
787
|
+
convertBuffer,
|
|
788
|
+
/** The `decode` `vi.fn`. */
|
|
789
|
+
decode,
|
|
790
|
+
/** The `encode` `vi.fn`. */
|
|
791
|
+
encode,
|
|
792
|
+
/** The `encodings` `vi.fn`. */
|
|
793
|
+
encodings,
|
|
641
794
|
setResolver(value) {
|
|
642
795
|
resolver = value;
|
|
643
796
|
},
|
|
644
797
|
get calls() {
|
|
645
798
|
return calls;
|
|
646
799
|
},
|
|
800
|
+
clear,
|
|
647
801
|
reset() {
|
|
648
802
|
resolver = null;
|
|
649
803
|
calls.length = 0;
|
|
804
|
+
convert.mockReset();
|
|
805
|
+
convert.mockImplementation(defaultConvert);
|
|
806
|
+
convertBuffer.mockReset();
|
|
807
|
+
convertBuffer.mockImplementation(defaultConvertBuffer);
|
|
808
|
+
decode.mockReset();
|
|
809
|
+
decode.mockImplementation(defaultDecode);
|
|
810
|
+
encode.mockReset();
|
|
811
|
+
encode.mockImplementation(defaultEncode);
|
|
812
|
+
encodings.mockReset();
|
|
813
|
+
encodings.mockImplementation(defaultEncodings);
|
|
650
814
|
}
|
|
651
815
|
}, () => {
|
|
652
816
|
root.iconv = prev;
|
|
@@ -655,99 +819,131 @@ function mockIconv() {
|
|
|
655
819
|
|
|
656
820
|
//#endregion
|
|
657
821
|
//#region src/vitest/mocks/idp.ts
|
|
822
|
+
const IDP_METHODS = [
|
|
823
|
+
"users",
|
|
824
|
+
"user",
|
|
825
|
+
"userByName",
|
|
826
|
+
"createUser",
|
|
827
|
+
"updateUser",
|
|
828
|
+
"deleteUser",
|
|
829
|
+
"sendPasswordResetEmail",
|
|
830
|
+
"unenrollMfa"
|
|
831
|
+
];
|
|
832
|
+
const IDP_USER_DEFAULT = {
|
|
833
|
+
id: "mock-id",
|
|
834
|
+
name: "mock-user",
|
|
835
|
+
disabled: false,
|
|
836
|
+
mfaEnrolled: false,
|
|
837
|
+
mfaFactorIds: []
|
|
838
|
+
};
|
|
658
839
|
const IDP_DEFAULTS = {
|
|
659
840
|
users: {
|
|
660
841
|
users: [],
|
|
661
842
|
nextPageToken: null,
|
|
662
843
|
totalCount: 0
|
|
663
844
|
},
|
|
664
|
-
user:
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
mfaEnrolled: false,
|
|
669
|
-
mfaFactorIds: []
|
|
670
|
-
},
|
|
671
|
-
userByName: {
|
|
672
|
-
id: "mock-id",
|
|
673
|
-
name: "mock-user",
|
|
674
|
-
disabled: false,
|
|
675
|
-
mfaEnrolled: false,
|
|
676
|
-
mfaFactorIds: []
|
|
677
|
-
},
|
|
678
|
-
createUser: {
|
|
679
|
-
id: "mock-id",
|
|
680
|
-
name: "mock-user",
|
|
681
|
-
disabled: false,
|
|
682
|
-
mfaEnrolled: false,
|
|
683
|
-
mfaFactorIds: []
|
|
684
|
-
},
|
|
685
|
-
updateUser: {
|
|
686
|
-
id: "mock-id",
|
|
687
|
-
name: "mock-user",
|
|
688
|
-
disabled: false,
|
|
689
|
-
mfaEnrolled: false,
|
|
690
|
-
mfaFactorIds: []
|
|
691
|
-
},
|
|
845
|
+
user: IDP_USER_DEFAULT,
|
|
846
|
+
userByName: IDP_USER_DEFAULT,
|
|
847
|
+
createUser: IDP_USER_DEFAULT,
|
|
848
|
+
updateUser: IDP_USER_DEFAULT,
|
|
692
849
|
deleteUser: true,
|
|
693
850
|
sendPasswordResetEmail: true,
|
|
694
851
|
unenrollMfa: true
|
|
695
852
|
};
|
|
696
853
|
/**
|
|
697
854
|
* Acquire a disposable mock for `tailor.idp`. Restored on dispose.
|
|
855
|
+
* @param options - Controls behavior for calls without a configured result
|
|
698
856
|
* @returns Disposable IDP mock control object
|
|
699
857
|
* @example
|
|
700
858
|
* ```typescript
|
|
701
859
|
* import { mockIdp } from "@tailor-platform/sdk/vitest";
|
|
702
860
|
*
|
|
703
|
-
* test("
|
|
861
|
+
* test("returns a user", async () => {
|
|
704
862
|
* using idp = mockIdp();
|
|
705
|
-
* idp.
|
|
706
|
-
*
|
|
707
|
-
*
|
|
863
|
+
* idp.namespace("my-idp").user.mockResolvedValue({
|
|
864
|
+
* id: "u-1",
|
|
865
|
+
* name: "alice",
|
|
866
|
+
* disabled: false,
|
|
867
|
+
* mfaEnrolled: false,
|
|
868
|
+
* mfaFactorIds: [],
|
|
869
|
+
* });
|
|
708
870
|
* // …
|
|
709
871
|
* });
|
|
710
872
|
* ```
|
|
711
873
|
*/
|
|
712
|
-
function mockIdp() {
|
|
874
|
+
function mockIdp(options = {}) {
|
|
713
875
|
const root = tailorRoot();
|
|
714
876
|
const prev = root.idp;
|
|
877
|
+
const { onUnhandled = "fallback" } = options;
|
|
715
878
|
const queue = [];
|
|
716
|
-
let resolver = () => null;
|
|
717
879
|
const calls = [];
|
|
880
|
+
let resolver = () => null;
|
|
881
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
882
|
+
function compatibilityArgs(method, args) {
|
|
883
|
+
return method === "users" && args.length === 0 ? [void 0] : args;
|
|
884
|
+
}
|
|
718
885
|
function handle(method, args, namespace) {
|
|
719
|
-
calls.push({
|
|
720
|
-
method,
|
|
721
|
-
args,
|
|
722
|
-
namespace
|
|
723
|
-
});
|
|
724
886
|
if (queue.length > 0) return queue.shift();
|
|
725
|
-
const resolved = resolver(method, args, namespace);
|
|
887
|
+
const resolved = resolver(method, compatibilityArgs(method, args), namespace);
|
|
726
888
|
if (resolved != null) return resolved;
|
|
727
|
-
|
|
728
|
-
return
|
|
889
|
+
if (onUnhandled === "error") throw new Error(`No IdP mock configured for "${namespace}.${method}"`);
|
|
890
|
+
return structuredClone(IDP_DEFAULTS[method]);
|
|
729
891
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
892
|
+
function createNamespaceMocks(namespace) {
|
|
893
|
+
return {
|
|
894
|
+
users: vi.fn(async (...args) => handle("users", args, namespace)),
|
|
895
|
+
user: vi.fn(async (...args) => handle("user", args, namespace)),
|
|
896
|
+
userByName: vi.fn(async (...args) => handle("userByName", args, namespace)),
|
|
897
|
+
createUser: vi.fn(async (...args) => handle("createUser", args, namespace)),
|
|
898
|
+
updateUser: vi.fn(async (...args) => handle("updateUser", args, namespace)),
|
|
899
|
+
deleteUser: vi.fn(async (...args) => handle("deleteUser", args, namespace)),
|
|
900
|
+
sendPasswordResetEmail: vi.fn(async (...args) => handle("sendPasswordResetEmail", args, namespace)),
|
|
901
|
+
unenrollMfa: vi.fn(async (...args) => handle("unenrollMfa", args, namespace))
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function namespace(name) {
|
|
905
|
+
const existing = namespaces.get(name);
|
|
906
|
+
if (existing) return existing;
|
|
907
|
+
const mocks = createNamespaceMocks(name);
|
|
908
|
+
namespaces.set(name, mocks);
|
|
909
|
+
return mocks;
|
|
910
|
+
}
|
|
911
|
+
function track(method, mock, namespaceName) {
|
|
912
|
+
return function(...args) {
|
|
913
|
+
calls.push({
|
|
914
|
+
method,
|
|
915
|
+
args: [...compatibilityArgs(method, args)],
|
|
916
|
+
namespace: namespaceName
|
|
917
|
+
});
|
|
918
|
+
return mock.apply(this, args);
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
const defaultClient = function(config) {
|
|
922
|
+
const mocks = namespace(config.namespace);
|
|
923
|
+
this.users = track("users", mocks.users, config.namespace);
|
|
924
|
+
this.user = track("user", mocks.user, config.namespace);
|
|
925
|
+
this.userByName = track("userByName", mocks.userByName, config.namespace);
|
|
926
|
+
this.createUser = track("createUser", mocks.createUser, config.namespace);
|
|
927
|
+
this.updateUser = track("updateUser", mocks.updateUser, config.namespace);
|
|
928
|
+
this.deleteUser = track("deleteUser", mocks.deleteUser, config.namespace);
|
|
929
|
+
this.sendPasswordResetEmail = track("sendPasswordResetEmail", mocks.sendPasswordResetEmail, config.namespace);
|
|
930
|
+
this.unenrollMfa = track("unenrollMfa", mocks.unenrollMfa, config.namespace);
|
|
931
|
+
};
|
|
932
|
+
const Client = vi.fn(defaultClient);
|
|
741
933
|
root.idp = { Client };
|
|
934
|
+
function allMocks() {
|
|
935
|
+
return [...namespaces.values()].flatMap((mocks) => IDP_METHODS.map((method) => mocks[method]));
|
|
936
|
+
}
|
|
742
937
|
return withDispose({
|
|
743
938
|
/** The mock IDP `Client` constructor (`vi.fn`). */
|
|
744
939
|
Client,
|
|
940
|
+
namespace,
|
|
745
941
|
setResolver(value) {
|
|
746
942
|
resolver = value;
|
|
747
943
|
},
|
|
748
944
|
/**
|
|
749
|
-
* Enqueue a single result for the next IDP call
|
|
750
|
-
*
|
|
945
|
+
* Enqueue a single result for the next IDP call.
|
|
946
|
+
* The queue is shared across all methods and namespaces.
|
|
751
947
|
* @param result - Result to return from the next IDP call
|
|
752
948
|
*/
|
|
753
949
|
enqueueResult(result) {
|
|
@@ -755,6 +951,7 @@ function mockIdp() {
|
|
|
755
951
|
},
|
|
756
952
|
/**
|
|
757
953
|
* Enqueue results for multiple subsequent IDP calls.
|
|
954
|
+
* The queue is shared across all methods and namespaces.
|
|
758
955
|
* @param results - Results to enqueue, one per upcoming call
|
|
759
956
|
*/
|
|
760
957
|
enqueueResults(...results) {
|
|
@@ -763,10 +960,18 @@ function mockIdp() {
|
|
|
763
960
|
get calls() {
|
|
764
961
|
return calls;
|
|
765
962
|
},
|
|
963
|
+
clear() {
|
|
964
|
+
calls.length = 0;
|
|
965
|
+
Client.mockClear();
|
|
966
|
+
for (const mock of allMocks()) mock.mockClear();
|
|
967
|
+
},
|
|
766
968
|
reset() {
|
|
767
969
|
queue.length = 0;
|
|
768
|
-
resolver = () => null;
|
|
769
970
|
calls.length = 0;
|
|
971
|
+
resolver = () => null;
|
|
972
|
+
Client.mockReset();
|
|
973
|
+
Client.mockImplementation(defaultClient);
|
|
974
|
+
for (const mock of allMocks()) mock.mockReset();
|
|
770
975
|
}
|
|
771
976
|
}, () => {
|
|
772
977
|
root.idp = prev;
|
|
@@ -775,6 +980,15 @@ function mockIdp() {
|
|
|
775
980
|
|
|
776
981
|
//#endregion
|
|
777
982
|
//#region src/vitest/mocks/tailordb.ts
|
|
983
|
+
function testRegex(regex, value) {
|
|
984
|
+
const lastIndex = regex.lastIndex;
|
|
985
|
+
regex.lastIndex = 0;
|
|
986
|
+
try {
|
|
987
|
+
return regex.test(value);
|
|
988
|
+
} finally {
|
|
989
|
+
regex.lastIndex = lastIndex;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
778
992
|
var MockQueryResult = class {
|
|
779
993
|
command;
|
|
780
994
|
rowCount;
|
|
@@ -790,27 +1004,62 @@ var MockQueryResult = class {
|
|
|
790
1004
|
* `tailordb.Client` whose `queryObject` is a shared `vi.fn()` (so query
|
|
791
1005
|
* responses can be staged before the client is constructed). Restored on
|
|
792
1006
|
* dispose.
|
|
1007
|
+
* @param options - Query fallback behavior
|
|
793
1008
|
* @returns Disposable TailorDB mock control object
|
|
794
1009
|
* @example
|
|
795
1010
|
* ```typescript
|
|
796
1011
|
* import { mockTailordb } from "@tailor-platform/sdk/vitest";
|
|
797
1012
|
*
|
|
798
|
-
* test("
|
|
1013
|
+
* test("query-based", async () => {
|
|
799
1014
|
* using db = mockTailordb();
|
|
800
|
-
* db.
|
|
1015
|
+
* db.onQuery({ sql: /FROM users/, params: ["u-1"] }).returnsRows([{ age: 30 }]);
|
|
801
1016
|
* // …
|
|
802
|
-
* expect(db.queryObject).
|
|
1017
|
+
* expect(db.queryObject).toHaveBeenCalled();
|
|
803
1018
|
* expect(db.Client).toHaveBeenCalledWith({ namespace: "tailordb" });
|
|
804
1019
|
* });
|
|
805
1020
|
* ```
|
|
806
1021
|
*/
|
|
807
|
-
function mockTailordb() {
|
|
1022
|
+
function mockTailordb(options = {}) {
|
|
808
1023
|
const root = tailordbRoot();
|
|
809
1024
|
const prevClient = root.Client;
|
|
810
|
-
const
|
|
811
|
-
|
|
1025
|
+
const rules = [];
|
|
1026
|
+
let queryResolver;
|
|
1027
|
+
function matchesQuery(matcher, query, params) {
|
|
1028
|
+
if (typeof matcher === "function") return matcher(query, params);
|
|
1029
|
+
if (typeof matcher === "string") return query === matcher;
|
|
1030
|
+
if (matcher instanceof RegExp) return testRegex(matcher, query);
|
|
1031
|
+
let sqlMatches;
|
|
1032
|
+
if (typeof matcher.sql === "string") sqlMatches = query === matcher.sql;
|
|
1033
|
+
else sqlMatches = testRegex(matcher.sql, query);
|
|
1034
|
+
if (!sqlMatches || matcher.params === void 0) return sqlMatches;
|
|
1035
|
+
if (typeof matcher.params === "function") return matcher.params(params);
|
|
1036
|
+
const expectedParams = matcher.params;
|
|
1037
|
+
return params.length === expectedParams.length && params.every((value, index) => isEqual(value, expectedParams[index]));
|
|
1038
|
+
}
|
|
1039
|
+
function queryResponse(response) {
|
|
1040
|
+
if (response.type === "error") throw response.error;
|
|
1041
|
+
return new MockQueryResult(response.rows);
|
|
1042
|
+
}
|
|
1043
|
+
async function defaultQuery(query, params = []) {
|
|
1044
|
+
for (let i = rules.length - 1; i >= 0; i -= 1) {
|
|
1045
|
+
const rule = rules[i];
|
|
1046
|
+
if (!rule || !matchesQuery(rule.matcher, query, params)) continue;
|
|
1047
|
+
const once = rule.once.shift();
|
|
1048
|
+
if (once) return queryResponse(once);
|
|
1049
|
+
if (rule.fallback) return queryResponse(rule.fallback);
|
|
1050
|
+
}
|
|
1051
|
+
if (queryResolver) return new MockQueryResult(queryResolver(query, params) ?? []);
|
|
1052
|
+
if (options.onUnhandled === "error") throw new Error(`No TailorDB query behavior matched: ${query}`);
|
|
1053
|
+
return new MockQueryResult([]);
|
|
1054
|
+
}
|
|
1055
|
+
const queryObject = vi.fn(defaultQuery);
|
|
1056
|
+
const defaultConnect = async () => {};
|
|
1057
|
+
const connect = vi.fn(defaultConnect);
|
|
812
1058
|
const createdClients = [];
|
|
813
|
-
|
|
1059
|
+
function enqueueRowsList(rowsList) {
|
|
1060
|
+
for (const rows of rowsList) queryObject.mockImplementationOnce(async () => new MockQueryResult(rows));
|
|
1061
|
+
}
|
|
1062
|
+
const defaultClient = function(config) {
|
|
814
1063
|
const record = {
|
|
815
1064
|
namespace: config?.namespace,
|
|
816
1065
|
ended: false
|
|
@@ -830,7 +1079,8 @@ function mockTailordb() {
|
|
|
830
1079
|
queryObject
|
|
831
1080
|
};
|
|
832
1081
|
};
|
|
833
|
-
}
|
|
1082
|
+
};
|
|
1083
|
+
const Client = vi.fn(defaultClient);
|
|
834
1084
|
root.Client = Client;
|
|
835
1085
|
return withDispose({
|
|
836
1086
|
/** The mock `tailordb.Client` constructor (`vi.fn`). */
|
|
@@ -842,7 +1092,53 @@ function mockTailordb() {
|
|
|
842
1092
|
* @param resolver - Function that returns rows for a given query and params
|
|
843
1093
|
*/
|
|
844
1094
|
setQueryResolver(resolver) {
|
|
845
|
-
|
|
1095
|
+
queryResolver = resolver;
|
|
1096
|
+
queryObject.mockImplementation(defaultQuery);
|
|
1097
|
+
},
|
|
1098
|
+
/**
|
|
1099
|
+
* Configure responses for queries matching SQL text, parameters, or a predicate.
|
|
1100
|
+
* More recently registered matchers take precedence.
|
|
1101
|
+
* Do not combine matchers with a direct `queryObject.mockImplementation()` override.
|
|
1102
|
+
* @param matcher - Query matcher
|
|
1103
|
+
* @returns Chainable query behavior
|
|
1104
|
+
*/
|
|
1105
|
+
onQuery(matcher) {
|
|
1106
|
+
const rule = {
|
|
1107
|
+
matcher,
|
|
1108
|
+
once: []
|
|
1109
|
+
};
|
|
1110
|
+
rules.push(rule);
|
|
1111
|
+
const behavior = {
|
|
1112
|
+
returnsRows(rows) {
|
|
1113
|
+
rule.fallback = {
|
|
1114
|
+
type: "rows",
|
|
1115
|
+
rows
|
|
1116
|
+
};
|
|
1117
|
+
return behavior;
|
|
1118
|
+
},
|
|
1119
|
+
returnsRowsOnce(rows) {
|
|
1120
|
+
rule.once.push({
|
|
1121
|
+
type: "rows",
|
|
1122
|
+
rows
|
|
1123
|
+
});
|
|
1124
|
+
return behavior;
|
|
1125
|
+
},
|
|
1126
|
+
rejects(error) {
|
|
1127
|
+
rule.fallback = {
|
|
1128
|
+
type: "error",
|
|
1129
|
+
error
|
|
1130
|
+
};
|
|
1131
|
+
return behavior;
|
|
1132
|
+
},
|
|
1133
|
+
rejectsOnce(error) {
|
|
1134
|
+
rule.once.push({
|
|
1135
|
+
type: "error",
|
|
1136
|
+
error
|
|
1137
|
+
});
|
|
1138
|
+
return behavior;
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
return behavior;
|
|
846
1142
|
},
|
|
847
1143
|
/**
|
|
848
1144
|
* Enqueue rows for the next `queryObject` call (FIFO; takes priority over
|
|
@@ -850,14 +1146,21 @@ function mockTailordb() {
|
|
|
850
1146
|
* @param rows - Row objects to return from the next `queryObject` call
|
|
851
1147
|
*/
|
|
852
1148
|
enqueueResult(...rows) {
|
|
853
|
-
|
|
1149
|
+
enqueueRowsList([rows]);
|
|
854
1150
|
},
|
|
855
1151
|
/**
|
|
856
1152
|
* Enqueue rows for multiple subsequent `queryObject` calls (FIFO).
|
|
857
1153
|
* @param rowsList - Rows arrays, one per upcoming query
|
|
858
1154
|
*/
|
|
859
1155
|
enqueueResults(...rowsList) {
|
|
860
|
-
|
|
1156
|
+
enqueueRowsList(rowsList);
|
|
1157
|
+
},
|
|
1158
|
+
/**
|
|
1159
|
+
* Enqueue row arrays for subsequent queries whose exact order is under test.
|
|
1160
|
+
* @param rowsList - Rows arrays, one per upcoming query
|
|
1161
|
+
*/
|
|
1162
|
+
enqueueRows(...rowsList) {
|
|
1163
|
+
enqueueRowsList(rowsList);
|
|
861
1164
|
},
|
|
862
1165
|
/**
|
|
863
1166
|
* All queries executed via `queryObject`, in order, derived from the vi.fn
|
|
@@ -877,13 +1180,24 @@ function mockTailordb() {
|
|
|
877
1180
|
get createdClients() {
|
|
878
1181
|
return createdClients;
|
|
879
1182
|
},
|
|
1183
|
+
/** Clear recorded calls while preserving configured query behavior. */
|
|
1184
|
+
clear() {
|
|
1185
|
+
queryObject.mockClear();
|
|
1186
|
+
connect.mockClear();
|
|
1187
|
+
Client.mockClear();
|
|
1188
|
+
createdClients.length = 0;
|
|
1189
|
+
},
|
|
880
1190
|
/** Reset query responses and recorded calls (keeps the mock installed). */
|
|
881
1191
|
reset() {
|
|
882
1192
|
queryObject.mockReset();
|
|
883
|
-
queryObject.mockImplementation(
|
|
884
|
-
connect.
|
|
885
|
-
|
|
1193
|
+
queryObject.mockImplementation(defaultQuery);
|
|
1194
|
+
connect.mockReset();
|
|
1195
|
+
connect.mockImplementation(defaultConnect);
|
|
1196
|
+
Client.mockReset();
|
|
1197
|
+
Client.mockImplementation(defaultClient);
|
|
886
1198
|
createdClients.length = 0;
|
|
1199
|
+
rules.length = 0;
|
|
1200
|
+
queryResolver = void 0;
|
|
887
1201
|
}
|
|
888
1202
|
}, () => {
|
|
889
1203
|
root.Client = prevClient;
|
|
@@ -892,6 +1206,30 @@ function mockTailordb() {
|
|
|
892
1206
|
|
|
893
1207
|
//#endregion
|
|
894
1208
|
//#region src/vitest/mocks/workflow.ts
|
|
1209
|
+
const originalProcedures = /* @__PURE__ */ new WeakMap();
|
|
1210
|
+
function replaceProcedure(target, key, current) {
|
|
1211
|
+
const original = originalProcedures.get(current) ?? current;
|
|
1212
|
+
const defaultImplementation = function(...args) {
|
|
1213
|
+
return Reflect.apply(original, this, args);
|
|
1214
|
+
};
|
|
1215
|
+
const mock = vi.fn(defaultImplementation);
|
|
1216
|
+
originalProcedures.set(mock, original);
|
|
1217
|
+
const record = target;
|
|
1218
|
+
record[key] = mock;
|
|
1219
|
+
return {
|
|
1220
|
+
mock,
|
|
1221
|
+
scoped: {
|
|
1222
|
+
mockClear: () => mock.mockClear(),
|
|
1223
|
+
mockReset: () => mock.mockReset(),
|
|
1224
|
+
restore: () => {
|
|
1225
|
+
if (record[key] === mock) record[key] = current;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
function replaceTrigger(definition) {
|
|
1231
|
+
return replaceProcedure(definition, "trigger", definition.trigger);
|
|
1232
|
+
}
|
|
895
1233
|
/**
|
|
896
1234
|
* Acquire a disposable mock for workflow operations (`tailor.workflow`).
|
|
897
1235
|
* Restored on dispose.
|
|
@@ -900,17 +1238,23 @@ function mockTailordb() {
|
|
|
900
1238
|
* ```typescript
|
|
901
1239
|
* import { mockWorkflow } from "@tailor-platform/sdk/vitest";
|
|
902
1240
|
*
|
|
903
|
-
* test("job
|
|
1241
|
+
* test("job trigger", async () => {
|
|
904
1242
|
* using wf = mockWorkflow();
|
|
905
|
-
* wf.
|
|
906
|
-
*
|
|
907
|
-
*
|
|
1243
|
+
* const job = wf.job(validateOrder);
|
|
1244
|
+
* job.mockResolvedValue({ valid: true });
|
|
1245
|
+
* await runWorkflowUnderTest();
|
|
1246
|
+
* expect(job).toHaveBeenCalled();
|
|
908
1247
|
* });
|
|
909
1248
|
* ```
|
|
910
1249
|
*/
|
|
911
1250
|
function mockWorkflow() {
|
|
912
1251
|
const root = tailorRoot();
|
|
913
1252
|
const prev = root.workflow;
|
|
1253
|
+
const prevEnv = readWorkflowTestEnv();
|
|
1254
|
+
const jobSpies = /* @__PURE__ */ new Map();
|
|
1255
|
+
const workflowSpies = /* @__PURE__ */ new Map();
|
|
1256
|
+
const waitPointMocks = /* @__PURE__ */ new Map();
|
|
1257
|
+
const scopedMocks = /* @__PURE__ */ new Set();
|
|
914
1258
|
const defaultTriggerJob = (jobName, args, _options) => {
|
|
915
1259
|
const body = getRegisteredJob(jobName);
|
|
916
1260
|
return body ? body(args, buildJobContext()) : null;
|
|
@@ -954,6 +1298,64 @@ function mockWorkflow() {
|
|
|
954
1298
|
/** The `resolve` `vi.fn`. */
|
|
955
1299
|
resolve,
|
|
956
1300
|
/**
|
|
1301
|
+
* Get a stable, typed mock for a workflow job's `trigger` method.
|
|
1302
|
+
* The real trigger behavior is used until an implementation or result is configured.
|
|
1303
|
+
* @param definition - Workflow job definition to mock
|
|
1304
|
+
* @returns Typed `trigger` mock for the definition
|
|
1305
|
+
*/
|
|
1306
|
+
job(definition) {
|
|
1307
|
+
const existing = jobSpies.get(definition);
|
|
1308
|
+
if (existing) return existing;
|
|
1309
|
+
const { mock, scoped } = replaceTrigger(definition);
|
|
1310
|
+
scopedMocks.add(scoped);
|
|
1311
|
+
jobSpies.set(definition, mock);
|
|
1312
|
+
return mock;
|
|
1313
|
+
},
|
|
1314
|
+
/**
|
|
1315
|
+
* Get a stable, typed mock for a workflow definition's `trigger` method.
|
|
1316
|
+
* The real trigger behavior is used until an implementation or result is configured.
|
|
1317
|
+
* @param definition - Workflow definition to mock
|
|
1318
|
+
* @returns Typed `trigger` mock for the definition
|
|
1319
|
+
*/
|
|
1320
|
+
workflow(definition) {
|
|
1321
|
+
const existing = workflowSpies.get(definition);
|
|
1322
|
+
if (existing) return existing;
|
|
1323
|
+
const { mock, scoped } = replaceTrigger(definition);
|
|
1324
|
+
workflowSpies.set(definition, mock);
|
|
1325
|
+
scopedMocks.add(scoped);
|
|
1326
|
+
return mock;
|
|
1327
|
+
},
|
|
1328
|
+
/**
|
|
1329
|
+
* Get stable, typed mocks for a wait point's `wait` and `resolve` methods.
|
|
1330
|
+
* @param definition - Wait point definition to mock
|
|
1331
|
+
* @returns Typed wait point mock control object
|
|
1332
|
+
*/
|
|
1333
|
+
waitPoint(definition) {
|
|
1334
|
+
const existing = waitPointMocks.get(definition);
|
|
1335
|
+
if (existing) return existing;
|
|
1336
|
+
const waitReplacement = replaceProcedure(definition, "wait", definition.wait);
|
|
1337
|
+
const resolveReplacement = replaceProcedure(definition, "resolve", definition.resolve);
|
|
1338
|
+
const waitSpy = waitReplacement.mock;
|
|
1339
|
+
const resolveSpy = resolveReplacement.mock;
|
|
1340
|
+
scopedMocks.add(waitReplacement.scoped);
|
|
1341
|
+
scopedMocks.add(resolveReplacement.scoped);
|
|
1342
|
+
const waitPointMock = {
|
|
1343
|
+
wait: waitSpy,
|
|
1344
|
+
resolve: resolveSpy,
|
|
1345
|
+
/**
|
|
1346
|
+
* Invoke the next and subsequent resolve callbacks with a wait payload.
|
|
1347
|
+
* @param payload - Payload originally supplied to the wait point
|
|
1348
|
+
*/
|
|
1349
|
+
setResolvePayload(payload) {
|
|
1350
|
+
resolveSpy.mockImplementation(async (_executionId, callback) => {
|
|
1351
|
+
platformSerialize(await callback(platformSerialize(payload)));
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
waitPointMocks.set(definition, waitPointMock);
|
|
1356
|
+
return waitPointMock;
|
|
1357
|
+
},
|
|
1358
|
+
/**
|
|
957
1359
|
* Set a fallback job handler. Called when the enqueue queue is empty.
|
|
958
1360
|
* @param handler - Function returning a result for a job name, args, and options
|
|
959
1361
|
*/
|
|
@@ -1048,6 +1450,15 @@ function mockWorkflow() {
|
|
|
1048
1450
|
key
|
|
1049
1451
|
}));
|
|
1050
1452
|
},
|
|
1453
|
+
/** Clear recorded calls while preserving configured responses. */
|
|
1454
|
+
clear() {
|
|
1455
|
+
triggerJobFunction.mockClear();
|
|
1456
|
+
triggerWorkflow.mockClear();
|
|
1457
|
+
resumeWorkflow.mockClear();
|
|
1458
|
+
wait.mockClear();
|
|
1459
|
+
resolve.mockClear();
|
|
1460
|
+
for (const mock of scopedMocks) mock.mockClear();
|
|
1461
|
+
},
|
|
1051
1462
|
/** Reset all workflow responses and recorded calls (keeps the mock installed). */
|
|
1052
1463
|
reset() {
|
|
1053
1464
|
triggerJobFunction.mockReset();
|
|
@@ -1060,11 +1471,14 @@ function mockWorkflow() {
|
|
|
1060
1471
|
wait.mockImplementation(() => null);
|
|
1061
1472
|
resolve.mockReset();
|
|
1062
1473
|
resolve.mockImplementation(async () => {});
|
|
1474
|
+
for (const mock of scopedMocks) mock.mockReset();
|
|
1063
1475
|
clearWorkflowTestEnv();
|
|
1064
1476
|
}
|
|
1065
1477
|
}, () => {
|
|
1478
|
+
for (const mock of scopedMocks) mock.restore();
|
|
1066
1479
|
root.workflow = prev;
|
|
1067
|
-
|
|
1480
|
+
if (prevEnv !== void 0) writeWorkflowTestEnv(prevEnv);
|
|
1481
|
+
else clearWorkflowTestEnv();
|
|
1068
1482
|
});
|
|
1069
1483
|
}
|
|
1070
1484
|
|