@onlineapps/conn-orch-validator 9.0.0 → 10.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 +373 -0
- package/README.md +83 -9
- package/docs/DESIGN.md +21 -7
- package/manifests/biz-service.manifest.json +28 -5
- package/package.json +2 -2
- package/src/CookbookTestRunner.js +84 -16
- package/src/ValidationOrchestrator.js +73 -20
- package/src/cli/biz-ci-gate.js +28 -14
- package/src/cli/oa-sync-template.js +23 -8
- package/src/cli/oa-validate.js +7 -1
- package/src/index.js +21 -13
- package/src/lint/scripts/lintScripts.js +65 -18
- package/src/manifest/checks/composeRunnerBlock.js +37 -20
- package/src/manifest/checks/discoveryOrphan.js +2 -1
- package/src/manifest/checks/docsLintBridge.js +79 -21
- package/src/manifest/checks/gitTracked.js +12 -1
- package/src/manifest/checks/libraryPackage.js +3 -1
- package/src/manifest/checks/libraryWorkspace.js +18 -3
- package/src/manifest/checks/readmeRegion.js +9 -1
- package/src/manifest/checks/serviceConfig.js +29 -12
- package/src/manifest/checks/serviceFiles.js +34 -7
- package/src/manifest/checks/serviceIdentityRows.js +3 -1
- package/src/manifest/checks/serviceRuntime.js +3 -1
- package/src/manifest/discovery.js +25 -7
- package/src/manifest/runManifest.js +58 -7
- package/src/manifest/workspaceRoot.js +91 -5
- package/src/sync/serviceTemplate.js +76 -7
- package/src/sync/sharedEnv.js +11 -4
- package/src/sync/uniformFiles.js +91 -21
- package/src/utils/bizCiGateContract.js +25 -1
- package/src/utils/installContract.js +46 -5
- package/src/utils/libCompat.js +39 -19
- package/src/utils/preValidation.js +56 -11
- package/src/utils/stepFailure.js +106 -19
- package/src/utils/testCoverageContract.js +60 -2
- package/src/utils/throwawaySchema.js +92 -7
- package/src/validatorIdentity.js +31 -0
- package/src/validators/ServiceStructureValidator.js +41 -15
- package/src/validators/ValidationProofGenerator.js +73 -34
- package/templates/business-service/.dockerignore +9 -1
- package/templates/business-service/.gitlab-ci.yml +91 -25
- package/templates/business-service/README.md +14 -5
- package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +17 -5
- package/templates/business-service/config/env-templates/shared.env +7 -1
- package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +1 -1
- package/templates/business-service/docs/80-setup/VALIDATION.md +1 -1
- package/templates/business-service/jest.config.js +9 -1
- package/templates/business-service/package.json.template +1 -1
- package/src/mocks/MockStorage.js +0 -188
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# --- oa-dockerignore v1
|
|
2
2
|
# Derived from templates/business-service/gitignore, the declaration .gitignore reads too —
|
|
3
3
|
# what a LOCAL production build must not copy into the image (being ignored by git
|
|
4
|
-
# excludes nothing from COPY . .)
|
|
4
|
+
# excludes nothing from COPY . .), PLUS the test tree: tests stay in the repository
|
|
5
|
+
# and out of the artefact that runs, the same decision the library uniform makes as
|
|
6
|
+
# L-PACK-TESTS. The one exception is tests/cookbooks, which is not a test but a
|
|
7
|
+
# declaration the runtime reads — Tier-1 of the boot runs those cookbooks at phase
|
|
8
|
+
# 0.2, and an image without them refuses its own validation proof as NO_TESTS.
|
|
9
|
+
# Everything above this block is this repository's own.
|
|
5
10
|
node_modules
|
|
6
11
|
**/node_modules
|
|
7
12
|
config/runtime
|
|
@@ -38,5 +43,8 @@ Thumbs.db
|
|
|
38
43
|
**/Thumbs.db
|
|
39
44
|
.oa_drive_deps_hash
|
|
40
45
|
**/.oa_drive_deps_hash
|
|
46
|
+
tests
|
|
47
|
+
**/tests
|
|
48
|
+
!tests/cookbooks
|
|
41
49
|
.git
|
|
42
50
|
# --- end oa-dockerignore v1
|
|
@@ -2,19 +2,28 @@
|
|
|
2
2
|
# Written by @onlineapps/conn-orch-validator (row G-CI of
|
|
3
3
|
# manifests/biz-service.manifest.json). The PLATFORM half of this pipeline: which
|
|
4
4
|
# pipelines run at all, how a service is built, how its image is identified (R5),
|
|
5
|
-
# how the digest reaches the deploy (R1/R2), the uniform gate that runs
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
5
|
+
# how the digest reaches the deploy (R1/R2), the uniform gate that runs on every
|
|
6
|
+
# pipeline (job validate-uniform, confirmation biz-service-manifest 010) and
|
|
7
|
+
# again before the SSH step as the binding instance (008), and the post-deploy
|
|
8
|
+
# gate that runs after it (confirmation deploy-gate-targets 003). The
|
|
9
|
+
# installation contract is checked by that uniform gate, in the manifest rows
|
|
10
|
+
# G-SETUP, D-DB-PACKAGE and D-DB-HEADERS, and no longer by a job of its own
|
|
11
|
+
# (d.470). npx oa-sync-template .gitlab-ci.yml --target . rewrites everything
|
|
12
|
+
# between these two markers and nothing outside them.
|
|
12
13
|
#
|
|
13
14
|
# Outside them is this repository's own: its test job - which database, which
|
|
14
15
|
# ci:gate:* steps and which artefacts its integration needs is a fact about the
|
|
15
16
|
# service - and whatever else it runs. The stages this block declares (test,
|
|
16
17
|
# build, secret-detection, deploy) cover those jobs too; measured over the eight
|
|
17
18
|
# biz repositories on 2026-09-11, none declares a fifth stage.
|
|
19
|
+
#
|
|
20
|
+
# One thing the platform can only RECOMMEND about that half, because it does not
|
|
21
|
+
# own it: give the service's own jobs the `devel` branch as well. The post-commit
|
|
22
|
+
# mirror pushes every commit there, and a `rules:` list naming only main,
|
|
23
|
+
# production and merge requests is why that branch produced no pipeline at all
|
|
24
|
+
# (010 point 2). The block does its side of it - validate-uniform and
|
|
25
|
+
# secret_detection run on devel; whether a service's own test job should too is
|
|
26
|
+
# the service's call.
|
|
18
27
|
include:
|
|
19
28
|
- template: Jobs/Secret-Detection.gitlab-ci.yml
|
|
20
29
|
|
|
@@ -33,6 +42,73 @@ variables:
|
|
|
33
42
|
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
|
34
43
|
IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest
|
|
35
44
|
|
|
45
|
+
# The layout and the runtime every uniform run needs, declared ONCE for the two
|
|
46
|
+
# jobs that run it: the continuous check below and the binding one in
|
|
47
|
+
# deploy-production (confirmations biz-service-manifest 008 and 010). A hidden
|
|
48
|
+
# key is a declaration and never a job - GitLab runs nothing whose name starts
|
|
49
|
+
# with a dot - so this adds no gate, it gives the two that exist one source.
|
|
50
|
+
.oa-uniform:
|
|
51
|
+
# The uniform runs in these jobs, so they need the runtime the pinned engine
|
|
52
|
+
# declares - the same major the test stage and the Dockerfile carry, which is
|
|
53
|
+
# what R3 of the deploy contract compares.
|
|
54
|
+
image: node:24-alpine
|
|
55
|
+
variables:
|
|
56
|
+
# The engine answers the rows that read the platform SSOT only for a service
|
|
57
|
+
# lying at <root>/api_biz/<service> beside <root>/api. GitLab decides where a
|
|
58
|
+
# project is checked out, so the job ASKS for the one place that IS that
|
|
59
|
+
# layout - no symlink (api rule: No Symlinks) and no second copy of this
|
|
60
|
+
# repository, which would leave two trees and no way to tell which one the
|
|
61
|
+
# verdict was about. Needs [runners.custom_build_dir] enabled on the runner.
|
|
62
|
+
#
|
|
63
|
+
# $CI_CONCURRENT_ID is the runner's slot number, and it is what keeps two
|
|
64
|
+
# pipelines of this project off each other's tree. Since 010 the uniform runs
|
|
65
|
+
# on EVERY pipeline, so two of them on one runner is the ordinary case: with
|
|
66
|
+
# one shared path the second run either refuses on an api clone it did not
|
|
67
|
+
# make, or measures the one the first run is still replacing. The slot sits
|
|
68
|
+
# ABOVE api_biz, so the sibling api/ still lands beside this checkout, which
|
|
69
|
+
# is the layout verify-deploy-uniform.sh checks.
|
|
70
|
+
GIT_CLONE_PATH: $CI_BUILDS_DIR/oa-uniform/$CI_CONCURRENT_ID/api_biz/__SERVICE_NAME__
|
|
71
|
+
# Where the platform SSOT is read from, and which state of it a run is
|
|
72
|
+
# measured against. Read access is granted by the api project's CI job-token
|
|
73
|
+
# allowlist - the owner's step, outside any repository (confirmation
|
|
74
|
+
# biz-service-manifest 008 point 2).
|
|
75
|
+
API_PROJECT_PATH: onlineapps/oadrive/infra-mono
|
|
76
|
+
API_UNIFORM_REF: production
|
|
77
|
+
|
|
78
|
+
# The uniform of THIS commit, on every pipeline - confirmation
|
|
79
|
+
# biz-service-manifest 010. Entry 008 places the BINDING run before the SSH step
|
|
80
|
+
# of a production deploy; it never said that is the only place it runs, and read
|
|
81
|
+
# that way it left a service unmeasured between two deploys. Measured on
|
|
82
|
+
# biz-converter 2026-09-16: no job ran the uniform outside the deploy job, the
|
|
83
|
+
# post-commit mirror pushed every commit to origin/devel and pipelines?ref=devel
|
|
84
|
+
# answered 0, and the last pipeline on main was 13 days old. So the same gate,
|
|
85
|
+
# the same bytes and the same arguments run here too, on a merge request, on
|
|
86
|
+
# main, on the devel mirror and on production - and the deploy job keeps its own
|
|
87
|
+
# run as the last, binding instance.
|
|
88
|
+
validate-uniform:
|
|
89
|
+
stage: test
|
|
90
|
+
extends: .oa-uniform
|
|
91
|
+
before_script:
|
|
92
|
+
# git: the gate clones api read-only beside this checkout. npm ci: the gate
|
|
93
|
+
# and the engine it runs are BOTH the ones this repository pinned, so the
|
|
94
|
+
# version measured is the version this service proved - never a floating
|
|
95
|
+
# download.
|
|
96
|
+
- apk add --no-cache git
|
|
97
|
+
- npm ci
|
|
98
|
+
script:
|
|
99
|
+
# The same invocation deploy-production makes. The script is not this
|
|
100
|
+
# repository's own file: no biz repository carries it (measured over all
|
|
101
|
+
# eight, 2026-09-16), and a copy generated into eight of them would be a
|
|
102
|
+
# second place to keep in step (change-discipline.md - One rail per
|
|
103
|
+
# concern). It comes from the package this repository pins, beside the
|
|
104
|
+
# engine it runs.
|
|
105
|
+
- sh node_modules/@onlineapps/conn-orch-validator/templates/business-service/scripts/verify-deploy-uniform.sh "$CI_PROJECT_DIR" "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/${API_PROJECT_PATH}.git" "$API_UNIFORM_REF"
|
|
106
|
+
rules:
|
|
107
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
108
|
+
- if: $CI_COMMIT_BRANCH == "main"
|
|
109
|
+
- if: $CI_COMMIT_BRANCH == "devel"
|
|
110
|
+
- if: $CI_COMMIT_BRANCH == "production"
|
|
111
|
+
|
|
36
112
|
build:
|
|
37
113
|
stage: build
|
|
38
114
|
image: docker:24
|
|
@@ -72,28 +148,18 @@ secret_detection:
|
|
|
72
148
|
rules:
|
|
73
149
|
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
74
150
|
- if: $CI_COMMIT_BRANCH == "main"
|
|
151
|
+
# The mirror branch too (010 point 2): a secret pushed there is pushed.
|
|
152
|
+
- if: $CI_COMMIT_BRANCH == "devel"
|
|
75
153
|
- if: $CI_COMMIT_BRANCH == "production"
|
|
76
154
|
|
|
77
155
|
deploy-production:
|
|
78
156
|
stage: deploy
|
|
79
|
-
# The uniform
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
|
|
157
|
+
# The BINDING uniform run happens here, before the SSH step (confirmation
|
|
158
|
+
# biz-service-manifest 008). The runtime it needs and the layout it is
|
|
159
|
+
# measured in are the shared declaration above - the same one validate-uniform
|
|
160
|
+
# reads, so the two runs cannot be measuring different things.
|
|
161
|
+
extends: .oa-uniform
|
|
83
162
|
variables:
|
|
84
|
-
# The engine answers the rows that read the platform SSOT only for a service
|
|
85
|
-
# lying at <root>/api_biz/<service> beside <root>/api. GitLab decides where a
|
|
86
|
-
# project is checked out, so the job ASKS for the one place that IS that
|
|
87
|
-
# layout — no symlink (api rule: No Symlinks) and no second copy of this
|
|
88
|
-
# repository, which would leave two trees and no way to tell which one the
|
|
89
|
-
# verdict was about. Needs [runners.custom_build_dir] enabled on the runner.
|
|
90
|
-
GIT_CLONE_PATH: $CI_BUILDS_DIR/oa-uniform/api_biz/__SERVICE_NAME__
|
|
91
|
-
# Where the platform SSOT is read from, and which state of it a production
|
|
92
|
-
# deploy is measured against. Read access is granted by the api project's CI
|
|
93
|
-
# job-token allowlist — the owner's step, outside any repository
|
|
94
|
-
# (confirmation biz-service-manifest 008 point 2).
|
|
95
|
-
API_PROJECT_PATH: onlineapps/oadrive/infra-mono
|
|
96
|
-
API_UNIFORM_REF: production
|
|
97
163
|
# Which checks judge this deploy. The gate has no default target: the job
|
|
98
164
|
# declares what it is measured by (confirmation deploy-gate-targets 002),
|
|
99
165
|
# and the name is the service's registry identity — the one
|
|
@@ -181,7 +247,7 @@ deploy-production:
|
|
|
181
247
|
# not look everywhere, and nothing reaches the box. There is no flag and no
|
|
182
248
|
# variable that turns this off (automation-gates.md §1 requirement 5).
|
|
183
249
|
- npm ci
|
|
184
|
-
- scripts/verify-deploy-uniform.sh "$CI_PROJECT_DIR" "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/${API_PROJECT_PATH}.git" "$API_UNIFORM_REF"
|
|
250
|
+
- sh node_modules/@onlineapps/conn-orch-validator/templates/business-service/scripts/verify-deploy-uniform.sh "$CI_PROJECT_DIR" "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/${API_PROJECT_PATH}.git" "$API_UNIFORM_REF"
|
|
185
251
|
# R1: refuse to deploy without the immutable target the build stage published.
|
|
186
252
|
- |
|
|
187
253
|
if [ -z "${BIZ_IMAGE_DIGEST:-}" ]; then
|
|
@@ -6,7 +6,7 @@ Every `api_biz/*/package.json` that `api/config/services.json` lists wears it (`
|
|
|
6
6
|
Duty sections that apply:
|
|
7
7
|
|
|
8
8
|
- `files`: F-INIT, F-JEST, F-RUNNER, F-GITIGNORE, F-DOCKERIGNORE, G-PROD, G-CI, G-PROD-IMAGE, G-README, G-SETUP, G-SETUP-INSTALL, G-SETUP-MATRIX, G-SETUP-VALIDATION, X-IGNORED, X-HOOKS, X-PREVAL, X-DB-CONFIG
|
|
9
|
-
- `scripts`: S-TEST, S-ALL-C, S-INT-C, S-COOKBOOKS, S-HOST, S-HOOKS
|
|
9
|
+
- `scripts`: S-TEST, S-ALL-C, S-INT-C, S-UNIT-C, S-COOKBOOKS, S-COOK-C, S-HOST, S-HOOKS
|
|
10
10
|
- `tooling`: S-SCRIPTS
|
|
11
11
|
- `config`: C-IDENTITY, C-SERVICE, C-OPS, C-CONTRACT, C-CONNECTORS, C-SERVICE-DEAD, C-ENV, G-SHARED-ENV, C-ENV-READS
|
|
12
12
|
- `runtime`: R-NODE, R-MEM, R-PID1, R-PORTS-DEV, R-PORTS
|
|
@@ -21,7 +21,7 @@ Paths a duty owns:
|
|
|
21
21
|
- `README.md`: G-README (from `./node_modules/@onlineapps/conn-orch-validator/manifests/biz-service.manifest.json`)
|
|
22
22
|
- `config/biz-docs-lint.tree.json`: C-LINT
|
|
23
23
|
- `config/env-templates`: C-ENV, D-DB-ACCOUNT
|
|
24
|
-
- `config/env-templates/shared.env`: G-SHARED-ENV (from
|
|
24
|
+
- `config/env-templates/shared.env`: G-SHARED-ENV (from `api/config/shared-env.json`)
|
|
25
25
|
- `config/service/config.json`: C-SERVICE
|
|
26
26
|
- `config/service/integration-contract.json`: C-CONTRACT, D-DB-COLLATION
|
|
27
27
|
- `config/service/operations.json`: C-OPS
|
|
@@ -125,7 +125,7 @@ own memory budget, so a heavy suite can no longer be killed together with the
|
|
|
125
125
|
service, and the `test` profile keeps it out of a plain `docker compose up`.
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
|
-
npm run test:container
|
|
128
|
+
npm run test:unit:container # unit suite, in the runner
|
|
129
129
|
npm run test:all:container # everything test:all chains, in the runner
|
|
130
130
|
```
|
|
131
131
|
|
|
@@ -141,14 +141,23 @@ the `Dockerfile` or to the dependencies:
|
|
|
141
141
|
docker compose --profile test build
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
Without it the next `npm run test:container` runs the image the runner was last
|
|
144
|
+
Without it the next `npm run test:unit:container` runs the image the runner was last
|
|
145
145
|
built from, and a suite then passes or fails on code that is no longer in this
|
|
146
146
|
repository — a green run that says nothing about what is committed.
|
|
147
147
|
|
|
148
|
+
`jest.config.js` is held byte for byte by row `F-JEST`, so a local
|
|
149
|
+
`setupFiles` entry pointing at a `tests/setup-env.js` of your own does not
|
|
150
|
+
survive a sync — and does not need to. What a suite needs from the environment it
|
|
151
|
+
gets from the runner's `env_file`, the same files the service itself loads, which
|
|
152
|
+
is why the runner exists at all; a setup file that put those names into
|
|
153
|
+
`process.env` would be a second, quieter source for them
|
|
154
|
+
(`.claude/rules/change-discipline.md` § One rail per concern). A suite that needs
|
|
155
|
+
a value nothing declares says so by failing on the name, which is the point.
|
|
156
|
+
|
|
148
157
|
`npm test` and `npm run test:unit` are what runs INSIDE that container; the
|
|
149
158
|
runner is the one rail a suite runs on (owner decision
|
|
150
159
|
`api/docs/governance/confirmations/biz-test-container.md` 001, and manifest rows
|
|
151
|
-
`S-ALL-C` and `S-HOST`). Running them on the host is not a second, quicker way
|
|
160
|
+
`S-UNIT-C`, `S-ALL-C`, `S-COOK-C` and `S-HOST`). Running them on the host is not a second, quicker way
|
|
152
161
|
to the same answer: the host has neither the service's `env_file` nor its
|
|
153
162
|
network, so what fails there says nothing about the service — which is why no
|
|
154
163
|
`test:host` script exists and why the uniform forbids one.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# __SERVICE_NAME__ environment
|
|
2
2
|
# Copy to config/env-active/__SERVICE_NAME__.env and adjust values.
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
#
|
|
4
|
+
# The service's own name is NOT here. config/service/config.json declares it and
|
|
5
|
+
# ServiceWrapper._serviceName() reads it there, passing it on to monitoring and
|
|
6
|
+
# to the MQ client; an env key repeating it would be a second bearer of one fact,
|
|
7
|
+
# and the copies measured on 2026-09-16 had already drifted from it (the template
|
|
8
|
+
# rendered the short name, every live file the registry name). Row C-IDENTITY
|
|
9
|
+
# holds every spelling in this repository to config/service/config.json.
|
|
5
10
|
|
|
6
11
|
# Schema migrations at deploy time. The deploy job (block `oa-ci v1` of
|
|
7
12
|
# .gitlab-ci.yml) runs api/scripts/lib/mariadb-migrations.sh between `compose
|
|
@@ -15,8 +20,15 @@ SERVICE_NAME=__SERVICE_NAME__
|
|
|
15
20
|
#
|
|
16
21
|
# Commented out because this scaffold has no database: its
|
|
17
22
|
# config/service/integration-contract.json declares no `database` block, the
|
|
18
|
-
# deploy step prints NOT APPLICABLE and reads neither name.
|
|
19
|
-
#
|
|
20
|
-
#
|
|
23
|
+
# deploy step prints NOT APPLICABLE and reads neither name.
|
|
24
|
+
#
|
|
25
|
+
# Where the values live is the host contract, not a choice: the deploy job reads
|
|
26
|
+
# config/env-active/<service>.env ON THE BOX, so a service that declares a
|
|
27
|
+
# `database` block uncomments both names HERE (the template, in git, values
|
|
28
|
+
# empty) and fills them in config/env-active/ on each machine - never in git. The
|
|
29
|
+
# account itself is not created by any of this: on a first deployment the owner
|
|
30
|
+
# of the box creates the schema and the service account by hand from the runbook,
|
|
31
|
+
# and the migration runner then runs under that account (db-migrations-first-deploy
|
|
32
|
+
# 001, phase 1).
|
|
21
33
|
# MARIADB_MIGRATION_USER=
|
|
22
34
|
# MARIADB_MIGRATION_PASSWORD=
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
# The runtime environment name every platform library resolves through its own runtime-config schema; without it a library cannot say which environment it is reporting from.
|
|
5
5
|
NODE_ENV=development
|
|
6
6
|
|
|
7
|
-
# The log level an
|
|
7
|
+
# The log level an infrastructure service's config/logging.json resolves through ${LOG_LEVEL}; one platform value so a stack does not log at a different level in every service. In the business chain the key is carried, not read: no library in the business chain reads it - @onlineapps/monitoring-core reads no environment variable at all (principle 1) and takes its level from the config the service hands it - so setting it on a business service changes nothing. It reaches every bearer because the shared key set is ONE file whose every copy is byte-identical to the platform template (confirmation biz-service-manifest 003 §18), never because each bearer reads every key.
|
|
8
8
|
LOG_LEVEL=info
|
|
9
9
|
|
|
10
|
+
# How large one log file may grow before @onlineapps/monitoring-core rotates it (src/logger.js REQUIRED_FILE_BOUNDS, src/config.js logMaxSize). From monitoring-core 3.0.0 the bound has no default in code, so a service that declares it nowhere fails at logger construction by design, and the value has to be declared - it is shared because the ceiling is a property of the box every service writes onto, not a preference of one service: the age bound LOG_MAX_DAYS alone let a single day's file fill the disk (hello wrote 2.4 GB/day before d.383). The platform value is 50 MB, the same in dev and production (owner decision docs/governance/confirmations/log-file-bounds.md 001).
|
|
11
|
+
LOG_MAX_SIZE_BYTES=52428800
|
|
12
|
+
|
|
13
|
+
# How many rotated files of one log row the directory keeps - the other half of the size bound, and equally without a default in @onlineapps/monitoring-core 3.0.0 (src/logger.js REQUIRED_FILE_BOUNDS, src/config.js logMaxFiles). Ten files of LOG_MAX_SIZE_BYTES is the 500 MB per service the platform declares as its ceiling, so the two keys are read together and travel together (owner decision docs/governance/confirmations/log-file-bounds.md 001).
|
|
14
|
+
LOG_MAX_FILES=10
|
|
15
|
+
|
|
10
16
|
# The HMAC-SHA256 secret the gateway and auth sign and verify access tokens with; it is shared because both ends of one token must hold the same value (docs/standards/JWT_AUTH.md).
|
|
11
17
|
JWT_SECRET=CHANGE_ME
|
|
12
18
|
|
|
@@ -23,7 +23,7 @@ Every command runs from the repository root (`api_biz/__SERVICE_NAME__/`).
|
|
|
23
23
|
node node_modules/@onlineapps/conn-orch-validator/src/cli/biz-ci-gate.js verify-contract
|
|
24
24
|
|
|
25
25
|
# 2 — unit suite, in the one-shot runner beside the service
|
|
26
|
-
npm run test:container
|
|
26
|
+
npm run test:unit:container
|
|
27
27
|
|
|
28
28
|
# 3 — cookbooks, in the same runner (it has the env files)
|
|
29
29
|
npm run test:cookbooks:container
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
testEnvironment: 'node',
|
|
3
3
|
testMatch: ['**/tests/**/*.test.js'],
|
|
4
|
-
|
|
4
|
+
// 120 s, because the limit is there to catch a test that HANGS and not a
|
|
5
|
+
// queue for the processor: on the shared dev machine (load average 47-94,
|
|
6
|
+
// the runners of several services at once) five integration suites failed in
|
|
7
|
+
// beforeAll at the previous 30 s while each of them passed when run alone,
|
|
8
|
+
// and the whole run passed at 180 s (BIZ-converter, d.580). The number is the
|
|
9
|
+
// platform's, not a service's - row F-JEST holds this file byte for byte.
|
|
10
|
+
testTimeout: 120000,
|
|
11
|
+
// One suite at a time, which is what the runner's memory norm assumes
|
|
12
|
+
// (api/docs/governance/confirmations/biz-memory-limits.md 001).
|
|
5
13
|
maxWorkers: 1
|
|
6
14
|
};
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test:ci": "jest",
|
|
14
14
|
"test:unit": "jest tests/unit",
|
|
15
15
|
"test:all": "npm run test:unit",
|
|
16
|
-
"test:container": "docker compose run --rm __CONTAINER_NAME___tests npm run test:unit",
|
|
16
|
+
"test:unit:container": "docker compose run --rm __CONTAINER_NAME___tests npm run test:unit",
|
|
17
17
|
"test:all:container": "docker compose run --rm __CONTAINER_NAME___tests npm run test:all",
|
|
18
18
|
"test:coverage": "jest --coverage",
|
|
19
19
|
"test:cookbooks": "node node_modules/@onlineapps/conn-orch-validator/src/cli/biz-ci-gate.js run-prevalidation",
|
package/src/mocks/MockStorage.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MockStorage - Simulates MinIO/S3 storage for testing
|
|
5
|
-
*/
|
|
6
|
-
class MockStorage {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.buckets = {};
|
|
9
|
-
this.metadata = {};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create bucket
|
|
14
|
-
*/
|
|
15
|
-
async createBucket(bucketName) {
|
|
16
|
-
if (!this.buckets[bucketName]) {
|
|
17
|
-
this.buckets[bucketName] = {};
|
|
18
|
-
}
|
|
19
|
-
return { success: true };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Put object
|
|
24
|
-
*/
|
|
25
|
-
async put(bucket, key, content, options = {}) {
|
|
26
|
-
if (!this.buckets[bucket]) {
|
|
27
|
-
await this.createBucket(bucket);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const data = typeof content === 'object' ? JSON.stringify(content) : content;
|
|
31
|
-
|
|
32
|
-
this.buckets[bucket][key] = {
|
|
33
|
-
content: data,
|
|
34
|
-
contentType: options.contentType || 'application/json',
|
|
35
|
-
metadata: options.metadata || {},
|
|
36
|
-
timestamp: Date.now(),
|
|
37
|
-
size: data.length
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Store metadata separately for querying
|
|
41
|
-
if (!this.metadata[bucket]) {
|
|
42
|
-
this.metadata[bucket] = {};
|
|
43
|
-
}
|
|
44
|
-
this.metadata[bucket][key] = {
|
|
45
|
-
size: data.length,
|
|
46
|
-
lastModified: Date.now(),
|
|
47
|
-
contentType: options.contentType || 'application/json'
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
success: true,
|
|
52
|
-
etag: `mock-etag-${Date.now()}`,
|
|
53
|
-
location: `${bucket}/${key}`
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get object
|
|
59
|
-
*/
|
|
60
|
-
async get(bucket, key) {
|
|
61
|
-
if (!this.buckets[bucket] || !this.buckets[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.');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const obj = this.buckets[bucket][key];
|
|
67
|
-
let content = obj.content;
|
|
68
|
-
|
|
69
|
-
// Try to parse JSON if content type suggests it
|
|
70
|
-
if (obj.contentType === 'application/json' && typeof content === 'string') {
|
|
71
|
-
try {
|
|
72
|
-
content = JSON.parse(content);
|
|
73
|
-
} catch (e) {
|
|
74
|
-
// Keep as string if parsing fails
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
content,
|
|
80
|
-
metadata: obj.metadata,
|
|
81
|
-
contentType: obj.contentType,
|
|
82
|
-
size: obj.size,
|
|
83
|
-
lastModified: obj.timestamp
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Delete object
|
|
89
|
-
*/
|
|
90
|
-
async delete(bucket, key) {
|
|
91
|
-
if (this.buckets[bucket]) {
|
|
92
|
-
delete this.buckets[bucket][key];
|
|
93
|
-
if (this.metadata[bucket]) {
|
|
94
|
-
delete this.metadata[bucket][key];
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return { success: true };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* List objects in bucket
|
|
102
|
-
*/
|
|
103
|
-
async list(bucket, prefix = '', limit = 1000) {
|
|
104
|
-
if (!this.buckets[bucket]) {
|
|
105
|
-
return { objects: [] };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const objects = Object.keys(this.buckets[bucket])
|
|
109
|
-
.filter(key => key.startsWith(prefix))
|
|
110
|
-
.slice(0, limit)
|
|
111
|
-
.map(key => ({
|
|
112
|
-
key,
|
|
113
|
-
size: this.buckets[bucket][key].size,
|
|
114
|
-
lastModified: this.buckets[bucket][key].timestamp
|
|
115
|
-
}));
|
|
116
|
-
|
|
117
|
-
return { objects };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Check if object exists
|
|
122
|
-
*/
|
|
123
|
-
async exists(bucket, key) {
|
|
124
|
-
return !!(this.buckets[bucket] && this.buckets[bucket][key]);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Get object metadata
|
|
129
|
-
*/
|
|
130
|
-
async getMetadata(bucket, key) {
|
|
131
|
-
if (!this.metadata[bucket] || !this.metadata[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.');
|
|
134
|
-
}
|
|
135
|
-
return this.metadata[bucket][key];
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Generate presigned URL (mock)
|
|
140
|
-
*/
|
|
141
|
-
async getPresignedUrl(bucket, key, expiresIn = 3600) {
|
|
142
|
-
return {
|
|
143
|
-
url: `http://mock-storage/${bucket}/${key}?token=mock-token-${Date.now()}`,
|
|
144
|
-
expiresAt: Date.now() + (expiresIn * 1000)
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Clear all data
|
|
150
|
-
*/
|
|
151
|
-
clear() {
|
|
152
|
-
this.buckets = {};
|
|
153
|
-
this.metadata = {};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get storage stats
|
|
158
|
-
*/
|
|
159
|
-
getStats() {
|
|
160
|
-
const stats = {
|
|
161
|
-
bucketCount: Object.keys(this.buckets).length,
|
|
162
|
-
totalObjects: 0,
|
|
163
|
-
totalSize: 0
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
Object.values(this.buckets).forEach(bucket => {
|
|
167
|
-
Object.values(bucket).forEach(obj => {
|
|
168
|
-
stats.totalObjects++;
|
|
169
|
-
stats.totalSize += obj.size;
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
return stats;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Copy object
|
|
178
|
-
*/
|
|
179
|
-
async copy(sourceBucket, sourceKey, destBucket, destKey) {
|
|
180
|
-
const source = await this.get(sourceBucket, sourceKey);
|
|
181
|
-
return await this.put(destBucket, destKey, source.content, {
|
|
182
|
-
contentType: source.contentType,
|
|
183
|
-
metadata: source.metadata
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
module.exports = MockStorage;
|