diff --git a/frontend/index.html b/frontend/index.html index d74ef35..3ddd1f8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7642,7 +7642,10 @@ async function profilerRunRule(r) { const n = Math.min(exp.length, act.length); if (exp.length !== act.length) { res.status = 'fail'; - res.checks.push({ label: 'content.records', expected: exp.length + ' records', actual: act.length + ' records', ok: false }); + const numRec = res.checks.find(c => c.label === 'numRecords'); + if (!numRec || numRec.ok) { + res.checks.push({ label: 'content.records', expected: exp.length + ' records', actual: act.length + ' records', ok: false }); + } } const matched = []; for (let j = 0; j < n; j++) { diff --git a/frontend/sw.js b/frontend/sw.js index 93541b0..c21ade2 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'otaman-v58'; +const CACHE = 'otaman-v59'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js index 6388e66..29a3fbb 100644 --- a/frontend/tests/profiler.test.js +++ b/frontend/tests/profiler.test.js @@ -474,6 +474,46 @@ test('profilerRunRule records which records matched on a record mismatch', async assert.deepStrictEqual(res.recordsMatched, [1, 3]); }); +const REC_SELECT_10 = { name: 'EF.X', fid: '6F3A', file_type: 'linear_fixed', file_size: null, record_len: 2, num_of_rec: 10, exists: true }; +const REC_EXP_30 = (() => { const r = []; for (let i = 1; i <= 30; i++) r.push({ num: i, data: 'AB' }); return r; })(); +const REC_ACT_10 = (() => { const r = []; for (let i = 1; i <= 10; i++) r.push({ num: i, data: 'AB' }); return r; })(); + +test('record count mismatch is reported once when numRecords is checked', async () => { + mockFetch({ '/api/select': () => REC_SELECT_10, '/api/read': () => ({ success: true, records: REC_ACT_10 }) }); + const res = await profilerRunRule({ + path: 'MF/7F20/6F3A', fileType: 'linear_fixed', recordLen: 2, numRecords: 30, fciMode: 'type_size', + content: { mode: 'exact', kind: 'record', records: REC_EXP_30 }, + }); + assert.strictEqual(res.status, 'fail'); + assert.ok(res.checks.some(c => c.label === 'numRecords' && c.ok === false)); + assert.ok(!res.checks.some(c => c.label === 'content.records')); +}); + +test('record count mismatch keeps content.records when numRecords is not checked (type mode)', async () => { + mockFetch({ '/api/select': () => REC_SELECT_10, '/api/read': () => ({ success: true, records: REC_ACT_10 }) }); + const res = await profilerRunRule({ + path: 'MF/7F20/6F3A', fileType: 'linear_fixed', recordLen: 2, numRecords: 30, fciMode: 'type', + content: { mode: 'exact', kind: 'record', records: REC_EXP_30 }, + }); + assert.strictEqual(res.status, 'fail'); + assert.ok(res.checks.some(c => c.label === 'content.records' && c.ok === false)); + assert.ok(!res.checks.some(c => c.label === 'numRecords')); +}); + +test('record count mismatch keeps content.records when numRecords passes but read differs', async () => { + mockFetch({ + '/api/select': () => ({ ...REC_SELECT_10, num_of_rec: 3 }), + '/api/read': () => ({ success: true, records: REC_ACT_10.slice(0, 2) }), + }); + const res = await profilerRunRule({ + path: 'MF/7F20/6F3A', fileType: 'linear_fixed', recordLen: 2, numRecords: 3, fciMode: 'type_size', + content: { mode: 'exact', kind: 'record', records: [{ num: 1, data: 'AB' }, { num: 2, data: 'AB' }, { num: 3, data: 'AB' }] }, + }); + assert.strictEqual(res.status, 'fail'); + assert.ok(res.checks.some(c => c.label === 'numRecords' && c.ok === true)); + assert.ok(res.checks.some(c => c.label === 'content.records' && c.ok === false)); +}); + test('profilerRenderReport includes the checked-aspects summary and matching-record note', () => { global.t = s => s; global.pysimCustomFiles = [];