/** * mp_lockanytable, exercised through the service that uses it. * * The SAS rejection/postdata services lock MPE_REVIEW and MPE_SUBMIT through * MPE_LOCKANYTABLE around each write, so two reviewers cannot append to the * same control tables at once. The mock wrote them straight through, leaving * the lock table empty and the guarantee unimplemented. * * Run with: node --test sas/mocks/test/ */ const test = require('node:test') const assert = require('node:assert') const { seededEstate, runService, readTable, writeTable } = require('./harness') const TABLE_ID = 'DC20261104-120000.000_000001_4242' /** A submission waiting for review, and an empty lock table. */ function withSubmission(estate) { writeTable(estate, 'MPE_SUBMIT', [ { TABLE_ID: TABLE_ID, BASE_LIB: 'TESTDATA', BASE_DS: 'DEMO_01', SUBMIT_STATUS_CD: 'SUBMITTED', NUM_OF_APPROVALS_REQUIRED: 1, NUM_OF_APPROVALS_REMAINING: 1 } ]) writeTable(estate, 'MPE_LOCKANYTABLE', []) } function reject(estate) { return JSON.parse( runService(estate, 'approvers/rejection', { tableName: 'BrowserParams', rows: [{ TABLE: TABLE_ID, STP_REASON: 'values look wrong' }] }) ) } test('a rejection locks and unlocks the tables it writes', () => { const estate = seededEstate() withSubmission(estate) const body = reject(estate) assert.equal(body.fromsas[0].RESPONSE, 'SUCCESS!') assert.equal(readTable(estate, 'MPE_SUBMIT')[0].SUBMIT_STATUS_CD, 'REJECTED') assert.equal(readTable(estate, 'MPE_REVIEW').length, 1) const locks = readTable(estate, 'MPE_LOCKANYTABLE') assert.deepEqual( locks.map((lock) => lock.LOCK_LIB + '.' + lock.LOCK_DS).sort(), ['DC_JSLIB.MPE_REVIEW', 'DC_JSLIB.MPE_SUBMIT'], 'one lock row per table the service writes' ) assert.ok( locks.every((lock) => lock.LOCK_STATUS_CD === 'UNLOCKED'), 'left unlocked - the lock is released after each write' ) assert.ok( locks.every((lock) => lock.LOCK_REF && lock.LOCK_USER_NM && lock.LOCK_PID), 'the row says who took it and why, as the macro records it' ) }) test('a lock held by another session refuses the write', () => { const estate = seededEstate() withSubmission(estate) writeTable(estate, 'MPE_LOCKANYTABLE', [ { LOCK_LIB: 'DC_JSLIB', LOCK_DS: 'MPE_SUBMIT', LOCK_STATUS_CD: 'LOCKED', LOCK_PID: 'another-session', LOCK_USER_NM: 'someone-else', LOCK_REF: 'still approving' } ]) const body = reject(estate) assert.deepEqual(body.sasjsAbort, [ { MSG: 'Unable to lock DC_JSLIB..MPE_SUBMIT via MPE_LOCKANYTABLE after 25 ' + 'attempts.\nPlease ask your administrator to investigate!', MAC: 'mp_lockanytable' } ]) assert.equal( readTable(estate, 'MPE_SUBMIT')[0].SUBMIT_STATUS_CD, 'SUBMITTED', 'the submission is untouched when the lock cannot be taken' ) assert.equal(readTable(estate, 'MPE_REVIEW').length, 0) }) test('the lock is released only by the session that took it', () => { const estate = seededEstate() withSubmission(estate) writeTable(estate, 'MPE_LOCKANYTABLE', [ { LOCK_LIB: 'DC_JSLIB', LOCK_DS: 'MPE_SUBMIT', LOCK_STATUS_CD: 'LOCKED', LOCK_PID: 'another-session', LOCK_USER_NM: 'someone-else', LOCK_REF: 'still approving' } ]) // the refusal must not have taken the other session's lock away reject(estate) const held = readTable(estate, 'MPE_LOCKANYTABLE').find( (lock) => lock.LOCK_DS === 'MPE_SUBMIT' ) assert.equal(held.LOCK_STATUS_CD, 'LOCKED') assert.equal(held.LOCK_PID, 'another-session') })