@voltro/testing 0.1.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 +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +4596 -0
- package/dist/dialect.d.ts +51 -0
- package/dist/dialect.js +363 -0
- package/dist/index.d.ts +202 -0
- package/dist/index.js +237 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Effect as e, Layer as t, Schema as n } from "effect";
|
|
2
|
+
import { allRegisteredTables as r } from "@voltro/database";
|
|
3
|
+
import { installEnvSnapshot as i } from "@voltro/env";
|
|
4
|
+
import { InMemoryDataStore as a, makeSchemaRegistry as o, wrapStoreWithMixinBehaviour as s } from "@voltro/runtime";
|
|
5
|
+
import { anonymousSubject as c } from "@voltro/protocol";
|
|
6
|
+
import { CurrentWorkflowRunId as l, inMemoryWorkflowEngineLayer as u, makeInMemoryRecorder as d } from "@voltro/workflow";
|
|
7
|
+
//#region src/invoke.ts
|
|
8
|
+
var f = async (e, t, r, i) => {
|
|
9
|
+
let a = e.input;
|
|
10
|
+
return t(await n.decodeUnknownPromise(a)(r), i);
|
|
11
|
+
}, p = class {
|
|
12
|
+
currentMs;
|
|
13
|
+
constructor(e = /* @__PURE__ */ new Date("2026-01-01T00:00:00Z")) {
|
|
14
|
+
this.currentMs = typeof e == "number" ? e : e.getTime();
|
|
15
|
+
}
|
|
16
|
+
now() {
|
|
17
|
+
return this.currentMs;
|
|
18
|
+
}
|
|
19
|
+
date() {
|
|
20
|
+
return new Date(this.currentMs);
|
|
21
|
+
}
|
|
22
|
+
advance(e) {
|
|
23
|
+
this.currentMs += typeof e == "number" ? e : m(e);
|
|
24
|
+
}
|
|
25
|
+
}, m = (e) => {
|
|
26
|
+
let t = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)\s*$/.exec(e);
|
|
27
|
+
if (!t) throw Error(`mockClock: cannot parse duration '${e}'`);
|
|
28
|
+
let n = Number(t[1]), r = t[2];
|
|
29
|
+
switch (r) {
|
|
30
|
+
case "ms": return n;
|
|
31
|
+
case "s": return n * 1e3;
|
|
32
|
+
case "m": return n * 6e4;
|
|
33
|
+
case "h": return n * 36e5;
|
|
34
|
+
case "d": return n * 864e5;
|
|
35
|
+
default: throw Error(`mockClock: unknown unit '${r}'`);
|
|
36
|
+
}
|
|
37
|
+
}, h = class {
|
|
38
|
+
sent = [];
|
|
39
|
+
send(e) {
|
|
40
|
+
this.sent.push({
|
|
41
|
+
...e,
|
|
42
|
+
sentAt: /* @__PURE__ */ new Date()
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
lastTo(e) {
|
|
46
|
+
for (let t = this.sent.length - 1; t >= 0; t--) if (this.sent[t].to === e) return this.sent[t];
|
|
47
|
+
}
|
|
48
|
+
clear() {
|
|
49
|
+
this.sent.length = 0;
|
|
50
|
+
}
|
|
51
|
+
}, g = class {
|
|
52
|
+
queue;
|
|
53
|
+
calls = [];
|
|
54
|
+
constructor(e) {
|
|
55
|
+
this.queue = [...e];
|
|
56
|
+
}
|
|
57
|
+
next(e) {
|
|
58
|
+
this.calls.push(e);
|
|
59
|
+
let t = this.queue.shift();
|
|
60
|
+
if (!t) throw Error("MockLLM: no more responses queued");
|
|
61
|
+
return t;
|
|
62
|
+
}
|
|
63
|
+
remaining() {
|
|
64
|
+
return this.queue.length;
|
|
65
|
+
}
|
|
66
|
+
}, _ = (e) => e, v = "00000000000000000000000000000000", y = (e) => {
|
|
67
|
+
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = (n) => {
|
|
68
|
+
let r = t.get(n);
|
|
69
|
+
return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
|
|
70
|
+
}, i = (r, i, a, o) => {
|
|
71
|
+
if (t.set(r, {
|
|
72
|
+
value: i,
|
|
73
|
+
expiresAt: a === void 0 ? null : e() + a
|
|
74
|
+
}), o) for (let e of o) {
|
|
75
|
+
let t = n.get(e) ?? /* @__PURE__ */ new Set();
|
|
76
|
+
t.add(r), n.set(e, t);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
get: async (e) => r(e) ? t.get(e).value : void 0,
|
|
81
|
+
set: async (e, t, n) => {
|
|
82
|
+
i(e, t, n?.ttlMs);
|
|
83
|
+
},
|
|
84
|
+
setWithTags: async (e, t, n) => {
|
|
85
|
+
i(e, t, n.ttlMs, n.tags);
|
|
86
|
+
},
|
|
87
|
+
remove: async (e) => t.delete(e),
|
|
88
|
+
has: async (e) => r(e),
|
|
89
|
+
clear: async () => {
|
|
90
|
+
t.clear(), n.clear();
|
|
91
|
+
},
|
|
92
|
+
invalidateTag: async (e) => {
|
|
93
|
+
let r = n.get(e);
|
|
94
|
+
if (r === void 0) return 0;
|
|
95
|
+
let i = 0;
|
|
96
|
+
for (let e of r) t.delete(e) && i++;
|
|
97
|
+
return n.delete(e), i;
|
|
98
|
+
},
|
|
99
|
+
wrap: async (e, n, a) => {
|
|
100
|
+
if (r(e)) return t.get(e).value;
|
|
101
|
+
let o = await a();
|
|
102
|
+
return i(e, o, n.ttlMs, n.tags), o;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}, b = (e) => {
|
|
106
|
+
let t = /* @__PURE__ */ new Map(), n = (n) => {
|
|
107
|
+
let r = t.get(n);
|
|
108
|
+
return r === void 0 ? !1 : r.expiresAt !== null && r.expiresAt <= e() ? (t.delete(n), !1) : !0;
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
get: async (e) => n(e) ? t.get(e).value : void 0,
|
|
112
|
+
getOrElse: async (e, r) => n(e) ? t.get(e).value : r(),
|
|
113
|
+
set: async (n, r, i) => {
|
|
114
|
+
t.set(n, {
|
|
115
|
+
value: r,
|
|
116
|
+
expiresAt: i?.ttlMs === void 0 ? null : e() + i.ttlMs
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
delete: async (e) => {
|
|
120
|
+
let r = n(e);
|
|
121
|
+
return t.delete(e), r;
|
|
122
|
+
},
|
|
123
|
+
has: async (e) => n(e),
|
|
124
|
+
list: async (e) => {
|
|
125
|
+
let r = [];
|
|
126
|
+
for (let i of t.keys()) n(i) && (e === "" || i.startsWith(e)) && r.push(i);
|
|
127
|
+
return r;
|
|
128
|
+
},
|
|
129
|
+
clear: async () => {
|
|
130
|
+
t.clear();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}, x = (e = {}) => {
|
|
134
|
+
i({
|
|
135
|
+
...process.env,
|
|
136
|
+
...e.env ?? {}
|
|
137
|
+
});
|
|
138
|
+
let t = o(e.tables ?? r()), n = new a(e.store ?? {}), l = new p(e.clockStart), u = new h(), d = new g(e.llmResponses ?? []), f = y(() => l.now()), m = b(() => l.now()), _ = (r) => {
|
|
139
|
+
let i = {
|
|
140
|
+
subject: r,
|
|
141
|
+
traceId: v
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
clock: l,
|
|
145
|
+
email: u,
|
|
146
|
+
llm: d,
|
|
147
|
+
...e.ai === void 0 ? {} : { ai: e.ai },
|
|
148
|
+
request: i,
|
|
149
|
+
cache: f,
|
|
150
|
+
kv: m,
|
|
151
|
+
store: s(n, {
|
|
152
|
+
subject: r,
|
|
153
|
+
schemaRegistry: t
|
|
154
|
+
}),
|
|
155
|
+
withSubject: (e, t) => Promise.resolve(t(_(e))),
|
|
156
|
+
withTenant: (e, t) => Promise.resolve(t(_({
|
|
157
|
+
...r,
|
|
158
|
+
tenantId: e
|
|
159
|
+
})))
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
return _(e.subject ?? c(null));
|
|
163
|
+
}, S = (e) => {
|
|
164
|
+
let t = /* @__PURE__ */ new Map();
|
|
165
|
+
for (let n of e) {
|
|
166
|
+
let e = t.get(n.stepName);
|
|
167
|
+
e === void 0 ? t.set(n.stepName, [n]) : e.push(n);
|
|
168
|
+
}
|
|
169
|
+
let n = [];
|
|
170
|
+
for (let [e, r] of t) {
|
|
171
|
+
r.sort((e, t) => e.attempt - t.attempt);
|
|
172
|
+
let t = r[r.length - 1], i = t.errorTag !== null || t.errorMessage !== null ? {
|
|
173
|
+
tag: t.errorTag,
|
|
174
|
+
message: t.errorMessage ?? ""
|
|
175
|
+
} : null;
|
|
176
|
+
n.push({
|
|
177
|
+
name: e,
|
|
178
|
+
attempt: t.attempt,
|
|
179
|
+
attempts: r.length,
|
|
180
|
+
status: t.status,
|
|
181
|
+
input: t.input,
|
|
182
|
+
output: t.output,
|
|
183
|
+
error: i,
|
|
184
|
+
durationMs: t.durationMs,
|
|
185
|
+
_startedAt: r[0].startedAt.getTime()
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return n.sort((e, t) => e._startedAt - t._startedAt), n.map(({ _startedAt: e, ...t }) => t);
|
|
189
|
+
}, C = (n) => {
|
|
190
|
+
let r = n.workflows ?? [], i = /* @__PURE__ */ new Map(), a = 0;
|
|
191
|
+
return {
|
|
192
|
+
start: async (o, s) => {
|
|
193
|
+
let c = r.find((e) => e.workflow.name === o);
|
|
194
|
+
if (c === void 0) throw Error(`makeWorkflowRunner: no workflow named '${o}' — pass it in makeWorkflowRunner({ ctx, workflows: [{ workflow, execute }] }).`);
|
|
195
|
+
let f = `wfrun_test_${++a}`, p = d(), m = c.workflow.toLayer(c.execute).pipe(t.provideMerge(u)), h = /* @__PURE__ */ new Date(), g = c.workflow.execute(s).pipe(e.locally(l, f), e.provide(p.layer), e.provide(m), e.either), _ = await e.runPromise(g), v = S(p.readSteps()), y = /* @__PURE__ */ new Date(), b;
|
|
196
|
+
if (_._tag === "Right") b = {
|
|
197
|
+
status: "succeeded",
|
|
198
|
+
output: _.right,
|
|
199
|
+
error: null,
|
|
200
|
+
steps: v,
|
|
201
|
+
runId: f
|
|
202
|
+
};
|
|
203
|
+
else {
|
|
204
|
+
let e = _.left;
|
|
205
|
+
b = {
|
|
206
|
+
status: "failed",
|
|
207
|
+
output: null,
|
|
208
|
+
error: {
|
|
209
|
+
tag: e && typeof e == "object" && "_tag" in e ? String(e._tag) : null,
|
|
210
|
+
message: e && typeof e == "object" && "message" in e && typeof e.message == "string" ? e.message : String(_.left)
|
|
211
|
+
},
|
|
212
|
+
steps: v,
|
|
213
|
+
runId: f
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return i.set(f, {
|
|
217
|
+
id: f,
|
|
218
|
+
executionId: f,
|
|
219
|
+
name: o,
|
|
220
|
+
status: b.status,
|
|
221
|
+
input: s,
|
|
222
|
+
output: b.output,
|
|
223
|
+
subject: n.ctx.request.subject,
|
|
224
|
+
source: "test-runner",
|
|
225
|
+
steps: v,
|
|
226
|
+
startedAt: h,
|
|
227
|
+
finishedAt: y,
|
|
228
|
+
traceId: null,
|
|
229
|
+
parentExecutionId: null,
|
|
230
|
+
parentClosePolicy: null
|
|
231
|
+
}), b;
|
|
232
|
+
},
|
|
233
|
+
inspect: async (e) => i.get(e) ?? null
|
|
234
|
+
};
|
|
235
|
+
}, w = 1;
|
|
236
|
+
//#endregion
|
|
237
|
+
export { p as MockClock, h as MockEmail, g as MockLLM, w as TESTING_PRESET_VERSION, f as invoke, x as makeTestContext, C as makeWorkflowRunner, _ as mockStore };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voltro/testing",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"voltro",
|
|
7
|
+
"typescript",
|
|
8
|
+
"framework"
|
|
9
|
+
],
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"homepage": "https://voltro.dev",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"email": "support@voltro.dev"
|
|
14
|
+
},
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Voltro UG",
|
|
17
|
+
"url": "https://voltro.dev"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./dialect": {
|
|
27
|
+
"types": "./dist/dialect.d.ts",
|
|
28
|
+
"import": "./dist/dialect.js",
|
|
29
|
+
"default": "./dist/dialect.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"module": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=24.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@voltro/database": "0.1.0",
|
|
41
|
+
"@voltro/env": "0.1.0",
|
|
42
|
+
"@voltro/protocol": "0.1.0",
|
|
43
|
+
"@voltro/runtime": "0.1.0",
|
|
44
|
+
"@voltro/workflow": "0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"effect": "^3.21.4"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|