v3.26.0: per-TAR default keysets (the preset/SCP80/RAM rework)
Every preset TAR entry carries `keyset` (int 1-15) - the keyset every
operation addressed to that TAR uses: mandatory for any non-zero MSL
(the counters are keyset-linked), optional for MSL 00 (allowed: some
cards reject the no-security 00/00 KIc/KID form). The store fills it
automatically when the preset has a single keyset, the validation
refuses an entry naming an undefined keyset, and the schema is 6 (a v5
file loads leniently; editing it forces the fix).
The keyset selectors are gone: the SCP80 TAR field became a select of
the preset's entries (applying the TAR's keyset to the form and seeding
the SPI1 from its MSL); the RAM operations use the preset's ISD role
entry (the PWA sends only the preset id, the ISD TAR, SPI and the
counter override - the server resolves the keyset and the keys:
presets.tar_keyset, _preset_tar_keyset, _resolve_operation_keyset,
_op_keyset, used by send-ota's APDU path, ram-install, ram-install-app
and cap-compat); the counter probe and the test runner's scp80 steps
default to their TAR's keyset (an explicit kvn stays an override). A
security TAR without a keyset refuses the operation ("set it in the
Cards tab") - never a silent keyless send - and the start buttons wait
for a usable operation-TAR keyset.
The TAR probe checks the preset's TAR list only (the standard
TAR_PROBE_TARS list and the custom-TAR localStorage are gone; a `tars`
list selects a subset, a TAR outside the preset is refused), each row
using its own MSL and default keyset. The Cards editor's TAR table
gained a Keyset column (the only keyset preselected, an amber marker
when a security MSL lacks one).
Docs: help EN/RU, api.md's tar-probe contract, both AGENTS. Tests: the
store (the field, the auto-fill, the schema, the string forms), the
server resolution (send-ota + the refusal, the probe's per-row keysets
and refusals), the PWA (the TAR select, the RAM ISD resolution, the
keyset-free requests) - several fixtures updated for the new schema.
891 frontend + 885 Python; sw cache simple-v434.
This commit is contained in:
@@ -34,12 +34,12 @@ for (const fn of ['cardsTarList', 'cardsRoleDefault', 'cardsRoleTar',
|
||||
'spPresetCounterValue', 'spCntrPlaceholder', 'spCntrEffective', 'spCntrAhead',
|
||||
'spCntrAdjust',
|
||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
|
||||
'spTarOptionsHtml', 'spTarRender', 'spSpi1Seed', 'spTarKeysetGuard', 'cardsTarKeysetCell', 'cardsTarKeysetFromForm', 'cardsTarKeysetRefresh', 'cardsTarRowsRender', 'cardsTarRowHtml', 'cardsTarsFromTable', 'cardsFreeTarsFromForm', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
|
||||
'cardsKeysetAdd', 'cardsKeysetRemove', 'cardsKeysetKvnUpdate',
|
||||
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply',
|
||||
'spKeysetChanged', 'ramKeysetChanged', 'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard',
|
||||
'spTarListRender',
|
||||
'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard',
|
||||
'ramKeysetGuard',
|
||||
'esc',
|
||||
]) {
|
||||
code += extractFunc(html, fn) + '\n';
|
||||
}
|
||||
@@ -61,9 +61,22 @@ globalThis.t = s => s;
|
||||
function fakeEnv(selValue, selId, presetCntr) {
|
||||
const els = {};
|
||||
for (const id of ['sp-spi1', 'sp-spi2-hex', 'sp-kic-idx', 'sp-kic-alg',
|
||||
'sp-kid-idx', 'sp-kid-alg', 'sp-tar', 'sp-cntr', 'sp-kic-key', 'sp-kid-key']) {
|
||||
'sp-kid-idx', 'sp-kid-alg', 'sp-cntr', 'sp-kic-key', 'sp-kid-key']) {
|
||||
els[id] = { value: '' };
|
||||
}
|
||||
// the TAR is a select (v3.26.0): the stub models the surface the renderer
|
||||
// touches - setting innerHTML parses the option values and selectedIndex
|
||||
// moves the value, like a real <select>
|
||||
els['sp-tar'] = {
|
||||
value: '', disabled: false, options: [], _sel: 0, _html: '',
|
||||
set innerHTML(v) {
|
||||
this._html = v;
|
||||
this.options = Array.from(v.matchAll(/value="([^"]*)"/g), m => ({ value: m[1] }));
|
||||
},
|
||||
get innerHTML() { return this._html; },
|
||||
set selectedIndex(i) { this._sel = i; if (this.options[i]) this.value = this.options[i].value; },
|
||||
get selectedIndex() { return this._sel; },
|
||||
};
|
||||
// the combined key bytes the server reads (updateSpKic maintains them in
|
||||
// the real form): the fake preset's keyset resolves to kvn 1
|
||||
els['sp-kic-hex'] = { value: '15' };
|
||||
@@ -114,14 +127,16 @@ test('spRefreshFromPreset for the RAM selector targets the ISD TAR', () => {
|
||||
assert.strictEqual(env.els['sp-tar'].value, '000000');
|
||||
});
|
||||
|
||||
test('the pre-send refresh keeps a hand-edited packet TAR (v3.6.21)', () => {
|
||||
test('the pre-send refresh keeps the selected packet TAR', () => {
|
||||
// v3.26.0: the TAR is a preset selector - the pre-send refresh must keep
|
||||
// the operator's selection (a TAR outside the preset is not offered)
|
||||
const env = fakeEnv('0', 'sp-card-sel', '0000000020');
|
||||
env.els['sp-tar'].value = 'AF4D01'; // e.g. a push/link trigger target
|
||||
env.els['sp-tar'].value = 'B00000'; // the UICC RFM entry of the preset
|
||||
assert.strictEqual(spRefreshFromPreset('sp-card-sel', true), '0000000020');
|
||||
assert.strictEqual(env.els['sp-tar'].value, 'AF4D01', 'the typed TAR must survive the send');
|
||||
assert.strictEqual(env.els['sp-tar'].value, 'B00000', 'the selected TAR must survive the send');
|
||||
assert.strictEqual(env.els['sp-cntr'].value, '', 'the counter field stays an override');
|
||||
assert.match(env.els['sp-cntr'].placeholder, /0000000020/);
|
||||
// an explicit preset apply (or a pack) still sets the TAR
|
||||
// an explicit preset apply (or a pack) selects the operation's TAR
|
||||
cardsApplyFields(0);
|
||||
assert.strictEqual(env.els['sp-tar'].value, 'B00000');
|
||||
});
|
||||
|
||||
@@ -35,13 +35,13 @@ for (const fn of ['cardsFormValues', 'cardsClearForm', 'cardsEdit',
|
||||
'spCntrAhead', 'spCntrLocalSync', 'ramSaveCntr',
|
||||
'cardsApplyFields', 'cardsApply', 'ramApplyCard',
|
||||
'cardsScp80Complete', 'cardsScp81Complete', 'cardsAdmPresent',
|
||||
'spTarKeyForPack', 'spPresetTar', 'spTarListRender', 'packToSp',
|
||||
'spTarKeyForPack', 'spPresetTar', 'packToSp',
|
||||
'esc', 'escHtml',
|
||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
|
||||
'spTarOptionsHtml', 'spTarRender', 'spSpi1Seed', 'spTarKeysetGuard', 'cardsTarKeysetCell', 'cardsTarKeysetFromForm', 'cardsTarKeysetRefresh', 'cardsTarRowsRender', 'cardsTarRowHtml', 'cardsTarsFromTable', 'cardsFreeTarsFromForm', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
|
||||
'cardsKeysetAdd', 'cardsKeysetRemove', 'cardsKeysetKvnUpdate',
|
||||
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply', 'spKeysetChanged',
|
||||
'ramKeysetChanged', 'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard', 'ramKeysetGuard',
|
||||
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply',
|
||||
'ramPresetIdx', 'tarPresetIdx', 'spKeysetGuard', 'ramKeysetGuard',
|
||||
'cardsTarList', 'cardsRoleDefault', 'cardsRoleTar',
|
||||
'cardsTarEntry', 'cardsTarMsl', 'cardsTarText', 'spMslWarningText',
|
||||
'cardsTarRowHtml', 'cardsTarRowsRender', 'cardsTarAdd', 'cardsTarRemove',
|
||||
@@ -73,12 +73,11 @@ const CARD_IDS = ['cards-name','cards-iccid','cards-release','cards-pin1','cards
|
||||
// the TAR table rows: 0-2 are the fixed role rows, 3+ the free rows
|
||||
'cards-tr-0-tar','cards-tr-0-msl','cards-tr-1-tar','cards-tr-1-msl',
|
||||
'cards-tr-2-tar','cards-tr-2-msl',
|
||||
'cards-tr-3-tar','cards-tr-3-msl','cards-tr-3-desc',
|
||||
'cards-tr-4-tar','cards-tr-4-msl','cards-tr-4-desc'];
|
||||
'cards-tr-3-tar','cards-tr-3-msl','cards-tr-3-desc','cards-tr-3-keyset',
|
||||
'cards-tr-4-tar','cards-tr-4-msl','cards-tr-4-desc','cards-tr-4-keyset'];
|
||||
const SP_IDS = ['sp-card-sel','sp-tar','sp-spi1','sp-spi2','sp-spi2-sm','sp-spi2-hex',
|
||||
'sp-kic-idx','sp-kic-alg','sp-kic-hex','sp-kid-idx','sp-kid-alg','sp-kid-hex',
|
||||
'sp-cntr','sp-kic-key','sp-kid-key','sp-apdu','sp-result','sp-spi1-hex','sp-tar-list',
|
||||
'sp-keyset-sel','ram-keyset-sel'];
|
||||
'sp-cntr','sp-kic-key','sp-kid-key','sp-apdu','sp-result','sp-spi1-hex'];
|
||||
const CHAIN_IDS = ['chain-sim-preview','chain-usim-preview','chain-ram-preview',
|
||||
'ber-result','hota-preview'];
|
||||
|
||||
@@ -91,10 +90,10 @@ function ksRow(els, i, ks) {
|
||||
}
|
||||
|
||||
function tarRow(els, i, e) {
|
||||
for (const field of ['tar', 'msl', 'desc']) {
|
||||
for (const field of ['tar', 'msl', 'desc', 'keyset']) {
|
||||
const id = 'cards-tr-' + i + '-' + field;
|
||||
if (!els[id]) els[id] = fakeEl();
|
||||
els[id].value = e[field] || '';
|
||||
els[id].value = (e[field] === undefined || e[field] === null) ? '' : String(e[field]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +107,34 @@ function presetFixture(extra) {
|
||||
}, extra || {});
|
||||
}
|
||||
|
||||
// A <select>-like fake (the TAR selector): setting innerHTML parses the option
|
||||
// values (and the selected one) and selectedIndex moves the value, like a real
|
||||
// element. The accessors need defineProperties - Object.assign would flatten
|
||||
// them into data properties.
|
||||
function selectEl() {
|
||||
const el = fakeEl();
|
||||
el.options = [];
|
||||
el._sel = 0;
|
||||
el._html = '';
|
||||
el.disabled = false;
|
||||
Object.defineProperties(el, {
|
||||
innerHTML: {
|
||||
get() { return this._html; },
|
||||
set(v) {
|
||||
this._html = v;
|
||||
this.options = Array.from(v.matchAll(/value="([^"]*)"/g), m => ({ value: m[1] }));
|
||||
const sel = v.match(/<option value="([^"]*)" selected/);
|
||||
if (sel) this.value = sel[1];
|
||||
},
|
||||
},
|
||||
selectedIndex: {
|
||||
get() { return this._sel; },
|
||||
set(i) { this._sel = i; if (this.options[i]) this.value = this.options[i].value; },
|
||||
},
|
||||
});
|
||||
return el;
|
||||
}
|
||||
|
||||
function fakeEl() {
|
||||
const classes = new Set();
|
||||
return {
|
||||
@@ -134,6 +161,7 @@ let apiCalls = [];
|
||||
function setup() {
|
||||
const els = {};
|
||||
for (const id of CARD_IDS.concat(SP_IDS, CHAIN_IDS)) els[id] = fakeEl();
|
||||
els['sp-tar'] = selectEl(); // the TAR selector (v3.26.0)
|
||||
globalThis.document = { getElementById: id => els[id] || null, querySelectorAll: () => [] };
|
||||
globalThis.cards = [];
|
||||
globalThis._cardsEditIdx = null;
|
||||
@@ -257,9 +285,9 @@ test('clearing the card form keeps the role TAR/MSL defaults', () => {
|
||||
assert.match(els['cards-tars'].innerHTML, /cards-tr-2-tar"[^>]*value="B00001"/);
|
||||
// ... and the table reads back as the three role entries
|
||||
assert.deepStrictEqual(cardsTarsFromForm(), [
|
||||
{ role: 'isd', tar: '000000', msl: '16', desc: '' },
|
||||
{ role: 'uiccRfm', tar: 'B00000', msl: '16', desc: '' },
|
||||
{ role: 'usimRfm', tar: 'B00001', msl: '16', desc: '' },
|
||||
{ role: 'isd', tar: '000000', msl: '16', desc: '', keyset: null },
|
||||
{ role: 'uiccRfm', tar: 'B00000', msl: '16', desc: '', keyset: null },
|
||||
{ role: 'usimRfm', tar: 'B00001', msl: '16', desc: '', keyset: null },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -312,10 +340,10 @@ test('the form values carry the codes and the TAR table', () => {
|
||||
_cardsTarCount = 1;
|
||||
const v = cardsFormValues();
|
||||
assert.deepStrictEqual(v.tars, [
|
||||
{ role: 'isd', tar: '000001', msl: '0A', desc: '' },
|
||||
{ role: 'uiccRfm', tar: 'B00000', msl: '16', desc: '' },
|
||||
{ role: 'usimRfm', tar: 'B00003', msl: '1A', desc: '' },
|
||||
{ tar: 'AF4D02', msl: '0A', desc: 'My applet' },
|
||||
{ role: 'isd', tar: '000001', msl: '0A', desc: '', keyset: null },
|
||||
{ role: 'uiccRfm', tar: 'B00000', msl: '16', desc: '', keyset: null },
|
||||
{ role: 'usimRfm', tar: 'B00003', msl: '1A', desc: '', keyset: null },
|
||||
{ tar: 'AF4D02', msl: '0A', desc: 'My applet', keyset: null },
|
||||
]);
|
||||
assert.strictEqual(v.pin1, '1234');
|
||||
assert.strictEqual(v.puk1, '12345678');
|
||||
@@ -342,6 +370,11 @@ test('saving a preset posts the form with the pre-settled TAR table', async () =
|
||||
els['cards-name'].value = 'New card';
|
||||
ksRow(els, 0, { kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' });
|
||||
globalThis._cardsKeysetCount = 1;
|
||||
// the editor's render preselects the only keyset on every TAR row
|
||||
// (v3.26.0); the fake DOM is static, so the test does it explicitly
|
||||
tarRow(els, 0, { keyset: 1 });
|
||||
tarRow(els, 1, { keyset: 1 });
|
||||
tarRow(els, 2, { keyset: 1 });
|
||||
// the role fields are pre-settled by the markup; the test's fake elements
|
||||
// start empty - the defaults fill in
|
||||
els['cards-adm'].value = 'aa bb';
|
||||
@@ -352,9 +385,9 @@ test('saving a preset posts the form with the pre-settled TAR table', async () =
|
||||
assert.deepStrictEqual(apiCalls[0].body.keysets, [
|
||||
{ kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' }]);
|
||||
assert.deepStrictEqual(apiCalls[0].body.tars, [
|
||||
{ role: 'isd', tar: '000000', msl: '16', desc: '' },
|
||||
{ role: 'uiccRfm', tar: 'B00000', msl: '16', desc: '' },
|
||||
{ role: 'usimRfm', tar: 'B00001', msl: '16', desc: '' },
|
||||
{ role: 'isd', tar: '000000', msl: '16', desc: '', keyset: 1 },
|
||||
{ role: 'uiccRfm', tar: 'B00000', msl: '16', desc: '', keyset: 1 },
|
||||
{ role: 'usimRfm', tar: 'B00001', msl: '16', desc: '', keyset: 1 },
|
||||
]);
|
||||
assert.strictEqual(apiCalls[0].body.adm, 'AABB');
|
||||
});
|
||||
@@ -457,11 +490,14 @@ test('applying a preset uses the TAR of the current operation and seeds the SPI1
|
||||
cardsApply(0);
|
||||
assert.strictEqual(els['sp-tar'].value, 'B0000D');
|
||||
assert.strictEqual(els['sp-spi1'].value, '1A');
|
||||
// an empty stored TAR -> spec default
|
||||
// a preset without TAR entries (hand-made; the store fills the three
|
||||
// roles): the selector has no options and the SPI1 falls back to the
|
||||
// default MSL
|
||||
globalThis.cards = [{ name: 'D', keysets: [], tars: [] }];
|
||||
globalThis._spTarKey = 'isd';
|
||||
cardsApply(0);
|
||||
assert.strictEqual(els['sp-tar'].value, '000000');
|
||||
assert.strictEqual(els['sp-tar'].value, '');
|
||||
assert.match(els['sp-tar'].innerHTML, /no TAR in the preset/);
|
||||
assert.strictEqual(els['sp-spi1'].value, '16');
|
||||
});
|
||||
|
||||
@@ -494,15 +530,18 @@ test('spPresetTar reads the selected preset and falls back to the spec defaults'
|
||||
assert.strictEqual(spPresetTar('uiccRfm'), 'B00000');
|
||||
});
|
||||
|
||||
test('the TAR field suggestions come from the preset table', () => {
|
||||
test('the TAR selector lists the preset table', () => {
|
||||
// v3.26.0: the TAR is a select of the preset's entries (role label or
|
||||
// description, MSL and default keyset in the option text)
|
||||
const els = setup();
|
||||
spTarListRender(presetFixture({ tars: [
|
||||
{ role: 'isd', tar: '000000', msl: '16' },
|
||||
spTarRender(presetFixture({ tars: [
|
||||
{ role: 'isd', tar: '000000', msl: '16', keyset: 1 },
|
||||
{ tar: 'AF4D02', msl: '0A', desc: 'My applet' }] }));
|
||||
assert.match(els['sp-tar-list'].innerHTML, /value="000000"[^>]*label="ISD \(MSL 16\)"/);
|
||||
assert.match(els['sp-tar-list'].innerHTML, /value="AF4D02"[^>]*label="My applet \(MSL 0A\)"/);
|
||||
spTarListRender(null);
|
||||
assert.strictEqual(els['sp-tar-list'].innerHTML, '');
|
||||
assert.match(els['sp-tar'].innerHTML, /value="000000"[^>]*>000000 \u00b7 ISD \u00b7 MSL 16 \u00b7 Keyset 1</);
|
||||
assert.match(els['sp-tar'].innerHTML, /value="AF4D02"[^>]*>AF4D02 \u00b7 My applet \u00b7 MSL 0A</);
|
||||
spTarRender(null);
|
||||
assert.match(els['sp-tar'].innerHTML, /select a card preset/);
|
||||
assert.strictEqual(els['sp-tar'].disabled, true);
|
||||
});
|
||||
|
||||
test('packing a chain selects the TAR of its target', () => {
|
||||
@@ -533,17 +572,18 @@ test('packing a chain selects the TAR of its target', () => {
|
||||
assert.strictEqual(els['sp-apdu'].value, 'AA0100');
|
||||
});
|
||||
|
||||
test('packing without a selected preset uses the spec default TAR', () => {
|
||||
test('packing without a selected preset keeps the role for the next apply', () => {
|
||||
// v3.26.0: the TAR options come from the preset - without one the selector
|
||||
// stays on its placeholder and the role is applied when a preset arrives
|
||||
const els = setup();
|
||||
els['chain-sim-preview'].value = 'A0A4000002';
|
||||
packToSp('chain-sim-preview');
|
||||
assert.strictEqual(globalThis._spTarKey, 'uiccRfm');
|
||||
assert.strictEqual(els['sp-tar'].value, '', 'no preset -> no TAR options yet');
|
||||
globalThis.cards = [presetFixture()];
|
||||
els['sp-card-sel'].value = '0';
|
||||
cardsApply(0);
|
||||
assert.strictEqual(els['sp-tar'].value, 'B00000');
|
||||
els['chain-usim-preview'].value = '00A4000002';
|
||||
packToSp('chain-usim-preview');
|
||||
assert.strictEqual(els['sp-tar'].value, 'B00001');
|
||||
els['chain-ram-preview'].value = '80E29000';
|
||||
packToSp('chain-ram-preview');
|
||||
assert.strictEqual(els['sp-tar'].value, '000000');
|
||||
});
|
||||
|
||||
test('the TAR row values are left-aligned like the headers', () => {
|
||||
@@ -586,7 +626,8 @@ test('the TAR table renders the role rows and adds/removes free rows', () => {
|
||||
assert.strictEqual(_cardsTarCount, 2);
|
||||
// the new row renders empty
|
||||
tarRow(els, 4, { tar: '', msl: '', desc: '' });
|
||||
assert.deepStrictEqual(cardsFreeTarsFromForm(), [{ tar: 'AF4D02', msl: '0A', desc: 'two' }]);
|
||||
assert.deepStrictEqual(cardsFreeTarsFromForm(),
|
||||
[{ tar: 'AF4D02', msl: '0A', desc: 'two', keyset: null }]);
|
||||
// the role rows are never removable
|
||||
cardsTarRemove(0);
|
||||
assert.strictEqual(_cardsTarCount, 2);
|
||||
|
||||
@@ -43,6 +43,12 @@ code += extractFunc(html, 'cardsScp80Complete') + '\n';
|
||||
code += extractFunc(html, 'cardsScp81Complete') + '\n';
|
||||
code += extractFunc(html, 'cardsMatchedPreset') + '\n';
|
||||
code += extractFunc(html, 'spKeysetStartReady') + '\n';
|
||||
code += extractFunc(html, 'spKeysetGuard') + '\n';
|
||||
code += extractFunc(html, 'ramKeysetGuard') + '\n';
|
||||
code += extractFunc(html, 'spTarKeysetGuard') + '\n';
|
||||
code += extractFunc(html, 'cardsTarEntry') + '\n';
|
||||
code += extractFunc(html, 'cardsRoleTar') + '\n';
|
||||
code += extractFunc(html, 'cardsRoleDefault') + '\n';
|
||||
code += extractFunc(html, 'spPresetIdx') + '\n';
|
||||
code += extractFunc(html, 'ramPresetIdx') + '\n';
|
||||
code += extractFunc(html, 'tarPresetIdx') + '\n';
|
||||
|
||||
@@ -23,8 +23,9 @@ function extractFunc(src, name) {
|
||||
|
||||
let code = '';
|
||||
for (const fn of ['spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'spCounterTracked', 'spPresetIdx', 'ramPresetIdx',
|
||||
'tarPresetIdx', 'spKeysetSync', 'ramRender', 'esc', 'escHtml']) {
|
||||
'spTarOptionsHtml', 'spTarRender', 'spSpi1Seed', 'spTarKeysetGuard', 'cardsTarKeysetCell', 'cardsTarKeysetFromForm', 'cardsTarKeysetRefresh', 'cardsTarRowsRender', 'cardsTarRowHtml', 'cardsTarsFromTable', 'cardsFreeTarsFromForm', 'spCounterTracked', 'spPresetIdx', 'ramPresetIdx',
|
||||
'tarPresetIdx', 'spKeysetSync', 'ramRender', 'esc', 'escHtml',
|
||||
'cardsTarList', 'cardsTarEntry', 'cardsRoleTar']) {
|
||||
code += extractFunc(html, fn) + '\n';
|
||||
}
|
||||
code += 'function t(s){return s;}\n';
|
||||
@@ -91,20 +92,23 @@ test('spKeysetCheck enforces the keyset-number rules', () => {
|
||||
assert.match(spKeysetCheck(PRESET, '15', 'ZZ'), /KID/);
|
||||
});
|
||||
|
||||
test('spKeysetOptionsHtml lists the keysets and marks an undefined number', () => {
|
||||
const html1 = spKeysetOptionsHtml(PRESET, '15', '15');
|
||||
assert.match(html1, /value="1" selected/);
|
||||
assert.match(html1, /value="2"/);
|
||||
assert.ok(html1.indexOf('0000000005') > 0, 'the counters are shown');
|
||||
// a version the preset does not define is offered but marked
|
||||
const html2 = spKeysetOptionsHtml(PRESET, '35', '35');
|
||||
assert.match(html2, /value="3" selected/);
|
||||
assert.match(html2, /not defined in the preset/);
|
||||
// a preset without keysets: the form's version shows as undefined
|
||||
const html3 = spKeysetOptionsHtml({ keysets: [] }, '15', '15');
|
||||
assert.match(html3, /not defined in the preset/);
|
||||
// no keyset number in the form and no keysets at all
|
||||
assert.match(spKeysetOptionsHtml({}, '', ''), /no keyset/);
|
||||
test('spTarKeysetGuard follows the TAR default keyset (v3.26.0)', () => {
|
||||
const preset = Object.assign({}, PRESET, { tars: [
|
||||
{ role: 'isd', tar: '000000', msl: '16', keyset: 1 },
|
||||
{ role: 'uiccRfm', tar: 'B00000', msl: '16', keyset: 2 },
|
||||
{ role: 'usimRfm', tar: 'B00001', msl: '00' },
|
||||
{ tar: 'B00011', msl: '16', keyset: 3 }] });
|
||||
assert.strictEqual(spTarKeysetGuard(preset, '000000'), '');
|
||||
assert.strictEqual(spTarKeysetGuard(preset, 'B00001'), ''); // MSL 00: keyless is fine
|
||||
// a TAR naming a keyset the preset does not define
|
||||
assert.match(spTarKeysetGuard(preset, 'B00011'), /is not defined in the preset/);
|
||||
// a security TAR without a default keyset
|
||||
const noKeyset = Object.assign({}, PRESET, { tars: [
|
||||
{ role: 'isd', tar: '000000', msl: '16' }] });
|
||||
assert.match(spTarKeysetGuard(noKeyset, '000000'), /needs a default keyset/);
|
||||
// a TAR outside the preset is the server's business; no preset: nothing
|
||||
assert.strictEqual(spTarKeysetGuard(preset, 'B00099'), '');
|
||||
assert.strictEqual(spTarKeysetGuard(null, '000000'), '');
|
||||
});
|
||||
|
||||
test('the Cards form wires the keyset editor', () => {
|
||||
@@ -117,9 +121,14 @@ test('the Cards form wires the keyset editor', () => {
|
||||
// the keyset rows are built with the KIc/KID/key/counter inputs
|
||||
assert.match(html, /cards-ks-' \+ i \+ '-kic-key/);
|
||||
assert.match(html, /cards-ks-' \+ i \+ '-cntr/);
|
||||
// the selectors exist in both SCP80 views
|
||||
assert.match(html, /id="sp-keyset-sel" onchange="spKeysetChanged\(\)"/);
|
||||
assert.match(html, /id="ram-keyset-sel" onchange="ramKeysetChanged\(\)"/);
|
||||
// v3.26.0: the keyset selectors are gone - the SCP80 TAR select drives
|
||||
// the keyset, the RAM operations use the preset's ISD entry, and the TAR
|
||||
// table gained a keyset column
|
||||
assert.match(html, /id="sp-tar" onchange="spTarChanged\(\)"/);
|
||||
assert.ok(!html.includes('id="sp-keyset-sel"'));
|
||||
assert.ok(!html.includes('id="ram-keyset-sel"'));
|
||||
assert.ok(!html.includes('id="tar-keyset-sel"'));
|
||||
assert.match(html, /id="cards-tr-' \+ i \+ '-keyset"/);
|
||||
// the packet actions run the guard
|
||||
assert.ok(html.includes('const ksErr = spKeysetGuard();'));
|
||||
assert.ok(html.includes('const ksErr = ramKeysetGuard();'));
|
||||
@@ -136,14 +145,9 @@ test('spCounterTracked follows SPI1 b5b4 (TS 102 225 5.1.1)', () => {
|
||||
assert.strictEqual(spCounterTracked('zz'), false);
|
||||
});
|
||||
|
||||
test('ramRender rebuilds the keyset selector from the remembered preset', () => {
|
||||
// regression: the auto-selection runs before the RAM select has options, so
|
||||
// its keyset list was built with no preset and stayed on "not defined"
|
||||
test('ramRender keeps the remembered preset (v3.26.0: no keyset selector)', () => {
|
||||
const els = {
|
||||
'ram-card-sel': { value: '', innerHTML: '', appendChild() {} },
|
||||
'ram-keyset-sel': { value: '', innerHTML: '' },
|
||||
'sp-keyset-sel': { value: '', innerHTML: '' },
|
||||
'tar-keyset-sel': { value: '', innerHTML: '' },
|
||||
'sp-kic-hex': { value: '25' },
|
||||
'sp-kid-hex': { value: '25' },
|
||||
};
|
||||
@@ -160,9 +164,6 @@ test('ramRender rebuilds the keyset selector from the remembered preset', () =>
|
||||
ramRender();
|
||||
assert.strictEqual(els['ram-card-sel'].value, '0',
|
||||
'the preset select keeps the remembered card');
|
||||
assert.match(els['ram-keyset-sel'].innerHTML, /value="2" selected/);
|
||||
assert.ok(!els['ram-keyset-sel'].innerHTML.includes('not defined'),
|
||||
'the keyset selector must not show a stale "not defined" entry');
|
||||
});
|
||||
|
||||
test('the RAM and TAR views rebuild their keyset lists when their preset is known', () => {
|
||||
@@ -179,44 +180,32 @@ test('the RAM and TAR views rebuild their keyset lists when their preset is know
|
||||
assert.ok(html.includes("if (name === 'tar') { tarRender(); tarProbeRender(); }"));
|
||||
});
|
||||
|
||||
test('the keyset selector is disabled and cleared while no preset is selected', () => {
|
||||
test('the TAR selector waits for a preset and lists its entries', () => {
|
||||
const els = {
|
||||
'sp-keyset-sel': { value: '', innerHTML: '' },
|
||||
'ram-keyset-sel': { value: '', innerHTML: '' },
|
||||
'tar-keyset-sel': { value: '', innerHTML: '' },
|
||||
'sp-tar': { value: '', disabled: false, innerHTML: '', options: [] },
|
||||
'sp-card-sel': { value: '' },
|
||||
'ram-card-sel': { value: '' },
|
||||
'tar-card-sel': { value: '' },
|
||||
'sp-kic-hex': { value: '' },
|
||||
'sp-kid-hex': { value: '' },
|
||||
};
|
||||
globalThis.document = { getElementById: id => els[id] || null };
|
||||
globalThis.cards = [];
|
||||
spKeysetSync();
|
||||
for (const id of ['sp-keyset-sel', 'ram-keyset-sel', 'tar-keyset-sel']) {
|
||||
assert.strictEqual(els[id].disabled, true, id + ' must be disabled');
|
||||
assert.match(els[id].innerHTML, /select a card preset/);
|
||||
assert.ok(!els[id].innerHTML.includes('not defined'), id);
|
||||
}
|
||||
// a selected preset enables the selector and lists its keysets
|
||||
globalThis.cards = [{ name: 'EP', keysets: [
|
||||
{ kic: '15', kid: '15', kicKey: 'AA', kidKey: 'BB', cntr: '0000000001' }] }];
|
||||
spTarRender();
|
||||
assert.strictEqual(els['sp-tar'].disabled, true, 'no preset: the selector waits');
|
||||
assert.match(els['sp-tar'].innerHTML, /select a card preset/);
|
||||
// a selected preset enables the selector and lists its TARs with their
|
||||
// MSL and default keyset
|
||||
globalThis.cards = [{ name: 'EP', tars: [
|
||||
{ role: 'isd', tar: '000000', msl: '16', keyset: 1 },
|
||||
{ tar: 'AF4D02', msl: '0A', desc: 'My applet' }] }];
|
||||
els['sp-card-sel'].value = '0';
|
||||
els['ram-card-sel'].value = '0';
|
||||
els['tar-card-sel'].value = '0';
|
||||
els['sp-kic-hex'].value = '15';
|
||||
els['sp-kid-hex'].value = '15';
|
||||
spKeysetSync();
|
||||
for (const id of ['sp-keyset-sel', 'ram-keyset-sel', 'tar-keyset-sel']) {
|
||||
assert.strictEqual(els[id].disabled, false, id + ' must be enabled');
|
||||
assert.match(els[id].innerHTML, /value="1" selected/);
|
||||
}
|
||||
spTarRender();
|
||||
assert.strictEqual(els['sp-tar'].disabled, false);
|
||||
assert.match(els['sp-tar'].innerHTML, /value="000000"[^>]*>000000 \u00b7 ISD \u00b7 MSL 16 \u00b7 Keyset 1</);
|
||||
assert.match(els['sp-tar'].innerHTML, /value="AF4D02"[^>]*>AF4D02 \u00b7 My applet \u00b7 MSL 0A</);
|
||||
});
|
||||
|
||||
test('clearing the preset selection clears the keyset selectors', () => {
|
||||
test('clearing the preset selection resets the TAR selector', () => {
|
||||
const at = html.indexOf('function cardsApply(');
|
||||
assert.ok(at > 0);
|
||||
const body = html.slice(at, at + 600);
|
||||
assert.ok(body.includes('spKeysetSync();'),
|
||||
'cardsApply must sync the keyset selectors when no preset is selected');
|
||||
assert.ok(body.includes('spTarRender(null);'),
|
||||
'cardsApply must reset the TAR selector when no preset is selected');
|
||||
});
|
||||
|
||||
+13
-10
@@ -30,9 +30,9 @@ const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamB
|
||||
'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramCntrLowView', 'ramShowCntrLow',
|
||||
'ramCntrLowBox', 'ramCntrLowClear',
|
||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'spPresetIdx', 'tarPresetIdx',
|
||||
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply', 'spKeysetChanged',
|
||||
'ramKeysetChanged', 'ramPresetIdx', 'spKeysetGuard', 'ramKeysetGuard',
|
||||
'spTarOptionsHtml', 'spTarRender', 'spSpi1Seed', 'spTarKeysetGuard', 'cardsTarKeysetCell', 'cardsTarKeysetFromForm', 'cardsTarKeysetRefresh', 'cardsTarRowsRender', 'cardsTarRowHtml', 'cardsTarsFromTable', 'cardsFreeTarsFromForm', 'spPresetIdx', 'tarPresetIdx',
|
||||
'cardsKeysetsFromForm', 'spKeysetSync', 'spKeysetApply',
|
||||
'ramPresetIdx', 'spKeysetGuard', 'ramKeysetGuard',
|
||||
'ramCardIdxAfterRemove', 'ramClearResults', 'ramHideProgress', 'ramOpChanged', 'ramRender', 'ramApplyCard', 'ramExecute', 'decodePrivileges', 'ramActionBtn', 'ramCapToolkitMode', 'ramOpProgressText',
|
||||
'jcAidNorm', 'jcAidName', 'jcAidSuffix', 'jcAidHtml'];
|
||||
let code = '';
|
||||
@@ -1038,13 +1038,12 @@ test('every SCP80/RAM flow checks the low counter and stops', () => {
|
||||
assert.ok(go.includes('cardsEdit('), go);
|
||||
assert.ok(go.includes("getElementById('cards-ks-' + row + '-cntr')"), go);
|
||||
// the notice's sync probe posts to the bounded server endpoint with the
|
||||
// preset, the keyset the failed operation used and its SPI/TAR
|
||||
// preset, the operation's TAR (v3.26.0: the RAM view uses the ISD entry,
|
||||
// the server resolves the keyset) and its SPI
|
||||
const sync = extractFunc(html, 'ramSyncCounter');
|
||||
assert.ok(sync.includes("'/api/counter-probe'"), sync);
|
||||
assert.ok(sync.includes("own = view === 'sp'"), sync);
|
||||
assert.ok(sync.includes('kvnOf(own)'), sync);
|
||||
assert.ok(sync.includes('kvnOf(other)'), sync);
|
||||
assert.ok(sync.includes('spKeysetSync()'), 'the keyset labels must refresh');
|
||||
assert.ok(sync.includes("view === 'ram' ? cardsRoleTar(preset, 'isd')"), sync);
|
||||
assert.ok(sync.includes('spKeysetSync()'), 'the availability must refresh');
|
||||
assert.ok(sync.includes('spCntrPlaceholder()'),
|
||||
'the stored value must refresh the field placeholder (the field stays an override)');
|
||||
assert.ok(sync.includes("ramListingSpi2('40')"),
|
||||
@@ -1372,9 +1371,13 @@ test('getRamSpParams reads the computed SPI2 byte, not the base select', () => {
|
||||
assert.ok(/spi2: document\.getElementById\('sp-spi2-hex'\)/.test(fn), fn);
|
||||
});
|
||||
|
||||
test('getRamSpParams normalises the packet TAR to three bytes', () => {
|
||||
test('getRamSpParams sends the preset ISD TAR and no keyset fields (v3.26.0)', () => {
|
||||
// the server resolves the keyset from the preset's ISD TAR entry - the
|
||||
// PWA sends only the preset id, the ISD TAR, SPI and the counter override
|
||||
const fn = extractFunc(html, 'getRamSpParams');
|
||||
assert.ok(/tar:[\s\S]*?padEnd\(6, '0'\)\.slice\(0, 6\),/.test(fn), fn);
|
||||
assert.ok(fn.includes("tar: preset ? cardsRoleTar(preset, 'isd') : ''"), fn);
|
||||
assert.ok(!fn.includes('kic:'), 'the keyset fields are resolved server-side');
|
||||
assert.ok(!fn.includes('kicKey:'), 'the keys travel from the preset store');
|
||||
});
|
||||
|
||||
test('ramListingSpi2 requests the SMS-submit PoR for the listing queries', () => {
|
||||
|
||||
@@ -35,14 +35,20 @@ for (const fn of ['berLenStr', 'ramDeleteApdu', 'ramIncrementCntr', 'ramSaveCntr
|
||||
'ramCntrLowBox', 'ramCntrLowClear',
|
||||
'escHtml', 'esc',
|
||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||
'spKeysetOptionsHtml', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
|
||||
'spTarOptionsHtml', 'spTarRender', 'spSpi1Seed', 'spTarKeysetGuard', 'cardsTarKeysetCell', 'cardsTarKeysetFromForm', 'cardsTarKeysetRefresh', 'cardsTarRowsRender', 'cardsTarRowHtml', 'cardsTarsFromTable', 'cardsFreeTarsFromForm', 'cardsRoleTar', 'cardsTarEntry', 'cardsTarList', 'cardsRoleDefault', 'cardsKeysetRowHtml', 'cardsKeysetRowsRender',
|
||||
'cardsKeysetAdd', 'cardsKeysetRemove', 'cardsKeysetKvnUpdate',
|
||||
'spKeysetChanged', 'ramKeysetChanged', 'ramPresetIdx', 'spKeysetGuard',
|
||||
'ramPresetIdx', 'spKeysetGuard',
|
||||
'ramKeysetGuard',
|
||||
]) {
|
||||
code += extractFunc(html, fn) + '\n';
|
||||
}
|
||||
code = 'var _cardsKeysetCount = 0;\n' + code;
|
||||
// the role defaults the TAR helpers read (v3.26.0)
|
||||
for (const c of ['CARDS_TAR_DEFAULTS', 'CARDS_TAR_ROLES', 'CARDS_MSL_DEFAULT']) {
|
||||
const m = new RegExp('const\\s+' + c + '\\s*=\\s*([^;]+);').exec(html);
|
||||
if (!m) throw new Error('const ' + c + ' not found');
|
||||
code = 'var ' + c + ' = ' + m[1] + ';\n' + code;
|
||||
}
|
||||
eval(code);
|
||||
|
||||
function fakeEnv(por, success) {
|
||||
|
||||
@@ -23,78 +23,43 @@ function extractFunc(src, name) {
|
||||
|
||||
let code = '';
|
||||
for (const fn of ['esc', 'escHtml', 'tarProbeVerdictLabel', 'tarProbeHtml',
|
||||
'tarProbeRowsHtml', 'tarProbeCustomLoad', 'tarProbeCustomSave',
|
||||
'tarProbeAllRows', 'tarProbeChecked']) {
|
||||
'tarProbeRowsHtml', 'tarProbeAllRows', 'tarProbeChecked', 'cardsTarList']) {
|
||||
code += extractFunc(html, fn) + '\n';
|
||||
}
|
||||
code += "var TAR_PROBE_TARS = JSON.parse('" +
|
||||
JSON.stringify(eval('(' + html.match(/const TAR_PROBE_TARS = \[[\s\S]*?\n\];/)[0]
|
||||
.replace('const TAR_PROBE_TARS = ', '').replace(/;$/, '') + ')')) + "');\n";
|
||||
code += "var TAR_PROBE_STORAGE = 'simple_tar_probe';\n";
|
||||
code += 'function t(s){return s;}\n';
|
||||
eval(code);
|
||||
|
||||
function fakeStorage() {
|
||||
const map = {};
|
||||
globalThis.localStorage = {
|
||||
getItem: k => (k in map ? map[k] : null),
|
||||
setItem: (k, v) => { map[k] = String(v); },
|
||||
};
|
||||
return map;
|
||||
}
|
||||
|
||||
test('the standard list covers the Annex D allocations', () => {
|
||||
const tars = TAR_PROBE_TARS.map(e => e.tar);
|
||||
assert.ok(tars.length >= 10, tars.length);
|
||||
for (const want of ['000000', 'B20100', 'B00000', 'B00001', 'B00010',
|
||||
'B00120', 'B00130', 'B00140', 'B20000', 'B20200', 'B20201', 'B00200']) {
|
||||
assert.ok(tars.includes(want), want);
|
||||
}
|
||||
// every row carries the spec document it comes from
|
||||
TAR_PROBE_TARS.forEach(e => {
|
||||
assert.match(e.tar, /^[0-9A-F]{6}$/);
|
||||
assert.ok(e.label, e.tar);
|
||||
});
|
||||
});
|
||||
|
||||
test('tarProbeRowsHtml renders checked rows and marks custom ones', () => {
|
||||
test('tarProbeRowsHtml renders the preset rows checked', () => {
|
||||
// v3.26.0: the rows come from the preset - no custom rows, no remove
|
||||
// buttons
|
||||
const out = tarProbeRowsHtml([
|
||||
{ tar: '000000', label: 'Issuer Security Domain (compact)', doc: 'TS 102 226' },
|
||||
{ tar: '123456', label: 'my applet', custom: true },
|
||||
{ tar: '000000', label: 'ISD', doc: 'MSL 16 \u00b7 Keyset 1' },
|
||||
{ tar: 'AF4D02', label: 'My applet' },
|
||||
]);
|
||||
assert.match(out, /value="000000" checked/);
|
||||
assert.match(out, /title="TS 102 226"/);
|
||||
assert.match(out, /Issuer Security Domain \(compact\)/);
|
||||
assert.match(out, /value="123456" checked/);
|
||||
assert.match(out, /tarProbeRemoveCustom\('123456'\)/, 'custom rows get a remove button');
|
||||
assert.ok(!/tarProbeRemoveCustom\('000000'\)/.test(out), 'standard rows have none');
|
||||
assert.match(out, /title="MSL 16/);
|
||||
assert.match(out, /ISD/);
|
||||
assert.match(out, /value="AF4D02" checked/);
|
||||
assert.ok(!out.includes('tarProbeRemoveCustom'), 'no custom rows anymore');
|
||||
// escaping
|
||||
const evil = tarProbeRowsHtml([{ tar: 'AA<BB', label: '<i>x', custom: true }]);
|
||||
const evil = tarProbeRowsHtml([{ tar: 'AA<BB', label: '<i>x' }]);
|
||||
assert.ok(!evil.includes('AA<BB'));
|
||||
assert.ok(!evil.includes('<i>x'));
|
||||
});
|
||||
|
||||
test('custom TARs round-trip through localStorage (best effort)', () => {
|
||||
fakeStorage();
|
||||
assert.deepStrictEqual(tarProbeCustomLoad(), []);
|
||||
tarProbeCustomSave([{ tar: '123456', label: 'mine' }, { tar: 'aabbcc' }]);
|
||||
const back = tarProbeCustomLoad();
|
||||
assert.deepStrictEqual(back, [{ tar: '123456', label: 'mine', custom: true },
|
||||
{ tar: 'AABBCC', label: '', custom: true }]);
|
||||
// garbage is dropped, never fatal
|
||||
localStorage.setItem('simple_tar_probe', '{not json');
|
||||
assert.deepStrictEqual(tarProbeCustomLoad(), []);
|
||||
localStorage.setItem('simple_tar_probe', JSON.stringify([{ tar: 'zz' }, { tar: 'B0B0B0' }]));
|
||||
assert.deepStrictEqual(tarProbeCustomLoad().map(e => e.tar), ['B0B0B0']);
|
||||
});
|
||||
|
||||
test('tarProbeAllRows appends the custom TARs to the standard ones', () => {
|
||||
fakeStorage();
|
||||
tarProbeCustomSave([{ tar: '123456', label: 'mine' }]);
|
||||
const rows = tarProbeAllRows();
|
||||
assert.strictEqual(rows.length, TAR_PROBE_TARS.length + 1);
|
||||
assert.strictEqual(rows[rows.length - 1].tar, '123456');
|
||||
assert.strictEqual(rows[rows.length - 1].custom, true);
|
||||
test('tarProbeAllRows lists the selected preset TARs (v3.26.0)', () => {
|
||||
globalThis.cards = [{ name: 'C', tars: [
|
||||
{ role: 'isd', tar: '000000', msl: '16', keyset: 1 },
|
||||
{ tar: 'AF4D02', msl: '0A', desc: 'My applet' }] }];
|
||||
globalThis.tarPresetIdx = () => 0;
|
||||
assert.deepStrictEqual(tarProbeAllRows(), [
|
||||
{ tar: '000000', label: 'ISD', doc: 'MSL 16 \u00b7 Keyset 1' },
|
||||
{ tar: 'AF4D02', label: 'My applet', doc: 'MSL 0A' },
|
||||
]);
|
||||
// no preset: no rows
|
||||
globalThis.tarPresetIdx = () => -1;
|
||||
assert.deepStrictEqual(tarProbeAllRows(), []);
|
||||
delete globalThis.tarPresetIdx;
|
||||
});
|
||||
|
||||
test('tarProbeChecked reads the checked boxes in list order', () => {
|
||||
@@ -141,21 +106,24 @@ test('the SCP80 tab wires the TAR probe pill', () => {
|
||||
assert.match(html, /<div id="scp80-sub-tar" class="hidden">/);
|
||||
assert.ok(html.includes("document.getElementById('scp80-sub-tar').classList.toggle('hidden', name !== 'tar');"));
|
||||
assert.ok(html.includes("tar: 'tar-probe'"), 'the help anchor points at the new section');
|
||||
// its own preset + keyset selectors and the warning banner
|
||||
// its own preset selector and the warning banner; v3.26.0: the probe
|
||||
// checks the preset's TAR list - no keyset selector, no custom TARs
|
||||
assert.match(html, /id="tar-card-sel" onchange="tarApplyCard\(this.value\)"/);
|
||||
assert.match(html, /id="tar-keyset-sel" onchange="tarKeysetChanged\(\)"/);
|
||||
assert.match(html, /id="tar-preset-warning"/);
|
||||
// the checklist, the add form, the APDU and the Execute button
|
||||
assert.ok(!html.includes('id="tar-keyset-sel"'), 'the probe keyset selector is gone');
|
||||
assert.ok(!html.includes('tarProbeAddCustom'), 'no arbitrary TARs anymore');
|
||||
assert.match(html, /TARs to probe \(the preset's TAR list\)/);
|
||||
// the checklist, the APDU and the Execute button
|
||||
assert.match(html, /id="tar-list"/);
|
||||
assert.match(html, /onclick="tarProbeSelectAll\(true\)"/);
|
||||
assert.match(html, /onclick="tarProbeAddCustom\(\)"/);
|
||||
assert.match(html, /id="tar-apdu"[^>]*value="00A40000023F00"/);
|
||||
assert.match(html, /onclick="tarExecute\(\)"/);
|
||||
assert.match(html, /id="tar-result"/);
|
||||
// the run
|
||||
// the run: the preset and the checked TARs travel - the server resolves
|
||||
// each row's keyset
|
||||
assert.ok(html.includes("pysimFetch('/api/tar-probe', body)"));
|
||||
assert.ok(html.includes('tars: tars'));
|
||||
assert.ok(html.includes("getRamSpParams('tar-card-sel')"));
|
||||
assert.ok(!html.includes("getRamSpParams('tar-card-sel')"));
|
||||
assert.ok(html.includes('if (data.final_cntr) ramSaveCntr(data.final_cntr);'));
|
||||
// the RAM-era leftovers are gone
|
||||
assert.ok(!html.includes('id="ram-tar-rows"'));
|
||||
|
||||
Reference in New Issue
Block a user