@voltro/testing 0.85.0 → 0.87.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/dist/invoke.js ADDED
@@ -0,0 +1,82 @@
1
+ import { queryCacheOf as e, resetAfterCommit as t, restInvocationOf as n, rpcInterceptorFor as r, runAfterCommit as i, runInStoreTransaction as a, serviceLayerFor as o } from "./testContext.js";
2
+ import { Cause as s, Effect as c, Exit as l, Schema as u } from "effect";
3
+ import { SubjectService as d, checkGuardsEffect as f } from "@voltro/protocol";
4
+ import { cachedComputedCall as p, computedCacheKey as m, makeEffectStoreLayer as h, runProvidedEffect as g, runWithDeadlockRetry as _ } from "@voltro/runtime";
5
+ //#region src/invoke.ts
6
+ var v = (e, t) => {
7
+ let n = o(t);
8
+ return (n === void 0 ? e : c.provide(e, n)).pipe(c.provide(h(t.store)), c.provideService(d, t.request.subject));
9
+ }, y = async (o, h, y, x) => {
10
+ let S = o.input, C = await u.decodeUnknownPromise(S)(y), w = o.kind, T = async (e) => {
11
+ let t = h(C, e);
12
+ return c.isEffect(t) ? g(v(t, e)) : t;
13
+ }, E = async () => {
14
+ let t = e(x), n = o.cache;
15
+ if (t === void 0 || n === void 0) return b;
16
+ let r = t.deps.policyFor(n);
17
+ if (r === void 0) return b;
18
+ let i = o, a = i.name ?? "", s = i.source === void 0 ? [] : typeof i.source == "string" ? [i.source] : [...i.source], l = await t.rowFilter(), u = m({
19
+ name: a,
20
+ codeIdentity: t.deps.codeIdentity,
21
+ output: i.output,
22
+ input: i.input,
23
+ decodedInput: C,
24
+ scope: r.scope,
25
+ subject: x.request.subject,
26
+ rowFilter: l,
27
+ declaredSources: s
28
+ });
29
+ return "bypass" in u ? b : g(p({
30
+ cache: t.deps,
31
+ policy: r,
32
+ key: u.key,
33
+ name: a,
34
+ sources: s,
35
+ output: i.output,
36
+ subject: x.request.subject,
37
+ rowFilter: l,
38
+ ctx: x,
39
+ execute: async (e, t) => {
40
+ let n = h(C, e);
41
+ return c.isEffect(n) ? g(v(n.pipe(c.provideService(d, t)), x)) : n;
42
+ },
43
+ onDescriptor: () => {},
44
+ isDescriptor: () => !1,
45
+ unwrap: (e) => e
46
+ }).pipe(c.map(({ value: e }) => e)));
47
+ }, D = async () => {
48
+ let e = o.guards;
49
+ if (e !== void 0 && e.length > 0) {
50
+ let t = await c.runPromise(f(x.request.subject, e, C));
51
+ if (t !== null) throw t;
52
+ }
53
+ if (w === "query") {
54
+ let e = await E();
55
+ if (e !== b) return e;
56
+ }
57
+ if (w !== "mutation") return T(x);
58
+ let n = await _(async () => a(x, async (e) => (t(x), T(e))), { delay: () => Promise.resolve() });
59
+ return await i(x), n;
60
+ }, O = w === "mutation" || w === "query" || w === "action" ? w : void 0, k = O === void 0 ? void 0 : r(x, O);
61
+ if (k === void 0 || O === void 0) return await D();
62
+ let A = c.tryPromise({
63
+ try: () => D(),
64
+ catch: (e) => e
65
+ }), j = n(x), M = k(A, {
66
+ tag: o.name ?? "",
67
+ kind: O,
68
+ input: C,
69
+ subject: x.request.subject,
70
+ traceId: x.request.traceId,
71
+ ...x.request.spanId === void 0 ? {} : { spanId: x.request.spanId },
72
+ ...j === void 0 ? {} : {
73
+ surface: "rest",
74
+ publicApi: j.publicApi
75
+ },
76
+ ...j?.remoteAddr === void 0 ? {} : { remoteAddr: j.remoteAddr }
77
+ }), N = await c.runPromiseExit(M);
78
+ if (l.isSuccess(N)) return N.value;
79
+ throw s.squash(N.cause);
80
+ }, b = Symbol("voltro:testing:not-cached");
81
+ //#endregion
82
+ export { y as invoke };
@@ -0,0 +1,78 @@
1
+ import { Effect as e } from "effect";
2
+ //#region src/mockClock.ts
3
+ var t = Symbol.for("voltro/testing/mockClock.installed"), n = () => globalThis, r = (e, t) => new Proxy(e, {
4
+ construct: (e, n, r) => Reflect.construct(e, n.length === 0 ? [t()] : n, r),
5
+ apply: () => new e(t()).toString(),
6
+ get: (e, n, r) => n === "now" ? () => t() : Reflect.get(e, n, r)
7
+ }), i = (e) => {
8
+ if (typeof e == "number") return e;
9
+ let t = e instanceof Date ? e.getTime() : Date.parse(e);
10
+ if (Number.isNaN(t)) throw Error(`mockClock: cannot read an instant from '${String(e)}'`);
11
+ return t;
12
+ }, a = class {
13
+ currentMs;
14
+ constructor(e = "2026-01-01T00:00:00Z") {
15
+ this.currentMs = i(e);
16
+ }
17
+ now() {
18
+ return this.currentMs;
19
+ }
20
+ date() {
21
+ return new Date(this.currentMs);
22
+ }
23
+ advance(e) {
24
+ this.currentMs += typeof e == "number" ? e : l(e);
25
+ }
26
+ set(e) {
27
+ this.currentMs = i(e);
28
+ }
29
+ get installed() {
30
+ return n()[t]?.clock === this;
31
+ }
32
+ install() {
33
+ let e = n(), i = e[t];
34
+ if (i !== void 0) throw Error("mockClock: global time is already faked" + (i.clock === this ? " by this same clock" : " by another MockClock") + ". Uninstall it before installing again — a nested install restores the outer fake on the way out, which leaves the realm frozen with nothing pointing at why. `withFrozenTime` always restores, including on a throw.");
35
+ let a = globalThis.Date;
36
+ return e[t] = {
37
+ realDate: a,
38
+ clock: this
39
+ }, globalThis.Date = r(a, () => this.currentMs), () => {
40
+ this.uninstall();
41
+ };
42
+ }
43
+ uninstall() {
44
+ let e = n(), r = e[t];
45
+ r !== void 0 && r.clock === this && (globalThis.Date = r.realDate, delete e[t]);
46
+ }
47
+ }, o = (e) => typeof e == "object" && !!e && typeof e.then == "function", s = (e, t) => {
48
+ let n = e instanceof a ? e : new a(e);
49
+ n.install();
50
+ let r = !1;
51
+ try {
52
+ let e = t(n);
53
+ return o(e) ? (r = !0, e.then((e) => (n.uninstall(), e), (e) => {
54
+ throw n.uninstall(), e;
55
+ })) : e;
56
+ } finally {
57
+ r || n.uninstall();
58
+ }
59
+ }, c = (t) => e.acquireRelease(e.sync(() => {
60
+ let e = t instanceof a ? t : new a(t);
61
+ return e.install(), e;
62
+ }), (t) => e.sync(() => {
63
+ t.uninstall();
64
+ })), l = (e) => {
65
+ let t = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)\s*$/.exec(e);
66
+ if (!t) throw Error(`mockClock: cannot parse duration '${e}'`);
67
+ let n = Number(t[1]), r = t[2];
68
+ switch (r) {
69
+ case "ms": return n;
70
+ case "s": return n * 1e3;
71
+ case "m": return n * 6e4;
72
+ case "h": return n * 36e5;
73
+ case "d": return n * 864e5;
74
+ default: throw Error(`mockClock: unknown unit '${r}'`);
75
+ }
76
+ };
77
+ //#endregion
78
+ export { a as MockClock, c as frozenTime, s as withFrozenTime };
@@ -0,0 +1,19 @@
1
+ //#region src/mockLLM.ts
2
+ var e = class {
3
+ queue;
4
+ calls = [];
5
+ constructor(e) {
6
+ this.queue = [...e];
7
+ }
8
+ next(e) {
9
+ this.calls.push(e);
10
+ let t = this.queue.shift();
11
+ if (!t) throw Error("MockLLM: no more responses queued");
12
+ return t;
13
+ }
14
+ remaining() {
15
+ return this.queue.length;
16
+ }
17
+ };
18
+ //#endregion
19
+ export { e as MockLLM };
@@ -0,0 +1,26 @@
1
+ //#region src/mockWebhooks.ts
2
+ var e = class {
3
+ emitted = [];
4
+ async emit(e, t) {
5
+ let n = typeof e == "string" ? e : e.name ?? e.id ?? e.event ?? "(unnamed event)";
6
+ return this.emitted.push({
7
+ event: n,
8
+ payload: t,
9
+ emittedAt: /* @__PURE__ */ new Date()
10
+ }), { delivered: 0 };
11
+ }
12
+ async listTargets(e, t) {
13
+ return [];
14
+ }
15
+ payloadsFor(e) {
16
+ return this.emitted.filter((t) => t.event === e).map((e) => e.payload);
17
+ }
18
+ last(e) {
19
+ for (let t = this.emitted.length - 1; t >= 0; t--) if (this.emitted[t].event === e) return this.emitted[t];
20
+ }
21
+ clear() {
22
+ this.emitted.length = 0;
23
+ }
24
+ };
25
+ //#endregion
26
+ export { e as MockWebhooks };
package/dist/platform.js CHANGED
@@ -10,11 +10,7 @@ var r = (r, i) => {
10
10
  t(Number.isFinite(e.clock.now())).toBe(!0);
11
11
  }), n("storage: the local area is a synchronous store that reads back what it wrote", async () => {
12
12
  let e = await o(), n = e.storage("local");
13
- if (n === null) {
14
- t(e.storage("session")).toBeNull();
15
- return;
16
- }
17
- n.setItem(a, "v1"), t(n.getItem(a)).toBe("v1"), n.removeItem?.(a);
13
+ n === null ? t(e.storage("session")).toBeNull() : (n.setItem(a, "v1"), t(n.getItem(a)).toBe("v1"), n.removeItem?.(a));
18
14
  }), n("secureStorage: honours its declared kind", async () => {
19
15
  let e = await o();
20
16
  e.capabilities.secureTokenStorage ? (t(e.secureStorage.kind).not.toBe("none"), await e.secureStorage.set(a, "secret"), t(await e.secureStorage.get(a)).toBe("secret"), await e.secureStorage.delete(a), t(await e.secureStorage.get(a)).toBeNull()) : (t(e.secureStorage.kind).toBe("none"), t(await e.secureStorage.get(a)).toBeNull(), await t(e.secureStorage.set(a, "secret")).rejects.toThrow());
@@ -0,0 +1,27 @@
1
+ import e from "node:net";
2
+ import { describe as t } from "vitest";
3
+ //#region src/reachable.ts
4
+ var n = (t) => new Promise((n) => {
5
+ let r = new e.Socket(), i = (e) => {
6
+ clearTimeout(a), r.destroy(), n(e);
7
+ }, a = setTimeout(() => i(!1), t.timeoutMs ?? 750);
8
+ r.once("connect", () => i(!0)), r.once("error", () => i(!1)), r.connect(t.port, t.host);
9
+ }), r = async (e, n, r, i) => {
10
+ if (typeof n != "string" || typeof r != "function" || typeof i != "function") throw TypeError(`describeIfAvailable('${e}', ...): expected (label, dependency, probe, suite) — got dependency: ${typeof n}, probe: ${typeof r}, suite: ${typeof i}. Three arguments means the suite body is being used as the probe: it would run at file scope, unconditionally, and the skip would silently do nothing.`);
11
+ let a = !1;
12
+ try {
13
+ a = await r();
14
+ } catch {
15
+ a = !1;
16
+ }
17
+ t.skipIf(!a)(a ? e : `${e} [SKIPPED: needs ${n}]`, i);
18
+ }, i = async (e, t, i) => {
19
+ await r(e, `${t.name} at ${t.host}:${t.port}`, () => n(t), i);
20
+ }, a = async (e, t) => {
21
+ try {
22
+ let n = await t();
23
+ n !== void 0 && n !== "" && console.log(`[engine] ${e}: ${n}`);
24
+ } catch {}
25
+ };
26
+ //#endregion
27
+ export { r as describeIfAvailable, i as describeIfReachable, n as isTcpReachable, a as reportEngineVersion };
@@ -0,0 +1,53 @@
1
+ import { Effect as e, Schema as t } from "effect";
2
+ import { ApiUnavailable as n } from "@voltro/protocol";
3
+ //#region src/serviceApiMock.ts
4
+ var r = (r, i, a = {}) => {
5
+ let o = a.name ?? "mock", s = [], c = /* @__PURE__ */ new Map(), l = (e, t) => `${e}\u0000${JSON.stringify(t)}`, u = async (e, a, c) => {
6
+ let l = r[e];
7
+ if (l === void 0) throw Error(`makeServiceApiMock: '${e}' is not a service procedure of this api`);
8
+ let u = l.input, d = t.encodeUnknownSync(u)(a), f = t.decodeUnknownSync(u)(d);
9
+ s.push({
10
+ tag: e,
11
+ input: f,
12
+ idempotencyKey: c?.idempotencyKey
13
+ });
14
+ let p = i[e];
15
+ if (p === void 0) throw new n({
16
+ api: o,
17
+ tag: e,
18
+ reason: "transport",
19
+ message: "no handler in makeServiceApiMock"
20
+ });
21
+ let m = await p(f), h = l.output;
22
+ return t.decodeUnknownSync(h)(t.encodeUnknownSync(h)(m));
23
+ }, d = (t) => {
24
+ let n = t.join("."), r = Object.assign((e, t) => u(n, e, t), {
25
+ effect: (t, r) => e.tryPromise({
26
+ try: () => u(n, t, r),
27
+ catch: (e) => e
28
+ }),
29
+ subscribe: (e, t) => {
30
+ let r = l(n, e), i = c.get(r) ?? /* @__PURE__ */ new Set();
31
+ return c.set(r, i), i.add(t.onValue), u(n, e).then(t.onValue, (e) => t.onError?.(e)), { unsubscribe: () => {
32
+ i.delete(t.onValue);
33
+ } };
34
+ }
35
+ });
36
+ return new Proxy(r, { get: (e, n) => {
37
+ if (typeof n == "string") {
38
+ if (n === "effect" || n === "subscribe") return e[n];
39
+ if (n !== "then") return d([...t, n]);
40
+ }
41
+ } });
42
+ };
43
+ return {
44
+ api: new Proxy({}, { get: (e, t) => typeof t == "string" && t !== "then" ? d([t]) : void 0 }),
45
+ calls: (e) => e === void 0 ? [...s] : s.filter((t) => t.tag === e),
46
+ emit: (e, n, i) => {
47
+ let a = r[e], o = a === void 0 ? i : t.decodeUnknownSync(a.output)(t.encodeUnknownSync(a.output)(i));
48
+ for (let t of c.get(l(e, n)) ?? []) t(o);
49
+ }
50
+ };
51
+ };
52
+ //#endregion
53
+ export { r as makeServiceApiMock };
@@ -0,0 +1,11 @@
1
+ import { anonymousSubject as e, systemSubject as t } from "@voltro/protocol";
2
+ //#region src/subject.ts
3
+ var n = "test-tenant", r = (e, t, n) => ({
4
+ type: e,
5
+ id: t,
6
+ tenantId: n.tenantId ?? "test-tenant",
7
+ scopes: n.scopes ?? [],
8
+ ...n.metadata === void 0 ? {} : { metadata: n.metadata }
9
+ }), i = (e, t = {}) => r("user", e, t), a = (e, t = {}) => r("apiKey", e, t), o = (e, t = {}) => r("serviceAccount", e, t), s = (t = null) => e(t), c = (e = "job:test", n) => n === void 0 ? t(e) : t(e, n);
10
+ //#endregion
11
+ export { n as TEST_TENANT_ID, s as anonymous, a as apiKey, o as serviceAccount, c as system, i as user };
@@ -0,0 +1,16 @@
1
+ import { changeDelete as e, changeInsert as t, changeSoftDelete as n, changeUpdate as r } from "@voltro/database";
2
+ import { createLogger as i } from "@voltro/logger";
3
+ //#region src/subscribeContext.ts
4
+ var a = (e = {}) => {
5
+ let t = e.id ?? "test", n = e.store ?? new Proxy({}, { get: (e, t) => {
6
+ throw Error(`makeSubscribeContext: this subscriber reached ctx.store.${String(t)} and the test passed no store. Pass \`{ store: makeTestContext().store }\` so the subscriber and the code under test share one — an empty stand-in would let a read of the wrong table pass.`);
7
+ } });
8
+ return {
9
+ id: t,
10
+ log: e.log ?? i({ scope: `subscribe:${t}` }),
11
+ store: n,
12
+ ...e.publish === void 0 ? {} : { publish: e.publish }
13
+ };
14
+ };
15
+ //#endregion
16
+ export { e as changeDelete, t as changeInsert, n as changeSoftDelete, r as changeUpdate, a as makeSubscribeContext };
@@ -0,0 +1,99 @@
1
+ import { AnyEventDescriptor } from '@voltro/protocol';
2
+ import { Context } from 'effect';
3
+ import { Effect } from 'effect';
4
+ import { Layer } from 'effect';
5
+ import { PluginInspectRequest } from '@voltro/protocol';
6
+ import { PluginInspectResponse } from '@voltro/protocol';
7
+ import { PluginPrivacyRequest } from '@voltro/protocol';
8
+ import { PluginPrivacyTarget } from '@voltro/protocol';
9
+ import { VoltroPlugin } from '@voltro/protocol';
10
+
11
+ /**
12
+ * Register the system contract against real exports and descriptor surfaces.
13
+ * Refuses incomplete inventories before registering any tests. This suite does
14
+ * not establish SQL parity, ordinary boot, four dashboard layers or capacity.
15
+ */
16
+ export declare const systemContractConformance: (plugin: VoltroPlugin, options: SystemContractConformanceOptions) => void;
17
+
18
+ export declare interface SystemContractConformanceOptions {
19
+ readonly manifest: SystemContractManifest;
20
+ /** Actual module namespace imports, never copies of the system exports. */
21
+ readonly strategies: Readonly<Record<string, unknown>>;
22
+ readonly ports: Readonly<Record<string, unknown>>;
23
+ readonly testing: Readonly<Record<string, unknown>>;
24
+ /** Actual descriptors used by discovery/codegen. Hidden events are not inferred. */
25
+ readonly eventCatalog: ReadonlyArray<AnyEventDescriptor>;
26
+ readonly eventFixtures: Readonly<Record<string, SystemEventFixture>>;
27
+ readonly stages: Readonly<Record<'take' | 'configure' | 'hook' | 'override' | 'extendOrReplace', SystemContractProbe>>;
28
+ readonly privacy: Readonly<Record<string, (kind: PluginPrivacyTarget['kind']) => Effect.Effect<SystemPrivacyFixture, unknown>>>;
29
+ /** Exactly one case per contributed endpoint, keyed as "GET path". */
30
+ readonly inspect: Readonly<Record<string, SystemInspectFixture>>;
31
+ }
32
+
33
+ /** Export this inventory beside the system's public strategies and ports. */
34
+ export declare interface SystemContractManifest {
35
+ readonly slots: Readonly<Record<string, {
36
+ readonly defaultExport: string;
37
+ readonly fixtureExport: string;
38
+ }>>;
39
+ readonly ports: Readonly<Record<string, {
40
+ readonly tagExport: string;
41
+ readonly fakeLayerExport: string;
42
+ readonly fixtureExport: string;
43
+ }>>;
44
+ readonly events: ReadonlyArray<string>;
45
+ }
46
+
47
+ /** A measured observation. Use inputs that distinguish defaults from overrides. */
48
+ export declare interface SystemContractProbe {
49
+ readonly run: () => Effect.Effect<unknown, unknown>;
50
+ readonly expected: unknown;
51
+ }
52
+
53
+ export declare interface SystemEventFixture {
54
+ readonly descriptor: AnyEventDescriptor;
55
+ readonly key: unknown;
56
+ readonly payload: unknown;
57
+ }
58
+
59
+ export declare interface SystemInspectFixture {
60
+ readonly request: PluginInspectRequest;
61
+ readonly expected: PluginInspectResponse;
62
+ }
63
+
64
+ export declare interface SystemPortFixture {
65
+ readonly kind: 'port';
66
+ readonly tag: unknown;
67
+ readonly fakeLayer: unknown;
68
+ readonly probe: SystemContractProbe;
69
+ }
70
+
71
+ /** Preserve concrete Tag/Layer types; the suite owns scope acquisition and cleanup. */
72
+ export declare const systemPortFixture: <Identifier, Service>(options: {
73
+ readonly tag: Context.Service<Identifier, Service>;
74
+ readonly fakeLayer: Layer.Layer<Identifier>;
75
+ readonly read: Effect.Effect<unknown, unknown, Identifier>;
76
+ readonly expected: unknown;
77
+ }) => SystemPortFixture;
78
+
79
+ /** A fresh, seeded fixture; read() must include peer/tenant rows that must survive. */
80
+ export declare interface SystemPrivacyFixture {
81
+ /** Explicitly reviewed tenant-wide table without subject facts; erase retains every row. */
82
+ readonly subjectData?: 'none';
83
+ readonly request: PluginPrivacyRequest;
84
+ readonly read: () => Effect.Effect<ReadonlyArray<unknown>, unknown>;
85
+ readonly before: ReadonlyArray<unknown>;
86
+ readonly after: ReadonlyArray<unknown>;
87
+ readonly exported: unknown;
88
+ readonly close: Effect.Effect<void>;
89
+ }
90
+
91
+ /** A ./testing export that exercises the imported default and configured override. */
92
+ export declare interface SystemSlotFixture {
93
+ readonly kind: 'slot';
94
+ readonly defaultImplementation: unknown;
95
+ readonly defaultProbe: SystemContractProbe;
96
+ readonly overrideProbe: SystemContractProbe;
97
+ }
98
+
99
+ export { }
package/dist/system.js ADDED
@@ -0,0 +1,94 @@
1
+ import { testEventBus as e } from "./testEventBus.js";
2
+ import { Context as t, Effect as n, Layer as r } from "effect";
3
+ import { createPluginPrivacyRegistry as i } from "@voltro/protocol";
4
+ import { describe as a, expect as o, it as s } from "vitest";
5
+ //#region src/systemContractConformance.ts
6
+ var c = (e) => ({
7
+ kind: "port",
8
+ tag: e.tag,
9
+ fakeLayer: e.fakeLayer,
10
+ probe: {
11
+ run: () => n.scoped(e.read.pipe(n.provide(e.fakeLayer))),
12
+ expected: e.expected
13
+ }
14
+ }), l = (e, t) => {
15
+ if (e.some((e) => !e.trim()) || new Set(e).size !== e.length) throw Error(`Invalid system ${t} inventory`);
16
+ }, u = (e, t, n) => {
17
+ if (l(e, n), l(t, n), JSON.stringify([...e].sort()) !== JSON.stringify([...t].sort())) throw Error(`Incomplete system ${n} fixtures`);
18
+ }, d = (e) => typeof e == "object" && !!e, f = (e) => d(e) && typeof e.run == "function" && e.expected !== void 0, p = (e) => d(e) && e.kind === "slot" && typeof e.defaultImplementation == "function" && f(e.defaultProbe) && f(e.overrideProbe), m = (e) => d(e) && e.kind === "port" && t.isKey(e.tag) && r.isLayer(e.fakeLayer) && f(e.probe), h = (e, t) => `${e} ${t.replace(/^\//, "")}`, g = (t, r) => {
19
+ let { manifest: c } = r, g = t.services !== void 0 || (t.routes?.length ?? 0) > 0 || (t.extendSchema?.tables?.length ?? 0) > 0 || (t.inspectEndpoints?.length ?? 0) > 0 || t.onActivate !== void 0 || t.interceptMutation !== void 0;
20
+ if (!t.name.trim() || !g || Object.keys(c.slots).length === 0) throw Error("System conformance requires an implemented system with strategy slots");
21
+ let _ = (t.extendSchema?.tables ?? []).map((e) => {
22
+ if (!d(e) || typeof e.tableName != "string") throw Error("System conformance requires named table descriptors");
23
+ return e.tableName;
24
+ });
25
+ u(Object.keys(r.stages), [
26
+ "take",
27
+ "configure",
28
+ "hook",
29
+ "override",
30
+ "extendOrReplace"
31
+ ], "stages");
32
+ for (let e of Object.values(r.stages)) if (!f(e)) throw Error("System stages require measured probes");
33
+ u(Object.keys(r.privacy), _, "privacy"), u(r.eventCatalog.map((e) => e.name), c.events, "events"), u(Object.keys(r.eventFixtures), c.events, "events");
34
+ let v = t.inspectEndpoints ?? [];
35
+ u(Object.keys(r.inspect), v.map((e) => h(e.method, e.path)), "inspect");
36
+ let y = Object.entries(c.slots).map(([e, t]) => {
37
+ l([e], "slots");
38
+ let n = r.strategies[t.defaultExport], i = r.testing[t.fixtureExport];
39
+ if (typeof n != "function" || !p(i) || i.defaultImplementation !== n) throw Error(`System slot requires its actual default export and fixture: ${e}`);
40
+ return {
41
+ name: e,
42
+ fixture: i
43
+ };
44
+ }), b = Object.entries(c.ports).map(([e, t]) => {
45
+ l([e], "ports");
46
+ let n = r.testing[t.fixtureExport];
47
+ if (!m(n) || n.tag !== r.ports[t.tagExport] || n.fakeLayer !== r.testing[t.fakeLayerExport]) throw Error(`System port requires its actual Tag, fake Layer and fixture: ${e}`);
48
+ return {
49
+ name: e,
50
+ fixture: n
51
+ };
52
+ });
53
+ for (let e of r.eventCatalog) if (e.kind !== "event" || r.eventFixtures[e.name]?.descriptor !== e) throw Error(`System event requires its actual catalog descriptor: ${e.name}`);
54
+ for (let e of v) {
55
+ let t = r.inspect[h(e.method, e.path)];
56
+ if (t.request.method !== e.method || t.expected.kind !== "json" || (t.expected.status ?? 200) !== 200 || t.expected.data === void 0) throw Error("System inspect fixture must observe successful JSON");
57
+ }
58
+ let x = i([t]);
59
+ if (_.length > 0 && (!t.subjectPrivacy || x.coverage("user").missing.length > 0 || x.coverage("guest").missing.length > 0 || !t.subjectPrivacy.kinds.includes("user") || !t.subjectPrivacy.kinds.includes("guest"))) throw Error("System tables require user and guest erase/export coverage");
60
+ let S = async (e) => {
61
+ o(await n.runPromise(n.suspend(e.run))).toEqual(e.expected);
62
+ };
63
+ a(`system contract — ${t.name}`, () => {
64
+ for (let [e, t] of Object.entries(r.stages)) s(`stage ${e}`, () => S(t));
65
+ for (let { name: e, fixture: t } of y) s(`slot ${e}: exported default and configured override`, async () => {
66
+ o(t.overrideProbe.expected).not.toEqual(t.defaultProbe.expected), await S(t.defaultProbe), await S(t.overrideProbe);
67
+ });
68
+ for (let { name: e, fixture: t } of b) s(`port ${e}: scoped fake Layer`, () => S(t.probe));
69
+ for (let t of r.eventCatalog) s(`event ${t.name}: real validation and delivery`, async () => {
70
+ let n = r.eventFixtures[t.name], i = e(), a = i.subscribe(t, n.key);
71
+ try {
72
+ await i.publish(t, n.key, n.payload), o(a.received).toEqual([n.payload]), o(a.missedCount).toBe(0);
73
+ } finally {
74
+ a.stop(), i.reset();
75
+ }
76
+ });
77
+ for (let e of _) for (let i of ["user", "guest"]) s(`table ${e}: ${i} export, erase, peers and idempotent retry`, () => n.runPromise(n.acquireUseRelease(r.privacy[e](i), (e) => n.gen(function* () {
78
+ o(e.request.target.kind).toBe(i), o(e.before.length).toBeGreaterThan(0), e.subjectData === "none" ? o(e.before).toEqual(e.after) : o(e.before).not.toEqual(e.after), o(yield* e.read()).toEqual(e.before);
79
+ let n = yield* x.exportSubject(e.request);
80
+ o(n.plugins).toEqual([{
81
+ name: t.name,
82
+ data: e.exported
83
+ }]);
84
+ let r = yield* x.eraseSubject(e.request);
85
+ o(r.completed).toEqual([t.name]), o(yield* e.read()).toEqual(e.after), yield* x.eraseSubject(e.request), o(yield* e.read()).toEqual(e.after);
86
+ }), (e) => e.close)));
87
+ for (let e of v) s(`inspect ${h(e.method, e.path)}: actual handler`, async () => {
88
+ let t = r.inspect[h(e.method, e.path)];
89
+ o(await n.runPromise(n.suspend(() => e.handler(t.request)))).toEqual(t.expected);
90
+ });
91
+ });
92
+ };
93
+ //#endregion
94
+ export { g as systemContractConformance, c as systemPortFixture };
@@ -0,0 +1,111 @@
1
+ import { apiBindingTokenEnv as e, apiBindingUrlEnv as t, mintServiceToken as n, serviceCallerTokenEnv as r } from "@voltro/runtime";
2
+ import i from "node:net";
3
+ import { randomBytes as a } from "node:crypto";
4
+ import { spawn as o } from "node:child_process";
5
+ import { createRequire as s } from "node:module";
6
+ import { dirname as c, join as l } from "node:path";
7
+ //#region src/testApps.ts
8
+ var u = () => new Promise((e, t) => {
9
+ let n = i.createServer();
10
+ n.once("error", t), n.listen(0, "127.0.0.1", () => {
11
+ let t = n.address(), r = typeof t == "object" && t ? t.port : 0;
12
+ n.close(() => e(r));
13
+ });
14
+ }), d = async (e) => {
15
+ try {
16
+ return (await fetch(`http://127.0.0.1:${e}/internal/readiness`)).status === 200;
17
+ } catch {
18
+ return !1;
19
+ }
20
+ }, f = (e) => l(c(s(l(e, "package.json")).resolve("@voltro/cli/package.json")), "bin", "voltro.mjs"), p = 1e4, m = (e) => new Promise((t) => {
21
+ if (e.exitCode !== null || e.signalCode !== null) return t();
22
+ let n = setTimeout(() => {
23
+ e.kill("SIGKILL");
24
+ }, p);
25
+ e.once("exit", () => {
26
+ clearTimeout(n), t();
27
+ }), e.kill("SIGTERM");
28
+ }), h = async (i) => {
29
+ let s = Object.keys(i.apps);
30
+ if (s.length === 0) throw Error("startTestApps: no apps given");
31
+ let c = i.mode ?? "dev", l = i.bootTimeoutMs ?? 3e5, p = i.cli ?? f(i.apps[s[0]].root), h = /* @__PURE__ */ new Map();
32
+ for (let e of s) h.set(e, await u());
33
+ let g = /* @__PURE__ */ new Map(), _ = new Map(s.map((e) => [e, {}]));
34
+ for (let a of i.edges ?? []) {
35
+ for (let e of [a.caller, a.callee]) if (!s.includes(e)) throw Error(`startTestApps: edge names '${e}', which is not among the apps (${s.join(", ")})`);
36
+ let i = n();
37
+ g.set(`${a.caller}\u0000${a.callee}`, i), Object.assign(_.get(a.caller), {
38
+ [e(a.binding)]: i,
39
+ [t(a.binding)]: `ws://127.0.0.1:${h.get(a.callee)}/ws`
40
+ }), Object.assign(_.get(a.callee), { [r(a.caller)]: i });
41
+ }
42
+ let v = [], y = /* @__PURE__ */ new Set(), b = (e) => {
43
+ if (!v.includes(e)) {
44
+ if (y.has(e)) throw Error(`startTestApps: the edges form a cycle through '${e}' — no app can boot first`);
45
+ y.add(e);
46
+ for (let t of i.edges ?? []) t.caller === e && b(t.callee);
47
+ y.delete(e), v.push(e);
48
+ }
49
+ };
50
+ for (let e of s) b(e);
51
+ let x = [], S = /* @__PURE__ */ new Map(), C = async () => {
52
+ await Promise.all(x.map(m));
53
+ };
54
+ for (let e of v) {
55
+ let t = i.apps[e], n = h.get(e), r = "", s = o(process.execPath, [
56
+ p,
57
+ c,
58
+ ...c === "serve" ? ["."] : []
59
+ ], {
60
+ cwd: t.root,
61
+ env: {
62
+ ...process.env,
63
+ ...c === "serve" ? { VOLTRO_SESSION_SECRET: a(32).toString("base64url") } : {},
64
+ ...t.env,
65
+ ..._.get(e),
66
+ PORT: String(n)
67
+ },
68
+ stdio: [
69
+ "ignore",
70
+ "pipe",
71
+ "pipe"
72
+ ]
73
+ });
74
+ x.push(s), s.stdout?.on("data", (e) => {
75
+ r += e.toString();
76
+ }), s.stderr?.on("data", (e) => {
77
+ r += e.toString();
78
+ }), s.on("exit", (e, t) => {
79
+ r += `\n[exited] code=${String(e)} signal=${String(t)}`;
80
+ });
81
+ let u = {
82
+ name: e,
83
+ port: n,
84
+ url: `http://127.0.0.1:${n}`,
85
+ wsUrl: `ws://127.0.0.1:${n}/ws`,
86
+ log: () => r,
87
+ stop: () => m(s)
88
+ };
89
+ S.set(e, u);
90
+ let f = Date.now() + l;
91
+ for (; !await d(n);) {
92
+ if (s.exitCode !== null || s.signalCode !== null || Date.now() > f) {
93
+ await C();
94
+ let t = s.exitCode !== null || s.signalCode !== null ? "exited before it was ready" : `was not ready within ${l} ms`;
95
+ throw Error(`startTestApps: '${e}' ${t}\n${u.log().slice(-4e3)}`);
96
+ }
97
+ await new Promise((e) => setTimeout(e, 250));
98
+ }
99
+ }
100
+ return {
101
+ apps: Object.fromEntries(S),
102
+ token: (e, t) => {
103
+ let n = g.get(`${e}\u0000${t}`);
104
+ if (n === void 0) throw Error(`startTestApps: no edge ${e} → ${t}`);
105
+ return n;
106
+ },
107
+ stop: C
108
+ };
109
+ };
110
+ //#endregion
111
+ export { h as startTestApps };