Files
simple/tests/test_events.py
T
catarrh 152878062f v3.18.2: review fixes - sticky event-JSON error, store keep-on-load, write queue
The four findings from the review of the 3.15-3.18 batch:

- The event step's `_eventFieldsError` flag is cleared at the start of each
  collect: fixing malformed JSON used to leave the save refused until the
  editor was closed and reopened.  Regression test covers invalid -> fixed.
- The script store keeps an entry that no longer validates on load (a
  tightened engine rule or a hand edit): it is served, fails at run start with
  the validator's message and can be fixed in the editor, instead of being
  dropped from memory and erased by the next save.  Mutation paths still
  refuse it.  (The presets store already had this behaviour.)
- Writebacks are serialized per script (`_testWriteChains`): two quick saves
  of an unsaved draft no longer create two server-side scripts - the window
  could be minutes while a RAM operation holds the card lock.  Regression
  test asserts one create + one update.
- Deleting a script cancels its pending debounced save and marks it deleted,
  so a queued writeback never resurrects it; a create already in flight is
  undone with a delete.  Regression test covers the pending-save case.
- The event vectors moved to a shared fixture
  (frontend/tests/event_vectors.json) asserted by BOTH suites - a one-sided
  builder change now fails the other suite instead of drifting silently.

Tests: 816 frontend + 716 Python; sw cache simple-v383.
2026-10-04 18:27:04 +03:00

100 lines
4.4 KiB
Python

"""Server-side event-data builders (the semantic test-script `event` action).
The byte vectors live in the SHARED fixture `frontend/tests/event_vectors.json`:
the Node suite asserts the same file against the PWA's own `EVENT_FORMS`
builders, this suite asserts it against `events.py`, so neither implementation
can drift alone (a change on one side fails the other until the fixture and the
code agree).
"""
import json
import pathlib
import time
import unittest
from pysim_simple_server import events
VECTORS = json.loads((pathlib.Path(__file__).resolve().parents[1]
/ 'frontend' / 'tests' / 'event_vectors.json')
.read_text(encoding='utf-8'))
def st(year=2026, mon=9, day=30, hour=21, minute=55, sec=0):
return time.struct_time((year, mon, day, hour, minute, sec, 0, 0, -1))
class EventVectorTests(unittest.TestCase):
"""Byte parity with the PWA builders from the shared fixture."""
def test_the_shared_event_vectors(self):
self.assertTrue(VECTORS['events'], 'the fixture must not be empty')
for v in VECTORS['events']:
code = int(v['event'], 16)
self.assertEqual(events.build(code, v['fields']).upper(), v['hex'],
'event 0x%s %s' % (v['event'], v['fields']))
def test_the_shared_scts_vectors(self):
self.assertTrue(VECTORS['scts'], 'the fixture must not be empty')
for v in VECTORS['scts']:
stamp = time.struct_time((v['year'], v['month'], v['day'], v['hour'],
v['min'], v['sec'], 0, 0, -1))
self.assertEqual(events.scts_bytes(stamp, v['tz_east_min']).hex().upper(),
v['hex'])
def test_the_host_clock_date_time_object(self):
# the data-connection builder embeds the 8.39 object 26 07 ...
out = events.build(0x1D, {'status': '0', 'type': '0', 'ti': '00',
'loc_status': '0', 'datetime': 'now'},
now=st()).upper()
self.assertRegex(out, r'^9D0100AA01001C01002607[0-9A-F]{14}9B0100$')
def test_plmn_encoding(self):
self.assertEqual(events.plmn_hex('250', '01'), '52F010')
self.assertEqual(events.plmn_hex('310', '410'), '130014')
self.assertEqual(events.plmn_hex('262', '02'), '62F220')
self.assertEqual(events.plmn_hex('310', '260'), '130062')
class EventValidationTests(unittest.TestCase):
def test_resolve_event_accepts_names_and_hex(self):
self.assertEqual(events.resolve_event('location status'), 0x03)
self.assertEqual(events.resolve_event('Location_Status'), 0x03)
self.assertEqual(events.resolve_event('0B'), 0x0B)
self.assertEqual(events.resolve_event('0x12'), 0x12)
self.assertEqual(events.resolve_event(0x1D), 0x1D)
self.assertIsNone(events.resolve_event('nope'))
self.assertIsNone(events.resolve_event(300))
self.assertIsNone(events.resolve_event(True))
self.assertIsNone(events.resolve_event(''))
def test_field_validation(self):
with self.assertRaises(events.EventError):
events.build(0x03, {'status': '9'})
with self.assertRaises(events.EventError):
events.build(0x03, {'status': '0', 'mcc': '25'})
with self.assertRaises(events.EventError):
events.build(0x0B, {'tech': '300'})
with self.assertRaises(events.EventError):
events.build(0x12, {'reg_type': '0x12'})
with self.assertRaises(events.EventError):
events.build(0x12, {'reg_type': '9', 'tac': 'ABC'})
with self.assertRaises(events.EventError):
events.build(0x12, {'reg_type': '9', 'ext_info_type': '1', 'ext_info': 'A'})
with self.assertRaises(events.EventError):
events.build(0x1D, {'status': '0', 'apn': 'x' * 101})
with self.assertRaises(events.EventError):
events.build(0x1D, {'status': '0', 'ti': 'ZZ'})
# an event without a semantic builder is refused with a hint
with self.assertRaises(events.EventError) as ctx:
events.normalise(0x05, {})
self.assertIn('envelope', str(ctx.exception))
def test_normalise_returns_the_validated_fields(self):
fields = {'status': '1', 'mcc': '250'}
self.assertEqual(events.normalise(0x03, fields), fields)
self.assertEqual(events.normalise(0x0B, None), {})
if __name__ == '__main__':
unittest.main()