Files
dc/sas/mocks/test/parity/check.js
T
dc 49a9bbf022
Build / Build-and-ng-test (pull_request) Successful in 2m49s
Build / Build-and-test-development (pull_request) Successful in 12s
Lighthouse Checks / lighthouse (pull_request) Successful in 8m15s
feat(mocks): record getdata and viewlibs, and show what the mock gets wrong
Two more scenarios, and the recorder could not take either of them as it stood.

- editors/getdata reads a table input, not URL parameters, so record.js can now
  post one: it writes the informational header the server expects (the first
  line is the SAS input statement, not a list of column names), posts it as
  multipart/form-data, and takes --libref so the estate's control library name
  stays out of scenarios.json. replay.js hands the mock its own rows through
  mockInput, since the two sides do not share a libref.
- the test harness's webout() stub now honours the showmeta option and writes
  the body as members are added. Without them the mocks' own
  requireMemberMetadata() guard throws - it reads the body before CLOSE - and a
  service that stopped sending formats and labels would have passed here.
- --scenario accepts service/id, because two screens legitimately call their
  first service 'default'.

The two goldens are recorded but marked gated: false: the gate prints their
difference counts and does not fail, and check.js labels them ADVISORY. They are
a work list, not an acceptance.

editors/getdata/mpe_tables - 388 differences, all of them the mock's:
cols carries a DESC key SAS never emits and misses the LABEL/LONGDESC/COLTYPE
SAS reads from MPE_DATADICTIONARY; sasdata leaves blank character values out
instead of padding them and is not ordered by the business key; dqrules differ
in content and order; approvers is hardcoded to one user where SAS computes it
and returns none.

public/viewlibs - 4 differences: SAS answers LIBRARYID='V9' with a blank ENGINE
and LIBRARYNAME equal to the libref, and lists SASHELP; the mock answers
LIBRARYID=<libref>, ENGINE='V9', LIBRARYNAME='Data Controller(<libref>)' and has
no SASHELP.

dsmeta is excluded on purpose: it is the engine's report on the dataset (owner,
host, encoding, paths, created dates), estate state rather than behaviour, and
the projection refused the recorded response until it was dropped - the site id
and the service account name were in it.
2026-11-06 21:28:51 +00:00

83 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Replays every recorded golden against a mock estate and prints the
* differences.
*
* node sas/mocks/test/parity/check.js
* node sas/mocks/test/parity/check.js --scenario public/startupservice
* node sas/mocks/test/parity/check.js --advisory # report, exit 0
*
* This is the human-facing half of the same comparison `test/parity-check.test.js`
* runs as a gate; both go through `parity/replay.js`, so neither can drift from
* the other.
*
* Both sides are projections, so the values printed here are the placeholders
* 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')
const { seededEstate, runService } = require('../harness')
const { replay } = require('./replay')
function arg(name, fallback) {
const index = process.argv.indexOf('--' + name)
if (index < 0) return fallback
const next = process.argv[index + 1]
return next && next.indexOf('--') !== 0 ? next : true
}
try {
const only = arg('scenario', null)
const advisory = arg('advisory', false) === true
const scenarios = JSON.parse(
fs.readFileSync(path.join(__dirname, 'scenarios.json'), 'utf8')
).scenarios.filter(function (scenario) {
return !only || scenario.id === only || scenario.service === only
})
if (!scenarios.length) throw new Error('no scenario matched ' + only)
// the golden is recorded against a stock estate (makedata only), so the mock
// side is built the same way - fixtures would add datasets the golden never saw
const estate = seededEstate({ demo: false })
let failed = 0
for (const result of replay(estate, runService, scenarios)) {
const id = result.scenario.service + '/' + result.scenario.id
if (!result.golden) {
console.log('SKIP ' + id + ' - no golden')
continue
}
const advisory = result.scenario.gated === false
if (result.unexpected.length || result.stale.length) {
console.log(
(advisory ? 'ADVISORY ' : 'DIFF ') + id + ' - ' +
result.unexpected.length + ' unaccepted, ' +
result.stale.length + ' stale acceptance(s)'
)
for (const difference of result.unexpected.slice(0, 40)) {
console.log(' ' + difference)
}
for (const stale of result.stale) {
console.log(' ' + stale + ': accepted, but no longer differs')
}
// a scenario marked gated: false is reported, never counted
if (!advisory) failed++
continue
}
console.log(
'ok ' + id + ' (' + result.accepted.length + ' accepted difference(s))'
)
}
if (failed && !advisory) process.exitCode = 1
} catch (err) {
console.error(err.message)
process.exitCode = 1
}