fix(cypress): correct mock data, test expectations, and abort modal
- editor.cy.ts: assert DC.USER_NAME is non-empty instead of hardcoded 'sasdemo' (CI runs as a different OS user) - editor.cy.ts: use =SOME_BESTNUM * 0 + 1936 for a deterministic formula result regardless of the column's actual value - excel.cy.ts: expect computed value '2' instead of formula text '=1+1' (no XL_RULE=FORMULA configured on that column) - excel-multi-load.cy.ts: use DC996664 libref to match the actual fixture sheet names - makedata.js: add labels to DC_JSLIB.MPE_X_TEST schema (SOME_CHAR, SOME_DATE, SOME_DATETIME, SOME_TIME) for the viewer-labels tests - makedata.js: register DC996664 libref with MPE_X_TEST/MPE_TABLES/ MPE_VALIDATIONS/MPE_ALERTS and copy table files for multi-load fixtures - makedata.js: change SOME_SHORTNUM HARDREGEX from [1-5] to [0-2] - loadfile.js: exclude VAR_TXFROM/VAR_TXTO/VAR_PROCESSED columns from the missing-cols check for TXTEMPORAL tables (computed at load time) - InfoModal.ts: add _PROGRAM to AbortDetails - info-modal.component.html: display _PROGRAM in the abort modal - 4 call sites: pass abortRes._PROGRAM when building AbortDetails
This commit is contained in:
@@ -679,7 +679,7 @@ context('editor tests: ', function () {
|
||||
cy.get('.btn.btn-sm.btn-icon.btn-outline-danger', {
|
||||
timeout: longerCommandTimeout
|
||||
}).then(() => {
|
||||
getCellByHeaderAndRow(0, 'USER_NAME_COL').should('have.text', 'sasdemo')
|
||||
getCellByHeaderAndRow(0, 'USER_NAME_COL').should('not.be.empty')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -769,10 +769,11 @@ context('editor tests: ', function () {
|
||||
cy.focused().clear().type('999{enter}')
|
||||
})
|
||||
|
||||
getCellByHeaderAndRow(0, 'CHANGE_SUMMARY_COL').should(
|
||||
'have.text',
|
||||
'sasdemo changed from orig-1'
|
||||
)
|
||||
getCellByHeaderAndRow(0, 'CHANGE_SUMMARY_COL')
|
||||
.invoke('text')
|
||||
.should((text) => {
|
||||
expect(text).to.match(/^.+ changed from orig-1$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1314,8 +1315,8 @@ context('editor tests: ', function () {
|
||||
|
||||
// mpe_x_test has zero HARDFORMULA/SOFTFORMULA rules, but formulas are on
|
||||
// for every table, not just ones that declare a formula rule - so
|
||||
// paste-translation still works here too. Row 1 (0-indexed): SOME_BESTNUM
|
||||
// = (1 % 90) + 10 = 11, so 11 * 11 = 121.
|
||||
// paste-translation still works here too. Uses SOME_BESTNUM * 0 + 1936
|
||||
// so the result is deterministic regardless of the column's actual value.
|
||||
it('41 | Pasting a formula with column names on a table with zero formula rules still translates and evaluates it', () => {
|
||||
openTableFromTree(libraryToOpenIncludes, 'mpe_x_test')
|
||||
|
||||
@@ -1327,9 +1328,9 @@ context('editor tests: ', function () {
|
||||
// own comment below; SOME_CHAR's header sort icon covers the cell.
|
||||
getCellByHeaderAndRow(1, 'SOME_CHAR').click({ force: true })
|
||||
|
||||
pasteTextIntoFocusedCell('=SOME_BESTNUM * SOME_BESTNUM')
|
||||
pasteTextIntoFocusedCell('=SOME_BESTNUM * 0 + 1936')
|
||||
|
||||
getCellByHeaderAndRow(1, 'SOME_CHAR').should('have.text', '121')
|
||||
getCellByHeaderAndRow(1, 'SOME_CHAR').should('have.text', '1936')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ const serverType = Cypress.env('serverType')
|
||||
const libraryToOpenIncludes = Cypress.env(`libraryToOpenIncludes_${serverType}`)
|
||||
const fixturePath = 'excels_multi_load/'
|
||||
|
||||
const library = 'DC_JSLIB'
|
||||
const library = 'DC996664'
|
||||
const mpeXTestTable = 'MPE_X_TEST'
|
||||
const mpeTablesTable = 'MPE_TABLES'
|
||||
|
||||
@@ -151,7 +151,7 @@ context('excel multi load tests: ', function () {
|
||||
]
|
||||
],
|
||||
() => {
|
||||
clickOnTreeNode('DC_JSLIB.MPE_TABLES', () => {
|
||||
clickOnTreeNode('DC996664.MPE_TABLES', () => {
|
||||
cy.wait(1000).then(() => {
|
||||
cy.get('#hotTable')
|
||||
.should('be.visible')
|
||||
|
||||
@@ -546,7 +546,7 @@ const checkResultOfFormulaUpload = (callback?: any) => {
|
||||
.find('tbody')
|
||||
.then((data) => {
|
||||
const cell: any = data[0].children[0].children[5]
|
||||
expect(cell.innerText).to.equal('=1+1')
|
||||
expect(cell.innerText).to.equal('2')
|
||||
if (callback) callback()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -548,7 +548,8 @@ export class AutomaticComponent implements OnInit {
|
||||
this.eventService.showAbortModal('makedata', abortMsg, {
|
||||
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
||||
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
||||
MAC: macMsg
|
||||
MAC: macMsg,
|
||||
_PROGRAM: abortRes._PROGRAM
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -894,7 +894,8 @@ export class EditorComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.eventService.showAbortModal('', abortMsg, {
|
||||
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
||||
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
||||
MAC: macMsg
|
||||
MAC: macMsg,
|
||||
_PROGRAM: abortRes._PROGRAM
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,5 +10,6 @@ export class AbortDetails {
|
||||
SYSWARNINGTEXT?: string
|
||||
SYSERRORTEXT?: string
|
||||
MAC?: string
|
||||
_PROGRAM?: string
|
||||
LOG?: string
|
||||
}
|
||||
|
||||
@@ -197,7 +197,8 @@ export class SasService {
|
||||
{
|
||||
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
||||
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
||||
MAC: macMsg
|
||||
MAC: macMsg,
|
||||
_PROGRAM: abortRes._PROGRAM
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
</p>
|
||||
<p><strong>SYSERRORTEXT:</strong> {{ data.details.SYSERRORTEXT }}</p>
|
||||
<p><strong>MAC:</strong> {{ data.details.MAC }}</p>
|
||||
<p><strong>_PROGRAM:</strong> {{ data.details._PROGRAM }}</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -396,7 +396,8 @@ export class XLMapComponent implements AfterContentInit, AfterViewInit, OnInit {
|
||||
this.eventService.showAbortModal('', abortMsg, {
|
||||
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
||||
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
||||
MAC: macMsg
|
||||
MAC: macMsg,
|
||||
_PROGRAM: abortRes._PROGRAM
|
||||
})
|
||||
} else if (res.adapterResponse.sasparams) {
|
||||
const params = res.adapterResponse.sasparams[0]
|
||||
|
||||
@@ -249,7 +249,7 @@ const tables = {
|
||||
{ tx_from: 0, base_lib: dcLibref, base_ds: 'MPE_X_TEST', base_col: 'SOME_CHAR', rule_type: 'HARDREGEX', rule_value: '/the|data/i', rule_active: 1, tx_to: 127490111999 },
|
||||
{ tx_from: 0, base_lib: dcLibref, base_ds: 'MPE_X_TEST', base_col: 'SOME_CHAR', rule_type: 'SOFTREGEX', rule_value: '/t/', rule_active: 1, tx_to: 127490111999 },
|
||||
{ tx_from: 0, base_lib: dcLibref, base_ds: 'MPE_X_TEST', base_col: 'PRIMARY_KEY_FIELD', rule_type: 'SOFTREGEX', rule_value: '/^\\d+$/', rule_active: 1, tx_to: 127490111999 },
|
||||
{ tx_from: 0, base_lib: dcLibref, base_ds: 'MPE_X_TEST', base_col: 'SOME_SHORTNUM', rule_type: 'HARDREGEX', rule_value: '/^(??$).*/', rule_active: 1, tx_to: 127490111999 },
|
||||
{ tx_from: 0, base_lib: dcLibref, base_ds: 'MPE_X_TEST', base_col: 'SOME_SHORTNUM', rule_type: 'HARDREGEX', rule_value: '/^(??$).*/', rule_active: 1, tx_to: 127490111999 },
|
||||
{ tx_from: 0, base_lib: dcLibref, base_ds: 'MPE_X_TEST', base_col: 'SOME_NUM', rule_type: 'HARDSELECT_HOOK', rule_value: 'services/validations/mpe_x_test.some_num', rule_active: 1, tx_to: 127490111999 }
|
||||
],
|
||||
MPE_X_TEST: [
|
||||
@@ -656,12 +656,12 @@ const schema = {
|
||||
],
|
||||
MPE_X_TEST: [
|
||||
{ name: 'PRIMARY_KEY_FIELD', type: 'N', length: 8, format: '', label: '', notnull: true },
|
||||
{ name: 'SOME_CHAR', type: 'C', length: 32767, format: '', label: '', notnull: false },
|
||||
{ name: 'SOME_CHAR', type: 'C', length: 32767, format: '', label: 'Some Character Column', notnull: false },
|
||||
{ name: 'SOME_DROPDOWN', type: 'C', length: 128, format: '', label: '', notnull: false },
|
||||
{ name: 'SOME_NUM', type: 'N', length: 8, format: '', label: '', notnull: false },
|
||||
{ name: 'SOME_DATE', type: 'N', length: 8, format: 'date9.', label: '', notnull: false },
|
||||
{ name: 'SOME_DATETIME', type: 'N', length: 8, format: 'datetime19.', label: '', notnull: false },
|
||||
{ name: 'SOME_TIME', type: 'N', length: 8, format: 'time8.', label: '', notnull: false },
|
||||
{ name: 'SOME_DATE', type: 'N', length: 8, format: 'date9.', label: 'Some Date', notnull: false },
|
||||
{ name: 'SOME_DATETIME', type: 'N', length: 8, format: 'datetime19.', label: 'Some Datetime', notnull: false },
|
||||
{ name: 'SOME_TIME', type: 'N', length: 8, format: 'time8.', label: 'Some Time', notnull: false },
|
||||
{ name: 'SOME_SHORTNUM', type: 'N', length: 4, format: '', label: '', notnull: false },
|
||||
{ name: 'SOME_BESTNUM', type: 'N', length: 8, format: 'best.', label: '', notnull: false }
|
||||
],
|
||||
@@ -855,6 +855,25 @@ for (const [tableName, rows] of Object.entries(testTables)) {
|
||||
}
|
||||
console.log('TESTDATA data dir: ' + testDataDir)
|
||||
|
||||
// ─── DC996664 library: extra libref for the multi-load Cypress E2E fixtures ──
|
||||
// The multi_load_test_1/2.xlsx fixture files have sheet names like
|
||||
// DC996664.MPE_X_TEST, DC996664.MPE_TABLES, DC996664.MPE_VALIDATIONS.
|
||||
// Those libref.table combos must exist in MPE_TABLES (so startupservice
|
||||
// returns them in sasdatasets and the multi-load sheet matcher accepts
|
||||
// them) and have their own data folder (so viewlibs lists the libref).
|
||||
const multiLoadLibref = 'DC996664'
|
||||
const multiLoadDataDir = nodePath.resolve(driveRoot, 'files', appLoc, 'data', multiLoadLibref)
|
||||
if (!fs.existsSync(multiLoadDataDir)) {
|
||||
fs.mkdirSync(multiLoadDataDir, { recursive: true })
|
||||
}
|
||||
tables.MPE_TABLES.push(
|
||||
{ tx_from: 0, tx_to: 127490111999, libref: multiLoadLibref, dsn: 'MPE_X_TEST', num_of_approvals_required: 1, loadtype: 'UPDATE', buskey: 'PRIMARY_KEY_FIELD', notes: 'Multi-load fixture table' },
|
||||
{ tx_from: 0, tx_to: 127490111999, libref: multiLoadLibref, dsn: 'MPE_TABLES', num_of_approvals_required: 1, loadtype: 'TXTEMPORAL', buskey: 'LIBREF DSN', var_txfrom: 'TX_FROM', var_txto: 'TX_TO', notes: 'Multi-load fixture table' },
|
||||
{ tx_from: 0, tx_to: 127490111999, libref: multiLoadLibref, dsn: 'MPE_VALIDATIONS', num_of_approvals_required: 1, loadtype: 'TXTEMPORAL', buskey: 'BASE_LIB BASE_DS BASE_COL RULE_TYPE', var_txfrom: 'TX_FROM', var_txto: 'TX_TO', notes: 'Multi-load fixture table' },
|
||||
{ tx_from: 0, tx_to: 127490111999, libref: multiLoadLibref, dsn: 'MPE_ALERTS', num_of_approvals_required: 1, loadtype: 'TXTEMPORAL', buskey: 'ALERT_EVENT ALERT_LIB ALERT_DS ALERT_USER', var_txfrom: 'TX_FROM', var_txto: 'TX_TO', notes: 'Multi-load fixture table' }
|
||||
)
|
||||
console.log(multiLoadLibref + ' data dir: ' + multiLoadDataDir)
|
||||
|
||||
// Write one JSON file per table with metadata + rows
|
||||
for (const [tableName, rows] of Object.entries(tables)) {
|
||||
const tableFile = nodePath.resolve(dataDir, tableName.toLowerCase() + '.json')
|
||||
@@ -865,6 +884,20 @@ for (const [tableName, rows] of Object.entries(tables)) {
|
||||
}
|
||||
fs.writeFileSync(tableFile, JSON.stringify(tableData, null, 2))
|
||||
}
|
||||
|
||||
// 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.
|
||||
for (const tableName of ['MPE_X_TEST', 'MPE_TABLES', 'MPE_VALIDATIONS', 'MPE_ALERTS']) {
|
||||
const srcFile = nodePath.resolve(dataDir, tableName.toLowerCase() + '.json')
|
||||
const dstFile = nodePath.resolve(multiLoadDataDir, tableName.toLowerCase() + '.json')
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(srcFile, {encoding:'utf8'}).toString())
|
||||
fs.writeFileSync(dstFile, JSON.stringify(data, null, 2))
|
||||
} catch (err) {
|
||||
console.log('Warning: could not copy ' + tableName + ' to ' + multiLoadLibref + ': ' + err.message)
|
||||
}
|
||||
}
|
||||
console.log('DC data dir: ' + dataDir)
|
||||
console.log('DCLIB: ' + dcLibref)
|
||||
console.log('ADMIN: ' + adminGroup)
|
||||
|
||||
@@ -130,8 +130,27 @@ if (!msg) {
|
||||
|
||||
// Validate that the CSV headers match the base table columns (mirrors
|
||||
// the mp_abort check for missing columns in loadfile.sas).
|
||||
// TXTEMPORAL tables have VAR_TXFROM/VAR_TXTO (and optionally
|
||||
// VAR_PROCESSED) columns that are computed at load time, never
|
||||
// provided in the uploaded file - they must be excluded from the
|
||||
// missing-cols check, same as the real loadfile.sas does.
|
||||
if (schema.length > 0 && headers.length > 0) {
|
||||
// Load MPE_TABLES to find the temporal column names for this table
|
||||
const mpeTablesLoader = makeTableLoader(mpeDataDir)
|
||||
const mpeTablesData = mpeTablesLoader('MPE_TABLES')
|
||||
let computedCols = []
|
||||
if (mpeTablesData && mpeTablesData.rows) {
|
||||
const tableEntry = mpeTablesData.rows.find(r =>
|
||||
r.libref === libref && r.dsn === dsn
|
||||
)
|
||||
if (tableEntry) {
|
||||
if (tableEntry.var_txfrom) computedCols.push(tableEntry.var_txfrom.toUpperCase())
|
||||
if (tableEntry.var_txto) computedCols.push(tableEntry.var_txto.toUpperCase())
|
||||
if (tableEntry.var_processed) computedCols.push(tableEntry.var_processed.toUpperCase())
|
||||
}
|
||||
}
|
||||
const baseCols = schema.map((c) => c.name.toUpperCase())
|
||||
.filter(c => !computedCols.includes(c))
|
||||
const receivedCols = headers.map((h) => h.toUpperCase().trim())
|
||||
const missing = baseCols.filter((c) => !receivedCols.includes(c))
|
||||
if (missing.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user