Files
simple/frontend/tests/esim.test.js
T
catarrh 91c642a646 esim: decode EUICCInfo1/2 and the RAT per SGP.22 (no version bump yet)
The chip endpoint returned pySim's flattened EuiccInfo dict, whose classes
are incomplete: the capability fields are raw GreedyBytes, extCardResource
is raw bytes and several SGP.22 TLVs are missing from the class, so cards
showed 'unknown_ber_tlv_ie_99' and raw hex instead of decoded values.

- request the EUICCInfo1/2 and configured-address TLVs raw and decode them
  in esim.py per SGP.22 v2.6 5.7.8, cross-checked against lpac's
  es10c_ex.c: extended card resource, UICC/RSP capability bit lists (first
  octet = unused bits, MSB-first), CI PKI lists, category (both the
  implicit 0x8B and explicit 0xAB tag encodings), forbidden profile policy
  rules (0x99), ppVersion (0x04), sasAcreditationNumber (0x0C) and the
  optional certification data object / TRE fields; undecoded TLVs stay in
  raw_tlvs instead of being dropped.
- add the ES10b GetRat rules authorisation table (PPR ids, allowed
  operators, consent flag) to the chip response.
- PWA: label every new field, map nested labels per path component (the
  old code only matched whole keys), group the view into EUICCInfo1 /
  EUICCInfo2 / Addresses / RAT sections, render arrays of objects with
  index labels and translate the labels (RU).
- tests: decoders against a real card's values (077F3E1F80, 0490, 0640,
  81010082040006B32C83022646, the RAT fixture) and the frontend label
  mapping; sw.js simple-v229.
2026-09-21 23:56:41 +03:00

128 lines
4.7 KiB
JavaScript

const { test } = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
function extractFunc(src, name) {
const re = new RegExp('(?:async\\s+)?function\\s+' + name + '\\s*\\([^)]*\\)\\s*\\{');
const m = re.exec(src);
if (!m) throw new Error('function ' + name + ' not found');
let i = m.index + m[0].length - 1;
let depth = 0;
for (; i < src.length; i++) {
if (src[i] === '{') depth++;
else if (src[i] === '}') {
depth--;
if (depth === 0) break;
}
}
return src.slice(m.index, i + 1);
}
let code = '';
for (const fn of ['esimLabel', 'esimGroupEid', 'esimFieldRows', 'esimResultText',
'esimStateLabel', 'esimOperationsText', 'esimProfileRows']) {
code += extractFunc(html, fn) + '\n';
}
for (const c of ['ESIM_CHIP_LABELS', 'ESIM_RESULT_KEYS']) {
code += html.match(new RegExp('const ' + c + ' = \\{[\\s\\S]*?\\n\\};'))[0]
.replace('const ', 'var ') + '\n';
}
code += 'function t(s){return s;}\n';
eval(code);
test('esimGroupEid groups the hex digits in fours', () => {
assert.strictEqual(esimGroupEid('89049032000000000000000000000001'),
'8904 9032 0000 0000 0000 0000 0000 0001');
assert.strictEqual(esimGroupEid(''), '');
});
test('esimLabel maps known chip keys and prettifies the rest', () => {
assert.strictEqual(esimLabel('svn'), 'SVN');
assert.strictEqual(esimLabel('default_dp_address'), 'Default SM-DP+ address');
assert.strictEqual(esimLabel('installed_application'), 'Installed applications');
assert.strictEqual(esimLabel('forbidden_profile_policy_rules'), 'Forbidden PPRs');
assert.strictEqual(esimLabel('allowed_operators'), 'Allowed operators');
assert.strictEqual(esimLabel('some_unknown_key'), 'Some unknown key');
});
test('esimFieldRows flattens nested values and skips empties', () => {
const rows = esimFieldRows({
svn: '1.2.3', empty: null, blank: '', nested: { profile_version: '2.2' },
list: ['a', 'b'], zero: 0,
});
assert.deepStrictEqual(rows, [
['SVN', '1.2.3'],
['Nested / Profile version', '2.2'],
['List', 'a, b'],
['Zero', '0'],
]);
});
test('esimFieldRows labels nested keys per path component', () => {
const rows = esimFieldRows({
euicc_ci_pki_list_for_verification: { subject_key_identifier: '8137AB' },
ext_card_resource: {
installed_application: 0,
free_non_volatile_memory: 439084,
},
}, '');
assert.deepStrictEqual(rows, [
['CI PKI (verification) / Subject key identifier', '8137AB'],
['Card resource / Installed applications', '0'],
['Card resource / Free non-volatile memory', '439084'],
]);
});
test('esimFieldRows recurses into arrays of objects with index labels', () => {
const rows = esimFieldRows({
rat: [{
ppr_ids: ['ppr1', 'ppr2'],
allowed_operators: [{ plmn: 'EEEEEE', gid1: null, gid2: null }],
ppr_flags: ['consentRequired'],
}],
}, '');
assert.deepStrictEqual(rows, [
['Rules authorisation table 1 / PPR IDs', 'ppr1, ppr2'],
['Rules authorisation table 1 / Allowed operators 1 / PLMN', 'EEEEEE'],
['Rules authorisation table 1 / PPR flags', 'consentRequired'],
]);
});
test('esimResultText localizes result codes and passes errors through', () => {
assert.strictEqual(esimResultText({ result: 'catBusy' }), 'card is busy with a CAT session');
assert.strictEqual(esimResultText({ result: 'ok' }), 'ok');
assert.strictEqual(esimResultText({ error: 'no card' }), 'no card');
assert.strictEqual(esimResultText(null), '');
});
test('esimStateLabel and esimOperationsText map the profile metadata', () => {
assert.strictEqual(esimStateLabel('enabled'), 'Enabled');
assert.strictEqual(esimStateLabel('disabled'), 'Disabled');
assert.strictEqual(esimStateLabel('other'), 'other');
assert.strictEqual(esimOperationsText(['enable', 'delete']), 'enable, delete');
assert.strictEqual(esimOperationsText([]), '');
});
test('esimProfileRows renders the metadata in a stable order', () => {
const rows = esimProfileRows({
iccid: '8970119000004002667', isdp_aid: 'A0000005591010FFFFFFFF8900000100',
provider: 'Miranda', name: 'Miranda LTE', class: 'operational',
owner: '250-99', icon_type: 'png',
});
assert.deepStrictEqual(rows.map(r => r[0]),
['ICCID', 'ISD-P AID', 'Provider', 'Profile name', 'Class', 'Owner', 'Icon']);
assert.strictEqual(rows[0][1], '8970119000004002667');
});
test('the eSIM pill is wired into the Phone simulator tab', () => {
assert.ok(html.includes('data-phone-sub="esim"'));
assert.ok(html.includes('id="phone-sub-esim"'));
assert.ok(html.includes("'phone', 'tr', 'esim'"));
assert.ok(html.includes('id="esim-refresh-btn"'));
assert.ok(html.includes('id="esim-profiles"'));
assert.ok(html.includes('id="esim-notifications"'));
});