From ad543fd731a05c47ebc86ebbaa35cb1f1c721b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 10 Sep 2026 23:08:17 +0300 Subject: [PATCH] profiler: dedupe redundant record-count failure in check report A record file with both a numRecords metadata check and record content checks reported the same count mismatch twice (numRecords and content.records). profilerRunRule now omits the content.records line when the numRecords check already covers it, keeping it only when no numRecords check ran (FCP/FCI 'type' mode or numRecords cleared) or when numRecords passes but the read returns a different count. 3 new tests. SW cache v58 -> v59. --- frontend/index.html | 5 ++++- frontend/sw.js | 2 +- frontend/tests/profiler.test.js | 40 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) 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 = [];