@geekmidas/constructs 10.0.0-alpha.32 → 10.0.0-alpha.34

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.
@@ -237,7 +237,9 @@ export declare class NoTestManifest extends Error {
237
237
  }
238
238
  //#endregion
239
239
  //#region src/testing/featureTest.d.ts
240
- interface FeatureTestOptions<TBrowser extends Browser> {
240
+ /** How each database's test factory is built, keyed by its service name. */
241
+ type FactoryBuilders = Record<string, (db: Kysely<any>) => unknown>;
242
+ interface FeatureTestOptions<TBrowser extends Browser, TFactories extends FactoryBuilders = {}> {
241
243
  /**
242
244
  * What the app declares and the environment its test stage resolved.
243
245
  * Defaults to the manifest `gkm test` names in `GKM_TEST_MANIFEST`.
@@ -253,16 +255,50 @@ interface FeatureTestOptions<TBrowser extends Browser> {
253
255
  /** The browser each test gets — the generated one, with the app's clients. */
254
256
  browser?: new () => TBrowser;
255
257
  /**
256
- * The database a test receives as `db`, by construct id. Defaults to the
257
- * one the app's endpoints name with `.database()`.
258
+ * Each database's test factory — `createFactory` from
259
+ * `test/factories/<construct>.ts`, which the generated harness imports —
260
+ * keyed by the database's service name. Each is called once per test with
261
+ * that database's transaction, so what a test inserts through it is rolled
262
+ * back with everything else, and seen by the endpoints it calls.
258
263
  */
259
- database?: string;
264
+ factories?: TFactories;
260
265
  }
261
- interface FeatureContext<TBrowser extends Browser, DB> {
266
+ /** Each database's schema, keyed by its service name. */
267
+ type DatabaseSchemas = Record<string, unknown>;
268
+ /**
269
+ * The app's databases for this test, by service name — `db.get('database')`.
270
+ *
271
+ * Each is this test's transaction on that database: opened by whatever reaches
272
+ * it first — the test, a factory, an endpoint — and the same one for all of
273
+ * them, rolled back after the test.
274
+ */
275
+ interface TestDatabases<TDatabases extends DatabaseSchemas> {
276
+ get<K extends keyof TDatabases & string>(name: K): Promise<Kysely<TDatabases[K]>>;
277
+ }
278
+ /**
279
+ * Each of the app's databases' test factory, by service name —
280
+ * `await factories.get('database')`. Built on first use, on that database's
281
+ * transaction for this test — the one `db.get` returns — and the same factory
282
+ * for the rest of the test.
283
+ */
284
+ interface TestFactories<TFactories extends FactoryBuilders> {
285
+ get<K extends keyof TFactories & string>(name: K): Promise<ReturnType<TFactories[K]>>;
286
+ }
287
+ interface FeatureContext<TBrowser extends Browser, TDatabases extends DatabaseSchemas = {}, TFactories extends FactoryBuilders = {}> {
262
288
  /** This test's browser, already the global `fetch`. */
263
289
  browser: TBrowser;
264
- /** The app database's transaction for this test, rolled back after it. */
265
- db: Kysely<DB>;
290
+ /**
291
+ * The app's own databases — not a tenant an auth server owns, not a
292
+ * reader — by service name: `await db.get('database')`. The same
293
+ * transaction the test's endpoints and factories use.
294
+ */
295
+ db: TestDatabases<TDatabases>;
296
+ /**
297
+ * Each of the app's databases' test factory, by service name —
298
+ * `await factories.get('database')` — on the same transaction `db.get`
299
+ * returns.
300
+ */
301
+ factories: TestFactories<TFactories>;
266
302
  /** The mail sent to an address during this test, read from Mailpit. */
267
303
  mailbox: (address: string) => Mailbox;
268
304
  /**
@@ -291,13 +327,44 @@ interface PublishedMessage {
291
327
  type: string;
292
328
  payload: unknown;
293
329
  }
294
- type FeatureFn<TBrowser extends Browser, DB> = (context: FeatureContext<TBrowser, DB>) => unknown;
295
- interface FeatureIt<TBrowser extends Browser, DB> {
296
- (name: string, fn: FeatureFn<TBrowser, DB>, timeout?: number): void;
297
- only(name: string, fn: FeatureFn<TBrowser, DB>, timeout?: number): void;
298
- skip(name: string, fn: FeatureFn<TBrowser, DB>, timeout?: number): void;
330
+ type FeatureFn<TBrowser extends Browser, TDatabases extends DatabaseSchemas, TFactories extends FactoryBuilders> = (context: FeatureContext<TBrowser, TDatabases, TFactories>) => unknown;
331
+ interface FeatureIt<TBrowser extends Browser, TDatabases extends DatabaseSchemas = {}, TFactories extends FactoryBuilders = {}> {
332
+ (name: string, fn: FeatureFn<TBrowser, TDatabases, TFactories>, timeout?: number): void;
333
+ only(name: string, fn: FeatureFn<TBrowser, TDatabases, TFactories>, timeout?: number): void;
334
+ skip(name: string, fn: FeatureFn<TBrowser, TDatabases, TFactories>, timeout?: number): void;
335
+ }
336
+ export declare function featureTest<TBrowser extends Browser = Browser, TDatabases extends DatabaseSchemas = {}, TFactories extends FactoryBuilders = {}>(options?: FeatureTestOptions<TBrowser, TFactories>): FeatureIt<TBrowser, TDatabases, TFactories>;
337
+ /** What `signInWithMagicLink` uses of a better-auth client. */
338
+ interface MagicLinkAuthClient<TSession> {
339
+ signIn: {
340
+ magicLink(input: {
341
+ email: string;
342
+ }): Promise<{
343
+ error?: {
344
+ message?: string;
345
+ } | null;
346
+ }>;
347
+ };
348
+ getSession(): Promise<{
349
+ data: TSession | null;
350
+ }>;
351
+ }
352
+ /**
353
+ * Sign a test's browser in the way a person does: ask the auth server for a
354
+ * magic link, open the email it sent, follow the link.
355
+ *
356
+ * What the generated `browser.signIn(email)` calls. The address's mail is
357
+ * cleared first — so the link is this request's, not one an earlier test left
358
+ * — and cleared again after the test, like any address a test reads. Returns
359
+ * the session the auth server then reports, as better-auth hands it back.
360
+ */
361
+ export declare function signInWithMagicLink<TSession>(browser: Browser, auth: MagicLinkAuthClient<TSession>, email: string): Promise<TSession>;
362
+ /** `browser.signIn` finished without a session. */
363
+ export declare class SignInFailed extends Error {
364
+ readonly email: string;
365
+ readonly reason: string;
366
+ constructor(email: string, reason: string);
299
367
  }
300
- export declare function featureTest<TBrowser extends Browser = Browser, DB = unknown>(options?: FeatureTestOptions<TBrowser>): FeatureIt<TBrowser, DB>;
301
368
  /** A request reached an in-process server without a test to belong to. */
302
369
  export declare class UnknownTestContext extends Error {
303
370
  readonly url: string;
@@ -308,12 +375,18 @@ export declare class UnknownTestContext extends Error {
308
375
  export declare class NoInbox extends Error {
309
376
  constructor();
310
377
  }
311
- /** `featureTest({ database })` named a database the app does not declare. */
378
+ /** `db.get(name)` named no database the test is handed. */
312
379
  export declare class UnknownDatabase extends Error {
313
- readonly id: string;
314
- readonly declared: string[];
315
- constructor(id: string, declared: string[]);
380
+ readonly database: string;
381
+ readonly known: readonly string[];
382
+ constructor(database: string, known: readonly string[]);
383
+ }
384
+ /** A factory keyed by a service name no database the app declares has. */
385
+ export declare class UnknownFactory extends Error {
386
+ readonly factory: string;
387
+ readonly known: readonly string[];
388
+ constructor(factory: string, known: readonly string[]);
316
389
  }
317
390
  //#endregion
318
- export { type CreateMswHandlersOptions, type CreateMswHandlersResult, type DatabaseOf, type FeatureContext, type FeatureIt, type FeatureTestOptions, type RegisterContextOptions, TEST_CONTEXT_HEADER, type TestManifest, type TestManifestSource };
391
+ export { type CreateMswHandlersOptions, type CreateMswHandlersResult, type DatabaseOf, type DatabaseSchemas, type FactoryBuilders, type FeatureContext, type FeatureIt, type FeatureTestOptions, type MagicLinkAuthClient, type RegisterContextOptions, TEST_CONTEXT_HEADER, type TestDatabases, type TestFactories, type TestManifest, type TestManifestSource };
319
392
  //# sourceMappingURL=testing.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.mts","names":["TestBrowser"],"sources":["../../src/endpoints/MswEndpointAdaptor.ts","../../src/endpoints/TestEndpointAdaptor.ts","../../src/functions/TestFunctionAdaptor.ts","../../src/queue/TestQueueAdaptor.ts","../../src/subscribers/TestSubscriberAdaptor.ts","../../src/testing/manifest.ts","../../src/testing/featureTest.ts"],"mappings":"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"}
1
+ {"version":3,"file":"testing.d.mts","names":["TestBrowser"],"sources":["../../src/endpoints/MswEndpointAdaptor.ts","../../src/endpoints/TestEndpointAdaptor.ts","../../src/functions/TestFunctionAdaptor.ts","../../src/queue/TestQueueAdaptor.ts","../../src/subscribers/TestSubscriberAdaptor.ts","../../src/testing/manifest.ts","../../src/testing/featureTest.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAyDiB;;EAEhB;;;;;;;UAQgB;;EAEhB,WAAW;;EAEX;;EAEA,YAAY;;EAEZ;;;;;UAiBgB;;EAEhB,UAAU;;EAEV,kBAAkB,YAAY,SAAS;;;;;;;;;;;;;;;;;wBAkBxB,kBACf,WAAW,0CACX,SAAS,2BACP;;;KC9ES,iBAAiB;EAC5B,MAAM;EACN;EACA,SAAS;;qBAwCG,oBACZ,uBACA,gBAAgB,YAChB,eAAe,sBACf,mBAAmB,0CACnB,kBAAkB,gBAClB,gBAAgB,SAAS,QACzB,oBACA,wBAAwB,6CACxB,oDACA,sBAAsB,sCACtB,kDACA,qBAAqB,mCAAmC,kCAIxD,uBACA;mBAyCkB;UAgBT;SAvDF,0BACN,uBACA,gBAAgB,YAChB,eAAe,sBACf,mBAAmB,0CACnB,kBAAkB,gBAClB,gBAAgB,SAAS,QACzB,oBACA,wBAAwB,6CACxB,oDACA,sBAAsB,sCACtB,kDACA,qBAAqB,mCAAmC,kCAIxD,uBACA,8CAEA,WAAW,SACV,QACA,SACA,QACA,YACA,WACA,SACA,UACA,iBACA,4BACA,eACA,0BACA,cACA,WACA;EAKF,YACkB,UAAU,SAC1B,QACA,SACA,QACA,YACA,WACA,SACA,UACA,iBACA,4BACA,eACA,0BACA,cACA,WACA,uBAEO,mBAAkB;EAKrB,YACL,KAAK,mBACJ,QACA,WACA,iBACA,4BACA,eACA,0BACA,WACA,wBAEC,QAAQ,iBAAiB,oBAAoB;EAmM1C,QACL,KAAK,mBACJ,QACA,WACA,iBACA,4BACA,eACA,0BACA,WACA,wBAEC,QAAQ,oBAAoB;;;;;KAS3B,wBACJ,sBAAsB,wCACnB;;EAID,gBAAgB;;;;;KAMd,oBAAoB,yBAAyB;;EAI/C,UAAU;;KAGD,mBACX,eAAe,sBACf,kBAAkB,gBAClB,wBAAwB,6CACxB,oDACA,sBAAsB,sCACtB,mDACA,uBACA;EAEA,UAAU,cAAc;EACxB,SAAS;EACT,YAAY,QAAQ,4BAA4B;IAC7C,8BAA8B,UACjC,wBAAwB,iBACxB,oBAAoB;;;qBChZR,oBACZ,eAAe,kDACf,mBAAmB,0CACnB,kBAAkB,gBAClB,gBAAgB,SAAS,QACzB,wBAAwB,6CACxB,oDACA,sBAAsB,sCACtB,kDACA,uBACA,8CACA,qBAAqB,mCAAmC;mBAwCtC;UAcT;SAjDF,2BACN,eAAe,kDACf,mBAAmB,0CACnB,kBAAkB,gBAClB,gBAAgB,SAAS,QACzB,wBAAwB,6CACxB,oDACA,sBAAsB,sCACtB,kDACA,uBACA,8CACA,qBAAqB,mCAAmC,kCAKxD,KAAK,SACJ,QACA,WACA,SACA,YACA,iBACA,4BACA,eACA,0BACA,WACA,sBACA;EAOF,YACkB,IAAI,SACpB,QACA,WACA,SACA,YACA,iBACA,4BACA,eACA,0BACA,WACA,sBACA,oBAGO,mBAAkB;EAKrB,OACL,KAAK,oBACJ,QACA,WACA,iBACA,4BACA,WACA,gBAEC,QAAQ,oBAAoB;;KAoGpB,oBACX,eAAe,kDACf,kBAAkB,gBAClB,wBAAwB,6CACxB,oDACA,uBACA,qBAAqB,mCAAmC;EAKxD,OAAO,8BAA8B;EACrC,UAAU,cAAc;EACxB,YAAY,QAAQ,4BAA4B;EAChD,KAAK;EACL,UAAU,QAAQ;IACf;EAAgC,OAAO;;;;;;;;;;;;;qBCvM9B,iBACZ,+BACA,iBAAiB,mBAAmB,kBACpC,kBAAkB,gBAClB,gBAAgB,SAAS;mBAOP;UACT;SANF;EAIP,YACkB,OAAO,MAAM,OAAO,UAAU,WAAW,UAClD,mBAAkB;EAGrB,OACL,SAAS,iBAAiB,UAAU,aAClC;;KAsBQ,iBACX,iBAAiB,mBAAmB,kBACpC,kBAAkB;EAElB,UAAU,oBAAoB;EAC9B,WAAW,cAAc;;;;KC/CrB,qBACJ,mBAAmB,iCACnB,6BACG,mBAAmB,sBACpB,QAAQ,wBAAwB;EAAe,MAAM;;qBAG3C,sBACZ,kBAAkB,gBAClB,gBAAgB,SAAS,QACzB,kBAAkB,0CAClB,wBAAwB,6CACxB,oDACA,0BACC,wBAAwB,6BAA6B,wBAAwB;mBAO5D;UAQT;SAbF;EAIP,YACkB,YAAY,WAC5B,WACA,SACA,WACA,iBACA,4BACA,oBAEO,mBAAkB;EAGrB,OACL,SAAS,sBACR,iBACA,mBACA,aAEC,QAAQ,oBAAoB;UA4DvB;;KAaG,sBACX,wBAAwB,6CACxB,sCACA,kBAAkB;EAElB,QAAQ,qBAAqB,iBAAiB;EAC9C,WAAW,cAAc;;;;;;;;;;;;;;;;UClHT;;EAEhB;;EAEA;;UAGgB;;EAEhB;;EAEA,YAAY;IAAiB;IAAc,QAAQ;;;EAEnD;IAAa;IAAiB,QAAQ;;;;;;EAKtC,KAAK;;;qBAIO;;;;;wBAMG,iBAAiB,gBAAgB,MAAM;;qBAsC1C,uBAAuB;EACnC;;;;;KC3BW,kBAAkB,gBAAgB,IAAI;UAEjC,mBAChB,iBAAiBA,SACjB,mBAAmB;;;;;EAMnB,WAAW;;;;;;;EAOX,UAAU,SAAS,eAAe;;EAElC,oBAAoB;;;;;;;;EAQpB,YAAY;;;KAID,kBAAkB;;;;;;;;UASb,cAAc,mBAAmB;EACjD,IAAI,gBAAgB,qBACnB,MAAM,IACJ,QAAQ,OAAO,WAAW;;;;;;;;UASb,cAAc,mBAAmB;EACjD,IAAI,gBAAgB,qBACnB,MAAM,IACJ,QAAQ,WAAW,WAAW;;UAGjB,eAChB,iBAAiBA,SACjB,mBAAmB,sBACnB,mBAAmB;;EAGnB,SAAS;;;;;;EAMT,IAAI,cAAc;;;;;;EAMlB,WAAW,cAAc;;EAEzB,UAAU,oBAAoB;;;;;;EAM9B,YAAY;IAAW;QAAiB;;;;;;EAMxC,aAAa,UAAU,0CACtB,YAAY,MACR,oBAAoB;;EAEzB,QAAQ,UAAU,2BAA2B,OAAO,MAAM,eAAe;;KAGrE,oBAAoB,KACxB,UAAU,iBACH,iBACA,eACA,iBACA,uBACA,kCACA,qBAEJ,sBACA,WACA,SACA,WACA,iBACA,4BACA;KAIC,eAAe,KACnB,UAAU,YAAY,aAAa,gBAAgB,iBAAiB,WACjE,iBAAiB,OAAO,UAAU,WAAW;;KAIrC,WAAW,KACtB,UAAU,qBAAqB,WAAW;;UAG1B;EAChB;EACA;;KAGI,UACJ,iBAAiBA,SACjB,mBAAmB,iBACnB,mBAAmB,oBACf,SAAS,eAAe,UAAU,YAAY;UAElC,UAChB,iBAAiBA,SACjB,mBAAmB,sBACnB,mBAAmB;GAGlB,cACA,IAAI,UAAU,UAAU,YAAY,aACpC;EAED,KACC,cACA,IAAI,UAAU,UAAU,YAAY,aACpC;EAED,KACC,cACA,IAAI,UAAU,UAAU,YAAY,aACpC;;wBAsCc,YACf,iBAAiBA,UAAcA,SAC/B,mBAAmB,sBACnB,mBAAmB,sBAEnB,UAAS,mBAAmB,UAAU,cACpC,UAAU,UAAU,YAAY;;UAkZlB,oBAAoB;EACpC;IACC,UAAU;MACT;QACG;MAAU;QAAU;;;;EAEzB,cAAc;IAAU,MAAM;;;;;;;;;;;;wBAYT,oBAAoB,UACzC,SAASA,SACT,MAAM,oBAAoB,WAC1B,gBACE,QAAQ;;qBAgCE,qBAAqB;WAEvB;WACA;EAFV,YACU,eACA;;;qBAQE,2BAA2B;WAE7B;WACA;EAFV,YACU,aACA;;;qBAcE,gBAAgB;EAC5B;;;qBAWY,wBAAwB;WAE1B;WACA;EAFV,YACU,kBACA;;;qBAeE,uBAAuB;WAEzB;WACA;EAFV,YACU,iBACA"}
@@ -503,10 +503,14 @@ function featureTest(options = {}) {
503
503
  const manifest = options.manifest ?? loadTestManifest();
504
504
  const BrowserClass = options.browser ?? Browser;
505
505
  let app;
506
+ let factoryDatabases = [];
507
+ let owned = [];
506
508
  let network;
507
509
  let restoreFetch = () => {};
508
510
  beforeAll(async () => {
509
- app = await load(manifest, options.database, options.modules ?? {});
511
+ app = await load(manifest, options.modules ?? {});
512
+ owned = ownDatabases(app, manifest);
513
+ factoryDatabases = databasesFor(options.factories ?? {}, owned);
510
514
  for (const database of app.databases) bindToTests(database);
511
515
  network = setupServer(...surfaceHandlers(app.endpoints, manifest.env), ...app.auths.map((auth) => authHandler(auth, manifest.env, app.envParser)));
512
516
  network.listen({ onUnhandledRequest(request, print) {
@@ -529,6 +533,11 @@ function featureTest(options = {}) {
529
533
  addresses: /* @__PURE__ */ new Set(),
530
534
  published: /* @__PURE__ */ new Map()
531
535
  };
536
+ const readMail = app.readMail;
537
+ if (readMail) state.mailbox = (address) => {
538
+ state.addresses.add(address);
539
+ return readMail(address);
540
+ };
532
541
  contexts.set(id, state);
533
542
  await state.discovery.register(recorders(app.channels, state));
534
543
  try {
@@ -536,13 +545,29 @@ function featureTest(options = {}) {
536
545
  const browser = new BrowserClass();
537
546
  const restore = browser.install();
538
547
  try {
548
+ const transaction = (database) => state.transactions.get(database.id, urlOf(database, app.envParser), database.clientConfig);
549
+ const db = { get: (name) => {
550
+ const database = owned.find((candidate) => candidate.service.serviceName === name);
551
+ if (!database) throw new UnknownDatabase(name, owned.map((candidate) => candidate.service.serviceName));
552
+ return transaction(database);
553
+ } };
554
+ const built = /* @__PURE__ */ new Map();
539
555
  await fn({
540
556
  browser,
541
- db: app.database ? await state.transactions.get(app.database.id, urlOf(app.database, app.envParser), app.database.clientConfig) : void 0,
557
+ db,
558
+ factories: { get: (name) => {
559
+ const database = factoryDatabases.find(([candidate]) => candidate === name)?.[1];
560
+ if (!database) throw new UnknownFactory(name, factoryDatabases.map(([candidate]) => candidate));
561
+ let factory = built.get(name);
562
+ if (!factory) {
563
+ factory = transaction(database).then(options.factories[name]);
564
+ built.set(name, factory);
565
+ }
566
+ return factory;
567
+ } },
542
568
  mailbox: (address) => {
543
- if (!app.readMail) throw new NoInbox();
544
- state.addresses.add(address);
545
- return app.readMail(address);
569
+ if (!state.mailbox) throw new NoInbox();
570
+ return state.mailbox(address);
546
571
  },
547
572
  published: (channel) => [...state.published.get(channel.id) ?? []],
548
573
  subscriber: (subscriber) => new TestSubscriberAdaptor(subscriber, state.discovery),
@@ -568,7 +593,7 @@ function featureTest(options = {}) {
568
593
  * The same modules the app runs, so a construct here *is* the app's construct —
569
594
  * the one its endpoints were built from — never a copy declared again.
570
595
  */
571
- async function load(manifest, database, modules) {
596
+ async function load(manifest, modules) {
572
597
  const envParser = new EnvironmentParser({ ...manifest.env });
573
598
  const imported = async ({ file, export: name }) => (modules[file] ?? await import(file))[name];
574
599
  const constructs = await Promise.all(Object.values(manifest.constructs).map(({ source }) => imported(source)));
@@ -586,7 +611,6 @@ async function load(manifest, database, modules) {
586
611
  auths,
587
612
  emails,
588
613
  channels,
589
- database: databaseOf(databases, endpoints, database),
590
614
  ...inbox ? { readMail: createMailbox({ inbox }) } : {}
591
615
  };
592
616
  }
@@ -609,18 +633,22 @@ function recorders(channels, state) {
609
633
  });
610
634
  }
611
635
  /**
612
- * The database a test sees as `db`: the one named, or else the one the
613
- * endpoints were given with `.database()` — found by the service they hold,
614
- * which is the construct's own.
636
+ * The databases a test is handed: the app's own. A schema tenant an auth server
637
+ * owns is reached through that server, as the app reaches it, and a reader is
638
+ * the same database through a read-only role — neither is another database to
639
+ * test against.
615
640
  */
616
- function databaseOf(databases, endpoints, named) {
617
- if (named) {
618
- const found = databases.find(({ id }) => id === named);
619
- if (!found) throw new UnknownDatabase(named, databases.map(({ id }) => id));
620
- return found;
621
- }
622
- const services = new Set(endpoints.map((e) => e.databaseService).filter(Boolean));
623
- return databases.find((database) => services.has(database.service));
641
+ function ownDatabases(app, manifest) {
642
+ const owners = new Set(app.auths.map((auth) => auth.databaseId));
643
+ return app.databases.filter((database) => !owners.has(database.id) && manifest.constructs[database.id]?.kind !== "database-reader");
644
+ }
645
+ /** Each factory's database, found by the service name it is keyed by. */
646
+ function databasesFor(factories, databases) {
647
+ return Object.keys(factories).map((name) => {
648
+ const database = databases.find((candidate) => candidate.service.serviceName === name);
649
+ if (!database) throw new UnknownFactory(name, databases.map((candidate) => candidate.service.serviceName));
650
+ return [name, database];
651
+ });
624
652
  }
625
653
  /**
626
654
  * Make a database resolve, inside a test, to that test's transaction.
@@ -698,6 +726,42 @@ function trim(url) {
698
726
  function isLocal(url) {
699
727
  return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname.endsWith(".localhost");
700
728
  }
729
+ /**
730
+ * Sign a test's browser in the way a person does: ask the auth server for a
731
+ * magic link, open the email it sent, follow the link.
732
+ *
733
+ * What the generated `browser.signIn(email)` calls. The address's mail is
734
+ * cleared first — so the link is this request's, not one an earlier test left
735
+ * — and cleared again after the test, like any address a test reads. Returns
736
+ * the session the auth server then reports, as better-auth hands it back.
737
+ */
738
+ async function signInWithMagicLink(browser, auth, email) {
739
+ const context = currentTestContext();
740
+ const state = context && contexts.get(context.id);
741
+ if (!state) throw new UnknownTestContext("signIn", context?.id);
742
+ if (!state.mailbox) throw new NoInbox();
743
+ const inbox = state.mailbox(email);
744
+ await inbox.clear();
745
+ const requested = await auth.signIn.magicLink({ email });
746
+ if (requested.error) throw new SignInFailed(email, `the auth server refused the magic link: ${requested.error.message ?? "no reason given"}`);
747
+ const mail = await inbox.last();
748
+ if (!mail.link) throw new SignInFailed(email, "its email held no link");
749
+ await browser.visit(mail.link);
750
+ const { data } = await auth.getSession();
751
+ if (!data) throw new SignInFailed(email, "the auth server reported no session after the link was opened");
752
+ return data;
753
+ }
754
+ /** `browser.signIn` finished without a session. */
755
+ var SignInFailed = class extends Error {
756
+ email;
757
+ reason;
758
+ constructor(email, reason) {
759
+ super(`Signing ${email} in failed: ${reason}.`);
760
+ this.email = email;
761
+ this.reason = reason;
762
+ this.name = "SignInFailed";
763
+ }
764
+ };
701
765
  /** A request reached an in-process server without a test to belong to. */
702
766
  var UnknownTestContext = class extends Error {
703
767
  url;
@@ -716,18 +780,29 @@ var NoInbox = class extends Error {
716
780
  this.name = "NoInbox";
717
781
  }
718
782
  };
719
- /** `featureTest({ database })` named a database the app does not declare. */
783
+ /** `db.get(name)` named no database the test is handed. */
720
784
  var UnknownDatabase = class extends Error {
721
- id;
722
- declared;
723
- constructor(id, declared) {
724
- super(`featureTest was asked for the database '${id}', and the app declares ${declared.length ? declared.map((d) => `'${d}'`).join(", ") : "none"}.`);
725
- this.id = id;
726
- this.declared = declared;
785
+ database;
786
+ known;
787
+ constructor(database, known) {
788
+ super(`db.get('${database}') names no database of this app's. ` + (known.length > 0 ? `Its databases: ${known.join(", ")}. ` : "It declares no database of its own. ") + "A tenant an auth server owns is reached through that server, and a reader through the database it reads.");
789
+ this.database = database;
790
+ this.known = known;
727
791
  this.name = "UnknownDatabase";
728
792
  }
729
793
  };
794
+ /** A factory keyed by a service name no database the app declares has. */
795
+ var UnknownFactory = class extends Error {
796
+ factory;
797
+ known;
798
+ constructor(factory, known) {
799
+ super(`There is a test factory for '${factory}', and no database this app declares is called that. ` + (known.length > 0 ? `Its databases: ${known.join(", ")}. ` : "It declares no database. ") + `Name the file after the database construct: test/factories/<construct>.ts.`);
800
+ this.factory = factory;
801
+ this.known = known;
802
+ this.name = "UnknownFactory";
803
+ }
804
+ };
730
805
  //#endregion
731
- export { NoInbox, NoTestManifest, TEST_CONTEXT_HEADER, TEST_MANIFEST_ENV, TestEndpointAdaptor, TestFunctionAdaptor, TestQueueAdaptor, TestSubscriberAdaptor, UnknownDatabase, UnknownTestContext, createMswHandlers, featureTest, loadTestManifest };
806
+ export { NoInbox, NoTestManifest, SignInFailed, TEST_CONTEXT_HEADER, TEST_MANIFEST_ENV, TestEndpointAdaptor, TestFunctionAdaptor, TestQueueAdaptor, TestSubscriberAdaptor, UnknownDatabase, UnknownFactory, UnknownTestContext, createMswHandlers, featureTest, loadTestManifest, signInWithMagicLink };
732
807
 
733
808
  //# sourceMappingURL=testing.mjs.map