v3.25.3: the card's BIP commands during test runs
The runner's three FETCH/TR sites (the proactive-drain action, the expectation, the cleanup drain) built every TERMINAL RESPONSE from the script, so a card-announced OPEN CHANNEL during a run was answered "performed successfully" without opening a channel - SEND DATA was dropped and RECEIVE DATA returned nothing (silent data loss, broken applet state). _test_step_tr now routes 0x40-0x44 through _handle_bip_command: the terminal answers for real, so the card's channels work inside a run. The script keeps an escape hatch - a respond with `raw` TLVs or a non-ok result wins (the applet error-path tests). The BIP events flow during a run again for the channels the card itself opened: _bip_data_available and _bip_flush_channel_events no longer stop at the run guard (a channel only exists because the card opened it) and the runner flushes the link events after each TR, so an applet's own HTTP/BIP conversation completes inside a scripted run. BIP control stays blocked (409). Tests: the drain/expect/cleanup routing, the raw and non-ok-result overrides, the run-time data-available and link events. 883 frontend + 873 Python; sw cache simple-v427.
This commit is contained in:
+27
-2
@@ -185,11 +185,13 @@ class TestProactiveChainOwnership(unittest.TestCase):
|
||||
1100-command loop held _CARD_LOCK and blocked a run's start)."""
|
||||
|
||||
def setUp(self):
|
||||
self.saved = (S._TEST_RUNNING, S._server_ref, S._CARD_CONNECTED)
|
||||
self.saved = (S._TEST_RUNNING, S._server_ref, S._CARD_CONNECTED,
|
||||
S._BIP_LINK_EVENTS, S._FLUSHING_CHANNEL_EVENTS)
|
||||
S._TEST_RUNNING = False
|
||||
|
||||
def tearDown(self):
|
||||
S._TEST_RUNNING, S._server_ref, S._CARD_CONNECTED = self.saved
|
||||
(S._TEST_RUNNING, S._server_ref, S._CARD_CONNECTED,
|
||||
S._BIP_LINK_EVENTS, S._FLUSHING_CHANNEL_EVENTS) = self.saved
|
||||
|
||||
def _fetches(self, scc):
|
||||
return [a for a in scc.sent if a.lower().startswith('8012')]
|
||||
@@ -238,6 +240,29 @@ class TestProactiveChainOwnership(unittest.TestCase):
|
||||
S._sim_resume_poll()
|
||||
timer.assert_not_called()
|
||||
|
||||
def test_data_available_flows_during_a_run(self):
|
||||
# a BIP channel only exists because the card opened it: the event is
|
||||
# the card's own conversation continuing, so it flows during a run
|
||||
# (v3.25.3 - the card must not wait for the run to end)
|
||||
ch = types.SimpleNamespace(peer_closed=False, rx=b'ABCD', id=1)
|
||||
S._server_ref = types.SimpleNamespace(scc=object(), event_list=[0x09])
|
||||
S._CARD_CONNECTED = True
|
||||
S._TEST_RUNNING = True
|
||||
with mock.patch.object(S, '_send_event_download') as send:
|
||||
ok = S._bip_data_available(ch)
|
||||
self.assertTrue(ok)
|
||||
send.assert_called_once()
|
||||
self.assertEqual(send.call_args[0][1], 0x09)
|
||||
|
||||
def test_link_events_are_taken_during_a_run(self):
|
||||
# the queued link events describe the card's own channels: the flush
|
||||
# no longer stops at the run guard (v3.25.3)
|
||||
S._TEST_RUNNING = True
|
||||
S._BIP_LINK_EVENTS = False
|
||||
with mock.patch.object(S._BIP, 'take_pending_events') as take:
|
||||
S._bip_flush_channel_events(None)
|
||||
take.assert_called_once()
|
||||
|
||||
def test_every_run_end_resumes_the_poll(self):
|
||||
# the two worker bodies, their two exception paths and the spawn
|
||||
# failure all call the helper
|
||||
|
||||
@@ -2056,5 +2056,84 @@ class TestAdmGate(unittest.TestCase):
|
||||
self.assertTrue(ok)
|
||||
|
||||
|
||||
class TestRunnerBipCommands(RunnerTestCase):
|
||||
"""The runner's steps let the terminal answer the card's own BIP commands
|
||||
(v3.25.3): a channel the card opens during a run really opens and its data
|
||||
really flows - unless the script overrides the answer with `raw` TLVs or a
|
||||
non-ok result (an applet error path to test)."""
|
||||
|
||||
OPEN = 'D02B8103014001820281828500B50103B9020200C70B076D656761666F6E2E7275'
|
||||
BIP_TR = bytes.fromhex('010301400002028281030100')
|
||||
|
||||
def _tr(self, scc):
|
||||
return [a for a in scc.sent if a.startswith('8014')][-1]
|
||||
|
||||
def _pending(self, scc, cmd):
|
||||
scc.push('80F2', '', '9109') # STATUS: the command is pending
|
||||
scc.push('8012', cmd, '9000')
|
||||
scc.push('8014', '', '9000')
|
||||
|
||||
def _run_bip(self, scc, steps):
|
||||
with mock.patch.object(S, '_handle_bip_command',
|
||||
return_value=self.BIP_TR) as bip:
|
||||
run = self.run_script(FakeServer(scc), steps)
|
||||
return run, bip
|
||||
|
||||
def test_a_drain_lets_the_terminal_answer_bip(self):
|
||||
scc = FakeScc()
|
||||
scc.push('80F2', '', '9109').push('8012', self.OPEN, '9000')
|
||||
scc.push('8014', '', '9000').push('80F2', '', '9000')
|
||||
run, bip = self._run_bip(scc, [
|
||||
{'type': 'action', 'kind': 'proactive-drain',
|
||||
'params': {'attempts': 2, 'respond': {'result': 'ok'}}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
self.assertEqual(bip.call_count, 1)
|
||||
self.assertEqual(bip.call_args[0][2], 0x40)
|
||||
self.assertTrue(self._tr(scc).endswith(self.BIP_TR.hex().upper()),
|
||||
self._tr(scc))
|
||||
|
||||
def test_an_expectation_lets_the_terminal_answer_bip(self):
|
||||
scc = FakeScc()
|
||||
self._pending(scc, self.OPEN)
|
||||
run, bip = self._run_bip(scc, [
|
||||
{'type': 'action', 'kind': 'status', 'params': {'attempts': 2}},
|
||||
{'type': 'expect', 'command': 'ANY', 'respond': {'result': 'ok'}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
self.assertEqual(bip.call_count, 1)
|
||||
|
||||
def test_a_raw_answer_overrides_the_terminal_bip(self):
|
||||
scc = FakeScc()
|
||||
self._pending(scc, self.OPEN)
|
||||
_run, bip = self._run_bip(scc, [
|
||||
{'type': 'action', 'kind': 'status', 'params': {'attempts': 2}},
|
||||
{'type': 'expect', 'command': 'ANY',
|
||||
'respond': {'result': 'ok', 'raw': '010301400002028281030100'}},
|
||||
])
|
||||
self.assertEqual(bip.call_count, 0)
|
||||
self.assertTrue(self._tr(scc).endswith('010301400002028281030100'),
|
||||
self._tr(scc))
|
||||
|
||||
def test_a_non_ok_result_overrides_the_terminal_bip(self):
|
||||
scc = FakeScc()
|
||||
self._pending(scc, self.OPEN)
|
||||
_run, bip = self._run_bip(scc, [
|
||||
{'type': 'action', 'kind': 'status', 'params': {'attempts': 2}},
|
||||
{'type': 'expect', 'command': 'ANY', 'respond': {'result': 'refused'}},
|
||||
])
|
||||
self.assertEqual(bip.call_count, 0)
|
||||
self.assertIn('83022200', self._tr(scc))
|
||||
|
||||
def test_the_cleanup_drain_handles_bip(self):
|
||||
scc = FakeScc()
|
||||
scc.push('8012', self.OPEN, '9000')
|
||||
with mock.patch.object(S, '_handle_bip_command',
|
||||
return_value=self.BIP_TR) as bip:
|
||||
drained = S._test_drain_pending(scc, 9)
|
||||
self.assertEqual(bip.call_count, 1)
|
||||
self.assertIn('OPEN CHANNEL', drained.get('note', ''))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user