@voltro/testing 0.84.0 → 0.86.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 +1215 -0
- package/THIRD-PARTY-NOTICES.md +118 -600
- package/dist/client.d.ts +4 -9
- package/dist/client.js +73 -98
- package/dist/dialect.d.ts +1 -1
- package/dist/dialect.js +244 -145
- package/dist/factory.js +65 -0
- package/dist/fixtureRow.js +30 -0
- package/dist/formBindingHarness.js +46 -0
- package/dist/hydrateFixtureRows.js +22 -0
- package/dist/index.d.ts +13 -13
- package/dist/index.js +19 -933
- package/dist/invoke.js +44 -0
- package/dist/mockClock.js +78 -0
- package/dist/mockLLM.js +19 -0
- package/dist/mockWebhooks.js +26 -0
- package/dist/platform.js +1 -5
- package/dist/reachable.js +27 -0
- package/dist/serviceApiMock.js +53 -0
- package/dist/subject.js +11 -0
- package/dist/subscribeContext.js +16 -0
- package/dist/testApps.js +111 -0
- package/dist/testContext.js +248 -0
- package/dist/testEventBus.js +73 -0
- package/dist/testRequest.js +119 -0
- package/dist/workflowRunner.js +85 -0
- package/package.json +10 -11
package/dist/factory.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { fixtureRow as e, nextSequence as t } from "./fixtureRow.js";
|
|
2
|
+
import { missingRequiredColumns as n } from "@voltro/database";
|
|
3
|
+
//#region src/factory.ts
|
|
4
|
+
var r = (e) => {
|
|
5
|
+
let n = {};
|
|
6
|
+
for (let [r, i] of Object.entries(e)) n[r] = typeof i == "function" ? i(t()) : i;
|
|
7
|
+
return n;
|
|
8
|
+
}, i = (e) => {
|
|
9
|
+
for (let [t, n] of Object.entries(e.fields)) if (n.type === "id") return t;
|
|
10
|
+
return "id";
|
|
11
|
+
}, a = async (e, t, r, a, s) => {
|
|
12
|
+
let c = {};
|
|
13
|
+
for (let l of n(t, r)) {
|
|
14
|
+
let n = t.fields[l];
|
|
15
|
+
if (n === void 0 || n.type !== "reference" || n.references === void 0) continue;
|
|
16
|
+
let r = n.references();
|
|
17
|
+
if (s.includes(r.tableName)) throw Error(`defineFactory: creating '${t.tableName}' requires '${r.tableName}' via '${l}', and that reference is cyclic (${[...s, r.tableName].join(" → ")}). Insert one side first and pass the id: create(store, { ${l}: existing.id }).`);
|
|
18
|
+
let u = a[l];
|
|
19
|
+
c[l] = (u === void 0 ? await o(e, r, {}, {}, [...s, r.tableName]) : await u.create(e, {}))[n.referenceColumn ?? i(r)];
|
|
20
|
+
}
|
|
21
|
+
return c;
|
|
22
|
+
}, o = async (t, n, r, i, o) => {
|
|
23
|
+
let s = {
|
|
24
|
+
...await a(t, n, r, i, o),
|
|
25
|
+
...r
|
|
26
|
+
};
|
|
27
|
+
return t.insert(n.tableName, e(n, s));
|
|
28
|
+
}, s = (e, t) => typeof e == "function" ? e(t) : e ?? {}, c = (t, n = {}) => {
|
|
29
|
+
let i = n.traits ?? {}, a = n.associations ?? {}, c = (n) => {
|
|
30
|
+
let l = (e = {}) => ({
|
|
31
|
+
...r(n),
|
|
32
|
+
...e
|
|
33
|
+
}), u = (n = {}) => e(t, l(n));
|
|
34
|
+
return {
|
|
35
|
+
table: t,
|
|
36
|
+
build: u,
|
|
37
|
+
buildList: (e, t) => Array.from({ length: e }, (e, n) => u(s(t, n))),
|
|
38
|
+
create: (e, n = {}) => o(e, t, l(n), a, [t.tableName]),
|
|
39
|
+
createList: async (e, n, r) => {
|
|
40
|
+
let i = [];
|
|
41
|
+
for (let c = 0; c < n; c++) i.push(await o(e, t, l(s(r, c)), a, [t.tableName]));
|
|
42
|
+
return i;
|
|
43
|
+
},
|
|
44
|
+
with: (...e) => {
|
|
45
|
+
let r = n;
|
|
46
|
+
for (let n of e) {
|
|
47
|
+
let e = i[n];
|
|
48
|
+
if (e === void 0) throw Error(`defineFactory: '${t.tableName}' has no trait '${n}'. Declared: ${Object.keys(i).join(", ") || "(none)"}.`);
|
|
49
|
+
r = {
|
|
50
|
+
...r,
|
|
51
|
+
...e
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return c(r);
|
|
55
|
+
},
|
|
56
|
+
extend: (e) => c({
|
|
57
|
+
...n,
|
|
58
|
+
...e
|
|
59
|
+
})
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
return c(n.defaults ?? {});
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { c as defineFactory };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AUTO_FILLED_COLUMNS as e, missingRequiredColumns as t } from "@voltro/database";
|
|
2
|
+
//#region src/fixtureRow.ts
|
|
3
|
+
var n = new Set(e), r = 0, i = () => ++r, a = (e, t) => {
|
|
4
|
+
if (t.oneOf && t.oneOf.length > 0) return t.oneOf[0];
|
|
5
|
+
if (t.enumValues && t.enumValues.length > 0) return t.enumValues[0];
|
|
6
|
+
let n = t.unique === !0;
|
|
7
|
+
switch (t.type) {
|
|
8
|
+
case "text":
|
|
9
|
+
case "id":
|
|
10
|
+
case "enum": return n ? `${e}-${++r}` : e;
|
|
11
|
+
case "reference": return t.referenceStorage === "numeric" ? n ? ++r : 0 : n ? `${e}-${++r}` : e;
|
|
12
|
+
case "integer":
|
|
13
|
+
case "real": return n ? ++r : 0;
|
|
14
|
+
case "decimal":
|
|
15
|
+
case "bigint": return n ? String(++r) : "0";
|
|
16
|
+
case "boolean": return !1;
|
|
17
|
+
case "timestamp":
|
|
18
|
+
case "date": return /* @__PURE__ */ new Date(0);
|
|
19
|
+
default: throw Error(`fixtureRow: column '${e}' is a required '${t.type}' with no default, and fixtureRow can't synthesize a safe placeholder for that type. Pass it explicitly: fixtureRow(table, { ${e}: … }).`);
|
|
20
|
+
}
|
|
21
|
+
}, o = (e, r = {}) => {
|
|
22
|
+
let i = {};
|
|
23
|
+
for (let o of t(e, r)) n.has(o) || (i[o] = a(o, e.fields[o]));
|
|
24
|
+
return {
|
|
25
|
+
...i,
|
|
26
|
+
...r
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { o as fixtureRow, i as nextSequence };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { makeVoltroTestClient as e } from "./client.js";
|
|
2
|
+
import { act as t, createElement as n } from "react";
|
|
3
|
+
import { useFormBinding as r } from "@voltro/client";
|
|
4
|
+
//#region src/formBindingHarness.tsx
|
|
5
|
+
var i = async (i, a = {}) => {
|
|
6
|
+
if (globalThis.document === void 0) throw Error("renderFormBinding needs a DOM to render hooks — add `// @vitest-environment jsdom` to the spec.");
|
|
7
|
+
let { createRoot: o } = await import("react-dom/client"), s = a.apiName ?? "app", c = e({
|
|
8
|
+
apiName: s,
|
|
9
|
+
...a.client ?? {},
|
|
10
|
+
mutations: {
|
|
11
|
+
[i]: a.mutation ?? (() => ({ ok: !0 })),
|
|
12
|
+
...a.client?.mutations ?? {}
|
|
13
|
+
}
|
|
14
|
+
}), l, u = () => (l = r(s, i, a.binding ?? {}), null), d = o(globalThis.document.createElement("div"));
|
|
15
|
+
return await t(async () => {
|
|
16
|
+
d.render(n(c.Provider, null, n(u)));
|
|
17
|
+
}), {
|
|
18
|
+
binding: () => l,
|
|
19
|
+
fill: (e, n) => {
|
|
20
|
+
t(() => {
|
|
21
|
+
l.field(e).setValue(n);
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
blur: (e) => {
|
|
25
|
+
t(() => {
|
|
26
|
+
l.field(e).onBlur();
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
submit: async () => {
|
|
30
|
+
let e;
|
|
31
|
+
return await t(async () => {
|
|
32
|
+
e = await l.submit();
|
|
33
|
+
}), e;
|
|
34
|
+
},
|
|
35
|
+
errors: () => l.errors,
|
|
36
|
+
state: () => l.state,
|
|
37
|
+
client: c,
|
|
38
|
+
unmount: () => {
|
|
39
|
+
t(() => {
|
|
40
|
+
d.unmount();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
export { i as renderFormBinding };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Result as e, Schema as t } from "effect";
|
|
2
|
+
//#region src/hydrateFixtureRows.ts
|
|
3
|
+
var n = t.decodeUnknownResult(t.DateFromString), r = /^\d{4}-\d{2}-\d{2}$/, i = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/, a = (t, a) => {
|
|
4
|
+
if (typeof t != "string" || !i.test(t) && !(a === "date" && r.test(t))) return !1;
|
|
5
|
+
let o = t.slice(0, 10), s = n(o);
|
|
6
|
+
return e.isSuccess(s) && Number.isFinite(s.success.getTime()) && s.success.toISOString().slice(0, 10) === o;
|
|
7
|
+
}, o = (t, r) => {
|
|
8
|
+
let i = Object.entries(t.fields).filter(([, e]) => e.type === "timestamp" || e.type === "date");
|
|
9
|
+
return r.map((r, o) => {
|
|
10
|
+
let s = { ...r };
|
|
11
|
+
for (let [c, l] of i) {
|
|
12
|
+
let i = r[c];
|
|
13
|
+
if (i == null || i instanceof Date && Number.isFinite(i.getTime())) continue;
|
|
14
|
+
let u = n(i);
|
|
15
|
+
if (!a(i, l.type) || e.isFailure(u) || !Number.isFinite(u.success.getTime())) throw TypeError(`hydrateFixtureRows: table '${t.tableName}', row ${o}, column '${c}' requires a valid Date or ISO date-time with Z or an offset${l.type === "date" ? " (YYYY-MM-DD is also accepted as UTC midnight)" : ""}.`);
|
|
16
|
+
s[c] = u.success;
|
|
17
|
+
}
|
|
18
|
+
return s;
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { o as hydrateFixtureRows };
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,6 @@ import { EventPublisher } from '@voltro/runtime';
|
|
|
13
13
|
import { InspectedStep } from '@voltro/workflow';
|
|
14
14
|
import { InspectedWorkflow } from '@voltro/workflow';
|
|
15
15
|
import { Layer } from 'effect';
|
|
16
|
-
import { ParseResult } from 'effect';
|
|
17
16
|
import { ProcedureDescriptor } from '@voltro/protocol';
|
|
18
17
|
import { PublicApiDescriptor } from '@voltro/protocol/rest';
|
|
19
18
|
import { PublicApiProfileConfig } from '@voltro/protocol';
|
|
@@ -27,6 +26,7 @@ import { RpcConnection } from '@voltro/runtime';
|
|
|
27
26
|
import { RpcInterceptor } from '@voltro/protocol';
|
|
28
27
|
import { RpcKind } from '@voltro/protocol';
|
|
29
28
|
import { Schema } from 'effect';
|
|
29
|
+
import { SchemaError } from 'effect/Schema';
|
|
30
30
|
import { Scope } from 'effect';
|
|
31
31
|
import { ServiceApi } from '@voltro/protocol';
|
|
32
32
|
import { Subject } from '@voltro/protocol';
|
|
@@ -678,8 +678,6 @@ export declare const nextSequence: () => number;
|
|
|
678
678
|
/** Delivery nudges emitted through `ctx.outbox` on this context. */
|
|
679
679
|
export declare const outboxNudgesOf: (ctx: TestContext) => ReadonlyArray<string>;
|
|
680
680
|
|
|
681
|
-
export { ParseResult }
|
|
682
|
-
|
|
683
681
|
/** The plugins a context was built with — for the REST harness, which hands
|
|
684
682
|
* their cross-cutting errors to the `publicApi` projection the way both boot
|
|
685
683
|
* paths do. */
|
|
@@ -809,6 +807,8 @@ export declare const runAfterCommit: (ctx: TestContext) => Promise<void>;
|
|
|
809
807
|
*/
|
|
810
808
|
export declare const runInStoreTransaction: <T>(ctx: TestContext, work: (txCtx: TestContext) => Promise<T>) => Promise<T>;
|
|
811
809
|
|
|
810
|
+
export { SchemaError }
|
|
811
|
+
|
|
812
812
|
/** A machine subject scoped to ONE tenant (a per-tenant integration, a
|
|
813
813
|
* `storeForTenant` view). NOT the cross-tenant `system()` below. */
|
|
814
814
|
export declare const serviceAccount: (id: string, options?: TestSubjectOptions) => Subject;
|
|
@@ -1020,11 +1020,11 @@ export declare interface TestEventBus {
|
|
|
1020
1020
|
* descriptor fails here, which is the point: a harness that skipped validation
|
|
1021
1021
|
* would let a test pass on a payload production rejects.
|
|
1022
1022
|
*/
|
|
1023
|
-
publish<Name extends string, Key extends Schema.
|
|
1023
|
+
publish<Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, payload: Schema.Schema.Type<Payload>): Promise<{
|
|
1024
1024
|
readonly n: number;
|
|
1025
1025
|
}>;
|
|
1026
1026
|
/** Publish and ASSERT it succeeded — the common case, one line. */
|
|
1027
|
-
subscribe<Name extends string, Key extends Schema.
|
|
1027
|
+
subscribe<Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, options?: {
|
|
1028
1028
|
readonly tenantId?: string | null;
|
|
1029
1029
|
readonly resume?: ReadonlyArray<{
|
|
1030
1030
|
origin: string;
|
|
@@ -1040,7 +1040,7 @@ export declare interface TestEventBus {
|
|
|
1040
1040
|
* is owed messages the ring never held — exactly the shape a slow consumer
|
|
1041
1041
|
* produces, deterministically.
|
|
1042
1042
|
*/
|
|
1043
|
-
skipSerials<Name extends string, Key extends Schema.
|
|
1043
|
+
skipSerials<Name extends string, Key extends Schema.Top, Payload extends Schema.Top>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, count: number, options?: {
|
|
1044
1044
|
readonly tenantId?: string | null;
|
|
1045
1045
|
}): void;
|
|
1046
1046
|
/** The underlying bus, for a test that needs something not exposed here. */
|
|
@@ -1094,13 +1094,13 @@ export declare interface TestResponse {
|
|
|
1094
1094
|
readonly headers: Readonly<Record<string, string | ReadonlyArray<string>>>;
|
|
1095
1095
|
/** The raw response body as text (empty string when there was none). */
|
|
1096
1096
|
readonly text: string;
|
|
1097
|
-
/**
|
|
1098
|
-
|
|
1099
|
-
|
|
1097
|
+
/** Byte-exact buffered body, including a consumed finite byte stream. SSE remains unconsumed. */
|
|
1098
|
+
readonly bytes?: Uint8Array;
|
|
1099
|
+
/** Parsed JSON when the response has a JSON content-type; otherwise
|
|
1100
|
+
* `undefined`. SSE responses remain unconsumed. */
|
|
1100
1101
|
readonly body: unknown;
|
|
1101
|
-
/**
|
|
1102
|
-
* `text
|
|
1103
|
-
* looking like an empty 200. */
|
|
1102
|
+
/** True for SSE and byte streams. Finite byte streams populate `bytes` and
|
|
1103
|
+
* `text`; SSE remains unconsumed, with empty text and undefined body. */
|
|
1104
1104
|
readonly stream: boolean;
|
|
1105
1105
|
}
|
|
1106
1106
|
|
|
@@ -1161,7 +1161,7 @@ export declare const withFrozenTime: <A>(at: Date | number | string | MockClock,
|
|
|
1161
1161
|
export declare const withRpcConnection: (ctx: TestContext, connection: RpcConnection | undefined) => TestContext;
|
|
1162
1162
|
|
|
1163
1163
|
/** A workflow + its execute function, as the framework pairs them via
|
|
1164
|
-
* `workflow.toLayer(execute)`. Structural (no
|
|
1164
|
+
* `workflow.toLayer(execute)`. Structural (no `effect/workflow` generic
|
|
1165
1165
|
* signature) so callers can pass the value from `workflow({ name, ... })`
|
|
1166
1166
|
* plus its `(payload, executionId) => Effect` execute directly — type-erased
|
|
1167
1167
|
* over the workflow's payload/success/error/requirement shapes. */
|