Files
dc/sas/mocks/test/createdemodata.test.js
T
dc 96f70d53b6
Build / Build-and-ng-test (pull_request) Successful in 5m11s
Build / Build-and-test-development (pull_request) Canceled after 3m24s
Build / lighthouse (pull_request) Canceled after 3m22s
feat(mocks): generate the DDL download from the table, not a sample
public/getddl answered one fixed Postgres-flavoured body for every request -
DC_JSLIB.MPE_X_TEST whatever dataset was asked for - which is the last read path
the client calls where the mock invented its answer.

It now builds the SAS-flavour DDL from the table file, the way mp_ds2ddl builds
it from dictionary.columns and dictionary.indexes: the header, the create table
with each column's type, format, NOT NULL and label, and the unique index. The
list-output spacing was derived from the estate's own answers and reproduces
them byte for byte for all forty control tables.

Recorded as public/getddl/mpe_tables, so a mock change that alters the text
fails the gate. The header credits a user and a timestamp, so the projection
masks both - a user id is not something to publish, and the comparison is about
the DDL.

TSQL and PGSQL are not generated yet: a caller asking for one gets the SAS text,
and the README says so rather than inventing a second dialect without a
recording to check it against.
2026-11-07 18:10:11 +00:00

260 lines
8.5 KiB
JavaScript

