@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +373 -0
  2. package/README.md +83 -9
  3. package/docs/DESIGN.md +21 -7
  4. package/manifests/biz-service.manifest.json +28 -5
  5. package/package.json +2 -2
  6. package/src/CookbookTestRunner.js +84 -16
  7. package/src/ValidationOrchestrator.js +73 -20
  8. package/src/cli/biz-ci-gate.js +28 -14
  9. package/src/cli/oa-sync-template.js +23 -8
  10. package/src/cli/oa-validate.js +7 -1
  11. package/src/index.js +21 -13
  12. package/src/lint/scripts/lintScripts.js +65 -18
  13. package/src/manifest/checks/composeRunnerBlock.js +37 -20
  14. package/src/manifest/checks/discoveryOrphan.js +2 -1
  15. package/src/manifest/checks/docsLintBridge.js +79 -21
  16. package/src/manifest/checks/gitTracked.js +12 -1
  17. package/src/manifest/checks/libraryPackage.js +3 -1
  18. package/src/manifest/checks/libraryWorkspace.js +18 -3
  19. package/src/manifest/checks/readmeRegion.js +9 -1
  20. package/src/manifest/checks/serviceConfig.js +29 -12
  21. package/src/manifest/checks/serviceFiles.js +34 -7
  22. package/src/manifest/checks/serviceIdentityRows.js +3 -1
  23. package/src/manifest/checks/serviceRuntime.js +3 -1
  24. package/src/manifest/discovery.js +25 -7
  25. package/src/manifest/runManifest.js +58 -7
  26. package/src/manifest/workspaceRoot.js +91 -5
  27. package/src/sync/serviceTemplate.js +76 -7
  28. package/src/sync/sharedEnv.js +11 -4
  29. package/src/sync/uniformFiles.js +91 -21
  30. package/src/utils/bizCiGateContract.js +25 -1
  31. package/src/utils/installContract.js +46 -5
  32. package/src/utils/libCompat.js +39 -19
  33. package/src/utils/preValidation.js +56 -11
  34. package/src/utils/stepFailure.js +106 -19
  35. package/src/utils/testCoverageContract.js +60 -2
  36. package/src/utils/throwawaySchema.js +92 -7
  37. package/src/validatorIdentity.js +31 -0
  38. package/src/validators/ServiceStructureValidator.js +41 -15
  39. package/src/validators/ValidationProofGenerator.js +73 -34
  40. package/templates/business-service/.dockerignore +9 -1
  41. package/templates/business-service/.gitlab-ci.yml +91 -25
  42. package/templates/business-service/README.md +14 -5
  43. package/templates/business-service/config/env-templates/__SERVICE_NAME__.env +17 -5
  44. package/templates/business-service/config/env-templates/shared.env +7 -1
  45. package/templates/business-service/docs/80-setup/PLATFORM_MATRIX.md +1 -1
  46. package/templates/business-service/docs/80-setup/VALIDATION.md +1 -1
  47. package/templates/business-service/jest.config.js +9 -1
  48. package/templates/business-service/package.json.template +1 -1
  49. 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 . .). Everything above this block is this repository's own.
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 before
6
- # the SSH step (confirmation biz-service-manifest 008) and the post-deploy gate
7
- # that runs after it (confirmation deploy-gate-targets 003). The installation
8
- # contract is checked by that uniform gate, in the manifest rows G-SETUP,
9
- # D-DB-PACKAGE and D-DB-HEADERS, and no longer by a job of its own (d.470).
10
- # npx oa-sync-template .gitlab-ci.yml --target . rewrites everything between
11
- # these two markers and nothing outside them.
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 runs here (confirmation biz-service-manifest 008), so the job
80
- # needs the runtime the pinned engine declares — the same major the test stage
81
- # and the Dockerfile carry, which is what R3 of the deploy contract compares.
82
- image: node:24-alpine
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 `./node_modules/@onlineapps/conn-orch-validator/templates/business-service/config/env-templates/shared.env`)
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 # unit suite, in the runner
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
- SERVICE_NAME=__SERVICE_NAME__
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. A service that
19
- # declares that block uncomments both here and fills the values in
20
- # config/env-active/ only - per machine, never in git.
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 infra service's config/logging.json resolves through ${LOG_LEVEL}; one platform value so a stack does not log at four different levels.
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
 
@@ -24,7 +24,7 @@ the service.
24
24
 
25
25
  ```bash
26
26
  docker compose up -d --build
27
- npm run test:container
27
+ npm run test:unit:container
28
28
  npm run test:cookbooks:container
29
29
  ```
30
30
 
@@ -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
- testTimeout: 30000,
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",
@@ -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;