@dunx/testing 1.0.1 → 1.2.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/index.js CHANGED
@@ -128,12 +128,13 @@ var warnAboutGlobals = (app, modules) => {
128
128
  console.warn(`createTestServer: ${orphaned.map((ctor) => ctor.name).join(", ")} ` + "implement Middleware and are in the graph under test, but no `middleware` " + "was supplied. If they are global in main.ts then this fixture is not the " + "application: no global guard runs and `onError` is the default mapper. " + "Export one httpOptions(config) and give it to both, or pass " + "`middleware: []` to say the omission is deliberate.");
129
129
  };
130
130
  var createTestServer = async (options) => {
131
- const { modules, overrides, prefix, requestLogging, ...http } = options;
131
+ const { modules, overrides, prefix, requestLogging, bootLogging, ...http } = options;
132
132
  const root = testRoot(modules);
133
133
  const app = await HttpFactory.create(root, {
134
134
  ...http,
135
135
  ...appOptions(overrides),
136
136
  requestLogging: requestLogging ?? false,
137
+ bootLogging: bootLogging ?? false,
137
138
  port: 0
138
139
  });
139
140
  if (http.middleware === undefined)
@@ -154,5 +155,5 @@ export {
154
155
  RecordingLogger
155
156
  };
156
157
 
157
- //# debugId=592E49A30BEDF3D864756E2164756E21
158
+ //# debugId=1573B94CB0D606A564756E2164756E21
158
159
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -5,9 +5,9 @@
5
5
  "import {\n AppFactory,\n type App,\n type AppOptions,\n type DynamicModule,\n type ModuleRef,\n type Registration,\n} from '@dunx/core';\n\n/**\n * The synthetic root. A named class rather than an object literal for the same\n * reason `@dunx/http`'s `HttpModule` is one: it is what a duplicate-binding error\n * would name if the harness itself ever bound anything.\n */\nclass TestModule {}\n\nexport interface TestAppOptions extends AppOptions {\n /**\n * The graph under test. A single module, or several - they become the `imports`\n * of one synthetic root, so no fixture module has to be written by hand.\n */\n readonly modules: ModuleRef | readonly ModuleRef[];\n}\n\nconst isList = (\n modules: ModuleRef | readonly ModuleRef[],\n): modules is readonly ModuleRef[] => Array.isArray(modules);\n\n/**\n * The root `createTestApp` boots. Exported for the case the harness deliberately\n * does not cover: configuring an `HttpApp` before `listen()` (`enableCors`, `use`,\n * `set`), which means calling `HttpFactory.create(testRoot(modules), …)` directly.\n */\nexport const testRoot = (\n modules: ModuleRef | readonly ModuleRef[],\n): DynamicModule => ({\n module: TestModule,\n imports: isList(modules) ? modules : [modules],\n});\n\n/** `exactOptionalPropertyTypes` separates an absent key from an undefined one. */\nexport const appOptions = (\n overrides: readonly Registration[] | undefined,\n): AppOptions => (overrides ? { overrides } : {});\n\n/**\n * The container the app under test would have, with the bindings named in\n * `overrides` **replaced in place**.\n *\n * ```ts\n * const app = await createTestApp({\n * modules: [UsersModule],\n * overrides: [provide(Clock, { useValue: new FixedClock('2026-01-01') })],\n * });\n * ```\n *\n * Replacement, not addition: the discarded provider is never instantiated, so an\n * async `useFactory` that would open the real database never runs. An override\n * naming a token nobody binds throws instead of passing silently.\n */\nexport const createTestApp = (options: TestAppOptions): Promise<App> =>\n AppFactory.create(testRoot(options.modules), appOptions(options.overrides));\n",
6
6
  "export interface JsonInit extends RequestInit {\n /**\n * Serialized as the request body, with `content-type: application/json` set\n * unless `headers` already carries one. Covers every verb, so there is no\n * `post()`/`put()`/`patch()` triple here. Takes precedence over `body`.\n */\n readonly json?: unknown;\n}\n\nexport interface JsonResponse<T> {\n readonly status: number;\n readonly headers: Headers;\n readonly body: T;\n}\n\nexport interface TestClient {\n /** The server's base URL, as `listen()` returned it. */\n readonly url: string;\n /** The raw `Response` - for bytes, HTML, or asserting on a header. */\n request(path?: string, init?: JsonInit): Promise<Response>;\n /** Status, headers and parsed body in one await, which is the common assertion. */\n json<T = unknown>(path?: string, init?: JsonInit): Promise<JsonResponse<T>>;\n}\n\nconst target = (base: string, path: string): URL => new URL(path, base);\n\nconst withJson = (init: JsonInit): RequestInit => {\n const { json, ...rest } = init;\n if (json === undefined) return rest;\n\n const headers = new Headers(init.headers);\n if (!headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n return { ...rest, headers, body: JSON.stringify(json) };\n};\n\n/**\n * `fetch` against one base URL, plus the JSON round-trip every suite otherwise\n * rewrites. Deliberately not an assertion DSL: it returns values that `expect`\n * already reads well, so failures point at the assertion rather than at a matcher\n * this package would have to define.\n *\n * `createTestServer` returns one of these already bound to the server it started;\n * this is here for an app booted some other way.\n */\nexport const testClient = (url: string): TestClient => ({\n url,\n request: (path = '', init: JsonInit = {}) =>\n fetch(target(url, path), withJson(init)),\n json: async <T>(path = '', init: JsonInit = {}): Promise<JsonResponse<T>> => {\n const response = await fetch(target(url, path), withJson(init));\n // Read as text first: a route that answered 204, HTML or a plain-text error\n // would otherwise fail with `JSON.parse`'s message and none of the context\n // needed to see why.\n const text = await response.text();\n try {\n return {\n status: response.status,\n headers: response.headers,\n body: JSON.parse(text) as T,\n };\n } catch {\n const body =\n text === ''\n ? 'an empty body'\n : `a ${response.headers.get('content-type') ?? 'typeless'} body:\\n\\n` +\n text.slice(0, 300);\n throw new Error(\n `${init.method ?? 'GET'} ${target(url, path).pathname} answered ` +\n `${response.status} with ${body}\\n\\nThat is not JSON - use request() ` +\n 'for a response that is not.',\n );\n }\n },\n});\n",
7
7
  "import { Logger, LogLevel, type LogMessage } from '@dunx/core';\n\nexport interface RecordedLog {\n readonly level: LogLevel;\n readonly message: LogMessage;\n readonly params: readonly unknown[];\n}\n\n/**\n * A {@link Logger} that keeps entries instead of writing them, so a suite can\n * assert on what was logged and stays quiet when it does not care.\n *\n * It is here because the contract is seven levels of three overloads each: every\n * suite that wants a silent logger would otherwise hand-write the same thirty\n * lines. Nothing is interpreted - no level filtering, no error promotion, no\n * merging of extras - because those are the backing logger's behaviour and\n * asserting against a reimplementation of them would prove nothing.\n *\n * ```ts\n * const logger = new RecordingLogger();\n * await createTestApp({\n * modules: [UsersModule],\n * overrides: [provide(Logger, { useValue: logger })],\n * });\n * expect(logger.at(LogLevel.WARN)).toHaveLength(0);\n * ```\n */\nexport class RecordingLogger extends Logger {\n /** Everything is recorded, so a suite can assert on a `verbose` call too. */\n readonly logLevel: LogLevel = LogLevel.VERBOSE;\n readonly entries: RecordedLog[] = [];\n\n verbose(message: LogMessage, ...params: unknown[]): void {\n this.#record(LogLevel.VERBOSE, message, params);\n }\n\n debug(message: LogMessage, ...params: unknown[]): void {\n this.#record(LogLevel.DEBUG, message, params);\n }\n\n info(message: LogMessage, ...params: unknown[]): void {\n this.#record(LogLevel.INFO, message, params);\n }\n\n /** @deprecated Use {@link RecordingLogger.info}. Recorded as `info` either way. */\n log(message: LogMessage, ...params: unknown[]): void {\n this.#record(LogLevel.INFO, message, params);\n }\n\n warn(message: LogMessage, ...params: unknown[]): void {\n this.#record(LogLevel.WARN, message, params);\n }\n\n error(message: LogMessage, ...params: unknown[]): void {\n this.#record(LogLevel.ERROR, message, params);\n }\n\n fatal(message: LogMessage, ...params: unknown[]): void {\n this.#record(LogLevel.FATAL, message, params);\n }\n\n at(level: LogLevel): readonly RecordedLog[] {\n return this.entries.filter((entry) => entry.level === level);\n }\n\n clear(): void {\n this.entries.length = 0;\n }\n\n #record(\n level: LogLevel,\n message: LogMessage,\n params: readonly unknown[],\n ): void {\n this.entries.push({ level, message, params });\n }\n}\n",
8
- "import {\n collectModules,\n readControllers,\n type Ctor,\n type ResolvedModule,\n} from '@dunx/core';\nimport {\n discoverRoutes,\n HttpFactory,\n type HttpApp,\n type HttpOptions,\n type Middleware,\n} from '@dunx/http';\nimport { appOptions, testRoot, type TestAppOptions } from './app.js';\nimport { testClient, type TestClient } from './client.js';\n\nexport interface TestServerOptions\n extends TestAppOptions, Omit<HttpOptions, 'port' | 'overrides'> {\n /**\n * `setGlobalPrefix`, applied before `listen()` so the client's URLs carry it.\n *\n * Explicitly `| undefined`, unlike the rest of the options: a suite that runs\n * the same fixture prefixed and unprefixed passes a variable here, and under\n * `exactOptionalPropertyTypes` that is otherwise a conditional spread. \"No\n * prefix\" and \"absent\" mean the same thing, so nothing is lost by allowing it.\n */\n readonly prefix?: string | undefined;\n}\n\nexport interface TestServer extends TestClient {\n readonly app: HttpApp;\n /** `app.shutdown()` - stops the server, then tears the container down. */\n close(): Promise<void>;\n}\n\nconst middlewareShaped = (ctor: Ctor<unknown>): boolean =>\n typeof (ctor.prototype as { handle?: unknown } | undefined)?.handle ===\n 'function';\n\n/** Every class provider in the graph that implements `Middleware`. */\nconst declaredMiddleware = (\n modules: readonly ResolvedModule[],\n): readonly Ctor<unknown>[] => {\n const found: Ctor<unknown>[] = [];\n for (const module of modules) {\n for (const entry of module.options.providers ?? []) {\n const ctor =\n typeof entry === 'function'\n ? entry\n : entry.provider.kind === 'class'\n ? entry.provider.ctor\n : undefined;\n if (ctor !== undefined && middlewareShaped(ctor)) found.push(ctor);\n }\n }\n return found;\n};\n\n/**\n * The guards `@UseGuards` already puts in the route table. They are not global, so\n * omitting `middleware` costs them nothing and warning about them would be noise.\n */\nconst scopedGuards = (\n app: HttpApp,\n modules: readonly ResolvedModule[],\n): ReadonlySet<Ctor<Middleware>> => {\n const applied = new Set<Ctor<Middleware>>();\n for (const module of modules) {\n for (const controller of readControllers(module)) {\n for (const route of discoverRoutes(app.get(controller) as object)) {\n for (const guard of route.guards ?? []) applied.add(guard);\n }\n }\n }\n return applied;\n};\n\n/**\n * The one silent way this harness can lie: `middleware` and `onError` default\n * away, so a suite that forgets them boots a server with no global guards and the\n * default error mapper, which still answers 200 where the application answers 401.\n * A first integration run of 12 pass / 10 fail is the reported cost of finding\n * that out by hand.\n *\n * So a `Middleware` implementation that is in the graph, is not attached by\n * `@UseGuards`, and was not passed here is worth one line on `console.warn` -\n * `console`, not the bound `Logger`, because a suite asserting on a\n * `RecordingLogger` should not see an entry the application never wrote.\n */\nconst warnAboutGlobals = (\n app: HttpApp,\n modules: readonly ResolvedModule[],\n): void => {\n const declared = declaredMiddleware(modules);\n if (declared.length === 0) return;\n const scoped = scopedGuards(app, modules);\n const orphaned = declared.filter(\n (ctor) => !scoped.has(ctor as Ctor<Middleware>),\n );\n if (orphaned.length === 0) return;\n\n console.warn(\n `createTestServer: ${orphaned.map((ctor) => ctor.name).join(', ')} ` +\n 'implement Middleware and are in the graph under test, but no `middleware` ' +\n 'was supplied. If they are global in main.ts then this fixture is not the ' +\n 'application: no global guard runs and `onError` is the default mapper. ' +\n 'Export one httpOptions(config) and give it to both, or pass ' +\n '`middleware: []` to say the omission is deliberate.',\n );\n};\n\n/**\n * A **real** `Bun.serve` on port 0, with the same override semantics as\n * {@link createTestApp}. Nothing is faked: `Bun.serve` binds in about a\n * millisecond, and a fake would only be able to prove the parts of the request\n * path dunx wrote rather than the parts Bun owns - routing, params, method\n * dispatch, upgrades.\n *\n * ```ts\n * const server = await createTestServer({ modules: [ApiModule], prefix: 'api' });\n * const { status, body } = await server.json('api/users');\n * await server.close();\n * ```\n *\n * Request logging is **off** unless asked for: it is on by default in production\n * for good reasons, none of which apply to a suite that would print one JSON line\n * per assertion.\n *\n * **An `HttpOptions` field not passed is absent, not inherited from production.**\n * `middleware` (where global guards live) and `onError` are the two that change\n * what the application does, so pass the same object `main.ts` passes - one\n * exported `httpOptions(config)` spread into both. Omitting `middleware` in a graph\n * that declares a `Middleware` no `@UseGuards` attaches writes one line to\n * `console.warn`; `middleware: []` says the omission is deliberate.\n */\nexport const createTestServer = async (\n options: TestServerOptions,\n): Promise<TestServer> => {\n const { modules, overrides, prefix, requestLogging, ...http } = options;\n\n const root = testRoot(modules);\n const app = await HttpFactory.create(root, {\n ...http,\n ...appOptions(overrides),\n requestLogging: requestLogging ?? false,\n port: 0,\n });\n if (http.middleware === undefined)\n warnAboutGlobals(app, collectModules(root));\n if (prefix !== undefined) app.setGlobalPrefix(prefix);\n\n return {\n ...testClient(await app.listen()),\n app,\n close: () => app.shutdown(),\n };\n};\n"
8
+ "import {\n collectModules,\n readControllers,\n type Ctor,\n type ResolvedModule,\n} from '@dunx/core';\nimport {\n discoverRoutes,\n HttpFactory,\n type HttpApp,\n type HttpOptions,\n type Middleware,\n} from '@dunx/http';\nimport { appOptions, testRoot, type TestAppOptions } from './app.js';\nimport { testClient, type TestClient } from './client.js';\n\nexport interface TestServerOptions\n extends TestAppOptions, Omit<HttpOptions, 'port' | 'overrides'> {\n /**\n * `setGlobalPrefix`, applied before `listen()` so the client's URLs carry it.\n *\n * Explicitly `| undefined`, unlike the rest of the options: a suite that runs\n * the same fixture prefixed and unprefixed passes a variable here, and under\n * `exactOptionalPropertyTypes` that is otherwise a conditional spread. \"No\n * prefix\" and \"absent\" mean the same thing, so nothing is lost by allowing it.\n */\n readonly prefix?: string | undefined;\n}\n\nexport interface TestServer extends TestClient {\n readonly app: HttpApp;\n /** `app.shutdown()` - stops the server, then tears the container down. */\n close(): Promise<void>;\n}\n\nconst middlewareShaped = (ctor: Ctor<unknown>): boolean =>\n typeof (ctor.prototype as { handle?: unknown } | undefined)?.handle ===\n 'function';\n\n/** Every class provider in the graph that implements `Middleware`. */\nconst declaredMiddleware = (\n modules: readonly ResolvedModule[],\n): readonly Ctor<unknown>[] => {\n const found: Ctor<unknown>[] = [];\n for (const module of modules) {\n for (const entry of module.options.providers ?? []) {\n const ctor =\n typeof entry === 'function'\n ? entry\n : entry.provider.kind === 'class'\n ? entry.provider.ctor\n : undefined;\n if (ctor !== undefined && middlewareShaped(ctor)) found.push(ctor);\n }\n }\n return found;\n};\n\n/**\n * The guards `@UseGuards` already puts in the route table. They are not global, so\n * omitting `middleware` costs them nothing and warning about them would be noise.\n */\nconst scopedGuards = (\n app: HttpApp,\n modules: readonly ResolvedModule[],\n): ReadonlySet<Ctor<Middleware>> => {\n const applied = new Set<Ctor<Middleware>>();\n for (const module of modules) {\n for (const controller of readControllers(module)) {\n for (const route of discoverRoutes(app.get(controller) as object)) {\n for (const guard of route.guards ?? []) applied.add(guard);\n }\n }\n }\n return applied;\n};\n\n/**\n * The one silent way this harness can lie: `middleware` and `onError` default\n * away, so a suite that forgets them boots a server with no global guards and the\n * default error mapper, which still answers 200 where the application answers 401.\n * A first integration run of 12 pass / 10 fail is the reported cost of finding\n * that out by hand.\n *\n * So a `Middleware` implementation that is in the graph, is not attached by\n * `@UseGuards`, and was not passed here is worth one line on `console.warn` -\n * `console`, not the bound `Logger`, because a suite asserting on a\n * `RecordingLogger` should not see an entry the application never wrote.\n */\nconst warnAboutGlobals = (\n app: HttpApp,\n modules: readonly ResolvedModule[],\n): void => {\n const declared = declaredMiddleware(modules);\n if (declared.length === 0) return;\n const scoped = scopedGuards(app, modules);\n const orphaned = declared.filter(\n (ctor) => !scoped.has(ctor as Ctor<Middleware>),\n );\n if (orphaned.length === 0) return;\n\n console.warn(\n `createTestServer: ${orphaned.map((ctor) => ctor.name).join(', ')} ` +\n 'implement Middleware and are in the graph under test, but no `middleware` ' +\n 'was supplied. If they are global in main.ts then this fixture is not the ' +\n 'application: no global guard runs and `onError` is the default mapper. ' +\n 'Export one httpOptions(config) and give it to both, or pass ' +\n '`middleware: []` to say the omission is deliberate.',\n );\n};\n\n/**\n * A **real** `Bun.serve` on port 0, with the same override semantics as\n * {@link createTestApp}. Nothing is faked: `Bun.serve` binds in about a\n * millisecond, and a fake would only be able to prove the parts of the request\n * path dunx wrote rather than the parts Bun owns - routing, params, method\n * dispatch, upgrades.\n *\n * ```ts\n * const server = await createTestServer({ modules: [ApiModule], prefix: 'api' });\n * const { status, body } = await server.json('api/users');\n * await server.close();\n * ```\n *\n * Request logging and boot logging are both **off** unless asked for: they are on by\n * default in production for good reasons, none of which apply to a suite that would\n * print one JSON line per assertion and one route table per file.\n *\n * **An `HttpOptions` field not passed is absent, not inherited from production.**\n * `middleware` (where global guards live) and `onError` are the two that change\n * what the application does, so pass the same object `main.ts` passes - one\n * exported `httpOptions(config)` spread into both. Omitting `middleware` in a graph\n * that declares a `Middleware` no `@UseGuards` attaches writes one line to\n * `console.warn`; `middleware: []` says the omission is deliberate.\n */\nexport const createTestServer = async (\n options: TestServerOptions,\n): Promise<TestServer> => {\n const { modules, overrides, prefix, requestLogging, bootLogging, ...http } =\n options;\n\n const root = testRoot(modules);\n const app = await HttpFactory.create(root, {\n ...http,\n ...appOptions(overrides),\n requestLogging: requestLogging ?? false,\n // Off for the same reason: a suite that boots a server per file does not want a\n // route table per file. A suite asserting on the table asks for it.\n bootLogging: bootLogging ?? false,\n port: 0,\n });\n if (http.middleware === undefined)\n warnAboutGlobals(app, collectModules(root));\n if (prefix !== undefined) app.setGlobalPrefix(prefix);\n\n return {\n ...testClient(await app.listen()),\n app,\n close: () => app.shutdown(),\n };\n};\n"
9
9
  ],
