@onlineapps/conn-orch-validator 6.0.1 → 8.0.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/CHANGELOG.md +2591 -2
- package/README.md +1075 -7
- package/docs/DESIGN.md +3 -1
- package/manifests/biz-service.manifest.json +658 -0
- package/manifests/library.manifest.json +324 -0
- package/package.json +12 -6
- package/src/CookbookTestRunner.js +422 -104
- package/src/CookbookTestUtils.js +7 -8
- package/src/ServiceReadinessValidator.js +78 -42
- package/src/ValidationOrchestrator.js +298 -75
- package/src/cli/biz-ci-gate.js +176 -33
- package/src/cli/oa-lint-scripts.js +221 -0
- package/src/cli/oa-sync-template.js +1020 -0
- package/src/cli/oa-validate.js +474 -0
- package/src/helpers/README.md +12 -2
- package/src/helpers/createServiceReadinessTests.js +75 -6
- package/src/index.js +33 -3
- package/src/lint/scripts/lintScripts.js +298 -0
- package/src/manifest/checks/composeRunnerBlock.js +222 -0
- package/src/manifest/checks/composeShape.js +165 -0
- package/src/manifest/checks/contractBridge.js +181 -0
- package/src/manifest/checks/discoveryOrphan.js +50 -0
- package/src/manifest/checks/docsLintBridge.js +553 -0
- package/src/manifest/checks/fileAbsent.js +35 -0
- package/src/manifest/checks/gitTracked.js +204 -0
- package/src/manifest/checks/index.js +111 -0
- package/src/manifest/checks/libraryContext.js +226 -0
- package/src/manifest/checks/libraryDocs.js +75 -0
- package/src/manifest/checks/libraryPackage.js +272 -0
- package/src/manifest/checks/librarySource.js +274 -0
- package/src/manifest/checks/libraryTests.js +121 -0
- package/src/manifest/checks/libraryWorkspace.js +293 -0
- package/src/manifest/checks/readmeRegion.js +135 -0
- package/src/manifest/checks/scriptHeaders.js +79 -0
- package/src/manifest/checks/serviceConfig.js +390 -0
- package/src/manifest/checks/serviceConnectors.js +81 -0
- package/src/manifest/checks/serviceDb.js +388 -0
- package/src/manifest/checks/serviceFiles.js +754 -0
- package/src/manifest/checks/serviceIdentityRows.js +351 -0
- package/src/manifest/checks/serviceRuntime.js +295 -0
- package/src/manifest/checks/serviceScripts.js +213 -0
- package/src/manifest/deployabilitySignal.js +121 -0
- package/src/manifest/discovery.js +386 -0
- package/src/manifest/loadManifest.js +62 -0
- package/src/manifest/manifestShape.js +446 -0
- package/src/manifest/report.js +245 -0
- package/src/manifest/runManifest.js +449 -0
- package/src/manifest/serviceIdentity.js +140 -0
- package/src/manifest/walk.js +74 -0
- package/src/manifest/workspaceRoot.js +242 -0
- package/src/mocks/MockMQClient.js +13 -30
- package/src/mocks/MockRegistry.js +4 -2
- package/src/mocks/MockStorage.js +4 -2
- package/src/sync/docsRegion.js +463 -0
- package/src/sync/generatedRegion.js +228 -0
- package/src/sync/readmeLocation.js +182 -0
- package/src/sync/readmePointer.js +477 -0
- package/src/sync/serviceTemplate.js +583 -0
- package/src/sync/sharedEnv.js +162 -0
- package/src/sync/uniformFiles.js +474 -0
- package/src/utils/bizCiGateContract.js +131 -7
- package/src/utils/connectorContract.js +97 -7
- package/src/utils/cookbookFormat.js +81 -40
- package/src/utils/deployContract.js +213 -13
- package/src/utils/envContract.js +57 -1
- package/src/utils/handlerRef.js +181 -0
- package/src/utils/installContract.js +287 -41
- package/src/utils/libCompat.js +29 -7
- package/src/utils/migrationOrder.js +163 -0
- package/src/utils/preValidation.js +20 -7
- package/src/utils/setupDatabase.js +194 -13
- package/src/utils/testCoverageContract.js +539 -0
- package/src/utils/testNamespace.js +247 -23
- package/src/utils/throwawaySchema.js +207 -0
- package/src/validators/ServiceStructureValidator.js +21 -20
- package/templates/business-service/.dockerignore +42 -0
- package/templates/business-service/.gitlab-ci.yml +290 -0
- package/templates/business-service/Dockerfile +27 -0
- package/templates/business-service/README.md +213 -0
- package/templates/business-service/config/biz-docs-lint.tree.json +10 -0
- package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +4 -0
- package/templates/business-service/config/env-templates/shared.env +65 -0
- package/templates/business-service/config/service/config.json +14 -0
- package/templates/business-service/config/service/integration-contract.json +12 -0
- package/templates/business-service/config/service/operations.json +41 -0
- package/templates/business-service/docker-compose.production.yml +60 -0
- package/templates/business-service/docker-compose.yml +93 -0
- package/templates/business-service/docs/80-setup/INSTALL.md +101 -0
- package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +65 -0
- package/templates/business-service/docs/80-setup/README.md +18 -0
- package/templates/business-service/docs/80-setup/VALIDATION.md +78 -0
- package/templates/business-service/docs/README.md +18 -0
- package/templates/business-service/gitignore +42 -0
- package/templates/business-service/index.js +10 -0
- package/templates/business-service/init.sh +54 -0
- package/templates/business-service/jest.config.js +6 -0
- package/templates/business-service/package.json.template +31 -0
- package/templates/business-service/scripts/verify-deploy-uniform.sh +180 -0
- package/templates/business-service/src/handlers/v3/echo.js +39 -0
- package/templates/business-service/tests/cookbooks/echo.json +36 -0
- package/templates/business-service/tests/unit/handler.test.js +78 -0
- package/src/WorkflowTestRunner.js +0 -402
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where the `from:` references of the manifest are resolved.
|
|
5
|
+
*
|
|
6
|
+
* Two facts, and they are not the same one:
|
|
7
|
+
*
|
|
8
|
+
* - the **api checkout** — the repository this package is part of, which owns
|
|
9
|
+
* the platform SSOT (`config/services.json`, `.nvmrc`, `config/libraries.json`);
|
|
10
|
+
* - the **workspace** — the directory holding that checkout and its siblings
|
|
11
|
+
* (`api_biz/*`, `fe_adminui`), which exist in local development and in
|
|
12
|
+
* nothing else.
|
|
13
|
+
*
|
|
14
|
+
* `api/` at the head of a manifest path is a CONVENTION of the declaring text,
|
|
15
|
+
* never the name of a directory on disk. GitLab CI checks the repository out
|
|
16
|
+
* under the project name (`infra-mono`), so joining the literal string onto the
|
|
17
|
+
* workspace resolves to a directory that is not there: measured 2026-09-09,
|
|
18
|
+
* `--library --all --workspace "$CI_PROJECT_DIR/.."` died with
|
|
19
|
+
* `[ManifestWorkspace] Workspace root does not carry api/config/services.json`,
|
|
20
|
+
* and every workspace row was reported NOT RUN — a green job that had checked
|
|
21
|
+
* nothing. `scripts/ci/lint-biz-docs.mjs` (§ resolveWorkspaceRelative) already
|
|
22
|
+
* answers this for the doc tree by deriving its `API_ROOT` from the script's own
|
|
23
|
+
* location; the rule here is the same one, for the same reason.
|
|
24
|
+
*
|
|
25
|
+
* So the api checkout is derived from THIS FILE'S location — the directory that
|
|
26
|
+
* carries this package as `shared/connector/<package>` and carries the marker —
|
|
27
|
+
* and the workspace is its parent. An explicit `--workspace` still wins, and
|
|
28
|
+
* fails fast when the root it names holds no api checkout. `oa-validate` asks
|
|
29
|
+
* for nothing else: which SSOT it reads no longer depends on where the shell
|
|
30
|
+
* happened to stand, nor on what the checkout is named (`automation-gates.md`
|
|
31
|
+
* §1.1, predictable).
|
|
32
|
+
*
|
|
33
|
+
* The upward search survives as `workspaceAbove`, for the callers whose subject
|
|
34
|
+
* IS a tree rather than the platform — `oa-sync-template --target <service>`,
|
|
35
|
+
* which writes into that tree, and the in-service `ValidationOrchestrator`,
|
|
36
|
+
* whose subject is the service it was constructed with. Those callers pass
|
|
37
|
+
* `startDir` and get the workspace THAT TREE belongs to. It is a different
|
|
38
|
+
* question, asked explicitly, never a fallback of the other one.
|
|
39
|
+
*
|
|
40
|
+
* An INSTALLED copy (`<service>/node_modules/@onlineapps/conn-orch-validator`)
|
|
41
|
+
* lies in no checkout, so it resolves nothing and the rows that need the SSOT
|
|
42
|
+
* are reported NOT RUN — loudly, never as a pass (`automation-gates.md` §5).
|
|
43
|
+
* That is what a service container has, and it is unchanged by this rule.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
const fs = require('fs');
|
|
47
|
+
const path = require('path');
|
|
48
|
+
|
|
49
|
+
/** The prefix every manifest path uses for the api checkout. */
|
|
50
|
+
const API_PREFIX = 'api';
|
|
51
|
+
|
|
52
|
+
/** What identifies an api checkout, whatever the checkout is named. */
|
|
53
|
+
const API_MARKER = 'config/services.json';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The same marker as a workspace-relative path — the shape the manifest and the
|
|
57
|
+
* CLIs write it in. Kept as the exported name it has always had.
|
|
58
|
+
*/
|
|
59
|
+
const WORKSPACE_MARKER = `${API_PREFIX}/${API_MARKER}`;
|
|
60
|
+
|
|
61
|
+
/** Where this package sits inside an api checkout, from the checkout down. */
|
|
62
|
+
const PACKAGE_LOCATION = ['shared', 'connector'];
|
|
63
|
+
|
|
64
|
+
/** This package's root: `<checkout>/shared/connector/<package>`. */
|
|
65
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
66
|
+
|
|
67
|
+
const carriesMarker = (dir) => fs.existsSync(path.join(dir, ...API_MARKER.split('/')));
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* One directory, one name: the path with every symlink resolved.
|
|
71
|
+
*
|
|
72
|
+
* Two roots reach a run by two different routes. The workspace root is derived
|
|
73
|
+
* from THIS FILE'S location, and node resolves a module's own path through its
|
|
74
|
+
* symlinks; the service or package root is an argument, and `path.resolve()`
|
|
75
|
+
* keeps the spelling the caller typed. On macOS `/var` is a symlink to
|
|
76
|
+
* `/private/var`, so a run over a directory below the system temp gets one root
|
|
77
|
+
* in each spelling — and "is the service inside the workspace", which is a
|
|
78
|
+
* string prefix, then answers no about a service that is plainly inside it.
|
|
79
|
+
*
|
|
80
|
+
* Measured 2026-09-09 over the pre-push export of this repository
|
|
81
|
+
* (`api/tests/scripts/library-uniform-hook.bats`, "over an export of this
|
|
82
|
+
* repository the sibling rows are NOT RUN"): the package root came back
|
|
83
|
+
* `/var/folders/…/export/api/shared/mq-client-core`, the workspace root
|
|
84
|
+
* `/private/var/folders/…/export`, and all six rows were reported `service root
|
|
85
|
+
* is outside the workspace root` — a sentence that was false, over a run whose
|
|
86
|
+
* real findings were never raised. A NOT RUN nobody can act on is the false
|
|
87
|
+
* guarantee of `automation-gates.md` §5.
|
|
88
|
+
*
|
|
89
|
+
* So every root passes through here BEFORE it is compared or printed, and it is
|
|
90
|
+
* one function rather than a `realpathSync` at each call site, because the
|
|
91
|
+
* fourth call site is the one that would forget (`change-discipline.md` § One
|
|
92
|
+
* rail per concern).
|
|
93
|
+
*
|
|
94
|
+
* A path that does not exist is NOT canonicalised into something else: the
|
|
95
|
+
* caller that owns the message about a missing root says it in its own words,
|
|
96
|
+
* so this refuses rather than guesses (`architecture-principles.md` §3).
|
|
97
|
+
*
|
|
98
|
+
* @param {string} dir an absolute or relative path to an existing directory
|
|
99
|
+
* @returns {string} the same directory, with no symlink left in the path
|
|
100
|
+
*/
|
|
101
|
+
function canonicalRoot(dir) {
|
|
102
|
+
if (typeof dir !== 'string' || dir.length === 0) {
|
|
103
|
+
throw new Error(`[ManifestWorkspace] Root path is required - canonicalRoot() got ${JSON.stringify(dir)}. `
|
|
104
|
+
+ 'Fix: pass the directory to canonicalise.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
return fs.realpathSync(path.resolve(dir));
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
throw new Error(`[ManifestWorkspace] Root does not exist - ${path.resolve(dir)} cannot be resolved, `
|
|
111
|
+
+ 'so it cannot be compared with the other roots of this run. '
|
|
112
|
+
+ 'Fix: pass an existing directory.', { cause });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The api checkout this package is part of, or `null` when it is part of none.
|
|
118
|
+
*
|
|
119
|
+
* Both conditions must hold, and neither is guessed at: the package lies where a
|
|
120
|
+
* checkout puts it, and the directory above carries the marker. An installed
|
|
121
|
+
* copy fails the first, a stray copy fails the second.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} packageRoot
|
|
124
|
+
* @returns {string|null}
|
|
125
|
+
*/
|
|
126
|
+
function checkoutCarrying(packageRoot) {
|
|
127
|
+
const segments = path.resolve(packageRoot).split(path.sep);
|
|
128
|
+
const parents = segments.slice(-1 - PACKAGE_LOCATION.length, -1);
|
|
129
|
+
if (parents.join('/') !== PACKAGE_LOCATION.join('/')) return null;
|
|
130
|
+
|
|
131
|
+
const candidate = path.resolve(packageRoot, ...PACKAGE_LOCATION.map(() => '..'), '..');
|
|
132
|
+
return carriesMarker(candidate) ? candidate : null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The api checkout this run speaks for, decided once, from this file's location. */
|
|
136
|
+
const API_CHECKOUT_ROOT = checkoutCarrying(PACKAGE_ROOT);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The api checkout of a given workspace: this package's own when the workspace
|
|
140
|
+
* is the one it lives in, and otherwise the checkout the prefix names literally
|
|
141
|
+
* — a fixture workspace, or any tree a `--workspace` points at.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} workspaceRoot
|
|
144
|
+
* @returns {string|null}
|
|
145
|
+
*/
|
|
146
|
+
function apiCheckoutOf(workspaceRoot) {
|
|
147
|
+
const root = path.resolve(workspaceRoot);
|
|
148
|
+
if (API_CHECKOUT_ROOT !== null && path.dirname(API_CHECKOUT_ROOT) === root) return API_CHECKOUT_ROOT;
|
|
149
|
+
|
|
150
|
+
const named = path.join(root, API_PREFIX);
|
|
151
|
+
return carriesMarker(named) ? named : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolve a manifest path — `api/config/services.json`, `api_biz/*/package.json`
|
|
156
|
+
* — to an absolute path under `workspaceRoot`.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} workspaceRoot
|
|
159
|
+
* @param {string} relative a `/`-separated workspace-relative path
|
|
160
|
+
* @returns {string}
|
|
161
|
+
*/
|
|
162
|
+
function resolveWorkspacePath(workspaceRoot, relative) {
|
|
163
|
+
if (typeof workspaceRoot !== 'string' || workspaceRoot.length === 0) {
|
|
164
|
+
throw new Error('[ManifestWorkspace] Workspace root is required - resolveWorkspacePath() got '
|
|
165
|
+
+ `${JSON.stringify(workspaceRoot)}. Fix: resolve it first (resolveWorkspaceRoot).`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const segments = String(relative).split('/').filter((segment) => segment.length > 0);
|
|
169
|
+
if (segments[0] !== API_PREFIX) return path.join(workspaceRoot, ...segments);
|
|
170
|
+
|
|
171
|
+
const apiRoot = apiCheckoutOf(workspaceRoot);
|
|
172
|
+
if (apiRoot === null) {
|
|
173
|
+
throw new Error(`[ManifestWorkspace] Workspace root holds no api checkout - ${path.resolve(workspaceRoot)} `
|
|
174
|
+
+ `carries no directory with ${API_MARKER}, so "${relative}" resolves to nothing. `
|
|
175
|
+
+ 'Fix: pass --workspace <root> holding the api checkout (under any name).');
|
|
176
|
+
}
|
|
177
|
+
return path.join(apiRoot, ...segments.slice(1));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The workspace a given tree belongs to: the nearest ancestor carrying the
|
|
182
|
+
* marker under the name the prefix writes. This answers a DIFFERENT question
|
|
183
|
+
* from the one above — not "which SSOT does this package speak for" but "which
|
|
184
|
+
* workspace is this tree part of" — and it is the question a run that writes
|
|
185
|
+
* INTO a tree has to ask (`oa-sync-template --target <service>`, and the
|
|
186
|
+
* in-service `ValidationOrchestrator` step, whose subject is the service it was
|
|
187
|
+
* constructed with). A caller states which question it is asking by passing
|
|
188
|
+
* `startDir` or leaving it out; neither is a default of the other.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} startDir
|
|
191
|
+
* @returns {string|null}
|
|
192
|
+
*/
|
|
193
|
+
function workspaceAbove(startDir) {
|
|
194
|
+
let current = path.resolve(startDir);
|
|
195
|
+
for (;;) {
|
|
196
|
+
if (fs.existsSync(path.join(current, ...WORKSPACE_MARKER.split('/')))) return current;
|
|
197
|
+
const parent = path.dirname(current);
|
|
198
|
+
if (parent === current) return null;
|
|
199
|
+
current = parent;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @param {{ explicit?: string|null, startDir?: string|null }} params
|
|
205
|
+
* @returns {string|null} absolute workspace root, or null when neither the named
|
|
206
|
+
* tree nor this package lies in one
|
|
207
|
+
*/
|
|
208
|
+
function resolveWorkspaceRoot({ explicit = null, startDir = null } = {}) {
|
|
209
|
+
if (explicit !== null && explicit !== undefined) {
|
|
210
|
+
const resolved = path.resolve(explicit);
|
|
211
|
+
if (apiCheckoutOf(resolved) === null) {
|
|
212
|
+
throw new Error(`[ManifestWorkspace] Workspace root does not carry ${WORKSPACE_MARKER} - ${resolved}, `
|
|
213
|
+
+ 'and this package\'s own checkout is not a directory under it. '
|
|
214
|
+
+ `Fix: pass --workspace <root> holding the api checkout — under any name, carrying ${API_MARKER}.`);
|
|
215
|
+
}
|
|
216
|
+
return canonicalRoot(resolved);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (startDir !== null && startDir !== undefined) {
|
|
220
|
+
if (typeof startDir !== 'string' || startDir.length === 0) {
|
|
221
|
+
throw new Error('[ManifestWorkspace] Start directory is required - resolveWorkspaceRoot({ startDir }) got '
|
|
222
|
+
+ `${JSON.stringify(startDir)}. Fix: pass the tree the run is about, or omit it to use this `
|
|
223
|
+
+ 'package\'s own checkout.');
|
|
224
|
+
}
|
|
225
|
+
const above = workspaceAbove(startDir);
|
|
226
|
+
return above === null ? null : canonicalRoot(above);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return API_CHECKOUT_ROOT === null ? null : canonicalRoot(path.dirname(API_CHECKOUT_ROOT));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
module.exports = {
|
|
233
|
+
resolveWorkspaceRoot,
|
|
234
|
+
canonicalRoot,
|
|
235
|
+
workspaceAbove,
|
|
236
|
+
resolveWorkspacePath,
|
|
237
|
+
apiCheckoutOf,
|
|
238
|
+
API_CHECKOUT_ROOT,
|
|
239
|
+
API_MARKER,
|
|
240
|
+
PACKAGE_ROOT,
|
|
241
|
+
WORKSPACE_MARKER
|
|
242
|
+
};
|
|
@@ -61,8 +61,10 @@ class MockMQClient {
|
|
|
61
61
|
*/
|
|
62
62
|
async publish(queue, message, options = {}) {
|
|
63
63
|
if (!this._connected) {
|
|
64
|
-
//
|
|
65
|
-
|
|
64
|
+
// The mock states the same refusal as BaseClient.publish (ConnectionError) under
|
|
65
|
+
// its own context — it does not copy that text, so the two can never be mistaken
|
|
66
|
+
// for one another in a log.
|
|
67
|
+
throw new Error('[MockMQClient] Cannot publish - client is not connected. Fix: call connect() first');
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
if (!this.queues[queue]) {
|
|
@@ -79,32 +81,13 @@ class MockMQClient {
|
|
|
79
81
|
this.queues[queue].push(messageWrapper);
|
|
80
82
|
this.publishedMessages.push({ queue, message, options, timestamp: Date.now() });
|
|
81
83
|
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
queue.endsWith('.request') &&
|
|
90
|
-
message &&
|
|
91
|
-
typeof message === 'object' &&
|
|
92
|
-
message.workflow_id &&
|
|
93
|
-
message.step_id
|
|
94
|
-
) {
|
|
95
|
-
const responseQueue = `workflow.${message.workflow_id}.response`;
|
|
96
|
-
const response = {
|
|
97
|
-
step_id: message.step_id,
|
|
98
|
-
result: {
|
|
99
|
-
processed: true,
|
|
100
|
-
operation: message.operation,
|
|
101
|
-
input: message.input,
|
|
102
|
-
output: { mocked: true }
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
// Enqueue response (does not recurse into auto-reply because queue prefix differs)
|
|
106
|
-
await this.publish(responseQueue, response);
|
|
107
|
-
}
|
|
84
|
+
// The auto-reply that used to sit here — publish to service.*.request, get a
|
|
85
|
+
// synthetic `workflow.<workflow_id>.response` back — existed for exactly one
|
|
86
|
+
// reader, and said so in its own comment: WorkflowTestRunner's unit tests.
|
|
87
|
+
// That rail was deleted on 2026-09-05, and nothing else in the workspace
|
|
88
|
+
// publishes or consumes those queues through this mock (measured: api,
|
|
89
|
+
// api_biz/*, fe_adminui). A mock that invents traffic nobody reads is not
|
|
90
|
+
// harmless — it makes a test look end-to-end when nothing answered.
|
|
108
91
|
|
|
109
92
|
// Trigger consumers if any
|
|
110
93
|
if (this.consumers[queue]) {
|
|
@@ -122,8 +105,8 @@ class MockMQClient {
|
|
|
122
105
|
*/
|
|
123
106
|
async consume(queue, callback, options = {}) {
|
|
124
107
|
if (!this._connected) {
|
|
125
|
-
//
|
|
126
|
-
throw new Error('Cannot consume
|
|
108
|
+
// Same shape as publish() above, and deliberately not a copy of BaseClient.consume.
|
|
109
|
+
throw new Error('[MockMQClient] Cannot consume - client is not connected. Fix: call connect() first');
|
|
127
110
|
}
|
|
128
111
|
|
|
129
112
|
this.consumers[queue] = {
|
|
@@ -18,7 +18,8 @@ class MockRegistry {
|
|
|
18
18
|
const { name, version, url, healthCheck, openapi } = serviceData;
|
|
19
19
|
|
|
20
20
|
if (!name) {
|
|
21
|
-
throw new Error('Service name is required'
|
|
21
|
+
throw new Error('[MockRegistry] Service name is required - Expected serviceData.name to be a '
|
|
22
|
+
+ 'non-empty string. Fix: pass { name } to register().');
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
this.services[name] = {
|
|
@@ -60,7 +61,8 @@ class MockRegistry {
|
|
|
60
61
|
*/
|
|
61
62
|
async heartbeat(serviceName) {
|
|
62
63
|
if (!this.services[serviceName]) {
|
|
63
|
-
throw new Error(`Service ${serviceName} not registered`
|
|
64
|
+
throw new Error(`[MockRegistry] Service ${serviceName} not registered - Expected a prior `
|
|
65
|
+
+ 'register() call for that name. Fix: register the service before sending a heartbeat.');
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
this.heartbeats[serviceName] = {
|
package/src/mocks/MockStorage.js
CHANGED
|
@@ -59,7 +59,8 @@ class MockStorage {
|
|
|
59
59
|
*/
|
|
60
60
|
async get(bucket, key) {
|
|
61
61
|
if (!this.buckets[bucket] || !this.buckets[bucket][key]) {
|
|
62
|
-
throw new Error(`Object not found: ${bucket}/${key}`
|
|
62
|
+
throw new Error(`[MockStorage] Object not found: ${bucket}/${key} - Expected the object to have `
|
|
63
|
+
+ 'been stored first. Fix: put(bucket, key, content) in the test setup before reading it.');
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
const obj = this.buckets[bucket][key];
|
|
@@ -128,7 +129,8 @@ class MockStorage {
|
|
|
128
129
|
*/
|
|
129
130
|
async getMetadata(bucket, key) {
|
|
130
131
|
if (!this.metadata[bucket] || !this.metadata[bucket][key]) {
|
|
131
|
-
throw new Error(`Metadata not found: ${bucket}/${key}`
|
|
132
|
+
throw new Error(`[MockStorage] Metadata not found: ${bucket}/${key} - Expected metadata written `
|
|
133
|
+
+ 'by put(). Fix: store the object with put(bucket, key, content) before reading its metadata.');
|
|
132
134
|
}
|
|
133
135
|
return this.metadata[bucket][key];
|
|
134
136
|
}
|