Files
simple/tests/test_uicc_files.py
T
catarrh a1892094f4 uicc_files: supplement optional files missing from pySim's model
Add a supplement to the cardless registry generator for the 13 files the
Services editor's FS check references but pySim does not emit (optional,
service-gated): EF.LRPLMNSI/IAL/IPD/3GPPPSDATAOFF/TVCONFIG, DF.MExE +
EF.MExE-ST/ORPK/ARPK/TPRPK, DF.SoLSA + EF.SAI/EF.SLL, DF.BCAST,
DF.ACDC + EF.ACDC_LIST, DF.TV, and DF.A2X + EF.AST/EF.A2X_CONFIG (under
DF.TELECOM).  Emitted with distinct (name, path) so same-FID files in
other DFs (EF.SAI vs EF.PBR, EF.AST) stay separate; ADF.USIM rows are
mirrored to ADF.USIM-NON-IMSI.  490 -> 527 entries.

Regenerated the asset; test_uicc_files.py pins the 20 new paths, the
FID-collision cases and (name, root) uniqueness.  1091 Python tests.
2026-10-26 00:01:23 +03:00

132 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Drift guard for the shipped standard file list (frontend/uicc_files.json).
The asset is generated from pySim's card profiles/application classes by
``pysim_simple_server.uicc_files``. When pySim adds, renames or moves files,
this test fails and the asset has to be regenerated with::
python -m pysim_simple_server.uicc_files --write frontend/uicc_files.json
"""
import json
import sys
import unittest
from pathlib import Path
# pySim checkout is a sibling of this repo; put it on sys.path so the builder
# (which imports pySim at module level) can be exercised against it.
PROJECTS = Path(__file__).resolve().parents[2]
PY_SIM = PROJECTS / 'pysim'
if str(PY_SIM) not in sys.path:
sys.path.insert(0, str(PY_SIM))
REPO = Path(__file__).resolve().parents[1]
ASSET = REPO / 'frontend' / 'uicc_files.json'
from pysim_simple_server import uicc_files
def _load():
with open(ASSET, 'r', encoding='utf-8') as fh:
return json.load(fh)
class TestUiccFilesAsset(unittest.TestCase):
def test_asset_matches_the_pysim_build(self):
doc = _load()
self.assertEqual(doc['files'], uicc_files.build(),
'frontend/uicc_files.json is out of date - regenerate it '
'with: python -m pysim_simple_server.uicc_files --write '
'frontend/uicc_files.json')
def test_document_has_a_date_and_a_non_empty_list(self):
doc = _load()
self.assertRegex(doc['generated'], r'^\d{4}-\d{2}-\d{2}$')
self.assertGreater(len(doc['files']), 300)
def test_known_standard_files_and_paths(self):
by_path = {f['path']: f for f in _load()['files']}
self.assertEqual(by_path['MF/2FE2']['name'], 'EF.ICCID')
self.assertEqual(by_path['MF/2FE2']['kind'], 'ef')
self.assertEqual(by_path['MF/7F10/6F3A']['name'], 'EF.ADN')
self.assertEqual(by_path['MF/7F10/6F3A']['sym'], 'MF/DF.TELECOM/EF.ADN')
self.assertEqual(by_path['MF/7F20']['name'], 'DF.GSM')
self.assertEqual(by_path['MF/7F20']['kind'], 'df')
self.assertEqual(by_path['ADF.USIM/6F07']['name'], 'EF.IMSI')
self.assertEqual(by_path['ADF.USIM']['kind'], 'adf')
self.assertEqual(by_path['ADF.USIM']['aid'], 'A0000000871002')
self.assertEqual(by_path['ADF.ISIM/6F02']['name'], 'EF.IMPI')
def test_paths_are_unique_and_well_formed(self):
files = _load()['files']
paths = [f['path'] for f in files]
self.assertEqual(len(paths), len(set(paths)))
for f in files:
root = f['root']
self.assertTrue(f['path'].startswith(root), f['path'])
self.assertIn(f['kind'], ('ef', 'df', 'adf'))
if f['kind'] == 'adf':
self.assertEqual(f['path'].upper(), f['name'].upper())
self.assertTrue(f['aid'])
else:
self.assertRegex(f['fid'], r'^[0-9A-F]{4}$')
self.assertTrue(f['name'].startswith(('EF.', 'DF.')))
def test_optional_files_missing_from_the_pysim_model(self):
"""The service-gated files pySim does not model (the SUPPLEMENT)."""
files = _load()['files']
by_path = {f['path']: f for f in files}
expected = {
# TS 31.102 4.2.86 / 4.2.100 / 4.2.102 / 4.2.108 / 4.2.109
'ADF.USIM/6FDC': 'EF.LRPLMNSI',
'ADF.USIM/6FF0': 'EF.IAL',
'ADF.USIM/6FF2': 'EF.IPD',
'ADF.USIM/6FF9': 'EF.3GPPPSDATAOFF',
'ADF.USIM/6FFB': 'EF.TVCONFIG',
# 4.4.4 DF.MExE
'ADF.USIM/5F3C': 'DF.MExE',
'ADF.USIM/5F3C/4F40': 'EF.MExE-ST',
'ADF.USIM/5F3C/4F41': 'EF.ORPK',
'ADF.USIM/5F3C/4F42': 'EF.ARPK',
'ADF.USIM/5F3C/4F43': 'EF.TPRPK',
# 4.4.1 DF.SoLSA
'ADF.USIM/5F70': 'DF.SoLSA',
'ADF.USIM/5F70/4F30': 'EF.SAI',
'ADF.USIM/5F70/4F31': 'EF.SLL',
# 4.4.7 / 4.4.9 / 4.4.10
'ADF.USIM/5F80': 'DF.BCAST',
'ADF.USIM/5FA0': 'DF.ACDC',
'ADF.USIM/5FA0/4F01': 'EF.ACDC_LIST',
'ADF.USIM/5FB0': 'DF.TV',
# 4.6.6 DF.A2X (DF.TELECOM)
'MF/7F10/5F3F': 'DF.A2X',
'MF/7F10/5F3F/4F01': 'EF.AST',
'MF/7F10/5F3F/4F02': 'EF.A2X_CONFIG',
}
for path, name in expected.items():
self.assertIn(path, by_path)
self.assertEqual(by_path[path]['name'], name)
self.assertEqual(by_path[path]['root'], path.split('/')[0])
self.assertEqual(by_path[path]['kind'],
'df' if name.startswith('DF.') else 'ef')
# The same FID under another DF stays a distinct entry (name, path).
self.assertEqual(by_path['ADF.USIM/5F3A/4F30']['name'], 'EF.PBR')
self.assertEqual(by_path['MF/7F10/5F3A/4F30']['name'], 'EF.PBR')
# The ADF.USIM files are mirrored under the non-IMSI USIM root.
for path, name in expected.items():
if not path.startswith('ADF.USIM/'):
continue
mirror = 'ADF.USIM-NON-IMSI' + path[len('ADF.USIM'):]
self.assertIn(mirror, by_path)
self.assertEqual(by_path[mirror]['name'], name)
self.assertEqual(by_path[mirror]['root'], 'ADF.USIM-NON-IMSI')
# (name, root) resolves each SUPPLEMENT file to exactly one path.
for path, name in expected.items():
root = path.split('/')[0]
self.assertEqual([f['path'] for f in files
if f['name'] == name and f['root'] == root], [path])
if __name__ == '__main__':
unittest.main()