The capability token to replace. Must already be provided.
The test double to register under it
A plugin to append to createTestApp's overrides, or to pass to
app.register() on an un-started application
Multi-provider capabilities cannot be overridden, and are refused. A token
registered with { multi: true } — the kernel's health-indicator,
metric-registration, openapi-schema, decorator-handler and
cli-command, and any an application registers itself — has no single
provider to replace: getAll returns the single and multi registrations
concatenated, so an override would add a provider while every real one kept
running. Detection is generic rather than a list of known tokens, so an
application's own multi capability is refused too. Exclude the plugin that
registers the provider instead.
During start() — from an onInit hook, not from
register() — if nothing provides token, or if token is a multi-provider
capability. Both checks run there because this plugin registers EARLY: at its
own register() a multi-provider capability has not accumulated its providers
yet, and a token it does not shadow may still be registered by a later plugin.
A silent no-op would leave the real service serving while the test reported
success.
import { createTestApp, overrideCapability } from '@setu-ts/testing';
import { CAPABILITIES } from '@setu-ts/common';
import { createApp } from '../setu.config.ts';
const app = await createTestApp({
app: createApp(),
overrides: [overrideCapability(CAPABILITIES.MAIL, { send: () => Promise.resolve() })],
});
Creates a plugin that REPLACES an already-provided capability with a test double, leaving the rest of the application's composition intact.
It is ordered after the provider and before ordinary consumers, so it reaches a consumer that resolves the capability during its own
register()as well as one that resolves it per request.This is the replacement mechanism AI_GUIDELINES §3.4 describes — "a replacement plugin registers the same capability token with
override: true" — with the three constraints that make it work applied for you:provides. A second plugin declaring a token the real one already declares is refused by the kernel before any of them runs (Capability 'x' is provided by both 'a' and 'b'), which is whycreateMockPlugincannot be used to override.{ override: true }, without which the kernel refuses a second registration of a live token.register()—NotificationPlugindoes exactly that. Ordering comes from anoptionalDependenciesedge on the token (the resolver visits a dependency first, whatever its priority band) plus an early priority, so the depth-first sort reaches this plugin, and through the edge its provider, before aPLUGIN_PRIORITY.NORMALconsumer.What it still cannot do: undo the provider's eager side effects. The real plugin's
register()has run by the time this one replaces its service, so a database adapter'sconnect()or a broker's dial has already happened. Only removing the plugin prevents that —createTestApp({ app, without }), orapp.unregister(name)directly.It requires the provider to declare the token in
provides. That is what the ordering edge hangs on, and it is how a plugin is depended upon at all. A plugin that registers a capability without declaring it cannot be ordered against, so the override registers first and the provider's own plain registration then fails the application's startup withCapability '<token>' is already registered. Declareprovides, or use the removal form below.To remove the provider instead of replacing it — which also prevents its eager side effects — supply the double as a provider ahead of its consumers: