fix(mocks): restore the MPE_X_TEST fixtures and pin the replay's timezone
Build / Build-and-ng-test (pull_request) Successful in 3m54s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m1s
Build / Build-and-test-development (pull_request) Failing after 34m59s

The parity gate's Cypress job went red after the seed work landed, and the two
causes are the same change seen from the fixture side:

- makedata now carries SAS's demo validation rules for MPE_X_TEST, including the
  HARDREGEX on SOME_CHAR that blocks any text but 'the'/'data'. The excel
  fixtures upload arbitrary values into that table and expect the submission to
  reach the approval screen, so ten excel specs timed out with no Approve
  button. createdemodata drops MPE_X_TEST's rules, which is what editor.cy.ts
  already documents ("MPE_X_TEST stays clean to match the excel fixtures").
- the LABELs the viewer-labels spec asserts went with the hand-written schema.
  createdemodata restores them, because a SAS estate has none and the control
  library should not claim otherwise.

Both are declared as fixture divergences in the parity README and asserted in
createdemodata.test.js, so the next change to either side fails a test instead
of a Cypress run.

Review findings:

- the SYSTIMEZONEOFFSET acceptance depended on the host's zone; both entry
  points pin TZ=UTC before the harness loads, so the accepted list is the same
  everywhere.
- the sas9 mock still published '+0100'; it now answers seconds east of UTC
  like the standard estate.
- the README promised an eight-digit id scan that IDENTIFIER_CLASSES no longer
  had. A DC-prefixed eight-digit class is back (a plain digit class would fire
  on a yyyymmdd date), with a case proving a date is not refused.
This commit is contained in:
dc committed 2026-11-06 18:41:13 +00:00
1 parent 94cd4912f9
commit dd2a777438
8 files changed
+94 -3

No files matched your search

