import { AppAdapter, AppModuleClass, AppModuleEntry, Application, ApplicationOptions, Container, ContextDecorator, ContextMetaKey, ContributorRegistration, ExecutionContext, KickPlugin, MetaValue, ModuleRoutes } from "@forinda/kickjs"; import express from "express"; //#region src/index.d.ts /** * Bootstrap options forwarded verbatim to the underlying Application so * test apps exercise the same HTTP pipeline shape as production. */ type BootstrapPassthroughOptions = Pick; /** * Options for creating a test application. * Disables helmet, cors, compression, and morgan by default. */ interface CreateTestAppOptions extends BootstrapPassthroughOptions { /** * Modules registered for the test run. Accepts both legacy class form * (`UserModule extends AppModule`) and `defineModule` factory output * (call the factory: `UserModule()`) — `Application` discriminates * at boot. */ modules: AppModuleEntry[]; /** Adapters to attach (auth, queue, devtools, etc.) */ adapters?: AppAdapter[]; /** * DI overrides applied after module registration. * * An object literal covers string and symbol keys. It cannot cover * `createToken()`, though — a token is a frozen OBJECT identified by * reference, and TypeScript rejects an object as a computed key * (`TS2464: A computed property name must be of type 'string', 'number', * 'symbol', or 'any'`). Since tokens are the documented way to bind an * interface to an implementation, that excluded exactly the key type most * worth overriding in a test. * * Worse, the obvious workaround compiles and does nothing: `[TOKEN.name]` is * a string, and the container keys tokens by reference, so the override is * accepted and never applied. * * Pass entries to override a token: * * ```ts * const DATABASE = createToken('app/Db/connection') * * await createTestApp({ * modules: [UserModule()], * overrides: [[DATABASE, fakeDb()]], * }) * ``` * * A `Map` works too. The object form is unchanged. */ overrides?: Record | ReadonlyArray | ReadonlyMap; /** * Use an isolated container instead of the global singleton. * Prevents concurrent tests from interfering with each other's DI state. * When true, Container.create() is used instead of Container.reset() + getInstance(). */ isolated?: boolean; } declare function createTestApp(options: CreateTestAppOptions): Promise<{ app: Application; /** * @deprecated Use `app.handle.bind(app)`. Only valid under the Express * runtime — under * any other engine this throws rather than handing back that engine's * instance mistyped as `express.Express`, which is how a suite ends up * silently exercising the wrong runtime. */ expressApp: express.Express; container: Container; }>; /** * Build a quick TestModule that explicitly registers dependencies. * Useful for integration tests that need to control the DI graph. * * @example * ```ts * const TestModule = createTestModule({ * register: (c) => { * c.registerFactory(USER_REPO, () => new InMemoryUserRepo()) * c.register(UserController) * }, * routes: () => buildRoutes(UserController, '/users'), * }) * ``` */ declare function createTestModule(config: { register: (container: Container) => void; routes: () => ModuleRoutes | ModuleRoutes[] | null; }): AppModuleClass; /** * Options for {@link runContributor}. */ interface RunContributorOptions { /** * Resolved deps passed to `resolve(ctx, deps)`. Skips the DI container * entirely — bypass for unit tests that want to assert pure resolve() * behaviour without standing up a container. * * Property names must match the spec's `deps` keys; values can be real * service instances, mocks, or test doubles. */ deps?: Record; /** * Pre-populate the fake context's metadata Map. Useful for testing * contributors with `dependsOn` without running the full pipeline: * pre-populate the dependency keys, then assert that `resolve()` reads * them and produces the expected output. */ initial?: Record; /** Override the fake context's `requestId` (default: `'test-req'`). */ requestId?: string; } /** * Result of {@link runContributor}. */ interface RunContributorResult { /** Value returned by the contributor's `resolve()` call. */ value: MetaValue; /** The fake `ExecutionContext` used during the run. */ ctx: ExecutionContext; /** * Final state of the fake context's metadata Map after `resolve()`. * Includes any `ctx.set(...)` calls the resolver made plus the final * resolved value under the contributor's own key. */ meta: Map; } /** * Run a single context contributor in isolation against a fake * {@link ExecutionContext}. Skips the container, the topo-sort, and the * §20.9 error matrix — calls `decorator.registration.resolve(ctx, deps)` * directly so unit tests can assert pure resolve behaviour. * * Errors thrown by `resolve()` propagate so tests can `expect(...).rejects` * or `await expect(...).rejects.toThrow()` against them. To exercise the * full error matrix (optional skip, onError replacement, etc.), build a * one-element pipeline with `buildPipeline()` and use `runContributors()` * directly. * * @example * ```ts * const LoadProject = defineContextDecorator({ * key: 'project', * dependsOn: ['tenant'], * deps: { repo: ProjectRepo }, * resolve: (ctx, { repo }) => repo.findByTenant(ctx.get('tenant')!.id), * }) * * const { value } = await runContributor(LoadProject, { * initial: { tenant: { id: 't-1' } }, * deps: { repo: new InMemoryProjectRepo([{ id: 'p-1', tenantId: 't-1' }]) }, * }) * expect(value).toEqual({ id: 'p-1', tenantId: 't-1' }) * ``` */ declare function runContributor = Record>(decorator: ContextDecorator, options?: RunContributorOptions): Promise>; /** * Options for {@link createTestPlugin}. */ interface CreateTestPluginOptions { /** * Use an isolated container (default: `true`). Isolated harnesses never * touch the global `Container.getInstance()` singleton, so concurrent * tests can run without stomping each other's DI state. */ isolated?: boolean; /** * Skip auto-invoking `plugin.register(container)` — useful when a test * wants to assert container state *before* the plugin touches it. * Defaults to `false` (register is called eagerly). */ skipRegister?: boolean; } /** * Result of {@link createTestPlugin}. Mirrors the spec sketch in * `architecture.md` §21.3.2 — `.container`, lifecycle invokers, and a * `runContributors()` helper that builds a one-plugin pipeline against a * fake `ExecutionContext`. */ interface PluginTestHarness { /** The plugin under test — exposed for direct introspection. */ readonly plugin: KickPlugin; /** Isolated (or shared) DI container the plugin registered into. */ readonly container: Container; /** * Invoke `plugin.onReady(container)`. No-op if the plugin does not * define the hook. */ callOnReady(): Promise; /** * Invoke `plugin.shutdown()`. No-op if the plugin does not define the * hook. */ shutdown(): Promise; /** * Returns the module classes the plugin ships via `plugin.modules?()`. * The harness does **not** auto-instantiate them — use `createTestApp` * for that. Useful for assertions like "the plugin exposes the module I * expect". */ modules(): AppModuleEntry[]; /** * Returns adapter instances the plugin ships via `plugin.adapters?()`. * Useful for testing the adapters alongside the plugin's DI bindings. */ adapters(): AppAdapter[]; /** * Returns middleware entries the plugin ships via `plugin.middleware?()`. */ middleware(): any[]; /** * Returns contributor registrations the plugin ships via * `plugin.contributors?()`. */ contributors(): ContributorRegistration[]; /** * Build a fake {@link ExecutionContext} pre-populated with the given * metadata. Symmetric with {@link runContributor}'s fake context. */ makeContext(initial?: Record): ExecutionContext; /** * Run every contributor the plugin ships through a built pipeline * against the given context. Dependencies resolve through the harness's * container (same path production uses). Throws if the plugin's * contributors reference `dependsOn` keys no one in the plugin provides. */ runContributors(ctx: ExecutionContext): Promise; } /** * Build an isolated test harness around a single {@link KickPlugin}. * Skips the full HTTP layer — symmetric with {@link runContributor} for * contributors and {@link createTestApp} for integration runs. * * @example * ```ts * const harness = await createTestPlugin(FlagsPlugin({ provider: scripted })) * * // The plugin's register() already ran — resolve any bindings. * const flags = harness.container.resolve(FLAGS_SERVICE) * * // Drive the post-bootstrap lifecycle. * await harness.callOnReady() * * // Exercise contributors shipped by the plugin. * const ctx = harness.makeContext({ requestId: 'req-1' }) * await harness.runContributors(ctx) * expect(ctx.get('flags')).toEqual({ beta: true }) * * await harness.shutdown() * ``` */ declare function createTestPlugin(plugin: KickPlugin, options?: CreateTestPluginOptions): Promise; /** Alias for {@link createTestPlugin}, matching the `testPlugin` name used in the architecture spec. */ declare const testPlugin: typeof createTestPlugin; //#endregion export { CreateTestAppOptions, CreateTestPluginOptions, PluginTestHarness, RunContributorOptions, RunContributorResult, createTestApp, createTestModule, createTestPlugin, runContributor, testPlugin }; //# sourceMappingURL=index.d.mts.map