10
- "mappings": ";;AAAA;AAAA;AAAA;AAAA;AAcA,MAAM,WAAW;AAAC;AAUlB,IAAM,SAAS,CACb,YACoC,MAAM,QAAQ,OAAO;AAOpD,IAAM,WAAW,CACtB,aACmB;AAAA,EACnB,QAAQ;AAAA,EACR,SAAS,OAAO,OAAO,IAAI,UAAU,CAAC,OAAO;AAC/C;AAGO,IAAM,aAAa,CACxB,cACgB,YAAY,EAAE,UAAU,IAAI,CAAC;AAiBxC,IAAM,gBAAgB,CAAC,YAC5B,WAAW,OAAO,SAAS,QAAQ,OAAO,GAAG,WAAW,QAAQ,SAAS,CAAC;;ACrC5E,IAAM,SAAS,CAAC,MAAc,SAAsB,IAAI,IAAI,MAAM,IAAI;AAEtE,IAAM,WAAW,CAAC,SAAgC;AAAA,EAChD,QAAQ,SAAS,SAAS;AAAA,EAC1B,IAAI,SAAS;AAAA,IAAW,OAAO;AAAA,EAE/B,MAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AAAA,EACxC,IAAI,CAAC,QAAQ,IAAI,cAAc,GAAG;AAAA,IAChC,QAAQ,IAAI,gBAAgB,kBAAkB;AAAA,EAChD;AAAA,EACA,OAAO,KAAK,MAAM,SAAS,MAAM,KAAK,UAAU,IAAI,EAAE;AAAA;AAYjD,IAAM,aAAa,CAAC,SAA6B;AAAA,EACtD;AAAA,EACA,SAAS,CAAC,OAAO,IAAI,OAAiB,CAAC,MACrC,MAAM,OAAO,KAAK,IAAI,GAAG,SAAS,IAAI,CAAC;AAAA,EACzC,MAAM,OAAU,OAAO,IAAI,OAAiB,CAAC,MAAgC;AAAA,IAC3E,MAAM,WAAW,MAAM,MAAM,OAAO,KAAK,IAAI,GAAG,SAAS,IAAI,CAAC;AAAA,IAI9D,MAAM,OAAO,MAAM,SAAS,KAAK;AAAA,IACjC,IAAI;AAAA,MACF,OAAO;AAAA,QACL,QAAQ,SAAS;AAAA,QACjB,SAAS,SAAS;AAAA,QAClB,MAAM,KAAK,MAAM,IAAI;AAAA,MACvB;AAAA,MACA,MAAM;AAAA,MACN,MAAM,OACJ,SAAS,KACL,kBACA,KAAK,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA;AAAA,IAC7C,KAAK,MAAM,GAAG,GAAG;AAAA,MACvB,MAAM,IAAI,MACR,GAAG,KAAK,UAAU,SAAS,OAAO,KAAK,IAAI,EAAE,uBAC3C,GAAG,SAAS,eAAe;AAAA;AAAA,qCAC3B,6BACJ;AAAA;AAAA;AAGN;;AC3EA;AAAA;AA2BO,MAAM,wBAAwB,OAAO;AAAA,EAEjC,WAAqB,SAAS;AAAA,EAC9B,UAAyB,CAAC;AAAA,EAEnC,OAAO,CAAC,YAAwB,QAAyB;AAAA,IACvD,KAAK,QAAQ,SAAS,SAAS,SAAS,MAAM;AAAA;AAAA,EAGhD,KAAK,CAAC,YAAwB,QAAyB;AAAA,IACrD,KAAK,QAAQ,SAAS,OAAO,SAAS,MAAM;AAAA;AAAA,EAG9C,IAAI,CAAC,YAAwB,QAAyB;AAAA,IACpD,KAAK,QAAQ,SAAS,MAAM,SAAS,MAAM;AAAA;AAAA,EAI7C,GAAG,CAAC,YAAwB,QAAyB;AAAA,IACnD,KAAK,QAAQ,SAAS,MAAM,SAAS,MAAM;AAAA;AAAA,EAG7C,IAAI,CAAC,YAAwB,QAAyB;AAAA,IACpD,KAAK,QAAQ,SAAS,MAAM,SAAS,MAAM;AAAA;AAAA,EAG7C,KAAK,CAAC,YAAwB,QAAyB;AAAA,IACrD,KAAK,QAAQ,SAAS,OAAO,SAAS,MAAM;AAAA;AAAA,EAG9C,KAAK,CAAC,YAAwB,QAAyB;AAAA,IACrD,KAAK,QAAQ,SAAS,OAAO,SAAS,MAAM;AAAA;AAAA,EAG9C,EAAE,CAAC,OAAyC;AAAA,IAC1C,OAAO,KAAK,QAAQ,OAAO,CAAC,UAAU,MAAM,UAAU,KAAK;AAAA;AAAA,EAG7D,KAAK,GAAS;AAAA,IACZ,KAAK,QAAQ,SAAS;AAAA;AAAA,EAGxB,OAAO,CACL,OACA,SACA,QACM;AAAA,IACN,KAAK,QAAQ,KAAK,EAAE,OAAO,SAAS,OAAO,CAAC;AAAA;AAEhD;;AC5EA;AAAA;AAAA;AAAA;AAMA;AAAA;AAAA;AAAA;AA6BA,IAAM,mBAAmB,CAAC,SACxB,OAAQ,KAAK,WAAgD,WAC7D;AAGF,IAAM,qBAAqB,CACzB,YAC6B;AAAA,EAC7B,MAAM,QAAyB,CAAC;AAAA,EAChC,WAAW,UAAU,SAAS;AAAA,IAC5B,WAAW,SAAS,OAAO,QAAQ,aAAa,CAAC,GAAG;AAAA,MAClD,MAAM,OACJ,OAAO,UAAU,aACb,QACA,MAAM,SAAS,SAAS,UACtB,MAAM,SAAS,OACf;AAAA,MACR,IAAI,SAAS,aAAa,iBAAiB,IAAI;AAAA,QAAG,MAAM,KAAK,IAAI;AAAA,IACnE;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAOT,IAAM,eAAe,CACnB,KACA,YACkC;AAAA,EAClC,MAAM,UAAU,IAAI;AAAA,EACpB,WAAW,UAAU,SAAS;AAAA,IAC5B,WAAW,cAAc,gBAAgB,MAAM,GAAG;AAAA,MAChD,WAAW,SAAS,eAAe,IAAI,IAAI,UAAU,CAAW,GAAG;AAAA,QACjE,WAAW,SAAS,MAAM,UAAU,CAAC;AAAA,UAAG,QAAQ,IAAI,KAAK;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAeT,IAAM,mBAAmB,CACvB,KACA,YACS;AAAA,EACT,MAAM,WAAW,mBAAmB,OAAO;AAAA,EAC3C,IAAI,SAAS,WAAW;AAAA,IAAG;AAAA,EAC3B,MAAM,SAAS,aAAa,KAAK,OAAO;AAAA,EACxC,MAAM,WAAW,SAAS,OACxB,CAAC,SAAS,CAAC,OAAO,IAAI,IAAwB,CAChD;AAAA,EACA,IAAI,SAAS,WAAW;AAAA,IAAG;AAAA,EAE3B,QAAQ,KACN,qBAAqB,SAAS,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,KAAK,IAAI,OAC9D,+EACA,8EACA,4EACA,iEACA,qDACJ;AAAA;AA2BK,IAAM,mBAAmB,OAC9B,YACwB;AAAA,EACxB,QAAQ,SAAS,WAAW,QAAQ,mBAAmB,SAAS;AAAA,EAEhE,MAAM,OAAO,SAAS,OAAO;AAAA,EAC7B,MAAM,MAAM,MAAM,YAAY,OAAO,MAAM;AAAA,OACtC;AAAA,OACA,WAAW,SAAS;AAAA,IACvB,gBAAgB,kBAAkB;AAAA,IAClC,MAAM;AAAA,EACR,CAAC;AAAA,EACD,IAAI,KAAK,eAAe;AAAA,IACtB,iBAAiB,KAAK,eAAe,IAAI,CAAC;AAAA,EAC5C,IAAI,WAAW;AAAA,IAAW,IAAI,gBAAgB,MAAM;AAAA,EAEpD,OAAO;AAAA,OACF,WAAW,MAAM,IAAI,OAAO,CAAC;AAAA,IAChC;AAAA,IACA,OAAO,MAAM,IAAI,SAAS;AAAA,EAC5B;AAAA;",
11
- "debugId": "592E49A30BEDF3D864756E2164756E21",
10
+ "mappings": ";;AAAA;AAAA;AAAA;AAAA;AAcA,MAAM,WAAW;AAAC;AAUlB,IAAM,SAAS,CACb,YACoC,MAAM,QAAQ,OAAO;AAOpD,IAAM,WAAW,CACtB,aACmB;AAAA,EACnB,QAAQ;AAAA,EACR,SAAS,OAAO,OAAO,IAAI,UAAU,CAAC,OAAO;AAC/C;AAGO,IAAM,aAAa,CACxB,cACgB,YAAY,EAAE,UAAU,IAAI,CAAC;AAiBxC,IAAM,gBAAgB,CAAC,YAC5B,WAAW,OAAO,SAAS,QAAQ,OAAO,GAAG,WAAW,QAAQ,SAAS,CAAC;;ACrC5E,IAAM,SAAS,CAAC,MAAc,SAAsB,IAAI,IAAI,MAAM,IAAI;AAEtE,IAAM,WAAW,CAAC,SAAgC;AAAA,EAChD,QAAQ,SAAS,SAAS;AAAA,EAC1B,IAAI,SAAS;AAAA,IAAW,OAAO;AAAA,EAE/B,MAAM,UAAU,IAAI,QAAQ,KAAK,OAAO;AAAA,EACxC,IAAI,CAAC,QAAQ,IAAI,cAAc,GAAG;AAAA,IAChC,QAAQ,IAAI,gBAAgB,kBAAkB;AAAA,EAChD;AAAA,EACA,OAAO,KAAK,MAAM,SAAS,MAAM,KAAK,UAAU,IAAI,EAAE;AAAA;AAYjD,IAAM,aAAa,CAAC,SAA6B;AAAA,EACtD;AAAA,EACA,SAAS,CAAC,OAAO,IAAI,OAAiB,CAAC,MACrC,MAAM,OAAO,KAAK,IAAI,GAAG,SAAS,IAAI,CAAC;AAAA,EACzC,MAAM,OAAU,OAAO,IAAI,OAAiB,CAAC,MAAgC;AAAA,IAC3E,MAAM,WAAW,MAAM,MAAM,OAAO,KAAK,IAAI,GAAG,SAAS,IAAI,CAAC;AAAA,IAI9D,MAAM,OAAO,MAAM,SAAS,KAAK;AAAA,IACjC,IAAI;AAAA,MACF,OAAO;AAAA,QACL,QAAQ,SAAS;AAAA,QACjB,SAAS,SAAS;AAAA,QAClB,MAAM,KAAK,MAAM,IAAI;AAAA,MACvB;AAAA,MACA,MAAM;AAAA,MACN,MAAM,OACJ,SAAS,KACL,kBACA,KAAK,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA;AAAA,IAC7C,KAAK,MAAM,GAAG,GAAG;AAAA,MACvB,MAAM,IAAI,MACR,GAAG,KAAK,UAAU,SAAS,OAAO,KAAK,IAAI,EAAE,uBAC3C,GAAG,SAAS,eAAe;AAAA;AAAA,qCAC3B,6BACJ;AAAA;AAAA;AAGN;;AC3EA;AAAA;AA2BO,MAAM,wBAAwB,OAAO;AAAA,EAEjC,WAAqB,SAAS;AAAA,EAC9B,UAAyB,CAAC;AAAA,EAEnC,OAAO,CAAC,YAAwB,QAAyB;AAAA,IACvD,KAAK,QAAQ,SAAS,SAAS,SAAS,MAAM;AAAA;AAAA,EAGhD,KAAK,CAAC,YAAwB,QAAyB;AAAA,IACrD,KAAK,QAAQ,SAAS,OAAO,SAAS,MAAM;AAAA;AAAA,EAG9C,IAAI,CAAC,YAAwB,QAAyB;AAAA,IACpD,KAAK,QAAQ,SAAS,MAAM,SAAS,MAAM;AAAA;AAAA,EAI7C,GAAG,CAAC,YAAwB,QAAyB;AAAA,IACnD,KAAK,QAAQ,SAAS,MAAM,SAAS,MAAM;AAAA;AAAA,EAG7C,IAAI,CAAC,YAAwB,QAAyB;AAAA,IACpD,KAAK,QAAQ,SAAS,MAAM,SAAS,MAAM;AAAA;AAAA,EAG7C,KAAK,CAAC,YAAwB,QAAyB;AAAA,IACrD,KAAK,QAAQ,SAAS,OAAO,SAAS,MAAM;AAAA;AAAA,EAG9C,KAAK,CAAC,YAAwB,QAAyB;AAAA,IACrD,KAAK,QAAQ,SAAS,OAAO,SAAS,MAAM;AAAA;AAAA,EAG9C,EAAE,CAAC,OAAyC;AAAA,IAC1C,OAAO,KAAK,QAAQ,OAAO,CAAC,UAAU,MAAM,UAAU,KAAK;AAAA;AAAA,EAG7D,KAAK,GAAS;AAAA,IACZ,KAAK,QAAQ,SAAS;AAAA;AAAA,EAGxB,OAAO,CACL,OACA,SACA,QACM;AAAA,IACN,KAAK,QAAQ,KAAK,EAAE,OAAO,SAAS,OAAO,CAAC;AAAA;AAEhD;;AC5EA;AAAA;AAAA;AAAA;AAMA;AAAA;AAAA;AAAA;AA6BA,IAAM,mBAAmB,CAAC,SACxB,OAAQ,KAAK,WAAgD,WAC7D;AAGF,IAAM,qBAAqB,CACzB,YAC6B;AAAA,EAC7B,MAAM,QAAyB,CAAC;AAAA,EAChC,WAAW,UAAU,SAAS;AAAA,IAC5B,WAAW,SAAS,OAAO,QAAQ,aAAa,CAAC,GAAG;AAAA,MAClD,MAAM,OACJ,OAAO,UAAU,aACb,QACA,MAAM,SAAS,SAAS,UACtB,MAAM,SAAS,OACf;AAAA,MACR,IAAI,SAAS,aAAa,iBAAiB,IAAI;AAAA,QAAG,MAAM,KAAK,IAAI;AAAA,IACnE;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAOT,IAAM,eAAe,CACnB,KACA,YACkC;AAAA,EAClC,MAAM,UAAU,IAAI;AAAA,EACpB,WAAW,UAAU,SAAS;AAAA,IAC5B,WAAW,cAAc,gBAAgB,MAAM,GAAG;AAAA,MAChD,WAAW,SAAS,eAAe,IAAI,IAAI,UAAU,CAAW,GAAG;AAAA,QACjE,WAAW,SAAS,MAAM,UAAU,CAAC;AAAA,UAAG,QAAQ,IAAI,KAAK;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAeT,IAAM,mBAAmB,CACvB,KACA,YACS;AAAA,EACT,MAAM,WAAW,mBAAmB,OAAO;AAAA,EAC3C,IAAI,SAAS,WAAW;AAAA,IAAG;AAAA,EAC3B,MAAM,SAAS,aAAa,KAAK,OAAO;AAAA,EACxC,MAAM,WAAW,SAAS,OACxB,CAAC,SAAS,CAAC,OAAO,IAAI,IAAwB,CAChD;AAAA,EACA,IAAI,SAAS,WAAW;AAAA,IAAG;AAAA,EAE3B,QAAQ,KACN,qBAAqB,SAAS,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,KAAK,IAAI,OAC9D,+EACA,8EACA,4EACA,iEACA,qDACJ;AAAA;AA2BK,IAAM,mBAAmB,OAC9B,YACwB;AAAA,EACxB,QAAQ,SAAS,WAAW,QAAQ,gBAAgB,gBAAgB,SAClE;AAAA,EAEF,MAAM,OAAO,SAAS,OAAO;AAAA,EAC7B,MAAM,MAAM,MAAM,YAAY,OAAO,MAAM;AAAA,OACtC;AAAA,OACA,WAAW,SAAS;AAAA,IACvB,gBAAgB,kBAAkB;AAAA,IAGlC,aAAa,eAAe;AAAA,IAC5B,MAAM;AAAA,EACR,CAAC;AAAA,EACD,IAAI,KAAK,eAAe;AAAA,IACtB,iBAAiB,KAAK,eAAe,IAAI,CAAC;AAAA,EAC5C,IAAI,WAAW;AAAA,IAAW,IAAI,gBAAgB,MAAM;AAAA,EAEpD,OAAO;AAAA,OACF,WAAW,MAAM,IAAI,OAAO,CAAC;AAAA,IAChC;AAAA,IACA,OAAO,MAAM,IAAI,SAAS;AAAA,EAC5B;AAAA;",
11
+ "debugId": "1573B94CB0D606A564756E2164756E21",
12
12
  "names": []
