v3.15.0: test-script fixes for applet testing - preset id, kvn, PoR and file-list checks

Three gaps blocked SCP80 applet testing from the PWA:

- testRunPreset() still marshalled the pre-v3.8 flat preset fields
  (kic/kid/keys/counter/tar/spi1/spi2, all empty on a stored preset), so
  every SCP80 run failed with "SCP80 preset is incomplete: no keyset is
  defined".  The PWA sends the matched preset's id now and the server
  resolves it from the store (_test_run_preset_from_body; the inline
  `preset` body stays for API callers), so a run always uses the stored
  keysets, TARs and counters.  Regression tests on both sides.
- testscript._normalise_params dropped the scp80 step's `kvn` (the editor
  exposed it and the runner and validation consumed it, so scripts
  silently used the preset's first keyset).  kvn is validated (1-15) and
  passed through; the client-side problem check bounds it too.
- The PoR delivered as a SEND SHORT MESSAGE (PoR-in-submit, SPI2 bit 0x20)
  is decoded for the expect step that fetches it: the new `por` content
  check asserts the response status / R-APDU SW / response data, decoded
  with the preceding scp80 step's context (concatenated SMS accumulate
  across expectations).  The inline transport stays on the action
  (check.por / check.data).
- The new `files` content check asserts the File List of a REFRESH
  (TS 102 223 8.18, tag 12/92) - e.g. qualifier 00 = NAA initialization
  with full file change notification - as an order-insensitive set of FID
  paths.

The step editor exposes both checks; help EN/RU, docs/api.md and the new
authoring reference docs/test-scripts.md document the format.  Tests: 803
frontend + 680 Python; sw cache simple-v378.
This commit is contained in:
2026-10-04 17:09:06 +03:00
parent 3c5882d16d
commit abc9ea2751
11 changed files with 699 additions and 33 deletions
+29
View File
@@ -253,6 +253,10 @@ def _normalise_params(kind, p):
for key in ('spi1', 'spi2'):
if out.get(key) and len(out[key]) != 2:
raise ScriptError('scp80: %s must be 1 byte' % key)
# The keyset number (KIc/KID b8..b5, TS 102 225 5.1.2): the keys and
# the counter stay preset-owned, the step only picks which keyset.
if p.get('kvn') not in (None, ''):
out['kvn'] = _int(p.get('kvn'), 'scp80 keyset number', 1, 15)
return out
if kind == 'status':
attempts = p.get('attempts')
@@ -355,6 +359,31 @@ def _normalise_content_check(c, default_level):
spec = _check_spec(c.get('value') if 'value' in c else c, 'raw check')
return {'kind': 'raw', 'mode': spec['mode'], 'value': spec['value'],
'on_fail': level}
if kind == 'por':
out = {'kind': 'por', 'on_fail': level}
status = c.get('status')
if status is not None and str(status).strip():
out['status'] = str(status).strip()
if c.get('sw') not in (None, ''):
out['sw'] = _check_spec(c.get('sw'), 'por check sw')
if c.get('data') not in (None, ''):
out['data'] = _check_spec(c.get('data'), 'por check data')
if not (out.get('status') or out.get('sw') or out.get('data')):
raise ScriptError('por check: status, sw or data is required')
return out
if kind == 'files':
val = c.get('files', c.get('value'))
if isinstance(val, str):
val = [x for x in re.split(r'[,\s]+', val) if x]
if not isinstance(val, list) or not val:
raise ScriptError('files check: a list of file paths is required')
files = []
for f in val:
path = re.sub(r'\s', '', str(f)).upper()
if not re.fullmatch(r'(?:[0-9A-F]{2})+', path) or len(path) < 4:
raise ScriptError('files check: each path must be hex FID bytes: %r' % f)
files.append(path)
return {'kind': 'files', 'files': files, 'on_fail': level}
raise ScriptError('unknown check kind %r' % c.get('kind'))