v3.22.0: the event action uses the Simulator's forms (all modelled events)

The test-script event action edited its fields as a JSON textarea and only
accepted the four events events.py builds; every other event needed the raw
envelope action with hand-copied hex.  It now renders the same EVENT_FORMS
form the Phone tab's send dialog uses, for every modelled event (29; no
release gating - a script may target another card).

- PWA: the field renderer/collector is shared (eventFormFieldsHtml /
  eventFormValues / eventFormPatternError / eventFormSrc / eventFieldGroupsUpdate);
  the Phone tab keeps its ids, its release gating and its onchange updaters,
  the step editor uses test-f-ev- ids, the generic group updater and a live
  Event data (hex) preview with the one-ENVELOPE check.  The event select
  lists all EVENT_FORMS events (the numeric keys are decimal strings -
  parseInt(k, 10)); the action stores the collected fields, the built data
  and the event's source (fixed cfg.src or the cfg.srcField input - the
  action never carried src before, so MT call / IMS registration / slices
  could not be network-sourced).  The JSON textarea is gone.
- engine: the `event` action keeps `fields` validation for the four
  server-built events (a stray data hex is ignored) and accepts the
  form-built `data` for every other event - hex-validated and checked
  against events.EVENT_DATA_MAX (244, the one-ENVELOPE budget; server.py's
  _EVENT_DATA_MAX now reads it from events.py).  The runner builds the four
  at run time (fresh host clock) and sends the stored data for the rest.
- tests: the engine's data path (accepted / missing / malformed / oversized,
  the four still fields-built), the runner sending the data (fake scc), the
  editor's collect/prefill/patterns/source (Node), the shared helpers.
  Docs: test-scripts.md, api.md, help EN/RU.  Tests: 838 frontend +
  797 Python; sw cache simple-v401.
This commit is contained in:
2026-10-06 01:07:12 +03:00
parent bcd9fb9a4c
commit 40c1e0f148
11 changed files with 389 additions and 112 deletions
+23 -12
View File
@@ -243,23 +243,34 @@ def _normalise_params(kind, p, command_resolver=None):
out['src'] = src
return out
if kind == 'event':
# The semantic event download: the builder owns the field set and the
# byte layout (events.py mirrors the PWA's forms for the supported
# events; every other event uses the raw `envelope` action).
# The semantic event download: the same forms the Simulator uses
# (EVENT_FORMS). The four events events.py models are built from
# `fields` (validated here, so a script fails before the run starts);
# every other event carries the form-built `data` hex the PWA
# produces - the single-envelope budget is checked here too.
code = events.resolve_event(p.get('event'))
if code is None:
raise ScriptError('event: a supported event name or a hex byte is '
'required (supported: %s)'
% ', '.join('%s/0x%02X' % (events.event_name(c), c)
for c in sorted(events.BUILDERS)))
raise ScriptError('event: a hex byte or a known event name is required')
raw = p.get('fields')
if raw is not None and not isinstance(raw, dict):
raise ScriptError('event: fields must be an object')
try:
fields = events.normalise(code, raw or {})
except events.EventError as e:
raise ScriptError('event 0x%02X: %s' % (code, e))
out = {'event': code, 'fields': fields}
out = {'event': code}
if code in events.BUILDERS:
try:
out['fields'] = events.normalise(code, raw or {})
except events.EventError as e:
raise ScriptError('event 0x%02X: %s' % (code, e))
else:
data = re.sub(r'\s', '', str(p.get('data') or '')).upper()
if not re.fullmatch(r'(?:[0-9A-F]{2})+', data):
raise ScriptError('event 0x%02X: no server-side builder for this '
'event - pass the form-built `data` hex (or '
'use the raw envelope action)' % code)
if len(data) // 2 > events.EVENT_DATA_MAX:
raise ScriptError('event 0x%02X: the event data does not fit one '
'ENVELOPE (max %d bytes; chained delivery is '
'not implemented)' % (code, events.EVENT_DATA_MAX))
out['data'] = data
src = str(p.get('src') or '').strip().upper()
if src:
if not re.fullmatch(r'[0-9A-F]{2}', src):