v3.19.0 (cont.): live fixtures; menu text match is case-insensitive by default
No test scripts have been authored yet, so the format is still free to
change - the menu-select text match is now case-insensitive by default
(menu labels are UI text; case_sensitive: true still forces an exact-case
match, and a miss on it names the case-insensitive candidates).
Verification against the live card's own bytes (captured 2026-10-04):
- the Annex A Variant 2 Cyrillic menu (ids 128..130) resolves by text and
the case behaviour above is pinned ('кофе' -> item 130);
- the applet's SEND SHORT MESSAGE (alpha 'Отправка...', SMS-SUBMIT to
+79332505884) pins the new alpha and sms checks and the run-log line.
docs/help/api updated; sw cache simple-v388 (help pages changed).
Tests: 819 frontend + 726 Python.
This commit is contained in:
+2
-1
@@ -671,7 +671,8 @@ optional `src` - the device-identities source override),
|
||||
`event` (`event` name or hex + `fields`, optional `src` - the semantic event
|
||||
download for 0x03/0x0B/0x12/0x1D), `menu-select` (`item_id` 1-255, or `text` +
|
||||
`mode` `exact`/`contains` with an
|
||||
optional `case_sensitive` - the text is resolved against the card's cached
|
||||
optionally `case_sensitive: true` for an exact-case match, case-insensitive by
|
||||
default - the text is resolved against the card's cached
|
||||
SET UP MENU, which is refreshed whenever the card sends the menu), `file-write`
|
||||
(`path`, `data`, `mode` `auto`/`binary`/`record`, `record`), `file-read` (same,
|
||||
verifies `check.data`),
|
||||
|
||||
@@ -177,7 +177,7 @@ continue correctly.
|
||||
|---|---|---|
|
||||
| `envelope` | `event` 0–255, `data` hex (optional), optional `src` | ENVELOPE(Event Download) with raw data; `src` overrides the device-identities source (`82` terminal / `83` network) |
|
||||
| `event` | `event` (name or hex), `fields` (object), optional `src` | semantic ENVELOPE(Event Download) for 0x03/0x0B/0x12/0x1D (see below) |
|
||||
| `menu-select` | `item_id` 1–255 **or** `text` + `mode` `exact`/`contains` (+ `case_sensitive`), not both | ENVELOPE(Menu Selection); the text is resolved against the card's cached menu - refreshed whenever the card sends SET UP MENU, so it follows the applet's install parameters |
|
||||
| `menu-select` | `item_id` 1–255 **or** `text` + `mode` `exact`/`contains` (+ `case_sensitive: true` for an exact-case match — the default is case-insensitive), not both | ENVELOPE(Menu Selection); the text is resolved against the card's cached menu - refreshed whenever the card sends SET UP MENU, so it follows the applet's install parameters |
|
||||
| `file-write` | `path`, `data`, `mode` `auto`/`binary`/`record`, `record` 1–255 | UPDATE BINARY/RECORD |
|
||||
| `file-read` | `path`, `mode`, `record` | READ BINARY/RECORD; verify with `check.data` |
|
||||
| `apdu` | `apdu` hex | raw transport, no auto-handler |
|
||||
@@ -362,8 +362,10 @@ then fetches.
|
||||
]}
|
||||
```
|
||||
|
||||
The text is matched against the card's cached menu; the cache is refreshed
|
||||
whenever the card sends SET UP MENU (e.g. after an install + REFRESH). If the
|
||||
The text is matched against the card's cached menu, **case-insensitively by
|
||||
default** (`case_sensitive: true` forces an exact-case match); the cache is
|
||||
refreshed whenever the card sends SET UP MENU (e.g. after an install +
|
||||
REFRESH). If the
|
||||
script must wait for the menu, add a `status` action and an
|
||||
`expect SET UP MENU` before the selection.
|
||||
|
||||
@@ -427,6 +429,9 @@ PoR-in-submit (with the transport asserted by the expectation):
|
||||
has no entry in the preset and the step carries no `spi1`.
|
||||
- `no proactive command pending …` - an expectation ran without a `91XX` from
|
||||
the previous step (add a `status` action when the card delivers on poll).
|
||||
- `menu-select: no menu item matches …` - the error lists the cached menu; a
|
||||
miss on an explicit `case_sensitive: true` names the case-insensitive
|
||||
candidates.
|
||||
- `card verdict: no PoR (the card sent none for SPI2 …)` - an SCP80 operation
|
||||
expected a PoR that never arrived (check SPI2 and the transport).
|
||||
|
||||
|
||||
@@ -563,7 +563,7 @@
|
||||
<h3 id="test-script" class="text-lg font-medium mb-2">8.9 Тест-скрипт</h3>
|
||||
<p class="text-sm mb-2"><strong>Тест-скрипт</strong> — упорядоченный список <strong>действий</strong> и <strong>ожиданий</strong> проактивных команд, выполняемый на подключённой карте на стороне сервера. Пока запуск активен, карта принадлежит скрипту: остальные операции с картой отклоняются (HTTP 409), фоновый опрос STATUS приостанавливается; отчёт о запуске хранится в памяти (в том числе после перезагрузки страницы) до нажатия <strong>Очистить отчёт</strong>. Скрипты хранятся в <strong>хранилище на сервере</strong> (<code class="font-mono text-sm">~/.pysim-simple-server/test_scripts.json</code>, <code class="font-mono text-sm">--test-scripts</code>), поэтому все браузеры работают с одним набором; изменения записываются в хранилище по мере правки (кнопки «Сохранить» нет — черновик без валидного шага остаётся локальным); экспорт/импорт JSON остаётся для резервных копий и переноса скриптов между стендами (старый экспорт из localStorage импортируется как есть).</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Действия</strong> — <em>Envelope</em> (Event Download: сырой тип события + данные, с опциональным переопределением источника — 82 терминал / 83 сеть) или <em>Event</em> (семантические формы: Location status, Access technology change, Network rejection, Data connection status change — поля редактируются как объект JSON в редакторе шагов, а для остальных событий форма события на вкладке «Телефон» показывает готовый hex), <em>Menu Selection</em> (item id 1–255 или текст пункта с точным/частичным совпадением — текст подбирается по меню карты, которое следует за параметрами установки апплета), <em>UPDATE/READ файла</em> (путь + режим; чтение проверяет данные файла), <em>сырой APDU</em> (без авто-обработчика), <em>SCP80</em> (C-APDU или готовый защищённый пакет, опциональные переопределения номера keyset (kvn) / TAR / SPI1 / SPI2 и формат команды — компактный или обёрнутый в шаблон Command Scripting TS 102 226 §5.2.1 <em>expanded</em> (AA) / <em>expanded AE 80</em>; ключи и счётчик берутся из пресета карты, подобранного по ICCID подключённой карты — запуск называет пресет в хранилище, поэтому всегда используются его keysets, TAR и счётчик — а счётчик записывается обратно после запуска) и <em>STATUS</em> (при <em>попытках</em> > 1 опрашивает, пока карта не объявит команду). У каждого действия есть проверка ответа: ожидаемые SW и данные (точно или маска с подстановкой <code class="font-mono text-sm">?</code> — <code class="font-mono text-sm">91??</code>) и проверка PoR для SCP80 (<em>любой / без PoR / por_ok</em> или своя проверка декодированного PoR — статус / SW / данные; транспорт через SEND SHORT MESSAGE работает так же).</li>
|
||||
<li><strong>Действия</strong> — <em>Envelope</em> (Event Download: сырой тип события + данные, с опциональным переопределением источника — 82 терминал / 83 сеть) или <em>Event</em> (семантические формы: Location status, Access technology change, Network rejection, Data connection status change — поля редактируются как объект JSON в редакторе шагов, а для остальных событий форма события на вкладке «Телефон» показывает готовый hex), <em>Menu Selection</em> (item id 1–255 или текст пункта с точным/частичным совпадением, по умолчанию без учёта регистра — текст подбирается по меню карты, которое следует за параметрами установки апплета), <em>UPDATE/READ файла</em> (путь + режим; чтение проверяет данные файла), <em>сырой APDU</em> (без авто-обработчика), <em>SCP80</em> (C-APDU или готовый защищённый пакет, опциональные переопределения номера keyset (kvn) / TAR / SPI1 / SPI2 и формат команды — компактный или обёрнутый в шаблон Command Scripting TS 102 226 §5.2.1 <em>expanded</em> (AA) / <em>expanded AE 80</em>; ключи и счётчик берутся из пресета карты, подобранного по ICCID подключённой карты — запуск называет пресет в хранилище, поэтому всегда используются его keysets, TAR и счётчик — а счётчик записывается обратно после запуска) и <em>STATUS</em> (при <em>попытках</em> > 1 опрашивает, пока карта не объявит команду). У каждого действия есть проверка ответа: ожидаемые SW и данные (точно или маска с подстановкой <code class="font-mono text-sm">?</code> — <code class="font-mono text-sm">91??</code>) и проверка PoR для SCP80 (<em>любой / без PoR / por_ok</em> или своя проверка декодированного PoR — статус / SW / данные; транспорт через SEND SHORT MESSAGE работает так же).</li>
|
||||
<li><strong>Ожидания</strong> — карта должна объявить проактивную команду на предыдущем шаге (SW <code class="font-mono text-sm">91XX</code>, TS 102 221 §7.4.2.1 / TS 102 223 §6.3); <code class="font-mono text-sm">9000</code> означает «команды нет» и считается ошибкой — добавьте действие STATUS, если апплет выдаёт команду при опросе. Проверяются тип команды, квалификатор и содержимое: текст / пункт / raw, проверка <em>Alpha</em> (идентификатор Alpha команды, тег <code class="font-mono text-sm">05</code>/<code class="font-mono text-sm">85</code> — отличается от текстовой строки <code class="font-mono text-sm">8D</code>), проверка <em>SMS TPDU</em> (submit-TPDU команды SEND SHORT MESSAGE: получатель / PID / DCS / UDL / данные — для собственного SMS апплета), проверка <em>PoR</em> (SEND SHORT MESSAGE несёт PoR, когда пакет запросил PoR-in-submit, бит SPI2 <code class="font-mono text-sm">20</code>: статус ответа, SW R-APDU и данные ответа; перед этим должен выполниться шаг SCP80) и проверка <em>Files</em> (File List команды REFRESH, например квалификатор <code class="font-mono text-sm">00</code> = NAA initialization with full file change notification). <strong>TERMINAL RESPONSE</strong> (результат, идентификатор пункта, текст, доп. TLV) является частью инструкции.</li>
|
||||
<li><strong>Уровни ошибок</strong> — несовпадение помечает шаг как <em>Ошибка</em> (скрипт останавливается) или <em>Предупреждение</em> (продолжает); каждая проверка показывается с ожидаемым и фактическим значением.</li>
|
||||
<li><strong>Шаблоны</strong> — Empty, <em>Обзор меню STK</em>, <em>Апплет через SCP80</em>; редактор шагов — формный (поля действия Event вводятся объектом JSON), отдельные скрипты экспортируются/импортируются как JSON.</li>
|
||||
|
||||
+1
-1
@@ -562,7 +562,7 @@
|
||||
<h3 id="test-script" class="text-lg font-medium mb-2">8.9 Test script</h3>
|
||||
<p class="text-sm mb-2">A <strong>test script</strong> is an ordered list of <strong>actions</strong> and proactive-command <strong>expectations</strong> executed server-side on the equipped card. While a run is active the card belongs to the script: other card actions are refused (HTTP 409) and background STATUS polling is paused; the run report is kept in memory (also after reloading the page) until <strong>Clear report</strong>. Scripts live in the <strong>server-side store</strong> (<code class="font-mono text-sm">~/.pysim-simple-server/test_scripts.json</code>, <code class="font-mono text-sm">--test-scripts</code>), so every browser manages the same set; edits are written to the store as you change them (there is no Save button — a draft without a valid step stays local until it is valid); JSON export/import remains for backup and for moving scripts between benches (the old localStorage export imports as-is).</p>
|
||||
<ul class="list-disc list-inside text-sm space-y-1 mb-3">
|
||||
<li><strong>Actions</strong> — <em>Envelope</em> (Event Download: raw event type + data, with an optional source override — 82 terminal / 83 network) or <em>Event</em> (the semantic forms: Location status, Access technology change, Network rejection, Data connection status change — the fields are edited as a JSON object in the step editor, and the Phone tab's event form shows the built hex for the other events), <em>Menu Selection</em> (item id 1–255, or the item's text with an exact/contains match — the text is resolved against the card's menu, which follows the applet's install parameters), <em>UPDATE/READ file</em> (path + mode; reads verify the file data), <em>raw APDU</em> (no auto-handler), <em>SCP80</em> (C-APDU or a pre-built secured packet, optional keyset number (kvn) / TAR / SPI1 / SPI2 overrides and a command format — compact, or the TS 102 226 §5.2.1 Command Scripting template <em>expanded</em> (AA) / <em>expanded AE 80</em> around the C-APDU; the keys and the counter come from the card preset matched by the equipped card's ICCID — the run names the stored preset, so it always uses its keysets, TARs and counter — and the counter is written back to it after the run) and <em>STATUS</em> (with <em>attempts</em> > 1 it polls until the card announces a command). Every action has a response check: expected SW and data (exact, or a mask with <code class="font-mono text-sm">?</code> nibble wildcards — <code class="font-mono text-sm">91??</code>) plus a PoR check for SCP80 (<em>any / no PoR / por_ok</em>, or a custom assertion of the decoded PoR — status / SW / data; the SEND SHORT MESSAGE transport works the same).</li>
|
||||
<li><strong>Actions</strong> — <em>Envelope</em> (Event Download: raw event type + data, with an optional source override — 82 terminal / 83 network) or <em>Event</em> (the semantic forms: Location status, Access technology change, Network rejection, Data connection status change — the fields are edited as a JSON object in the step editor, and the Phone tab's event form shows the built hex for the other events), <em>Menu Selection</em> (item id 1–255, or the item's text with an exact/contains match, case-insensitive by default — the text is resolved against the card's menu, which follows the applet's install parameters), <em>UPDATE/READ file</em> (path + mode; reads verify the file data), <em>raw APDU</em> (no auto-handler), <em>SCP80</em> (C-APDU or a pre-built secured packet, optional keyset number (kvn) / TAR / SPI1 / SPI2 overrides and a command format — compact, or the TS 102 226 §5.2.1 Command Scripting template <em>expanded</em> (AA) / <em>expanded AE 80</em> around the C-APDU; the keys and the counter come from the card preset matched by the equipped card's ICCID — the run names the stored preset, so it always uses its keysets, TARs and counter — and the counter is written back to it after the run) and <em>STATUS</em> (with <em>attempts</em> > 1 it polls until the card announces a command). Every action has a response check: expected SW and data (exact, or a mask with <code class="font-mono text-sm">?</code> nibble wildcards — <code class="font-mono text-sm">91??</code>) plus a PoR check for SCP80 (<em>any / no PoR / por_ok</em>, or a custom assertion of the decoded PoR — status / SW / data; the SEND SHORT MESSAGE transport works the same).</li>
|
||||
<li><strong>Expectations</strong> — the card must have announced a proactive command in the previous step (SW <code class="font-mono text-sm">91XX</code>, TS 102 221 §7.4.2.1 / TS 102 223 §6.3); a <code class="font-mono text-sm">9000</code> means “no command” and is an error — add a STATUS action when the applet delivers on poll. The command type, qualifier and contents are verified: text / item / raw checks, an <em>Alpha</em> check (the command's Alpha identifier, tag <code class="font-mono text-sm">05</code>/<code class="font-mono text-sm">85</code> — distinct from the <code class="font-mono text-sm">8D</code> text string), an <em>SMS TPDU</em> check (the submit TPDU of a SEND SHORT MESSAGE: destination / PID / DCS / UDL / user data — for an applet's own SMS), a <em>PoR</em> check (a SEND SHORT MESSAGE carries the PoR when the packet requested PoR-in-submit, SPI2 bit <code class="font-mono text-sm">20</code>: the response status, the R-APDU SW and the response data; make sure an SCP80 step ran first) and a <em>Files</em> check (the File List of a REFRESH, e.g. qualifier <code class="font-mono text-sm">00</code> = NAA initialization with full file change notification). The scripted <strong>TERMINAL RESPONSE</strong> (result, item identifier, text, extra TLVs) is part of the instruction.</li>
|
||||
<li><strong>Fail levels</strong> — a mismatch flags the step as <em>Error</em> (terminates the script) or <em>Warning</em> (continues); each check is reported with expected vs actual values.</li>
|
||||
<li><strong>Templates</strong> — Empty, <em>STK menu browsing</em>, <em>Applet via SCP80</em>; the step editor is form-based (the event action's fields are a JSON object) and individual scripts export/import as JSON.</li>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'simple-v387';
|
||||
const CACHE = 'simple-v388';
|
||||
const URLS = [
|
||||
'index.html',
|
||||
'help.html',
|
||||
|
||||
@@ -6033,22 +6033,34 @@ def _test_run_scp80(server, step, ctx):
|
||||
def _menu_item_by_text(server, p):
|
||||
"""Resolve a menu-select step's text against the cached SET UP MENU items
|
||||
(the item ids vary with the applet's install parameters, so a script
|
||||
matches the text the card actually shows). Raises a ScriptError naming
|
||||
matches the text the card actually shows; the match is case-insensitive
|
||||
unless the step sets `case_sensitive: true`). Raises a ScriptError naming
|
||||
the available items when nothing - or more than one item - matches."""
|
||||
menu = getattr(server, 'sim_menu', None) or {}
|
||||
items = menu.get('items') or []
|
||||
spec = {'mode': p.get('mode', 'exact'), 'value': p['text'],
|
||||
'case_sensitive': p.get('case_sensitive', True)}
|
||||
'case_sensitive': p.get('case_sensitive', False)}
|
||||
matches = [it for it in items
|
||||
if testscript.match_text(spec, it.get('text') or '')]
|
||||
if not matches:
|
||||
listing = ', '.join('%s=%r' % (it.get('id'), it.get('text'))
|
||||
for it in items)
|
||||
hint = ''
|
||||
if spec.get('case_sensitive') and items:
|
||||
# A strict-case miss is a common authoring slip with non-ASCII
|
||||
# menus: name the items a case-insensitive match would find.
|
||||
loose = [it for it in items if testscript.match_text(
|
||||
dict(spec, case_sensitive=False), it.get('text') or '')]
|
||||
if loose:
|
||||
hint = (' - a case-insensitive match exists (%s): set '
|
||||
'"case_sensitive": false or fix the case'
|
||||
% ', '.join('%s=%r' % (it.get('id'), it.get('text'))
|
||||
for it in loose))
|
||||
raise testscript.ScriptError(
|
||||
'menu-select: no menu item matches %r (%s)'
|
||||
'menu-select: no menu item matches %r (%s)%s'
|
||||
% (p['text'], 'menu: ' + listing if listing
|
||||
else 'no menu cached - fetch the menu (a SET UP MENU '
|
||||
'expectation) first'))
|
||||
'expectation) first', hint))
|
||||
if len(matches) > 1:
|
||||
raise testscript.ScriptError(
|
||||
'menu-select: %d items match %r (%s) - use the item id'
|
||||
|
||||
@@ -281,7 +281,10 @@ def _normalise_params(kind, p):
|
||||
if mode not in ('exact', 'contains'):
|
||||
raise ScriptError('menu-select: mode must be exact or contains')
|
||||
out['mode'] = mode
|
||||
out['case_sensitive'] = bool(p.get('case_sensitive', True))
|
||||
# Menu labels are UI text: case-insensitive by default (no test
|
||||
# scripts predate this choice); `case_sensitive: true` forces an
|
||||
# exact-case match.
|
||||
out['case_sensitive'] = bool(p.get('case_sensitive', False))
|
||||
if not out:
|
||||
raise ScriptError('menu-select: item_id or text is required')
|
||||
if 'item_id' in out and 'text' in out:
|
||||
|
||||
@@ -127,11 +127,17 @@ class TestValidation(unittest.TestCase):
|
||||
script = T.normalise_script({'steps': [
|
||||
{'type': 'action', 'kind': 'menu-select', 'params': {'item_id': 3}},
|
||||
{'type': 'action', 'kind': 'menu-select',
|
||||
'params': {'text': 'My menu', 'mode': 'contains', 'case_sensitive': False}},
|
||||
'params': {'text': 'My menu', 'mode': 'contains'}},
|
||||
{'type': 'action', 'kind': 'menu-select',
|
||||
'params': {'text': 'Exact', 'mode': 'exact', 'case_sensitive': True}},
|
||||
]}, _resolver)
|
||||
self.assertEqual(script['steps'][0]['params'], {'item_id': 3})
|
||||
# the text match is case-insensitive by default (menu labels are UI
|
||||
# text); an explicit true is kept for an exact-case match
|
||||
self.assertEqual(script['steps'][1]['params'],
|
||||
{'text': 'My menu', 'mode': 'contains', 'case_sensitive': False})
|
||||
self.assertEqual(script['steps'][2]['params'],
|
||||
{'text': 'Exact', 'mode': 'exact', 'case_sensitive': True})
|
||||
with self.assertRaises(T.ScriptError):
|
||||
T.normalise_script({'steps': [{'type': 'action', 'kind': 'menu-select',
|
||||
'params': {}}]}, _resolver)
|
||||
@@ -573,6 +579,38 @@ class TestRunnerActions(RunnerTestCase):
|
||||
self.assertIn("3='Alpha'", note)
|
||||
self.assertEqual(scc.sent, [])
|
||||
|
||||
# The live card's menu (captured 2026-10-04 20:58): Annex A Variant 2
|
||||
# Cyrillic items under the card's ids 128..130 - the compressed
|
||||
# `81 <num_chars> 08 <chars>` form (the menu-select text must resolve
|
||||
# against it; the card's ids stay out of the script).
|
||||
LIVE_MENU = {'title': 'GPB MOBILE', 'items': [
|
||||
{'id': 128, 'text': 'Выбор сети'}, {'id': 129, 'text': 'Язык'},
|
||||
{'id': 130, 'text': 'Кофе'}]}
|
||||
|
||||
def test_menu_text_selection_matches_variant2_cyrillic(self):
|
||||
scc = FakeScc().push('80C2', '', '9000')
|
||||
server = FakeServer(scc)
|
||||
server.sim_menu = dict(self.LIVE_MENU)
|
||||
# a lowercase contains match resolves by default (case-insensitive)
|
||||
run = self.run_script(server, [
|
||||
{'type': 'action', 'kind': 'menu-select',
|
||||
'params': {'text': 'кофе'}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
self.assertIn('D30702020181900182', ''.join(scc.sent))
|
||||
# an explicit case-sensitive spec misses and names the loose match
|
||||
server = FakeServer(FakeScc())
|
||||
server.sim_menu = dict(self.LIVE_MENU)
|
||||
run = self.run_script(server, [
|
||||
{'type': 'action', 'kind': 'menu-select',
|
||||
'params': {'text': 'кофе', 'case_sensitive': True}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'error')
|
||||
note = run['steps'][0].get('note') or ''
|
||||
self.assertIn("no menu item matches 'кофе'", note)
|
||||
self.assertIn('case-insensitive match exists', note)
|
||||
self.assertIn("130='Кофе'", note)
|
||||
|
||||
def test_menu_text_selection_refuses_an_ambiguous_match(self):
|
||||
scc = FakeScc()
|
||||
server = FakeServer(scc)
|
||||
@@ -1009,6 +1047,39 @@ class TestRunnerActions(RunnerTestCase):
|
||||
self.assertEqual(labels['SMS DCS']['expected'], 'AA')
|
||||
self.assertEqual(labels['SMS DCS']['actual'], '00')
|
||||
|
||||
# The live applet's SEND SHORT MESSAGE (captured 2026-10-04 20:58): the
|
||||
# Alpha identifier is Annex A Variant 2 Cyrillic ("Отправка...") and the
|
||||
# TPDU is a real SMS-SUBMIT to +79332505884 with 7-bit user data.
|
||||
APP_SEND_SM_CMD = ('D02F810301130082028183050E810B089EC2BFC0B0B2BAB02E2E2E'
|
||||
'0B1401000B919733525088F40000084B1C12579C9D83')
|
||||
|
||||
def test_expect_alpha_and_sms_checks_on_the_live_applet_sms(self):
|
||||
scc = FakeScc()
|
||||
scc.push('80F2', '', '9102')
|
||||
scc.push('8012', self.APP_SEND_SM_CMD, '9000')
|
||||
scc.push('8014', '', '9000')
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stderr(buf):
|
||||
run = self.run_script(FakeServer(scc), [
|
||||
{'type': 'action', 'kind': 'status', 'params': {'attempts': 2},
|
||||
'check': {'sw': {'mode': 'mask', 'value': '91??'}}},
|
||||
{'type': 'expect', 'command': 'SEND SHORT MESSAGE',
|
||||
'checks': [{'kind': 'alpha', 'mode': 'exact', 'value': 'Отправка...'},
|
||||
{'kind': 'sms', 'da': '79332505884', 'pid': '00',
|
||||
'dcs': '00', 'udl': 8, 'ud': '4B1C12579C9D83'}],
|
||||
'respond': {'result': 'ok'}},
|
||||
])
|
||||
self.assertEqual(run['status'], 'ok', run['steps'])
|
||||
labels = {c['label']: c for c in run['steps'][1]['checks']}
|
||||
self.assertTrue(labels['Alpha']['ok'], labels)
|
||||
self.assertEqual(labels['Alpha']['actual'], 'Отправка...')
|
||||
for label in ('SMS DA', 'SMS PID', 'SMS DCS', 'SMS UDL', 'SMS UD'):
|
||||
self.assertTrue(labels[label]['ok'], labels)
|
||||
self.assertEqual(run['steps'][1]['sms']['da'], '79332505884')
|
||||
log = buf.getvalue()
|
||||
self.assertIn('TEST-RUN step 2: SMS DA=79332505884 PID=00 DCS=00 UDL=8 '
|
||||
'UD=4B1C12579C9D83', log)
|
||||
|
||||
def test_scp80_format_wraps_the_apdu(self):
|
||||
# `format` wraps the C-APDU in the expanded Command Scripting template
|
||||
# (TS 102 226 5.2.1): `expanded` = AA/<len>/22/..., `expanded-ae` =
|
||||
|
||||
Reference in New Issue
Block a user