@endora-commerce/test-kit 0.100.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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +108 -0
  3. package/dist/database/index.d.ts +17 -0
  4. package/dist/database/index.d.ts.map +1 -0
  5. package/dist/database/index.js +17 -0
  6. package/dist/database/index.js.map +1 -0
  7. package/dist/database/lease.d.ts +115 -0
  8. package/dist/database/lease.d.ts.map +1 -0
  9. package/dist/database/lease.js +204 -0
  10. package/dist/database/lease.js.map +1 -0
  11. package/dist/database/provision.d.ts +135 -0
  12. package/dist/database/provision.d.ts.map +1 -0
  13. package/dist/database/provision.js +457 -0
  14. package/dist/database/provision.js.map +1 -0
  15. package/dist/database/run-isolation.d.ts +356 -0
  16. package/dist/database/run-isolation.d.ts.map +1 -0
  17. package/dist/database/run-isolation.js +490 -0
  18. package/dist/database/run-isolation.js.map +1 -0
  19. package/dist/server/compose-test-server.d.ts +251 -0
  20. package/dist/server/compose-test-server.d.ts.map +1 -0
  21. package/dist/server/compose-test-server.js +359 -0
  22. package/dist/server/compose-test-server.js.map +1 -0
  23. package/dist/server/composition.d.ts +112 -0
  24. package/dist/server/composition.d.ts.map +1 -0
  25. package/dist/server/composition.js +32 -0
  26. package/dist/server/composition.js.map +1 -0
  27. package/dist/server/index.d.ts +13 -0
  28. package/dist/server/index.d.ts.map +1 -0
  29. package/dist/server/index.js +12 -0
  30. package/dist/server/index.js.map +1 -0
  31. package/dist/support/entity-index.d.ts +91 -0
  32. package/dist/support/entity-index.d.ts.map +1 -0
  33. package/dist/support/entity-index.js +56 -0
  34. package/dist/support/entity-index.js.map +1 -0
  35. package/dist/support/index.d.ts +134 -0
  36. package/dist/support/index.d.ts.map +1 -0
  37. package/dist/support/index.js +153 -0
  38. package/dist/support/index.js.map +1 -0
  39. package/package.json +66 -0
