@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.
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/database/index.d.ts +17 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +17 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/lease.d.ts +115 -0
- package/dist/database/lease.d.ts.map +1 -0
- package/dist/database/lease.js +204 -0
- package/dist/database/lease.js.map +1 -0
- package/dist/database/provision.d.ts +135 -0
- package/dist/database/provision.d.ts.map +1 -0
- package/dist/database/provision.js +457 -0
- package/dist/database/provision.js.map +1 -0
- package/dist/database/run-isolation.d.ts +356 -0
- package/dist/database/run-isolation.d.ts.map +1 -0
- package/dist/database/run-isolation.js +490 -0
- package/dist/database/run-isolation.js.map +1 -0
- package/dist/server/compose-test-server.d.ts +251 -0
- package/dist/server/compose-test-server.d.ts.map +1 -0
- package/dist/server/compose-test-server.js +359 -0
- package/dist/server/compose-test-server.js.map +1 -0
- package/dist/server/composition.d.ts +112 -0
- package/dist/server/composition.d.ts.map +1 -0
- package/dist/server/composition.js +32 -0
- package/dist/server/composition.js.map +1 -0
- package/dist/server/index.d.ts +13 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +12 -0
- package/dist/server/index.js.map +1 -0
- package/dist/support/entity-index.d.ts +91 -0
- package/dist/support/entity-index.d.ts.map +1 -0
- package/dist/support/entity-index.js +56 -0
- package/dist/support/entity-index.js.map +1 -0
- package/dist/support/index.d.ts +134 -0
- package/dist/support/index.d.ts.map +1 -0
- package/dist/support/index.js +153 -0
- package/dist/support/index.js.map +1 -0
- 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":"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"}
|
|
@@ -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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"}
|