@lunora/testing 1.0.0-alpha.3 → 1.0.0-alpha.30
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/LICENSE.md +6 -0
- package/README.md +12 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/{lunoraTest-BUg4Ebv8.mjs → lunoraTest-DO9gV_fa.mjs} +24 -6
- package/package.json +6 -5
package/dist/index.d.mts
CHANGED
|
@@ -141,6 +141,19 @@ type FunctionRegistry = Record<string, RegisteredAction<any, any> | RegisteredMu
|
|
|
141
141
|
* clearly-throwing stubs for unsupported surfaces.
|
|
142
142
|
*/
|
|
143
143
|
interface LunoraTestOptions {
|
|
144
|
+
/**
|
|
145
|
+
* Injectable `ctx.env` for every context (query / mutation / action). When
|
|
146
|
+
* provided, handlers that read `ctx.env.SOME_KEY` (the validated `defineEnv`
|
|
147
|
+
* surface) see this object. Left unset it stays `undefined` — matching the
|
|
148
|
+
* optional `ctx.env?` field, so graceful `ctx.env?.KEY` access still yields
|
|
149
|
+
* `undefined` rather than throwing. Not a throwing stub for exactly that
|
|
150
|
+
* reason: `env` is designed to be legitimately absent.
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const t = lunoraTest(schema, { env: { STRIPE_KEY: "sk_test_…" } });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
env?: Record<string, unknown>;
|
|
144
157
|
/**
|
|
145
158
|
* Injectable `fetch` implementation for action contexts. When provided,
|
|
146
159
|
* `ctx.fetch` in every `action` (and `withIdentity` views) resolves to this
|
|
@@ -171,6 +184,16 @@ interface LunoraTestOptions {
|
|
|
171
184
|
* ```
|
|
172
185
|
*/
|
|
173
186
|
functions?: FunctionRegistry;
|
|
187
|
+
/**
|
|
188
|
+
* Fixed value for `ctx.now` (epoch ms) in every context. Production captures
|
|
189
|
+
* `Date.now()` once per execution; in tests a fixed `now` makes time-dependent
|
|
190
|
+
* handlers deterministic. Defaults to the wall clock at harness creation.
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* const t = lunoraTest(schema, { now: 1_700_000_000_000 });
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
now?: number;
|
|
174
197
|
}
|
|
175
198
|
/**
|
|
176
199
|
* The in-memory test harness returned by {@link lunoraTest}. Mirrors the first
|
|
@@ -253,6 +276,8 @@ interface TestHarness {
|
|
|
253
276
|
*
|
|
254
277
|
* **v1 surfaces now supported:**
|
|
255
278
|
*
|
|
279
|
+
* - `ctx.env` (all contexts): inject the validated env via `options.env`; unset it
|
|
280
|
+
* stays `undefined`, matching the optional `ctx.env?` field.
|
|
256
281
|
* - `ctx.fetch` (actions): inject a custom `fetch` via `options.fetch`.
|
|
257
282
|
* - `ctx.scheduler` (mutations + actions): fully functional fake with virtual clock;
|
|
258
283
|
* control via `harness.scheduler.advance(ms)` / `runPending()` / `list()`.
|
package/dist/index.d.ts
CHANGED
|
@@ -141,6 +141,19 @@ type FunctionRegistry = Record<string, RegisteredAction<any, any> | RegisteredMu
|
|
|
141
141
|
* clearly-throwing stubs for unsupported surfaces.
|
|
142
142
|
*/
|
|
143
143
|
interface LunoraTestOptions {
|
|
144
|
+
/**
|
|
145
|
+
* Injectable `ctx.env` for every context (query / mutation / action). When
|
|
146
|
+
* provided, handlers that read `ctx.env.SOME_KEY` (the validated `defineEnv`
|
|
147
|
+
* surface) see this object. Left unset it stays `undefined` — matching the
|
|
148
|
+
* optional `ctx.env?` field, so graceful `ctx.env?.KEY` access still yields
|
|
149
|
+
* `undefined` rather than throwing. Not a throwing stub for exactly that
|
|
150
|
+
* reason: `env` is designed to be legitimately absent.
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const t = lunoraTest(schema, { env: { STRIPE_KEY: "sk_test_…" } });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
env?: Record<string, unknown>;
|
|
144
157
|
/**
|
|
145
158
|
* Injectable `fetch` implementation for action contexts. When provided,
|
|
146
159
|
* `ctx.fetch` in every `action` (and `withIdentity` views) resolves to this
|
|
@@ -171,6 +184,16 @@ interface LunoraTestOptions {
|
|
|
171
184
|
* ```
|
|
172
185
|
*/
|
|
173
186
|
functions?: FunctionRegistry;
|
|
187
|
+
/**
|
|
188
|
+
* Fixed value for `ctx.now` (epoch ms) in every context. Production captures
|
|
189
|
+
* `Date.now()` once per execution; in tests a fixed `now` makes time-dependent
|
|
190
|
+
* handlers deterministic. Defaults to the wall clock at harness creation.
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* const t = lunoraTest(schema, { now: 1_700_000_000_000 });
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
now?: number;
|
|
174
197
|
}
|
|
175
198
|
/**
|
|
176
199
|
* The in-memory test harness returned by {@link lunoraTest}. Mirrors the first
|
|
@@ -253,6 +276,8 @@ interface TestHarness {
|
|
|
253
276
|
*
|
|
254
277
|
* **v1 surfaces now supported:**
|
|
255
278
|
*
|
|
279
|
+
* - `ctx.env` (all contexts): inject the validated env via `options.env`; unset it
|
|
280
|
+
* stays `undefined`, matching the optional `ctx.env?` field.
|
|
256
281
|
* - `ctx.fetch` (actions): inject a custom `fetch` via `options.fetch`.
|
|
257
282
|
* - `ctx.scheduler` (mutations + actions): fully functional fake with virtual clock;
|
|
258
283
|
* control via `harness.scheduler.advance(ms)` / `runPending()` / `list()`.
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { lunoraTest } from './packem_shared/lunoraTest-
|
|
1
|
+
export { lunoraTest } from './packem_shared/lunoraTest-DO9gV_fa.mjs';
|
|
2
2
|
export { extractLink, listCapturedMail, waitForMail } from '@lunora/mail/testing';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runShardMigrations, createShardCtxDb } from '@lunora/do';
|
|
2
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
3
|
import { DatabaseSync } from 'node:sqlite';
|
|
3
4
|
|
|
4
5
|
const createFakeScheduler = (getDispatch, getMutationContext, getFunctionRegistry) => {
|
|
@@ -100,7 +101,7 @@ const createSqlExec = () => {
|
|
|
100
101
|
return {
|
|
101
102
|
one() {
|
|
102
103
|
if (rows.length !== 1) {
|
|
103
|
-
throw new
|
|
104
|
+
throw new LunoraError("INTERNAL", `expected exactly one row, received ${String(rows.length)}`);
|
|
104
105
|
}
|
|
105
106
|
const [only] = rows;
|
|
106
107
|
return only;
|
|
@@ -138,7 +139,7 @@ const registeredFunctionKind = (value) => {
|
|
|
138
139
|
};
|
|
139
140
|
const registeredFunctionVisibility = (value) => typeof value === "object" && value !== null && value.visibility === "internal" ? "internal" : "public";
|
|
140
141
|
const unavailable = (surface) => {
|
|
141
|
-
throw new
|
|
142
|
+
throw new LunoraError("INTERNAL", `ctx.${surface} is not available in the in-memory @lunora/testing harness (v1)`);
|
|
142
143
|
};
|
|
143
144
|
const stubProxy = (surface) => /* @__PURE__ */ new Proxy((..._args) => unavailable(surface), {
|
|
144
145
|
apply: () => unavailable(surface),
|
|
@@ -286,18 +287,25 @@ const lunoraTest = (schema, options) => {
|
|
|
286
287
|
const { controls: schedulerControls, scheduler: fakeScheduler } = createFakeScheduler(
|
|
287
288
|
() => {
|
|
288
289
|
if (scheduledDispatchRef === void 0) {
|
|
289
|
-
throw new
|
|
290
|
+
throw new LunoraError(
|
|
291
|
+
"INTERNAL",
|
|
292
|
+
"[fake-scheduler] dispatch not yet available — scheduler.advance called before harness construction completed"
|
|
293
|
+
);
|
|
290
294
|
}
|
|
291
295
|
return scheduledDispatchRef;
|
|
292
296
|
},
|
|
293
297
|
() => {
|
|
294
298
|
if (mutationContextRef === void 0) {
|
|
295
|
-
throw new
|
|
299
|
+
throw new LunoraError(
|
|
300
|
+
"INTERNAL",
|
|
301
|
+
"[fake-scheduler] mutationContext not yet available — scheduler.advance called before harness construction completed"
|
|
302
|
+
);
|
|
296
303
|
}
|
|
297
304
|
return mutationContextRef;
|
|
298
305
|
},
|
|
299
306
|
() => functionRegistryMap
|
|
300
307
|
);
|
|
308
|
+
const harnessNow = options?.now ?? Date.now();
|
|
301
309
|
const makeHarness = (identity) => {
|
|
302
310
|
const auth = {
|
|
303
311
|
// eslint-disable-next-line unicorn/no-null -- AuthState.getIdentity's anonymous sentinel is `null` (mirrors a decoded JWT being absent)
|
|
@@ -308,21 +316,27 @@ const lunoraTest = (schema, options) => {
|
|
|
308
316
|
const queryContext = {
|
|
309
317
|
auth,
|
|
310
318
|
db: database,
|
|
319
|
+
env: options?.env,
|
|
311
320
|
log: noopLog,
|
|
321
|
+
now: harnessNow,
|
|
312
322
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- lazy closure: `runInternal` is invoked only when a handler calls ctx.runQuery, after construction completes
|
|
313
323
|
runQuery: (reference, args) => runInternal("query", reference, queryContext, args),
|
|
324
|
+
secrets: stubProxy("secrets"),
|
|
314
325
|
storage: stubProxy("storage"),
|
|
315
326
|
vectors: stubProxy("vectors")
|
|
316
327
|
};
|
|
317
328
|
const mutationContext = {
|
|
318
329
|
auth,
|
|
319
330
|
db: database,
|
|
331
|
+
env: options?.env,
|
|
320
332
|
log: noopLog,
|
|
333
|
+
now: harnessNow,
|
|
321
334
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- lazy closure: `runInternal` is invoked only when a handler calls ctx.runMutation, after construction completes
|
|
322
335
|
runMutation: (reference, args) => runInternal("mutation", reference, mutationContext, args),
|
|
323
336
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- lazy closure: `runInternal` is invoked only when a handler calls ctx.runQuery, after construction completes
|
|
324
337
|
runQuery: (reference, args) => runInternal("query", reference, queryContext, args),
|
|
325
338
|
scheduler: fakeScheduler,
|
|
339
|
+
secrets: stubProxy("secrets"),
|
|
326
340
|
storage: stubProxy("storage"),
|
|
327
341
|
vectors: stubProxy("vectors"),
|
|
328
342
|
workflows: stubProxy("workflows")
|
|
@@ -331,9 +345,11 @@ const lunoraTest = (schema, options) => {
|
|
|
331
345
|
const actionContext = {
|
|
332
346
|
auth,
|
|
333
347
|
db: database,
|
|
348
|
+
env: options?.env,
|
|
334
349
|
// Use the injected fetch when provided; fall back to the v1 stub otherwise.
|
|
335
350
|
fetch: options?.fetch ?? stubProxy("fetch"),
|
|
336
351
|
log: noopLog,
|
|
352
|
+
now: harnessNow,
|
|
337
353
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- lazy closure: `runInternal` is invoked only when a handler calls ctx.runAction, after construction completes
|
|
338
354
|
runAction: (reference, args) => runInternal("action", reference, actionContext, args),
|
|
339
355
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- lazy closure: `runInternal` is invoked only when a handler calls ctx.runMutation, after construction completes
|
|
@@ -341,6 +357,7 @@ const lunoraTest = (schema, options) => {
|
|
|
341
357
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- lazy closure: `runInternal` is invoked only when a handler calls ctx.runQuery, after construction completes
|
|
342
358
|
runQuery: (reference, args) => runInternal("query", reference, queryContext, args),
|
|
343
359
|
scheduler: fakeScheduler,
|
|
360
|
+
secrets: stubProxy("secrets"),
|
|
344
361
|
storage: stubProxy("storage"),
|
|
345
362
|
vectors: stubProxy("vectors"),
|
|
346
363
|
workflows: stubProxy("workflows")
|
|
@@ -348,10 +365,11 @@ const lunoraTest = (schema, options) => {
|
|
|
348
365
|
const runRegistered = (expected, reference, context, args, allowInternal) => {
|
|
349
366
|
const kind = registeredFunctionKind(reference);
|
|
350
367
|
if (kind !== expected) {
|
|
351
|
-
throw new
|
|
368
|
+
throw new LunoraError("INTERNAL", `expected a registered ${expected}, received a ${kind ?? "non-function"} reference`);
|
|
352
369
|
}
|
|
353
370
|
if (!allowInternal && registeredFunctionVisibility(reference) === "internal") {
|
|
354
|
-
throw new
|
|
371
|
+
throw new LunoraError(
|
|
372
|
+
"INTERNAL",
|
|
355
373
|
`"${expected}" is an internal function — it is unreachable from the external RPC boundary in production. Call it through ctx.run${expected.charAt(0).toUpperCase()}${expected.slice(1)} from another function instead.`
|
|
356
374
|
);
|
|
357
375
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/testing",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.30",
|
|
4
4
|
"description": "Testing toolkit for Lunora: an in-memory harness for queries, mutations, and actions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "packages/testing"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"dist",
|
|
28
|
+
"./dist",
|
|
29
29
|
"README.md",
|
|
30
30
|
"LICENSE.md",
|
|
31
31
|
"__assets__"
|
|
@@ -46,9 +46,10 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/do": "1.0.0-alpha.
|
|
50
|
-
"@lunora/
|
|
51
|
-
"@lunora/
|
|
49
|
+
"@lunora/do": "1.0.0-alpha.23",
|
|
50
|
+
"@lunora/errors": "1.0.0-alpha.1",
|
|
51
|
+
"@lunora/mail": "1.0.0-alpha.8",
|
|
52
|
+
"@lunora/server": "1.0.0-alpha.16"
|
|
52
53
|
},
|
|
53
54
|
"engines": {
|
|
54
55
|
"node": "^22.15.0 || >=24.11.0"
|