@@ -0,0 +1,251 @@
1
+ /**
2
+ * `composeTestServer` — the host-shaped half of `setupBackendServer`, lifted
3
+ * (feature 109, T023).
4
+ *
5
+ * ## What "host-shaped" means, precisely
6
+ *
7
+ * Everything in the harness's composition that is true of **any** Endora
8
+ * platform and mentions no module: open the ORM, build the container, register
9
+ * the ORM-derived names, construct the audit writer, the event bus, the command
10
+ * bus and the two Redis clients, prime the registry cache from the manifests,
11
+ * compose the settings and sales-channel kernels, run `composeModules` once,
12
+ * open the contribution window once, install the request-scope hook, run the
13
+ * boot phase once, build the server, and take all of it down again.
14
+ *
15
+ * Everything else — which modules, which schema, which fixtures, which actor a
16
+ * request carries — is the caller's, and reaches this function either as
17
+ * {@link PlatformComposition} or as one of the hooks below. The hooks are at
18
+ * the four points the boot order makes load-bearing (D-45) and nowhere else:
19
+ * before the modules register, in the contribution window, before the boot
20
+ * phase, and after the server is ready. A fifth hook would be a fifth place for
21
+ * two composition roots to drift.
22
+ *
23
+ * ## The one ordering rule, and why it is a shape rather than a comment
24
+ *
25
+ * `composeModules` runs once and `runBootHooks` runs once, immediately before
26
+ * the app is built, so a boot hook may resolve anything. A contribution over a
27
+ * name a module defaults goes in the single slot between them — earlier and the
28
+ * module's own registration overwrites it, later and a boot hook has already
29
+ * read the default. That slot is {@link ComposeTestServerOptions.contribute},
30
+ * and both edges are enforced by the platform rather than remembered: the early
31
+ * one because `ComposedModules` does not exist until every module has
32
+ * registered, the late one because `contribute` throws once `runBootHooks` has
33
+ * started.
34
+ *
35
+ * ## What it refuses, and with whose words
36
+ *
37
+ * A composition whose `modules` lacks a module declaring
38
+ * `activation.nonDeactivatable` is refused by `composeModules`, before the
39
+ * first module registers, with `RequiredModuleAbsentError` — naming the module,
40
+ * the sentence its own manifest gives and the remedy. This function adds no
41
+ * second check and catches nothing on that path (R2.4): a stranger composing an
42
+ * incomplete set has to get the platform's answer, because the kit's would be a
43
+ * paraphrase that goes stale.
44
+ *
45
+ * What it *does* do on that path is give back the two connections and the ORM
46
+ * it had already opened. That is not a swallow — the refusal is re-thrown
47
+ * unchanged, and unconditionally — it is the difference between a test that
48
+ * reports one refusal and a run that reports it once and then dies of a
49
+ * connection leak.
50
+ */
51
+ import type { EntityManager, MikroORM } from '@mikro-orm/postgresql';
52
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
53
+ import { Redis } from 'ioredis';
54
+ import { ApiInterceptorRegistry, AuditLogService, buildServer, composeModules, type KernelContainer, type ModulePlugin, type SalesChannelsKernel, type SettingsKernel } from '@endora-commerce/platform/composition';
55
+ import { CommandBus } from '@endora-commerce/platform/commands';
56
+ import { EventBus } from '@endora-commerce/platform/events';
57
+ import type { TenantContext } from '@endora-commerce/platform/tenancy';
58
+ import type { PlatformComposition } from './composition.js';
59
+ /**
60
+ * `buildServer`'s own options, named through its signature.
61
+ *
62
+ * `BuildServerOptions` is on no barrel and this feature widens none (R3.1), so
63
+ * the type is taken from the function that *is* published rather than by
64
+ * publishing a second symbol to spell it. It stays correct by construction: a
65
+ * field added to `buildServer` is a field here in the same compile.
66
+ */
67
+ type BuildServerOptions = Parameters<typeof buildServer>[0];
68
+ /**
69
+ * `ComposeModules`' return, named through its signature, for the same reason.
70
+ */
71
+ type ComposedModules = ReturnType<typeof composeModules>;
72
+ /** What every hook is handed before the modules have registered. */
73
+ export interface TestPlatformContext {
74
+ readonly orm: MikroORM;
75
+ /** A fresh tenant-filtered fork per call — the seam production forks through. */
76
+ readonly em: () => EntityManager;
77
+ readonly container: KernelContainer;
78
+ readonly eventBus: EventBus;
79
+ readonly commandBus: CommandBus;
80
+ readonly auditLogService: AuditLogService;
81
+ readonly apiInterceptors: ApiInterceptorRegistry;
82
+ readonly redis: Redis;
83
+ readonly redisSubscriber: Redis;
84
+ readonly settings: SettingsKernel;
85
+ readonly salesChannels: SalesChannelsKernel;
86
+ }
87
+ /** What every hook is handed once the modules have registered. */
88
+ export interface ComposedPlatformContext extends TestPlatformContext {
89
+ readonly composed: ComposedModules;
90
+ }
91
+ export interface ComposeTestServerOptions {
92
+ /** The four things only the caller knows. */
93
+ readonly composition: PlatformComposition;
94
+ /**
95
+ * Host values **no module defaults** — a deployment flag, a base URL, a
96
+ * substitute transport. Registered before the modules do, where a value comes
97
+ * into existence, because there is nothing to overwrite and therefore no
98
+ * contribution window (AGENTS.md § Composition item 8).
99
+ *
100
+ * The kit registers the platform's own here too: `redis`, `redisSubscriber`,
101
+ * `eventBus`, `commandBus`, `auditLogService`, `apiInterceptors`,
102
+ * `resolvedModuleRegistry` and `processRunsWorkers` (`false`). A caller's entry of the same name wins, and that
103
+ * is deliberate — the alternative is a caller with no way to substitute a
104
+ * connection.
105
+ */
106
+ readonly values?: Readonly<Record<string, unknown>>;
107
+ /**
108
+ * Truncate, seed, reconcile — whatever this caller's schema needs before the
109
+ * modules register.
110
+ *
111
+ * It runs after the ORM is open and the caches are the caller's to drop, and
112
+ * before `composeModules`, because a module's registration may not observe a
113
+ * half-seeded database.
114
+ */
115
+ readonly prepareDatabase?: (ctx: TestPlatformContext) => Promise<void>;
116
+ /**
117
+ * The contribution window (D-45, issue #52) — the one slot where a value a
118
+ * module defaults may be overwritten.
119
+ */
120
+ readonly contribute?: (ctx: ComposedPlatformContext) => void | Promise<void>;
121
+ /**
122
+ * Route plugins mounted after every module's own, after the modules' root
123
+ * plugins, and **before** the request-scope hook.
124
+ *
125
+ * This is where a harness's synthetic authentication goes: it registers after
126
+ * the platform's own auth plugin so its `onRequest` hook runs second and the
127
+ * synthetic actor wins, through the same decorator — and before the scope
128
+ * hook, which builds its {@link TenantContext} out of the actor that hook
129
+ * resolved.
130
+ */
131
+ readonly plugins?: readonly ModulePlugin[];
132
+ /**
133
+ * Route plugins mounted **after** the request-scope hook.
134
+ *
135
+ * The scope sits in the middle of the chain rather than at its end, so there
136
+ * are two sides to it and a caller needs both. Production's own root has the
137
+ * same shape: `authModulePlugin`, `tenantContextModulePlugin`, then
138
+ * `salesChannels.plugin` — and the sales-channel resolver is exactly why the
139
+ * second slot exists, because it writes the resolved channel into the open
140
+ * request scope and refuses when there is none.
141
+ *
142
+ * Both arrays are read after the contribution window closes, so a caller may
143
+ * push into either from inside `contribute`, where the value a plugin needs
144
+ * finally exists.
145
+ */
146
+ readonly scopedPlugins?: readonly ModulePlugin[];
147
+ /**
148
+ * Establish the ambient `TenantContext` for a request (Principle XI).
149
+ *
150
+ * Omitting it is not "no tenancy": the kit installs the platform's own
151
+ * `registerRequestScopeHook` either way, and the default context is a system
152
+ * one. There is no path through this function that leaves the hook off,
153
+ * because a server composed without it is one under which every tenant-scope
154
+ * test passes for the wrong reason.
155
+ */
156
+ readonly buildTenantContext?: (request: FastifyRequest) => Promise<TenantContext>;
157
+ /**
158
+ * Runs after every contribution and **before** the boot phase — a manifest
159
+ * reconcile, a settings write a boot hook will read.
160
+ */
161
+ readonly beforeBoot?: (ctx: ComposedPlatformContext) => Promise<void>;
162
+ /** Runs after `app.ready()`, with the composed platform and the app. */
163
+ readonly afterReady?: (ctx: ComposedPlatformContext, app: FastifyInstance) => Promise<void>;
164
+ /**
165
+ * Whatever this caller wants on `buildServer` — the error envelope, the
166
+ * OpenAPI metadata, a rate-limit ceiling.
167
+ *
168
+ * `modules` and `apiInterceptors` are the kit's and are ignored here: the
169
+ * first is assembled from the composition plus {@link plugins} plus the
170
+ * request-scope hook, in that order, and the second is the registry every
171
+ * module was handed at registration.
172
+ */
173
+ readonly server?: Omit<Partial<BuildServerOptions>, 'modules' | 'apiInterceptors'>;
174
+ /** Defaults to `REDIS_URL`, or `redis://localhost:6379`. */
175
+ readonly redisUrl?: string;
176
+ /**
177
+ * Register the **real** subscriber client rather than an inert one.
178
+ *
179
+ * Off by default, because one armed subscription per composition is the leak
180
+ * this opt-in was measured into existence to stop. A test that exercises the
181
+ * cross-process invalidation path asks for it.
182
+ */
183
+ readonly exercisePubSub?: boolean;
184
+ /**
185
+ * The wrapping order this instance declares for a name more than one module
186
+ * decorates (feature 107, FR-040/FR-041).
187
+ *
188
+ * A caller's fact and not the kit's: it is read from the deployment's own
189
+ * `divergence.ts` in this repository, and from whatever an instance keeps it
190
+ * in elsewhere. It is forwarded to `composeModules` unchanged and **checked,
191
+ * never applied** — the composer emits in its own topological order and
192
+ * drains decorations once; this declaration asserts that the resulting order
193
+ * was the intended one and `AmbiguousDecorationError` names it when the two
194
+ * disagree.
195
+ *
196
+ * It is an option rather than a fifth `PlatformComposition` member because
197
+ * `PlatformComposition`'s four members are the things a composition cannot be
198
+ * built without (R2.1); an instance that declares no ambiguity resolution
199
+ * composes perfectly well, and every test in this repository's own suite
200
+ * passes an empty one.
201
+ */
202
+ readonly decorationOrder?: Readonly<Record<string, readonly string[]>> | undefined;
203
+ /**
204
+ * Which module ids presence reports as enabled.
205
+ *
206
+ * Defaults to every module the composition's manifests declare, which is what
207
+ * a test wants: production loads presence from `module_registrations`, and a
208
+ * harness that never boots the orchestrator would otherwise 503 every gated
209
+ * route. An off-state test passes a narrower set.
210
+ */
211
+ readonly enabledModuleIds?: readonly string[];
212
+ }
213
+ /**
214
+ * A composed platform, and everything the kit itself owns in it.
215
+ *
216
+ * **It names no module**, which is the property Phase 2 is about: the
217
+ * application's own handle carries 29 module-specific fields today, and every
218
+ * one of them is a container resolution its readers can make for themselves.
219
+ * `container.cradle` is how a caller reaches a module's service from here.
220
+ */
221
+ export interface TestServerHandle {
222
+ readonly app: FastifyInstance;
223
+ readonly orm: MikroORM;
224
+ readonly em: () => EntityManager;
225
+ readonly container: KernelContainer;
226
+ readonly eventBus: EventBus;
227
+ readonly commandBus: CommandBus;
228
+ readonly auditLogService: AuditLogService;
229
+ readonly apiInterceptors: ApiInterceptorRegistry;
230
+ readonly redis: Redis;
231
+ readonly redisSubscriber: Redis;
232
+ /** Whether the real subscriber client was registered — `teardownTestServer` needs it. */
233
+ readonly pubSubArmed: boolean;
234
+ readonly settings: SettingsKernel;
235
+ readonly salesChannels: SalesChannelsKernel;
236
+ readonly composed: ComposedModules;
237
+ /** The composition's own closer, kept so teardown needs no second reference. */
238
+ readonly closeOrm: () => Promise<void>;
239
+ }
240
+ export declare function composeTestServer(options: ComposeTestServerOptions): Promise<TestServerHandle>;
241
+ /**
242
+ * Take the composed platform down.
243
+ *
244
+ * Every step is here rather than in a caller's own teardown for the reason
245
+ * `check:harness-teardown` refuses a hand-written one: a copy of this sequence
246
+ * is frozen at the moment it was copied, so it cannot learn about the container
247
+ * or the second Redis client, and both leak for the length of the run.
248
+ */
249
+ export declare function teardownTestServer(handle: TestServerHandle): Promise<void>;
250
+ export {};
251
+ //# sourceMappingURL=compose-test-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-test-server.d.ts","sourceRoot":"","sources":["../../src/server/compose-test-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,OAAO,EACL,sBAAsB,EACtB,eAAe,EAGf,WAAW,EACX,cAAc,EAcd,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;;GAOG;AACH,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;AAE5D;;GAEG;AACH,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAuBzD,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,EAAE,MAAM,aAAa,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,sBAAsB,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC;CAC7C;AAED,kEAAkE;AAClE,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AAED,MAAM,WAAW,wBAAwB;IACvC,6CAA6C;IAC7C,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACjD;;;;;;;;OAQG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAClF;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,uBAAuB,EAAE,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5F;;;;;;;;OAQG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,SAAS,GAAG,iBAAiB,CAAC,CAAC;IACnF,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;IACnF;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,aAAa,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,sBAAsB,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC;IAChC,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,gBAAgB,CAAC,CA6Q3B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhF"}
@@ -0,0 +1,359 @@
1
+ /**
2
+ * `composeTestServer` — the host-shaped half of `setupBackendServer`, lifted
3
+ * (feature 109, T023).
4
+ *
5
+ * ## What "host-shaped" means, precisely
6
+ *
7
+ * Everything in the harness's composition that is true of **any** Endora
8
+ * platform and mentions no module: open the ORM, build the container, register
9
+ * the ORM-derived names, construct the audit writer, the event bus, the command
10
+ * bus and the two Redis clients, prime the registry cache from the manifests,
11
+ * compose the settings and sales-channel kernels, run `composeModules` once,
12
+ * open the contribution window once, install the request-scope hook, run the
13
+ * boot phase once, build the server, and take all of it down again.
14
+ *
15
+ * Everything else — which modules, which schema, which fixtures, which actor a
16
+ * request carries — is the caller's, and reaches this function either as
17
+ * {@link PlatformComposition} or as one of the hooks below. The hooks are at
18
+ * the four points the boot order makes load-bearing (D-45) and nowhere else:
19
+ * before the modules register, in the contribution window, before the boot
20
+ * phase, and after the server is ready. A fifth hook would be a fifth place for
21
+ * two composition roots to drift.
22
+ *
23
+ * ## The one ordering rule, and why it is a shape rather than a comment
24
+ *
25
+ * `composeModules` runs once and `runBootHooks` runs once, immediately before
26
+ * the app is built, so a boot hook may resolve anything. A contribution over a
27
+ * name a module defaults goes in the single slot between them — earlier and the
28
+ * module's own registration overwrites it, later and a boot hook has already
29
+ * read the default. That slot is {@link ComposeTestServerOptions.contribute},
30
+ * and both edges are enforced by the platform rather than remembered: the early
31
+ * one because `ComposedModules` does not exist until every module has
32
+ * registered, the late one because `contribute` throws once `runBootHooks` has
33
+ * started.
34
+ *
35
+ * ## What it refuses, and with whose words
36
+ *
37
+ * A composition whose `modules` lacks a module declaring
38
+ * `activation.nonDeactivatable` is refused by `composeModules`, before the
39
+ * first module registers, with `RequiredModuleAbsentError` — naming the module,
40
+ * the sentence its own manifest gives and the remedy. This function adds no
41
+ * second check and catches nothing on that path (R2.4): a stranger composing an
42
+ * incomplete set has to get the platform's answer, because the kit's would be a
43
+ * paraphrase that goes stale.
44
+ *
45
+ * What it *does* do on that path is give back the two connections and the ORM
46
+ * it had already opened. That is not a swallow — the refusal is re-thrown
47
+ * unchanged, and unconditionally — it is the difference between a test that
48
+ * reports one refusal and a run that reports it once and then dies of a
49
+ * connection leak.
50
+ */
51
+ import { Redis } from 'ioredis';
52
+ import { ApiInterceptorRegistry, AuditLogService, activationDeclarationsFrom, capabilityRegistryFrom, buildServer, composeModules, composeSalesChannelsKernel, composeSettingsKernel, createRegistrationOwnership, createRootContainer, forkScopedEm, healthRoutePlugin, platformLogger, registerOrm, registerRequestScopeHook, registerValues, registryCache, requiredModulesFrom, systemTenantContext, } from '@endora-commerce/platform/composition';
53
+ import { CommandBus } from '@endora-commerce/platform/commands';
54
+ import { EventBus } from '@endora-commerce/platform/events';
55
+ import { mergeTestSupportRegistrations } from '../support/index.js';
56
+ /**
57
+ * A subscriber-shaped object that subscribes to nothing.
58
+ *
59
+ * Every composition *asks* for a subscriber, because a module that arms an
60
+ * invalidation channel from `onBoot` resolves one. Production hands it the real
61
+ * client; a test gets this unless it opted into pub/sub, because a live
62
+ * subscription per composition is a leak measured in hundreds of megabytes
63
+ * across a suite — and because a module that never receives an invalidation
64
+ * still behaves correctly, it just falls back to the cache's TTL.
65
+ */
66
+ function inertRedisSubscriber() {
67
+ const inert = {
68
+ subscribe: async () => 0,
69
+ on: () => inert,
70
+ removeAllListeners: () => inert,
71
+ unsubscribe: async () => 0,
72
+ disconnect: () => undefined,
73
+ };
74
+ return inert;
75
+ }
76
+ export async function composeTestServer(options) {
77
+ const { composition } = options;
78
+ const manifests = composition.manifests.map((entry) => entry.manifest);
79
+ const orm = await composition.orm.open();
80
+ // Mirror the production seam: forks stamp the tenant filter from the ambient
81
+ // `TenantContext`, which the request-scope hook below establishes.
82
+ const em = () => forkScopedEm(orm);
83
+ const redisUrl = options.redisUrl ?? process.env['REDIS_URL'] ?? 'redis://localhost:6379';
84
+ const redis = new Redis(redisUrl, { maxRetriesPerRequest: null, lazyConnect: false });
85
+ // ioredis puts a subscribed client into a mode where it will not accept
86
+ // ordinary commands, so production keeps subscriptions on a second
87
+ // connection. A harness with one client cannot exercise that path at all, and
88
+ // the pub/sub channel is the platform's only cross-process invalidation
89
+ // mechanism — the EventBus is in-process and the caches converge by TTL.
90
+ //
91
+ // **Opened only when it is armed** (issue #199). It used to be opened on
92
+ // every composition while reaching `teardownTestServer` only through
93
+ // `pubSubArmed`, so an unarmed composition — which is nearly every
94
+ // composition in the suite — left a connected ioredis client behind that
95
+ // nothing ever closed. Measured over twelve compose/teardown cycles in one
96
+ // process: `process.getActiveResourcesInfo()` reported one more
97
+ // `TCPSocketWrap` after each, twelve in all, and none after this change.
98
+ // (That call is not the general instrument — it excludes an `unref`ed timer,
99
+ // which is how the companion leak stayed invisible — but a socket is not
100
+ // `unref`ed and it does show one.)
101
+ //
102
+ // An open socket is a libuv handle and therefore a GC root. That is why it is
103
+ // worth repairing, and it is also the honest limit of the claim: measured on
104
+ // a full `test:backend` shard, this one is **not** where the heap went —
105
+ // `ksef`'s undisposed reconcile sweep was, and a run with that repaired and
106
+ // this left alone reads the same live set to 0.8 MB. What this costs is one
107
+ // ioredis client and one connection per composition, which a developer's
108
+ // long-lived local Redis notices long before the heap does.
109
+ // `heap-ceiling.test.ts` now refuses a `TCPWRAP` that survives a cycle.
110
+ //
111
+ // Not opening it is the repair rather than closing it, because a composition
112
+ // that never subscribes has no use for the connection: a resource that is
113
+ // never created cannot be left behind by any path, including the ones that
114
+ // throw before they reach a disconnect.
115
+ const pubSubArmed = options.exercisePubSub === true;
116
+ const realSubscriber = pubSubArmed
117
+ ? new Redis(redisUrl, { maxRetriesPerRequest: null, lazyConnect: false })
118
+ : undefined;
119
+ const redisSubscriber = realSubscriber ?? inertRedisSubscriber();
120
+ // Everything from here is disposable, and a failure between here and the
121
+ // handle has to give it back. The re-throw is unconditional: this catch
122
+ // releases resources and decides nothing.
123
+ const release = async () => {
124
+ redis.disconnect();
125
+ realSubscriber?.disconnect();
126
+ await composition.orm.close();
127
+ };
128
+ try {
129
+ const container = createRootContainer();
130
+ registerOrm(container, orm);
131
+ const registrationOwnership = createRegistrationOwnership();
132
+ const apiInterceptors = new ApiInterceptorRegistry({
133
+ isModuleEnabled: (moduleId) => registryCache.isEnabled(moduleId),
134
+ });
135
+ const auditLogService = new AuditLogService(em);
136
+ const eventBus = new EventBus();
137
+ const commandBus = new CommandBus(orm, auditLogService, eventBus);
138
+ const salesChannels = composeSalesChannelsKernel({
139
+ emFactory: em,
140
+ eventBus,
141
+ redis,
142
+ auditLogService,
143
+ });
144
+ const settings = composeSettingsKernel({
145
+ emFactory: em,
146
+ redis,
147
+ ...(process.env['SETTINGS_SECRET_ENCRYPTION_KEY']
148
+ ? { secretEncryptionKey: process.env['SETTINGS_SECRET_ENCRYPTION_KEY'] }
149
+ : {}),
150
+ });
151
+ const platform = {
152
+ orm,
153
+ em,
154
+ container,
155
+ eventBus,
156
+ commandBus,
157
+ auditLogService,
158
+ apiInterceptors,
159
+ redis,
160
+ redisSubscriber,
161
+ settings,
162
+ salesChannels,
163
+ };
164
+ await options.prepareDatabase?.(platform);
165
+ // Presence is seeded **before** the modules compose, not after: the enabled
166
+ // set is a precondition for every gated resolution, and a converted
167
+ // module's port is resolved as soon as something asks for it. While no
168
+ // module provided a port the late seeding was invisible; the first one that
169
+ // did turned it into `Module 'auth' is currently disabled` on a platform
170
+ // where nothing was disabled.
171
+ registryCache.setActivationDeclarations(activationDeclarationsFrom(manifests));
172
+ // Feature 132 — the derived capability families, from the same manifest list
173
+ // and at the same point the production root installs them
174
+ // (`loadModulePresence`). A harness that seeded presence but not the families
175
+ // would answer an empty family for every key, so a family owner's exclusion
176
+ // interceptor would be registered over nobody and every test of it would be
177
+ // green over a population it never had.
178
+ registryCache.setCapabilityDeclarations(capabilityRegistryFrom(manifests));
179
+ registryCache.__setEnabledForTesting([
180
+ ...(options.enabledModuleIds ?? manifests.map((manifest) => manifest.id)),
181
+ ]);
182
+ // The host values this root owns outright. No module registers a default
183
+ // for any of them, so they have no contribution window and are registered
184
+ // where the value comes into existence.
185
+ registerValues(container, {
186
+ redis,
187
+ redisSubscriber,
188
+ eventBus,
189
+ commandBus,
190
+ auditLogService,
191
+ apiInterceptors,
192
+ resolvedModuleRegistry: composition.manifests,
193
+ // Whether this process runs queue consumers — the platform's one
194
+ // module-agnostic switch (`specs/134-paid-module-extraction/` research
195
+ // D16). A harness runs none: every test process shares the queues.
196
+ processRunsWorkers: false,
197
+ ...options.values,
198
+ });
199
+ const composed = composeModules(composition.modules, {
200
+ container,
201
+ eventBus,
202
+ log: platformLogger(),
203
+ interceptorRegistry: apiInterceptors,
204
+ ownership: registrationOwnership,
205
+ // Issue #258 — derived from the same manifest set presence was seeded
206
+ // from, so a caller that withdraws a required module meets the refusal
207
+ // production would meet, at the point production meets it.
208
+ requiredModules: requiredModulesFrom(manifests),
209
+ // Feature 107 — the caller's declaration, forwarded unchanged. A root
210
+ // that resolves an ambiguity in production and not in its tests composes
211
+ // a different platform on the one axis a deployment can change.
212
+ decorationOrder: options.decorationOrder,
213
+ });
214
+ const composedContext = { ...platform, composed };
215
+ const buildTenantContext = options.buildTenantContext ??
216
+ (async (request) => systemTenantContext(`test-kit:${request.method} ${request.url}`));
217
+ // The sub-kernel names this composer composed the sub-kernels **for**,
218
+ // mirroring `compose-app.ts`' own contribution block.
219
+ //
220
+ // **They belong to the composer and not to the caller**, and the reason is
221
+ // measured rather than tidy: this function composes the sales-channel kernel
222
+ // itself — it has to, the subscriber ordering depends on it happening above
223
+ // `composeModules` — so a caller could only re-derive from a `salesChannels`
224
+ // it did not build. It did not, and the four names were absent from every
225
+ // container the kit composed: `inventory`'s channel-scoped stock read,
226
+ // `payment_methods`' and `delivery_methods`' auto-bind and every
227
+ // channel-bridge declaration each failed with `AwilixResolutionError` on
228
+ // their first call, in a composition that had booted cleanly.
229
+ //
230
+ // It stayed invisible in this repository because its reference harness
231
+ // registers all four itself under a comment reading *"mirrors
232
+ // `compose-app.ts`"* — the one caller that would have noticed had already
233
+ // worked around it. An out-of-tree host found them one failed boot at a
234
+ // time. `registerValues` overwrites, and the harness contributes identical
235
+ // values, so that workaround stays correct while it is being removed.
236
+ //
237
+ // First, so a caller that genuinely wants a different answer for one of them
238
+ // can still say so in `options.contribute` — the ordering `compose-app.ts`
239
+ // gives its own twenty for the same reason.
240
+ composed.contribute({
241
+ salesChannelsCache: salesChannels.cache,
242
+ salesChannelBridgeRegistry: salesChannels.bridgeRegistry,
243
+ salesChannelMembershipPort: salesChannels.membershipService,
244
+ salesChannelResolutionPort: salesChannels.resolver,
245
+ // The channel a **channel-scoped** settings read resolves against outside
246
+ // a request — worker, boot hook, CLI. D-48: the resolver cannot fail to
247
+ // find a default, so this cannot answer "none"; the return type keeps
248
+ // `null` because the *seam* still admits one.
249
+ settingsChannelResolver: async () => (await salesChannels.resolver.getSystemDefault()).id,
250
+ });
251
+ // The module test-support substitutions, then the caller's own: a test that
252
+ // wants a different stub than its module's default says so at its own call
253
+ // site, which is where the coupling is visible.
254
+ const supportRegistrations = mergeTestSupportRegistrations(composition.testSupport ?? []);
255
+ if (Object.keys(supportRegistrations).length > 0)
256
+ composed.contribute(supportRegistrations);
257
+ await options.contribute?.(composedContext);
258
+ await options.beforeBoot?.(composedContext);
259
+ // Assembled here rather than above the contribution window, and that is a
260
+ // guarantee rather than a placement: a caller adds to `options.plugins` or
261
+ // `options.scopedPlugins` from inside `contribute`, where the value a plugin
262
+ // needs finally exists. The reference application's harness pushes the
263
+ // sales-channel plugin and a test's `extraModules` exactly there.
264
+ const modules = [
265
+ // D-229 — the platform's liveness and readiness probe, in the position
266
+ // `composeApp` keeps it in. Not a module any more, and not a copy: the
267
+ // factory is the platform's, so the route this harness serves is the one
268
+ // production serves. `health_checks` existed in production and nowhere
269
+ // else for years because this root and that one wired it separately.
270
+ healthRoutePlugin({ orm, redis }),
271
+ // Every module's route contribution, in the composer's order, ahead of
272
+ // the root plugins for the same reason production keeps them there.
273
+ ...composed.sink.plugins,
274
+ async (app) => {
275
+ for (const plugin of composed.sink.rootPlugins)
276
+ await plugin(app);
277
+ },
278
+ ...(options.plugins ?? []),
279
+ async (app) => {
280
+ // Principle XI. The same hook factory the production composition root
281
+ // uses — two hand-written copies is how the request seam gets a leak
282
+ // that no test can see, and a composition without it is one under which
283
+ // every tenant-scope assertion passes for the wrong reason.
284
+ await registerRequestScopeHook(app, { buildTenantContext });
285
+ },
286
+ ...(options.scopedPlugins ?? []),
287
+ ];
288
+ // The explicit boot phase, run **once**, after every registration and every
289
+ // contribution and immediately before the app is built — exactly where a
290
+ // production composition root runs it (D-45). A boot hook may therefore
291
+ // resolve anything. It is also what closes the contribution window.
292
+ await composed.runBootHooks();
293
+ const app = await buildServer({
294
+ sessionCookieSecret: 'test-secret-do-not-use-in-production',
295
+ openApi: { title: 'Endora Commerce API (test)', version: 'test', serverUrl: 'http://localhost' },
296
+ disableRateLimit: true,
297
+ ...options.server,
298
+ modules,
299
+ apiInterceptors,
300
+ });
301
+ await app.ready();
302
+ await options.afterReady?.(composedContext, app);
303
+ return {
304
+ app,
305
+ orm,
306
+ em,
307
+ container,
308
+ eventBus,
309
+ commandBus,
310
+ auditLogService,
311
+ apiInterceptors,
312
+ redis,
313
+ redisSubscriber,
314
+ pubSubArmed,
315
+ settings,
316
+ salesChannels,
317
+ composed,
318
+ closeOrm: () => composition.orm.close(),
319
+ };
320
+ }
321
+ catch (error) {
322
+ await release();
323
+ throw error;
324
+ }
325
+ }
326
+ /**
327
+ * Take the composed platform down.
328
+ *
329
+ * Every step is here rather than in a caller's own teardown for the reason
330
+ * `check:harness-teardown` refuses a hand-written one: a copy of this sequence
331
+ * is frozen at the moment it was copied, so it cannot learn about the container
332
+ * or the second Redis client, and both leak for the length of the run.
333
+ */
334
+ export async function teardownTestServer(handle) {
335
+ await handle.app.close();
336
+ // Runs every registration's disposer and drops the resolution cache, so a
337
+ // file's composed services do not outlive its server.
338
+ await handle.container.dispose();
339
+ handle.redis.disconnect();
340
+ // Unsubscribe and drop listeners **before** disconnecting. A subscribed
341
+ // client that is merely disconnected keeps its subscription set, and ioredis
342
+ // re-establishes it on any reconnect.
343
+ handle.redisSubscriber.removeAllListeners('message');
344
+ // Only when something actually subscribed: `unsubscribe()` on a client that
345
+ // never entered subscriber mode rejects asynchronously from ioredis's socket
346
+ // close handler — a rejection no `try` around this call can catch, which
347
+ // surfaces as an unhandled rejection failing otherwise-green runs.
348
+ if (handle.pubSubArmed) {
349
+ try {
350
+ await handle.redisSubscriber.unsubscribe();
351
+ }
352
+ catch {
353
+ // Already closed — nothing left to unsubscribe from.
354
+ }
355
+ }
356
+ handle.redisSubscriber.disconnect();
357
+ await handle.closeOrm();
358
+ }
359
+ //# sourceMappingURL=compose-test-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-test-server.js","sourceRoot":"","sources":["../../src/server/compose-test-server.ts"],"names":[],"mappings":"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"}