fix(mocks): separate a body read failure from a missing showmeta floor
Build / Build-and-ng-test (pull_request) Successful in 5m9s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m46s
Build / Build-and-test-development (pull_request) Successful in 33m40s

Three review follow-ups on the guard the sas9 tree loads.

- A body that cannot be read is now its own error, naming the path and the
  filesystem message. Reporting it as missing metadata sent the reader to a
  @sasjs/utils reinstall that cannot fix a path.
- One statement of the dependency fact: showmeta arrived in @sasjs/utils
  4.6.0 and @sasjs/server declares that floor from 3.12.3 (3.12.0 and
  3.12.2 declare ^4.4.0, checked with npm view), so the message names the
  server version to install instead of "reinstall the server".
- CONTRIBUTING lists _program and weboutPath among the predeclared globals,
  which the guarded services use to locate the helper.

The sasjs tree's copy in dcMockUtils.js had the same two defects, so both
now say one thing rather than one tree being clearer than the other.
This commit is contained in:
dc committed 2026-11-04 14:43:26 +00:00
1 parent 7be3f84073
commit 744c89b919
3 files changed
+46 -19

No files matched your search

+1 -1
View File
@@ -147,7 +147,7 @@ The app will be at `http://localhost:4200`. It calls the JS mock services on por
### How the mocks work ### How the mocks work
Each `.js` file in `sas/mocks/sasjs/services/` corresponds to a real SAS service (e.g. `public/startupservice.js` mimics `services/public/startupservice.sas`). The JS runtime predeclares `fs` and `_webout`, and the services emit their members through the runtime's `webout()` - `webout('OPEN')`, one `webout('OBJ', name, rows)` (or `webout('ARR', name, rows)` for positional rows) per member, `webout('CLOSE')` - so the response envelope comes from the host and the file carries only what the SAS service returns. A few services still write the body themselves through `_webout`: the ones that emit plain text, CSV, DDL or HTML and set their own content headers (`admin/exportdb`, `admin/makedata`, `public/getrawdata`, `public/getddl`), and the lineage captures. Some mocks are stateful: they persist data to a `mock-storage/` folder on the SASjs Drive (e.g. `licence.json`, `users.json`, `filter.txt`) using the predeclared `fs` module. Each `.js` file in `sas/mocks/sasjs/services/` corresponds to a real SAS service (e.g. `public/startupservice.js` mimics `services/public/startupservice.sas`). The JS runtime predeclares `fs`, `_program` (the program's deployed path), `weboutPath` (the response body it writes) and `_webout`, and the services emit their members through the runtime's `webout()` - `webout('OPEN')`, one `webout('OBJ', name, rows)` (or `webout('ARR', name, rows)` for positional rows) per member, `webout('CLOSE')` - so the response envelope comes from the host and the file carries only what the SAS service returns. A few services still write the body themselves through `_webout`: the ones that emit plain text, CSV, DDL or HTML and set their own content headers (`admin/exportdb`, `admin/makedata`, `public/getrawdata`, `public/getddl`), and the lineage captures. Some mocks are stateful: they persist data to a `mock-storage/` folder on the SASjs Drive (e.g. `licence.json`, `users.json`, `filter.txt`) using the predeclared `fs` module.
The mock data includes sample tables (`DC996664.MPE_X_TEST`, `MPE_X_NEW`, `MPE_X_FORMULA_TEST`, etc.) with realistic columns, validation rules (HARDREGEX, SOFTREGEX, HARDSELECT, READONLY, HIDDEN, ROUND, NUMBER_FORMAT), and dropdown values. This is sufficient to exercise the full editor grid, approval workflow, viewer, and admin screens. The mock data includes sample tables (`DC996664.MPE_X_TEST`, `MPE_X_NEW`, `MPE_X_FORMULA_TEST`, etc.) with realistic columns, validation rules (HARDREGEX, SOFTREGEX, HARDSELECT, READONLY, HIDDEN, ROUND, NUMBER_FORMAT), and dropdown values. This is sufficient to exercise the full editor grid, approval workflow, viewer, and admin screens.
+25 -10
View File
@@ -14,21 +14,35 @@
* SYSCC 0 while the metadata is missing, which is the failure this prevents. * SYSCC 0 while the metadata is missing, which is the failure this prevents.
* *
* `showmeta` arrived in @sasjs/utils 4.6.0, and @sasjs/server only declares * `showmeta` arrived in @sasjs/utils 4.6.0, and @sasjs/server only declares
* `^4.6.0` from 3.12.3 - an older pinned server accepts the fourth argument and * that floor from 3.12.3 (3.12.0 and 3.12.2 declare `^4.4.0`) - an older pinned
* ignores it, writing the rows and dropping the metadata without a word. * server accepts the fourth argument and ignores it, writing the rows and
* Reading the output is how that becomes visible; an arity check * dropping the metadata without a word. Reading the output is how that becomes
* (`webout.length`) stops counting at the first default parameter, so a runtime * visible; an arity check (`webout.length`) stops counting at the first default
* that does carry the option can report three and be refused wrongly. * parameter, so a runtime that does carry the option can report three and be
* refused wrongly.
* *
* @param {string} bodyPath the response body the program is writing * @param {string} bodyPath the response body the program is writing
* @param {string} member the member the caller wrote * @param {string} member the member the caller wrote
*/ */
function requireMemberMetadata(bodyPath, member) { function requireMemberMetadata(bodyPath, member) {
let body = '' let body
try { try {
body = fs.readFileSync(bodyPath, 'utf8') body = fs.readFileSync(bodyPath, 'utf8')
} catch (err) { } catch (err) {
body = '' // A body that cannot be read is a path or filesystem problem, and reporting
// it as missing metadata would send the reader to a dependency reinstall
// that cannot fix it.
throw new Error(
"webout() could not read the response body at '" +
bodyPath +
"' (" +
(err && err.message ? err.message : err) +
"), so the column metadata for '" +
member +
"' cannot be checked. This is a path or filesystem problem, not a " +
'@sasjs/utils version problem - check the weboutPath of the program ' +
'that called it.'
)
} }
if (body.indexOf('"$' + String(member).toLowerCase() + '"') >= 0) return if (body.indexOf('"$' + String(member).toLowerCase() + '"') >= 0) return
@@ -37,8 +51,9 @@ function requireMemberMetadata(bodyPath, member) {
"webout() did not emit the column metadata for '" + "webout() did not emit the column metadata for '" +
member + member +
"', so the response would carry rows without their formats and labels. " + "', so the response would carry rows without their formats and labels. " +
'The options argument arrived in @sasjs/utils 4.6.0; @sasjs/server 3.12 ' + 'The options argument arrived in @sasjs/utils 4.6.0, and @sasjs/server ' +
'declares ^4.4.0 and can resolve 4.5.x. Reinstall the server, or upgrade ' + 'declares that floor from 3.12.3 - an older server accepts it and drops ' +
'@sasjs/utils to 4.6.0 or later.' 'it. Install @sasjs/server 3.12.3 or later, or @sasjs/utils 4.6.0 or ' +
'later, on the host running this program.'
) )
} }
+20 -8
View File
@@ -43,9 +43,11 @@ const MONTHS_NUM = {JAN:'01',FEB:'02',MAR:'03',APR:'04',MAY:'05',JUN:'06',JUL:'0
* exists to prevent. Before CLOSE the body cannot be closed, so the run fails. * exists to prevent. Before CLOSE the body cannot be closed, so the run fails.
* *
* A runtime without the option - an `@sasjs/utils` below 4.6.0, which * A runtime without the option - an `@sasjs/utils` below 4.6.0, which
* `@sasjs/server`'s `^4.4.0` range can resolve - writes the rows and drops the * `@sasjs/server`'s `^4.4.0` range can resolve on releases before 3.12.3 -
* metadata silently. The response still looks complete, so the estate quietly * writes the rows and drops the metadata silently. The response still looks
* tests less than it claims. Refuse it instead, and name the cause. * complete, so the estate quietly tests less than it claims. Refuse it instead,
* and name the cause. The sas9 tree carries the same function in
* `sas/mocks/sas9/sasjs/services/weboutGuard.js`; keep the two saying one thing.
* *
* Testing the output rather than the function is deliberate: an arity check * Testing the output rather than the function is deliberate: an arity check
* (`webout.length`) stops counting at the first default or rest parameter, so a * (`webout.length`) stops counting at the first default or rest parameter, so a
@@ -55,11 +57,20 @@ const MONTHS_NUM = {JAN:'01',FEB:'02',MAR:'03',APR:'04',MAY:'05',JUN:'06',JUL:'0
* @param {string} member the member the caller wrote * @param {string} member the member the caller wrote
*/ */
function requireMemberMetadata(bodyPath, member) { function requireMemberMetadata(bodyPath, member) {
let body = '' let body
try { try {
body = fs.readFileSync(bodyPath, 'utf8') body = fs.readFileSync(bodyPath, 'utf8')
} catch (err) { } catch (err) {
body = '' // A body that cannot be read is a path or filesystem problem, and
// reporting it as missing metadata would send the reader to a
// dependency reinstall that cannot fix it.
throw new Error(
"webout() could not read the response body at '" + bodyPath + "' (" +
(err && err.message ? err.message : err) + "), so the column " +
"metadata for '" + member + "' cannot be checked. This is a " +
'path or filesystem problem, not a @sasjs/utils version ' +
'problem - check the weboutPath of the program that called it.'
)
} }
if (body.indexOf('"$' + String(member).toLowerCase() + '"') >= 0) return if (body.indexOf('"$' + String(member).toLowerCase() + '"') >= 0) return
@@ -67,9 +78,10 @@ function requireMemberMetadata(bodyPath, member) {
throw new Error( throw new Error(
"webout() did not emit the column metadata for '" + member + "', so the " + "webout() did not emit the column metadata for '" + member + "', so the " +
'response would carry rows without their formats and labels. The ' + 'response would carry rows without their formats and labels. The ' +
'options argument arrived in @sasjs/utils 4.6.0; @sasjs/server 3.12 ' + 'options argument arrived in @sasjs/utils 4.6.0, and @sasjs/server ' +
'declares ^4.4.0 and can resolve 4.5.x. Reinstall the server, or ' + 'declares that floor from 3.12.3 - an older server accepts it and ' +
'upgrade @sasjs/utils to 4.6.0 or later.' 'drops it. Install @sasjs/server 3.12.3 or later, or @sasjs/utils ' +
'4.6.0 or later, on the host running this program.'
) )
} }