/**
* createdemodata carries the estate the Cypress suite needs but a Data
* Controller install does not have: the TESTDATA demo library, the deep and
* wide viewer fixtures and the multi-load library.
*
* It runs after makedata, so it must both add those and leave the control
* library alone otherwise; and it must be safe to run twice, because a reseed
* runs it twice on the same drive.
*
* Run with: node --test sas/mocks/test/
*/
const test = require('node:test')
const assert = require('node:assert')
const fs = require('node:fs')
const path = require('node:path')
const { seededEstate, runService, readTable } = require('./harness')
const FIXTURE_LIBS = ['TESTDATA', 'DC996664']
test('the fixtures are added to a makedata estate', () => {
const estate = seededEstate()
const data = path.join(estate.appDir, 'data')
assert.deepEqual(fs.readdirSync(data).sort(), [
'DC996664',
'DC_JSLIB',
'TESTDATA'
])
const tables = readTable(estate, 'MPE_TABLES')
for (const lib of FIXTURE_LIBS) {
assert.ok(
tables.some((row) => String(row.libref).toUpperCase() === lib),
lib + ' is registered in MPE_TABLES'
)
}
for (const name of ['MPE_X_SEARCH', 'MPE_X_WIDE']) {
assert.ok(
tables.some((row) => String(row.dsn).toUpperCase() === name),
name + ' is registered in MPE_TABLES'
)
}
assert.equal(readTable(estate, 'MPE_X_SEARCH').length, 1000)
assert.equal(readTable(estate, 'MPE_X_WIDE').length, 200)
assert.ok(readTable(estate, 'MPE_DERIVED').length > 0)
assert.ok(
readTable(estate, 'MPE_VALIDATIONS').some(
(row) => String(row.base_lib).toUpperCase() === 'TESTDATA'
),
'the TESTDATA rules are present'
)
assert.ok(
fs.existsSync(path.join(data, 'TESTDATA', 'demo_01.json')),
'the TESTDATA tables are written'
)
})
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'
)
// SAS's other demo rules for the table are the ones the mock has always had
// (the editor specs click a cell and expect a dropdown because of them), so
// only the SOME_SHORTNUM HARDREGEX is dropped - the excel fixtures hold 3,
// 18, 34 ... which it rejects.
const ruleTypes = rules.map((row) => row.base_col + ':' + row.rule_type)
assert.ok(
ruleTypes.includes('SOME_NUM:HARDSELECT_HOOK'),
'the select hook the editor specs rely on stays'
)
assert.ok(
!ruleTypes.some((rule) => rule === 'SOME_SHORTNUM:HARDREGEX'),
'the rule the excel fixtures cannot satisfy is gone'
)
const table = JSON.parse(
fs.readFileSync(
path.join(estate.appDir, 'data', 'DC_JSLIB', 'mpe_x_test.json'),
'utf8'
)
)
const byName = {}
// the table keeps the case the SAS model creates it with, which is lower
for (const column of table.columns) byName[column.name.toUpperCase()] = 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
// written with columns: [] and the viewer loses the order it renders.
const estate = seededEstate()
const tables = [
['DC_JSLIB', 'MPE_X_SEARCH'],
['DC_JSLIB', 'MPE_X_WIDE'],
['TESTDATA', 'MPE_X_NEW'],
['TESTDATA', 'DEMO_01'],
['DC996664', 'MPE_X_TEST']
]
for (const [lib, name] of tables) {
const file = path.join(
estate.appDir,
'data',
lib,
name.toLowerCase() + '.json'
)
const table = JSON.parse(fs.readFileSync(file, 'utf8'))
assert.ok(table.columns.length > 0, lib + '.' + name + ' has no columns')
const described = table.columns.map((column) => column.name.toUpperCase())
for (const field of Object.keys(table.rows[0] || {})) {
assert.ok(
described.includes(field.toUpperCase()),
lib + '.' + name + '.' + field + ' has no column metadata'
)
}
}
const search = JSON.parse(
fs.readFileSync(
path.join(estate.appDir, 'data', 'DC_JSLIB', 'mpe_x_search.json'),
'utf8'
)
)
assert.deepEqual(
search.columns.map((column) => column.name),
[
'PRIMARY_KEY_FIELD',
'SITE_NAME',
'VESSEL',
'SAMPLE_COUNT',
'DEPTH_M',
'SPECIES',
'CRUISE_DATE',
'EXPEDITION_ID',
'NOTES'
],
'the demo column order the search spec derives its counts from'
)
})
test('a second run does not accumulate rows', () => {
const estate = seededEstate()
const counted = () =>
[
'MPE_TABLES',
'MPE_VALIDATIONS',
'MPE_SELECTBOX',
'MPE_SECURITY',
'MPE_DERIVED'
]
.map((name) => name + '=' + readTable(estate, name).length)
.join(' ')
const first = counted()
runService(estate, 'admin/createdemodata', {})
assert.equal(counted(), first, 'createdemodata must be idempotent')
})
test('a derived row makedata owns survives the fixture rows', () => {
// MPE_DERIVED is a control table: createdemodata must add its TESTDATA rows
// without replacing what makedata wrote, or the first real derived rule on
// the estate would vanish on the next reseed.
const { writeTable } = require('./harness')
const estate = seededEstate()
const owned = {
tx_from: 0,
tx_to: 127490111999,
libref: 'DC_JSLIB',
dsn: 'MPE_X_TEST',
var_nm: 'MOCK_OWNED',
var_logic: 'SOME_NUM * 2',
var_type: 'N',
active: 1
}
writeTable(estate, 'MPE_DERIVED', readTable(estate, 'MPE_DERIVED').concat([owned]))
runService(estate, 'admin/createdemodata', {})
const rows = readTable(estate, 'MPE_DERIVED')
assert.ok(
rows.some((row) => row.var_nm === 'MOCK_OWNED'),
'the makedata-owned row is kept'
)
assert.equal(
rows.filter((row) => row.var_nm === 'MOCK_OWNED').length,
1,
'and it is not duplicated'
)
assert.ok(
rows.some((row) => String(row.libref).toUpperCase() === 'TESTDATA'),
'the fixture rows are still added'
)
})
test('it refuses to run before makedata', () => {
const { makeEstate } = require('./harness')
const estate = makeEstate()
assert.throws(
() => runService(estate, 'admin/createdemodata', {}),
/run services\/admin\/makedata first/,
'a missing control library is reported, not half-created'
)
})
test('it seeds into the library the settings name', () => {
// a deployment that moves the control library writes the new libref into
// settings.js; the service has to follow it rather than assume DC_JSLIB
const estate = seededEstate({ demo: false })
fs.renameSync(
path.join(estate.appDir, 'data', 'DC_JSLIB'),
path.join(estate.appDir, 'data', 'TESTFIX')
)
fs.writeFileSync(
path.join(estate.drive, 'files', 'Public', 'app', 'dc', 'services', 'settings.js'),
'var dcSettings = ' +
JSON.stringify({
dc_libref: 'TESTFIX',
dc_admin_group: 'AllUsers',
dcpath: 'x'
}) +
'\n'
)
runService(estate, 'admin/createdemodata', {})
assert.ok(
fs.existsSync(path.join(estate.appDir, 'data', 'TESTFIX', 'mpe_x_search.json')),
'the fixtures land in the control library the settings name'
)
assert.ok(
!fs.existsSync(path.join(estate.appDir, 'data', 'DC_JSLIB')),
'and not in the default one'
)
})
test('it still seeds when the settings parser is not deployed', () => {
// a drive whose settings parser has not been compiled is still a drive
// makedata seeded: the control library falls back to its default name rather
// than the service failing at load with an ENOENT before it can say anything
const estate = seededEstate({ demo: false })
fs.unlinkSync(
path.join(estate.drive, 'files', 'Public', 'app', 'dc', 'services', 'dcSettings.js')
)
const out = runService(estate, 'admin/createdemodata', {})
assert.ok(!/ENOENT/.test(out))
assert.ok(
fs.existsSync(path.join(estate.appDir, 'data', 'DC_JSLIB', 'mpe_x_search.json')),
'the fixtures land in the default control library'
)
})