v3.22.0 (review fixes): the suite members share the SCP80 counter; drain/PWA nits
The v3.22.0 review found that a suite's SCP80 counter state was rebuilt from the preset snapshot for every member: a second SCP80 script sent the counter the first had consumed (the store persists the advance, the in-memory preset copy does not move) and the card answered cntr_low. Reproduced with a two-member suite (both packets carried CNTR=0000000010). - runner: `_test_run_script` takes a `shared` counter state (created once per suite, passed to every member; a single script passes None) and copies the no-keyset counter back into it, so a suite behaves like one script for counter bookkeeping. Pinned by a two-member test asserting the second packet's counter is the first's + 1 and the final counters. - `_test_drain_pending`: the unknown-length FETCH uses 0xff - `'%02x' % 0x100` emitted an odd-length APDU (a malformed fetch) when a 9100 SW (length 0) reached the drain. - PWA: the suite save adopts the stored suite (the server normalises the setup-first/teardown-last order); the role rows' up/down buttons are disabled (only members move); deleting a suite cancels its pending name debounce (no late 404); `refreshDynamicI18n` redraws the test views, so a language switch updates the suites list/report immediately. - tests: the cross-member counter test (Python) and the save-adoption / move-guard / i18n wiring tests (Node). Tests: 835 frontend + 795 Python; sw cache simple-v398.
This commit is contained in:
@@ -1615,8 +1615,8 @@ class TestSuiteRunner(SuiteRunnerTestCase):
|
||||
real = S._test_run_script
|
||||
calls = []
|
||||
|
||||
def wrapped(srv, script, preset):
|
||||
ctx, stopped = real(srv, script, preset)
|
||||
def wrapped(srv, script, preset, shared=None):
|
||||
ctx, stopped = real(srv, script, preset, shared=shared)
|
||||
calls.append(script['name'])
|
||||
if srv is server and len(calls) == 1:
|
||||
S._TEST_RUN['stop'] = True # the operator stops here
|
||||
@@ -1638,8 +1638,8 @@ class TestSuiteRunner(SuiteRunnerTestCase):
|
||||
server = FakeServer(scc)
|
||||
real = S._test_run_script
|
||||
|
||||
def wrapped(srv, script, preset):
|
||||
ctx, stopped = real(srv, script, preset)
|
||||
def wrapped(srv, script, preset, shared=None):
|
||||
ctx, stopped = real(srv, script, preset, shared=shared)
|
||||
if srv is server and len(scc.sent) <= 1:
|
||||
srv.card_session += 1 # the card reset mid-suite
|
||||
return ctx, stopped
|
||||
@@ -1668,6 +1668,36 @@ class TestSuiteRunner(SuiteRunnerTestCase):
|
||||
self.assertEqual(run['suite']['members'][1]['note'],
|
||||
'no card in the reader')
|
||||
|
||||
def test_scp80_members_share_the_counter_state(self):
|
||||
# The counter is per keyset and per card session: the second SCP80
|
||||
# script must send what the first consumed + 1, not the suite-start
|
||||
# snapshot again (the store persists each packet's counter, but the
|
||||
# in-memory preset copy does not move).
|
||||
built = []
|
||||
server = FakeServer(FakeScc())
|
||||
step = {'type': 'action', 'kind': 'scp80',
|
||||
'params': {'apdu': '80E2900000', 'tar': 'B00000'}}
|
||||
script = T.normalise_script({'name': 'scp80', 'steps': [step]},
|
||||
S._test_command_type)
|
||||
preset = {'id': 'p1',
|
||||
'keysets': [{'kic': '15', 'kid': '15', 'kicKey': '00' * 16,
|
||||
'kidKey': '00' * 16, 'cntr': '0000000010'}],
|
||||
'tars': [{'role': 'isd', 'tar': 'B00000', 'msl': '16'}]}
|
||||
members = [({'script_id': 'a' * 32, 'role': 'member', 'on_fail': 'stop'}, script),
|
||||
({'script_id': 'b' * 32, 'role': 'member', 'on_fail': 'stop'}, script)]
|
||||
with mock.patch.object(S, '_build_secured_packet',
|
||||
side_effect=lambda *a, **k: (
|
||||
built.append(a[5]) or ('00' * 20, {}))), \
|
||||
mock.patch.object(S, '_send_secured_packet', return_value={
|
||||
'success': True, 'sw': '9000', 'response_data': '',
|
||||
'bytes': 10, 'segments': 1}), \
|
||||
mock.patch.object(S, '_decode_por', return_value=None), \
|
||||
mock.patch.object(S, '_preset_counter_persist', return_value=None):
|
||||
run = self.suite_run(server, members, preset=preset)
|
||||
self.assertEqual(built, ['0000000010', '0000000011'])
|
||||
self.assertEqual([m['status'] for m in run['suite']['members']], ['ok', 'ok'])
|
||||
self.assertEqual(run['scp80_counters'], {1: '0000000012'})
|
||||
|
||||
|
||||
class TestAdmGate(unittest.TestCase):
|
||||
"""The run prerequisite: a script/suite flagged `require_adm` runs only
|
||||
|
||||
Reference in New Issue
Block a user