Files
dc/sas/mocks/sasjs/services/auditors/getauditfile.js
T
dc 489819b051
Sites / deploy (push) Successful in 3m52s
Build / Build-and-ng-test (pull_request) Successful in 5m27s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m5s
Build / Build-and-test-development (pull_request) Successful in 33m8s
fix(mocks): address the audit-pack review findings
- zip entries carry flag bit 11 when the name is non-ASCII, so an extractor
  reads `BKP_` plus an accented filename back by name instead of CP437
- loadfile and stagedata name the staged CSV after the libds the request
  arrived with, before a hook can re-point the submission
- stagedata writes that CSV at all (%mp_ds2csv does), so a grid submission's
  audit pack carries the same artefact as an upload's
- loadfile writes no BKP_ file: loadfile.sas copies one only for a second,
  non-CSV fileref, which the mock does not accept
- getauditfile says access is unenforced rather than that there is no
  permission store (MPE_SECURITY is seeded)
- the special-missings clip no longer claims the estate carries a valid key
2026-10-24 20:06:09 +00:00

105 lines
3.5 KiB
JavaScript

const nodePath = require('path')
let appLoc = nodePath.join(..._program.split('services')[0].split('/'))
const sasjsRoot = nodePath.resolve(weboutPath, '..', '..', '..')
const driveRoot = nodePath.resolve(sasjsRoot, 'drive')
// Load shared DC mock utilities
eval(
fs.readFileSync(
nodePath.resolve(driveRoot, 'files', appLoc, 'services', 'dcMockUtils.js'),
'utf8'
)
)
const dcConf = loadDcSettings(driveRoot, appLoc)
const mpeDataDir = dcConf.dataDir
const stagingDir = nodePath.resolve(mpeDataDir, 'staging')
const mpeLoadTableData = makeTableLoader(mpeDataDir)
// ─── Parse input ──────────────────────────────────────────────────────────────
// The submission id arrives as the `table` URL param, as in getauditfile.sas.
let tableId = ''
if (typeof table !== 'undefined' && table) tableId = String(table).trim()
// libds comes from MPE_SUBMIT, the same lookup the SAS service makes before it
// decides the request is valid.
let libds = ''
if (tableId) {
const submit = mpeLoadTableData('MPE_SUBMIT')
const row =
submit && submit.rows
? submit.rows.find((r) => r.TABLE_ID === tableId)
: null
if (row) {
libds = (String(row.BASE_LIB || '') + '.' + String(row.BASE_DS || '')).toUpperCase()
}
}
// ─── Respond ──────────────────────────────────────────────────────────────────
if (!tableId || !libds) {
// getauditfile.sas aborts with the same message when either is missing.
webOutOpen()
webOutObj([{ MSG: 'Missing: libds table', MAC: 'getauditfile' }], 'sasjsAbort')
webOutObj([{ STATUS: 'ERROR', DSID: '' }], 'sasparams')
webOutClose()
} else {
// The SAS service also checks EDIT or APPROVE access (mpe_accesscheck). The
// mock seeds MPE_SECURITY but no mock service enforces it, so access is not
// emulated here.
// The approval folder is zipped as it stands. getauditfile.sas lists it with
// mp_dirlist and adds each file to the package under contents/.
const stageFolder = nodePath.resolve(stagingDir, tableId)
const names = []
if (fs.existsSync(stageFolder)) {
for (const name of fs.readdirSync(stageFolder)) {
const absolute = nodePath.resolve(stageFolder, name)
let stat
try {
stat = fs.statSync(absolute)
} catch (err) {
continue
}
if (stat.isFile()) names.push(name)
}
}
names.sort()
const entries = names.map((name) => ({
name: 'contents/' + name,
content: fs
.readFileSync(nodePath.resolve(stageFolder, name), { encoding: 'utf8' })
.toString()
}))
const archive = mpZipPackage(entries)
const streamed = mpStreamfile({
filename: tableId + '.zip',
contentType: 'application/zip',
bytes: archive
})
if (!streamed) {
// The browser host returns a text body and ignores the streamed headers, so
// the archive cannot reach the browser as a download. The pack is described
// instead, which is what a reviewer opening the tab gets there.
_webout = JSON.stringify(
{
table: tableId,
libds: libds,
archive: tableId + '.zip',
note: 'The browser mock host streams text only, so this lists the archive contents.',
contents: entries.map((entry) => ({
name: entry.name,
bytes: entry.content.length
}))
},
null,
2
)
}
}