@@ -80,7 +80,8 @@ webout('OBJ', 'globvars', [
DC_RESTRICT_EDITRECORD: 'NO',
TIMEZONE: 'Europe/London',
SYSTIMEZONEIDENT: 'Europe/London',
SYSTIMEZONEOFFSET: '+0100'
// seconds east of UTC, as SAS answers it ('3600' for BST), not +HHMM
SYSTIMEZONEOFFSET: '3600'
}
])
webout('ARR', 'xlmaps', [
@@ -104,7 +104,19 @@ tables.MPE_TABLES = tables.MPE_TABLES.filter(function (row) {
)
})
tables.MPE_VALIDATIONS = tables.MPE_VALIDATIONS.filter(function (row) {
return String(row.base_lib || row.BASE_LIB || '').toUpperCase() !== 'TESTDATA'
const lib = String(row.base_lib || row.BASE_LIB || '').toUpperCase()
const dsn = String(row.base_ds || row.BASE_DS || '').toUpperCase()
if (lib === testDataLibref) return false
/*
makedata carries SAS's demo rules for MPE_X_TEST, including a HARDREGEX on
SOME_CHAR ('/the|data/i') that blocks any other text. The excel fixtures
upload arbitrary values into that table and expect the submission to reach
the approval screen - editor.cy.ts says the same thing ("MPE_X_TEST stays
clean to match the excel fixtures"). The validation demos live on
TESTDATA.MPE_X_NEW, which is why they are fixtures.
*/
if (dsn === 'MPE_X_TEST') return false
return true
})
tables.MPE_SELECTBOX = tables.MPE_SELECTBOX.filter(function (row) {
return String(row.select_lib || row.SELECT_LIB || '').toUpperCase() !== 'TESTDATA'
@@ -1478,6 +1490,31 @@ for (const [tableName, rows] of Object.entries(tables)) {
saveTable(tableName, rows, schema[tableName])
}
/*
MPE_X_TEST's column LABELs are a fixture: viewer-labels.cy.ts asserts that
SOME_CHAR/SOME_DATE show a label that differs from the name while SOME_NUM
falls back to its name on a blank label. makedata carries SAS's own metadata,
where the table has no labels at all, so they are added here rather than
making the control library say something the SAS service does not.
*/
const xTestLabels = {
SOME_CHAR: 'Some Character Column',
SOME_DATE: 'Some Date',
SOME_DATETIME: 'Some Datetime',
SOME_TIME: 'Some Time'
}
const xTest = readTable('MPE_X_TEST')
if (xTest) {
saveTable(
'MPE_X_TEST',
xTest.rows,
xTest.columns.map(function (column) {
const label = xTestLabels[column.name.toUpperCase()]
return label ? Object.assign({}, column, { label: label }) : column
})
)
}
// Copy DC_JSLIB table files into DC996664 (same schema/rows) so the
// multi-load fixtures can fetch getdata for DC996664.MPE_X_TEST etc.
// Must run after the DC_JSLIB write loop above.
+24
View File
@@ -56,6 +56,30 @@ test('the fixtures are added to a makedata estate', () => {
)
})
test('MPE_X_TEST carries the excel/viewer fixtures, not the SAS demo rules', () => {
// makedata brings SAS's demo rules for MPE_X_TEST (a HARDREGEX on SOME_CHAR
// among them), which block the excel fixtures' arbitrary values before they
// reach the approval screen. The rules are dropped here and the LABELs the
// viewer-labels spec asserts are added; both are fixture decisions.
const estate = seededEstate()
const rules = readTable(estate, 'MPE_VALIDATIONS').filter(
(row) => String(row.base_ds).toUpperCase() === 'MPE_X_TEST'
)
assert.deepEqual(rules, [], 'MPE_X_TEST is rule-free on the mock estate')
const table = JSON.parse(
fs.readFileSync(
path.join(estate.appDir, 'data', 'DC_JSLIB', 'mpe_x_test.json'),
'utf8'
)
)
const byName = {}
for (const column of table.columns) byName[column.name] = column.label
assert.equal(byName.SOME_CHAR, 'Some Character Column')
assert.equal(byName.SOME_DATE, 'Some Date')
assert.equal(byName.SOME_NUM, '', 'a blank label falls back to the name')
})
test('every fixture table is written with its column metadata', () => {
// saveTable falls back to the columns of the file already on the drive, and
// a fresh estate has none - so a fixture with no schema of its own is
+4
View File
@@ -15,6 +15,10 @@
*
* Run with: node --test sas/mocks/test/
*/
// The mock derives SYSTIMEZONEOFFSET from the host, and the golden carries the
// estate's own offset: pin the zone so the accepted difference is the same on
// every machine, not only on the UTC container the suite usually runs in.
process.env.TZ = 'UTC'
const test = require('node:test')
const assert = require('node:assert')
+8
View File
@@ -153,6 +153,14 @@ test('a secret typed into a golden by hand is caught', () => {
assert.deepEqual(secretViolations(edited), ['body.globvars[0].LICENCE_KEY'])
})
test('the scan refuses a bare eight-digit DC id but not a date', () => {
// The claim in the README is that the publish scan catches an eight-digit id.
// It has to be DC-prefixed: a plain eight-digit class fires on a yyyymmdd
// date or an amount and refuses a clean golden.
assert.deepEqual(scan({ note: 'row DC12345678 was staged' }), { dcId: 1 })
assert.deepEqual(scan({ note: 'captured 20261106 at 14:21' }), {})
})
test('every committed golden is clean', () => {
const files = goldenFiles(GOLDEN)
assert.ok(files.length > 0, 'at least one golden is recorded')
+9
View File
@@ -47,6 +47,15 @@ The build pipeline cannot reach a SAS estate, so re-recording is an **ad-hoc job
- **Differences are declared, not ignored.** A scenario's `accepted` list in `scenarios.json` names the JSON path of each difference that is expected (the estate's own admin group, its timezone, SAS's space padding of a blank character value) with the reason. Anything else fails, and so does an accepted entry that has stopped differing - the list shrinks to the real residue instead of hiding a fix.
- **The refresh needs the estate.** Where `sas/.env.server` exists: `record.js --target server`, then `check.js`, then the suite; commit the changed projections. A non-empty diff names the member that moved, so the refresh is a reviewed change rather than a silent overwrite - and when only SAS moved (a version bump, an upstream service change), the diff in the pull request is the whole report.
## Fixture divergences that are deliberate
`createdemodata` owns two differences from a SAS estate, both driven by existing specs rather than by the product:
- MPE_X_TEST carries no validation rules on the mock, although `mpe_makedata.sas` seeds SAS's demo rules for it (a `HARDREGEX` on SOME_CHAR among them). The excel fixtures upload arbitrary values into that table and expect them to reach the approval screen; `editor.cy.ts` already says "MPE_X_TEST stays clean to match the excel fixtures". The validation demos live on `TESTDATA.MPE_X_NEW`.
- MPE_X_TEST's SOME_CHAR/SOME_DATE/SOME_DATETIME/SOME_TIME carry LABELs that differ from their names, which `viewer-labels.cy.ts` asserts, while a SAS estate has no labels on that table.
Both are asserted in `test/createdemodata.test.js`, so a change to either side is a failing test rather than a broken Cypress run. A future scenario covering `editors/getdata` on MPE_X_TEST would show them as accepted differences; today the startup scenario does not touch that table.
Recording is always against the synthetic estate, and that is enforced rather than asserted: `record.js` refuses any target that is not declared `"synthetic": true` in `sasjsconfig.json`. An agent refreshing goldens against a customer site would put their rows into a file that looks anonymous and is not, and no scan can tell the two apart afterwards.
## Status
+4
View File
@@ -15,6 +15,10 @@
* the golden already carries - <LIBREF>, <PATH>, <STAGING_ID> - and a log of a
* failure is as safe to paste as the golden itself.
*/
// The mock derives SYSTIMEZONEOFFSET from the host, and the golden carries the
// estate's own offset: pin the zone so the accepted difference is the same on
// every machine, not only on the UTC container the suite usually runs in.
process.env.TZ = 'UTC'
const fs = require('fs')
const path = require('path')
+5 -1
View File
@@ -84,7 +84,11 @@ const IDENTIFIER_CLASSES = {
ipv4: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
absolutePath: /\/(?:home|tmp|opt|var|srv|data)\//g,
libref: /\bDC\d{6}\b/g,
email: /[\w.+-]+@[\w-]+\.[\w.-]+/g
email: /[\w.+-]+@[\w-]+\.[\w.-]+/g,
// a bare DC-prefixed id, wider than a libref: the README promises the scan
// refuses an eight-digit id, and a plain digit class would refuse a date or
// an amount instead
dcId: /\bDC\d{8}\b/g
}
/** Every string in a value tree, for the deny-list check. */