13
13
  }
package/dist/server.d.ts CHANGED
@@ -30,9 +30,9 @@ export interface TestServer extends TestClient {
30
30
  * await server.close();
31
31
  * ```
32
32
  *
33
- * Request logging is **off** unless asked for: it is on by default in production
34
- * for good reasons, none of which apply to a suite that would print one JSON line
35
- * per assertion.
33
+ * Request logging and boot logging are both **off** unless asked for: they are on by
34
+ * default in production for good reasons, none of which apply to a suite that would
35
+ * print one JSON line per assertion and one route table per file.
36
36
  *
37
37
  * **An `HttpOptions` field not passed is absent, not inherited from production.**
38
38
  * `middleware` (where global guards live) and `onError` are the two that change
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunx/testing",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Test harness for dunx apps: a container with providers replaced in place, and a real Bun.serve on port 0",
5
5
  "keywords": [
6
6
  "bun",
@@ -51,8 +51,8 @@
51
51
  "@dunx/http": "workspace:*"
52
52
  },
53
53
  "peerDependencies": {
54
- "@dunx/core": "^1.0.1",
55
- "@dunx/http": "^1.0.1",
54
+ "@dunx/core": "^1.2.0",
55
+ "@dunx/http": "^1.2.0",
56
56
  "@types/bun": ">=1.3.0"
57
57
  },
58
58
  "peerDependenciesMeta": {