@voltro/testing 0.24.0 → 0.26.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 +670 -0
- package/dist/index.d.ts +81 -1
- package/dist/index.js +129 -59
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AppContext } from '@voltro/runtime';
|
|
2
2
|
import { Effect } from 'effect';
|
|
3
|
+
import { EventBus } from '@voltro/runtime';
|
|
4
|
+
import { EventDescriptor } from '@voltro/protocol';
|
|
3
5
|
import { InspectedStep } from '@voltro/workflow';
|
|
4
6
|
import { InspectedWorkflow } from '@voltro/workflow';
|
|
5
7
|
import { Layer } from 'effect';
|
|
@@ -283,7 +285,7 @@ export declare const mockStore: (seed: Record<string, ReadonlyArray<Row>>) => Re
|
|
|
283
285
|
export declare class MockWebhooks {
|
|
284
286
|
readonly emitted: EmittedWebhook[];
|
|
285
287
|
/** Mirrors `WebhooksServiceShape.emit`. Accepts the event id OR a
|
|
286
|
-
*
|
|
288
|
+
* declared event, exactly as the real service does — the
|
|
287
289
|
* handler under test should not have to be written differently to be
|
|
288
290
|
* testable. */
|
|
289
291
|
emit<P>(event: string | {
|
|
@@ -321,6 +323,13 @@ export declare interface ReachableTarget {
|
|
|
321
323
|
readonly timeoutMs?: number;
|
|
322
324
|
}
|
|
323
325
|
|
|
326
|
+
/** One delivery a test subscriber saw. */
|
|
327
|
+
export declare interface RecordedDelivery<P = unknown> {
|
|
328
|
+
readonly payload: P;
|
|
329
|
+
readonly origin: string;
|
|
330
|
+
readonly n: number;
|
|
331
|
+
}
|
|
332
|
+
|
|
324
333
|
/**
|
|
325
334
|
* Drop queued post-commit work WITHOUT running it.
|
|
326
335
|
*
|
|
@@ -401,8 +410,79 @@ export declare interface TestContext extends AppContext {
|
|
|
401
410
|
withTenant<T>(tenantId: string, fn: (ctx: TestContext) => T | Promise<T>): Promise<T>;
|
|
402
411
|
}
|
|
403
412
|
|
|
413
|
+
export declare interface TestEventBus {
|
|
414
|
+
/**
|
|
415
|
+
* Publish exactly as a handler would — through the real validation, the real
|
|
416
|
+
* size gate and the real serial assignment. A payload that does not match the
|
|
417
|
+
* descriptor fails here, which is the point: a harness that skipped validation
|
|
418
|
+
* would let a test pass on a payload production rejects.
|
|
419
|
+
*/
|
|
420
|
+
publish<Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, payload: Schema.Schema.Type<Payload>): Promise<{
|
|
421
|
+
readonly n: number;
|
|
422
|
+
}>;
|
|
423
|
+
/** Publish and ASSERT it succeeded — the common case, one line. */
|
|
424
|
+
subscribe<Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, options?: {
|
|
425
|
+
readonly tenantId?: string | null;
|
|
426
|
+
readonly resume?: ReadonlyArray<{
|
|
427
|
+
origin: string;
|
|
428
|
+
n: number;
|
|
429
|
+
}>;
|
|
430
|
+
}): TestSubscriber<Schema.Schema.Type<Payload>>;
|
|
431
|
+
/**
|
|
432
|
+
* Force a gap WITHOUT waiting for a buffer to overflow.
|
|
433
|
+
*
|
|
434
|
+
* A consumer's recovery path (`onMissed` → resync) is the hardest thing to
|
|
435
|
+
* test honestly, because provoking a real loss means racing a queue. This
|
|
436
|
+
* injects a delivery `count` serials ahead, so the next subscriber to resume
|
|
437
|
+
* is owed messages the ring never held — exactly the shape a slow consumer
|
|
438
|
+
* produces, deterministically.
|
|
439
|
+
*/
|
|
440
|
+
skipSerials<Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, count: number, options?: {
|
|
441
|
+
readonly tenantId?: string | null;
|
|
442
|
+
}): void;
|
|
443
|
+
/** The underlying bus, for a test that needs something not exposed here. */
|
|
444
|
+
readonly bus: EventBus;
|
|
445
|
+
/** Drop every subscriber and buffer. */
|
|
446
|
+
readonly reset: () => void;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* A running event bus with no transport.
|
|
451
|
+
*
|
|
452
|
+
* ```ts
|
|
453
|
+
* const events = testEventBus()
|
|
454
|
+
* const display = events.subscribe(gameStarted, { arenaId: 'a1' })
|
|
455
|
+
* await events.publish(gameStarted, { arenaId: 'a1' }, { gameId: 'g1' })
|
|
456
|
+
* expect(display.received).toEqual([{ gameId: 'g1' }])
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
export declare const testEventBus: (options?: TestEventBusOptions) => TestEventBus;
|
|
460
|
+
|
|
461
|
+
export declare interface TestEventBusOptions {
|
|
462
|
+
/** Default tenant for publishes and subscriptions. `null` = system. */
|
|
463
|
+
readonly tenantId?: string | null;
|
|
464
|
+
/** Ring depth — a small one makes an eviction test deterministic. */
|
|
465
|
+
readonly ringSize?: number;
|
|
466
|
+
readonly origin?: string;
|
|
467
|
+
}
|
|
468
|
+
|
|
404
469
|
export declare const TESTING_PRESET_VERSION: 1;
|
|
405
470
|
|
|
471
|
+
export declare interface TestSubscriber<P = unknown> {
|
|
472
|
+
/** Payloads delivered so far, in order. */
|
|
473
|
+
readonly received: ReadonlyArray<P>;
|
|
474
|
+
/** Full envelopes, when a test cares about serials or origin. */
|
|
475
|
+
readonly deliveries: ReadonlyArray<RecordedDelivery<P>>;
|
|
476
|
+
/** Losses the server could PROVE, with their cause. */
|
|
477
|
+
readonly missed: ReadonlyArray<{
|
|
478
|
+
readonly count: number;
|
|
479
|
+
readonly reason: 'buffer' | 'resume';
|
|
480
|
+
}>;
|
|
481
|
+
/** Total proven losses. */
|
|
482
|
+
readonly missedCount: number;
|
|
483
|
+
readonly stop: () => void;
|
|
484
|
+
}
|
|
485
|
+
|
|
406
486
|
/** A workflow + its execute function, as the framework pairs them via
|
|
407
487
|
* `workflow.toLayer(execute)`. Structural (no `@effect/workflow` generic
|
|
408
488
|
* signature) so callers can pass the value from `workflow({ name, ... })`
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { AUTO_FILLED_COLUMNS as e, allRegisteredTables as t, clearRelationsRegistry as n, missingRequiredColumns as r, registerRelations as i } from "@voltro/database";
|
|
2
2
|
import { Cause as a, Effect as o, Exit as s, Layer as c, Schema as l } from "effect";
|
|
3
|
-
import { SubjectService as u, anonymousSubject as d, checkGuardsEffect as f, composeRpcInterceptors as p,
|
|
4
|
-
import { InMemoryDataStore as
|
|
5
|
-
import { installEnvSnapshot as
|
|
6
|
-
import
|
|
7
|
-
import { describe as
|
|
8
|
-
import { CurrentWorkflowRunId as
|
|
3
|
+
import { SubjectService as u, anonymousSubject as d, checkGuardsEffect as f, composeRpcInterceptors as p, eventRoute as m, tenantScopedSubject as h } from "@voltro/protocol";
|
|
4
|
+
import { EventBus as g, InMemoryDataStore as _, applyRowFilterToDescriptor as v, clearSystemStoreHandle as y, getRowFilter as b, getSystemStoreHandle as x, makeAppAccess as ee, makeDataLoader as S, makeEffectStoreLayer as C, makeOutboxFacade as w, makeSchemaRegistry as T, publishEvent as te, resolveRowFilterScopeFor as E, runProvidedEffect as D, runWithDeadlockRetry as O, setSystemStoreHandle as k, wrapStoreWithMixinBehaviour as A } from "@voltro/runtime";
|
|
5
|
+
import { installEnvSnapshot as j } from "@voltro/env";
|
|
6
|
+
import M from "node:net";
|
|
7
|
+
import { describe as N } from "vitest";
|
|
8
|
+
import { CurrentWorkflowRunId as ne, inMemoryWorkflowEngineLayer as re, makeInMemoryRecorder as ie } from "@voltro/workflow";
|
|
9
9
|
//#region src/fixtureRow.ts
|
|
10
|
-
var
|
|
10
|
+
var ae = new Set(e), P = 0, oe = (e, t) => {
|
|
11
11
|
if (t.oneOf && t.oneOf.length > 0) return t.oneOf[0];
|
|
12
12
|
if (t.enumValues && t.enumValues.length > 0) return t.enumValues[0];
|
|
13
13
|
let n = t.unique === !0;
|
|
@@ -15,24 +15,24 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
15
15
|
case "text":
|
|
16
16
|
case "reference":
|
|
17
17
|
case "id":
|
|
18
|
-
case "enum": return n ? `${e}-${++
|
|
18
|
+
case "enum": return n ? `${e}-${++P}` : e;
|
|
19
19
|
case "integer":
|
|
20
|
-
case "real": return n ? ++
|
|
20
|
+
case "real": return n ? ++P : 0;
|
|
21
21
|
case "decimal":
|
|
22
|
-
case "bigint": return n ? String(++
|
|
22
|
+
case "bigint": return n ? String(++P) : "0";
|
|
23
23
|
case "boolean": return !1;
|
|
24
24
|
case "timestamp":
|
|
25
25
|
case "date": return /* @__PURE__ */ new Date(0);
|
|
26
26
|
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}: … }).`);
|
|
27
27
|
}
|
|
28
|
-
},
|
|
28
|
+
}, se = (e, t = {}) => {
|
|
29
29
|
let n = {};
|
|
30
|
-
for (let i of r(e, t))
|
|
30
|
+
for (let i of r(e, t)) ae.has(i) || (n[i] = oe(i, e.fields[i]));
|
|
31
31
|
return {
|
|
32
32
|
...n,
|
|
33
33
|
...t
|
|
34
34
|
};
|
|
35
|
-
},
|
|
35
|
+
}, F = class {
|
|
36
36
|
currentMs;
|
|
37
37
|
constructor(e = /* @__PURE__ */ new Date("2026-01-01T00:00:00Z")) {
|
|
38
38
|
this.currentMs = typeof e == "number" ? e : e.getTime();
|
|
@@ -44,9 +44,9 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
44
44
|
return new Date(this.currentMs);
|
|
45
45
|
}
|
|
46
46
|
advance(e) {
|
|
47
|
-
this.currentMs += typeof e == "number" ? e :
|
|
47
|
+
this.currentMs += typeof e == "number" ? e : I(e);
|
|
48
48
|
}
|
|
49
|
-
},
|
|
49
|
+
}, I = (e) => {
|
|
50
50
|
let t = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)\s*$/.exec(e);
|
|
51
51
|
if (!t) throw Error(`mockClock: cannot parse duration '${e}'`);
|
|
52
52
|
let n = Number(t[1]), r = t[2];
|
|
@@ -122,13 +122,13 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
122
122
|
let n = [...t];
|
|
123
123
|
t.length = 0;
|
|
124
124
|
for (let e of n) await e();
|
|
125
|
-
},
|
|
125
|
+
}, Y = (e) => W.get(e) ?? [], X = async (e, t) => {
|
|
126
126
|
let n = H.get(e);
|
|
127
127
|
return n === void 0 ? e.store.transactional(async (n) => t({
|
|
128
128
|
...e,
|
|
129
129
|
store: n
|
|
130
130
|
})) : n(t);
|
|
131
|
-
},
|
|
131
|
+
}, ce = (e) => {
|
|
132
132
|
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = (n) => {
|
|
133
133
|
let r = t.get(n);
|
|
134
134
|
return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
|
|
@@ -167,7 +167,7 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
167
167
|
return i(e, o, n.ttlMs, n.tags), o;
|
|
168
168
|
}
|
|
169
169
|
};
|
|
170
|
-
},
|
|
170
|
+
}, le = (e) => {
|
|
171
171
|
let t = /* @__PURE__ */ new Map(), n = (n) => {
|
|
172
172
|
let r = t.get(n);
|
|
173
173
|
return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
|
|
@@ -195,62 +195,62 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
195
195
|
t.clear();
|
|
196
196
|
}
|
|
197
197
|
};
|
|
198
|
-
},
|
|
199
|
-
let n =
|
|
200
|
-
|
|
198
|
+
}, ue = async (e, t) => {
|
|
199
|
+
let n = x();
|
|
200
|
+
k(e);
|
|
201
201
|
try {
|
|
202
202
|
return await t();
|
|
203
203
|
} finally {
|
|
204
|
-
n === void 0 ?
|
|
204
|
+
n === void 0 ? y() : k(n);
|
|
205
205
|
}
|
|
206
|
-
},
|
|
207
|
-
let r =
|
|
206
|
+
}, de = (e, t, n) => {
|
|
207
|
+
let r = fe, i;
|
|
208
208
|
return () => {
|
|
209
|
-
let a = t ??
|
|
210
|
-
return (i === void 0 || a !== r) && (r = a, i =
|
|
209
|
+
let a = t ?? b();
|
|
210
|
+
return (i === void 0 || a !== r) && (r = a, i = ue(n, () => D(E(a, e, (e) => {
|
|
211
211
|
console.error("[voltro:testing] row filter failed to load — reads refused for this subject", e);
|
|
212
212
|
})))), i;
|
|
213
213
|
};
|
|
214
|
-
},
|
|
215
|
-
if (n === "query") return async (n) => e.query(
|
|
214
|
+
}, fe = Symbol("unresolved"), Z = (e, t) => new Proxy(e, { get: (e, n) => {
|
|
215
|
+
if (n === "query") return async (n) => e.query(v(await t(), n));
|
|
216
216
|
let r = Reflect.get(e, n, e);
|
|
217
217
|
return typeof r == "function" ? r.bind(e) : r;
|
|
218
|
-
} }),
|
|
219
|
-
if (
|
|
218
|
+
} }), pe = (e = {}) => {
|
|
219
|
+
if (j({
|
|
220
220
|
...process.env,
|
|
221
221
|
...e.env ?? {}
|
|
222
222
|
}), e.relations !== void 0) {
|
|
223
223
|
n();
|
|
224
224
|
for (let t of e.relations) i(t);
|
|
225
225
|
}
|
|
226
|
-
let r =
|
|
226
|
+
let r = T(e.tables ?? t()), a = new _(e.store ?? {}), o = {
|
|
227
227
|
dataStore: a,
|
|
228
228
|
schemaRegistry: r
|
|
229
229
|
};
|
|
230
|
-
|
|
231
|
-
let s = new
|
|
230
|
+
k(o);
|
|
231
|
+
let s = new F(e.clockStart), c = new L(), l = new R(), u = new z(e.llmResponses ?? []), f = ce(() => s.now()), p = le(() => s.now()), m = (t, n = a, i) => {
|
|
232
232
|
let d = {
|
|
233
233
|
subject: t,
|
|
234
234
|
traceId: V
|
|
235
|
-
},
|
|
235
|
+
}, g = i?.queued ?? [], _ = i?.nudges ?? [], v = S({ store: n }), y = de(t, e.rowFilter, o), b = {
|
|
236
236
|
clock: s,
|
|
237
237
|
email: c,
|
|
238
238
|
webhooks: l,
|
|
239
239
|
llm: u,
|
|
240
240
|
...e.ai === void 0 ? {} : { ai: e.ai },
|
|
241
241
|
request: d,
|
|
242
|
-
access:
|
|
242
|
+
access: ee(t),
|
|
243
243
|
cache: f,
|
|
244
244
|
kv: p,
|
|
245
|
-
store:
|
|
245
|
+
store: A(Z(n, y), {
|
|
246
246
|
subject: t,
|
|
247
247
|
schemaRegistry: r
|
|
248
248
|
}),
|
|
249
|
-
storeForTenant: (e) =>
|
|
250
|
-
subject:
|
|
249
|
+
storeForTenant: (e) => A(Z(n, y), {
|
|
250
|
+
subject: h(t, e),
|
|
251
251
|
schemaRegistry: r
|
|
252
252
|
}),
|
|
253
|
-
outbox:
|
|
253
|
+
outbox: w({
|
|
254
254
|
store: n,
|
|
255
255
|
subject: t,
|
|
256
256
|
traceId: V,
|
|
@@ -258,33 +258,33 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
258
258
|
_.push(V);
|
|
259
259
|
},
|
|
260
260
|
afterCommit: (e) => {
|
|
261
|
-
|
|
261
|
+
g.push(e);
|
|
262
262
|
}
|
|
263
263
|
}),
|
|
264
264
|
load: v.load,
|
|
265
265
|
loadMany: v.loadMany,
|
|
266
|
-
withSubject: (e, t) => Promise.resolve(t(
|
|
267
|
-
queued:
|
|
266
|
+
withSubject: (e, t) => Promise.resolve(t(m(e, n, {
|
|
267
|
+
queued: g,
|
|
268
268
|
nudges: _
|
|
269
269
|
}))),
|
|
270
|
-
withTenant: (e, r) => Promise.resolve(r(
|
|
270
|
+
withTenant: (e, r) => Promise.resolve(r(m({
|
|
271
271
|
...t,
|
|
272
272
|
tenantId: e
|
|
273
273
|
}, n, {
|
|
274
|
-
queued:
|
|
274
|
+
queued: g,
|
|
275
275
|
nudges: _
|
|
276
276
|
})))
|
|
277
277
|
};
|
|
278
|
-
return U.set(
|
|
279
|
-
queued:
|
|
278
|
+
return U.set(b, g), W.set(b, _), G.set(b, e.plugins ?? []), H.set(b, (e) => n.transactional((n) => e(m(t, n, {
|
|
279
|
+
queued: g,
|
|
280
280
|
nudges: _
|
|
281
|
-
})))),
|
|
281
|
+
})))), b;
|
|
282
282
|
};
|
|
283
|
-
return
|
|
284
|
-
},
|
|
283
|
+
return m(e.subject ?? d(null));
|
|
284
|
+
}, me = (e, t) => e.pipe(o.provide(C(t.store)), o.provideService(u, t.request.subject)), he = async (e, t, n, r) => {
|
|
285
285
|
let i = e.input, c = await l.decodeUnknownPromise(i)(n), u = e.kind, d = async (e) => {
|
|
286
286
|
let n = t(c, e);
|
|
287
|
-
return o.isEffect(n) ?
|
|
287
|
+
return o.isEffect(n) ? D(me(n, e)) : n;
|
|
288
288
|
}, p = async () => {
|
|
289
289
|
let t = e.guards;
|
|
290
290
|
if (t !== void 0 && t.length > 0) {
|
|
@@ -292,7 +292,7 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
292
292
|
if (e !== null) throw e;
|
|
293
293
|
}
|
|
294
294
|
if (u !== "mutation") return d(r);
|
|
295
|
-
let n = await
|
|
295
|
+
let n = await O(async () => (q(r), X(r, async (e) => d(e))), { delay: () => Promise.resolve() });
|
|
296
296
|
return await J(r), n;
|
|
297
297
|
}, m = u === "mutation" || u === "query" || u === "action" ? u : void 0, h = m === void 0 ? void 0 : K(r, m);
|
|
298
298
|
if (h === void 0 || m === void 0) return await p();
|
|
@@ -310,7 +310,7 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
310
310
|
if (s.isSuccess(_)) return _.value;
|
|
311
311
|
throw a.squash(_.cause);
|
|
312
312
|
}, Q = (e) => new Promise((t) => {
|
|
313
|
-
let n = new
|
|
313
|
+
let n = new M.Socket(), r = (e) => {
|
|
314
314
|
clearTimeout(i), n.destroy(), t(e);
|
|
315
315
|
}, i = setTimeout(() => r(!1), e.timeoutMs ?? 750);
|
|
316
316
|
n.once("connect", () => r(!0)), n.once("error", () => r(!1)), n.connect(e.port, e.host);
|
|
@@ -321,10 +321,80 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
321
321
|
} catch {
|
|
322
322
|
i = !1;
|
|
323
323
|
}
|
|
324
|
-
|
|
325
|
-
},
|
|
324
|
+
N.skipIf(!i)(i ? e : `${e} [SKIPPED: needs ${t}]`, r);
|
|
325
|
+
}, ge = async (e, t, n) => {
|
|
326
326
|
await $(e, `${t.name} at ${t.host}:${t.port}`, () => Q(t), n);
|
|
327
|
-
},
|
|
327
|
+
}, _e = (e = {}) => {
|
|
328
|
+
let t = e.tenantId ?? null, n = new g({
|
|
329
|
+
origin: e.origin ?? "test",
|
|
330
|
+
...e.ringSize === void 0 ? {} : { ringSize: e.ringSize }
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
publish: async (e, r, i) => {
|
|
334
|
+
let a = await o.runPromise(o.either(te({
|
|
335
|
+
bus: n,
|
|
336
|
+
tenantId: t
|
|
337
|
+
}, e, r, i)));
|
|
338
|
+
if (a._tag === "Left") {
|
|
339
|
+
let t = a.left;
|
|
340
|
+
throw Error(`testEventBus.publish(${e.name}) was rejected: ${t._tag}`, { cause: t });
|
|
341
|
+
}
|
|
342
|
+
return { n: a.right.n };
|
|
343
|
+
},
|
|
344
|
+
subscribe: (e, r, i) => {
|
|
345
|
+
let a = [], o = [];
|
|
346
|
+
return {
|
|
347
|
+
get received() {
|
|
348
|
+
return a.map((e) => e.payload);
|
|
349
|
+
},
|
|
350
|
+
get deliveries() {
|
|
351
|
+
return a;
|
|
352
|
+
},
|
|
353
|
+
get missed() {
|
|
354
|
+
return o;
|
|
355
|
+
},
|
|
356
|
+
get missedCount() {
|
|
357
|
+
return o.reduce((e, t) => e + t.count, 0);
|
|
358
|
+
},
|
|
359
|
+
stop: n.subscribe({
|
|
360
|
+
tenantId: i?.tenantId ?? t,
|
|
361
|
+
event: e.name,
|
|
362
|
+
key: r,
|
|
363
|
+
listener: (e) => {
|
|
364
|
+
if (e.kind === "gap") {
|
|
365
|
+
o.push({
|
|
366
|
+
count: e.missed,
|
|
367
|
+
reason: e.reason
|
|
368
|
+
});
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
a.push({
|
|
372
|
+
payload: e.envelope.payload,
|
|
373
|
+
origin: e.envelope.origin,
|
|
374
|
+
n: e.envelope.n
|
|
375
|
+
});
|
|
376
|
+
},
|
|
377
|
+
...i?.resume === void 0 ? {} : { options: { resume: i.resume } }
|
|
378
|
+
})
|
|
379
|
+
};
|
|
380
|
+
},
|
|
381
|
+
skipSerials: (e, r, i, a) => {
|
|
382
|
+
let o = {
|
|
383
|
+
route: m(a?.tenantId ?? t, e.name, r),
|
|
384
|
+
event: e.name,
|
|
385
|
+
origin: "test-gap",
|
|
386
|
+
n: i + 1,
|
|
387
|
+
emittedAt: Date.now(),
|
|
388
|
+
payload: void 0
|
|
389
|
+
};
|
|
390
|
+
n.injectRemote(o);
|
|
391
|
+
},
|
|
392
|
+
bus: n,
|
|
393
|
+
reset: () => {
|
|
394
|
+
n.clear();
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}, ve = (e) => {
|
|
328
398
|
let t = /* @__PURE__ */ new Map();
|
|
329
399
|
for (let n of e) {
|
|
330
400
|
let e = t.get(n.stepName);
|
|
@@ -350,13 +420,13 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
350
420
|
});
|
|
351
421
|
}
|
|
352
422
|
return n.sort((e, t) => e._startedAt - t._startedAt), n.map(({ _startedAt: e, ...t }) => t);
|
|
353
|
-
},
|
|
423
|
+
}, ye = (e) => {
|
|
354
424
|
let t = e.workflows ?? [], n = /* @__PURE__ */ new Map(), r = 0;
|
|
355
425
|
return {
|
|
356
426
|
start: async (i, a) => {
|
|
357
427
|
let s = t.find((e) => e.workflow.name === i);
|
|
358
428
|
if (s === void 0) throw Error(`makeWorkflowRunner: no workflow named '${i}' — pass it in makeWorkflowRunner({ ctx, workflows: [{ workflow, execute }] }).`);
|
|
359
|
-
let l = `wfrun_test_${++r}`, u =
|
|
429
|
+
let l = `wfrun_test_${++r}`, u = ie(), d = s.workflow.toLayer(s.execute).pipe(c.provideMerge(re)), f = /* @__PURE__ */ new Date(), p = s.workflow.execute(a).pipe(o.locally(ne, l), o.provide(u.layer), o.provide(d), o.either), m = await o.runPromise(p), h = ve(u.readSteps()), g = /* @__PURE__ */ new Date(), _;
|
|
360
430
|
if (m._tag === "Right") _ = {
|
|
361
431
|
status: "succeeded",
|
|
362
432
|
output: m.right,
|
|
@@ -396,6 +466,6 @@ var P = new Set(e), F = 0, ne = (e, t) => {
|
|
|
396
466
|
},
|
|
397
467
|
inspect: async (e) => n.get(e) ?? null
|
|
398
468
|
};
|
|
399
|
-
},
|
|
469
|
+
}, be = 1;
|
|
400
470
|
//#endregion
|
|
401
|
-
export {
|
|
471
|
+
export { F as MockClock, L as MockEmail, z as MockLLM, R as MockWebhooks, be as TESTING_PRESET_VERSION, $ as describeIfAvailable, ge as describeIfReachable, se as fixtureRow, he as invoke, Q as isTcpReachable, pe as makeTestContext, ye as makeWorkflowRunner, B as mockStore, Y as outboxNudgesOf, q as resetAfterCommit, K as rpcInterceptorFor, J as runAfterCommit, X as runInStoreTransaction, _e as testEventBus };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Test utilities for Voltro apps — deterministic clock, captured emails, queued LLM responses, scoped subject/tenant runners, and a cross-dialect parity harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"node": ">=24.0.0"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@voltro/database": "0.
|
|
46
|
-
"@voltro/env": "0.
|
|
47
|
-
"@voltro/protocol": "0.
|
|
48
|
-
"@voltro/runtime": "0.
|
|
49
|
-
"@voltro/workflow": "0.
|
|
45
|
+
"@voltro/database": "0.26.0",
|
|
46
|
+
"@voltro/env": "0.26.0",
|
|
47
|
+
"@voltro/protocol": "0.26.0",
|
|
48
|
+
"@voltro/runtime": "0.26.0",
|
|
49
|
+
"@voltro/workflow": "0.26.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@voltro/client": "0.
|
|
52
|
+
"@voltro/client": "0.26.0",
|
|
53
53
|
"effect": "^3.22.0",
|
|
54
54
|
"react": "^19.0.0",
|
|
55
55
|
"@effect/sql": "^0.52.0"
|