fix: CAP compatibility test review fixes (v3.6.49)

Review of v3.6.48 found six issues; the two that matter in use:

- ramProbeParse('') errored, so the basic 'is this CAP compatible with
  this card at all?' run (no overrides) was refused by an alert.  An empty
  list is valid now: the test runs with the CAP's own versions.
- ramCompatVerdict said 'Import list rejected' for every failure.  It now
  distinguishes: a request error (no steps), a failed format probe (the
  card not answering), an INSTALL [for load] refusal, and only then the
  import list - each with its own headline.

Plus:
- the result line shows the applied probe (ramCompatProbeNote: 'probe:
  7 imports -> 0.0 · +1 appended', unmatched AIDs included);
- #ram-compat-block validates 1..240 like the install's block size;
- the server reports probe_unmatched (override AIDs matching no entry,
  previously silent when another override matched) and logs them;
- tests: the endpoint's probe-400 path (a mini CAP + a dummy scc),
  _cap_import_aids, the verdict cases and the probe note.

sw cache simple-v322; tests: 540 Python / 687 Node.
This commit is contained in:
2026-09-30 08:13:12 +03:00
parent 0dc79a3ba4
commit 80c26f14f2
7 changed files with 170 additions and 25 deletions
+41 -10
View File
@@ -22,7 +22,7 @@ function extractFunc(src, name) {
}
// Extract chain builder functions and dependencies
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramStepComponents', 'ramInstallFailHint', 'ramProbeParse', 'ramCompatVerdict', 'ramExpandedDetailsInit', 'ramExpandedDetailsChanged', 'ramGetStatusApdu', 'ramDeleteApdu',
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramStepLine', 'ramStepComponents', 'ramInstallFailHint', 'ramProbeParse', 'ramCompatVerdict', 'ramCompatProbeNote', 'ramExpandedDetailsInit', 'ramExpandedDetailsChanged', 'ramGetStatusApdu', 'ramDeleteApdu',
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'parseTLV', '_parseE3Entry',
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
@@ -787,8 +787,9 @@ test('ramProbeParse reads AID=version lines', () => {
// malformed lines and an empty field are errors
assert.ok(ramProbeParse('A0000000620101').error);
assert.ok(ramProbeParse('A0000000620101=x.y').error);
assert.ok(ramProbeParse('').error);
assert.ok(ramProbeParse(' \n# only comments\n').error);
// an empty list is valid: the test then runs with the CAP's own versions
assert.deepStrictEqual(ramProbeParse(''), { map: {}, additions: [] });
assert.deepStrictEqual(ramProbeParse(' \n# only comments\n'), { map: {}, additions: [] });
// '+AID=version' appends a synthetic import (a card-capability query)
r = ramProbeParse('+A0000000090005FFFFFFFF8912000000=1.11');
assert.deepStrictEqual(r.map, {});
@@ -800,17 +801,47 @@ test('ramProbeParse reads AID=version lines', () => {
assert.ok(ramProbeParse('+all=1.0').error);
});
test('ramCompatVerdict reports the import-gate verdict', () => {
test('ramCompatVerdict distinguishes where the test failed', () => {
globalThis.t = s => s;
const ok = ramCompatVerdict({ imports_ok: true, boundary_block: 3, total_blocks: 240 });
assert.strictEqual(ok, 'Import list accepted \u2014 tested through LOAD 3/240');
const bad = ramCompatVerdict({ imports_ok: false, boundary_block: 3, total_blocks: 240,
error: 'remote SW 6438' });
assert.ok(bad.startsWith('Import list rejected \u2014 tested through LOAD 3/240'), bad);
assert.ok(bad.includes('remote SW 6438'), bad);
const step = (name, ok) => ok
? { name: name, por_status: 'por_ok', por_sw: '9000' }
: { name: name, por_status: 'por_ok', por_sw: '6438', por_error: 'remote SW 6438' };
assert.strictEqual(ramCompatVerdict({ imports_ok: true, boundary_block: 3, total_blocks: 240,
steps: [step('FORMAT CHECK (compact)', true), step('INSTALL [for load]', true), step('LOAD (3/240)', true)] }),
'Import list accepted \u2014 tested through LOAD 3/240');
assert.strictEqual(ramCompatVerdict({ imports_ok: false, boundary_block: 3, total_blocks: 240,
error: 'remote SW 6438',
steps: [step('FORMAT CHECK (compact)', true), step('INSTALL [for load]', true), step('LOAD (3/240)', false)] }),
'Import list rejected \u2014 tested through LOAD 3/240 \u00b7 remote SW 6438');
// the card never answered the format probe
assert.strictEqual(ramCompatVerdict({ imports_ok: false, boundary_block: 1, total_blocks: 240,
steps: [step('FORMAT CHECK (compact)', false), step('FORMAT CHECK (expanded)', false)] }),
'The card did not answer the format probe');
// the INSTALL [for load] request itself was refused
assert.strictEqual(ramCompatVerdict({ imports_ok: false, boundary_block: 1, total_blocks: 240,
error: 'remote SW 6985',
steps: [step('FORMAT CHECK (compact)', true), step('INSTALL [for load]', false)] }),
'Load request rejected \u2014 remote SW 6985');
// a request error without steps
assert.strictEqual(ramCompatVerdict({ error: 'import probe: invalid version' }),
'Error: import probe: invalid version');
assert.strictEqual(ramCompatVerdict(null), '');
});
test('ramCompatProbeNote summarizes the applied probe', () => {
globalThis.t = s => s;
assert.strictEqual(ramCompatProbeNote({ probe_imports: [
{ aid: 'A1', from: '1.3', to: '0.0' }, { aid: 'A2', from: '1.4', to: '0.0' },
{ aid: 'A3', from: null, to: '1.0' }] }),
'probe: 2 imports \u2192 0.0 \u00b7 +1 appended');
assert.strictEqual(ramCompatProbeNote({ probe_imports: [
{ aid: 'A1', from: '1.3', to: '0.0' }, { aid: 'A2', from: '1.4', to: '1.0' }] }),
'probe: 2 imports \u2192 mixed');
assert.strictEqual(ramCompatProbeNote({ probe_imports: [], probe_unmatched: ['DEADBEEF'] }),
'probe: unmatched: DEADBEEF');
assert.strictEqual(ramCompatProbeNote({}), '');
});
test('ramExecute dispatches the compatibility test op', () => {
const src = extractFunc(html, 'ramExecute');
assert.ok(/op === 'compat'/.test(src), 'the compat op must be dispatched');