v3.17.0: test scripts for the applet handoff - menu by text, spec-order TRs, codings
The applet-testing batch agreed after v3.15/3.16:
- menu-select by text: the step names the item by `text` (+ `mode`
exact/contains, `case_sensitive`) instead of the install-dependent id
(both or neither is refused); the runner resolves it against the card's
cached menu and raises a step error naming the available items when nothing
(or several items) matches. The cache is refreshed on every SET UP MENU
fetch - the proactive chain and the expect step - so an applet install (new
items/ids after its REFRESH) no longer leaves a stale menu behind. The
step editor offers the id or the text + mode and the client-side pre-check
bounds them.
- TERMINAL RESPONSE object order: both builders emitted the Item identifier
before the Result (and the scripted builder its Text string before it too);
TS 102 223 6.8.0 mandates Command details, Device identities, Result,
Duration, Text string, Item identifier. `testscript.build_tr` and
`_menu_send_response` now follow it; `respond.raw` is documented as
appended after the standard objects, and the exact-byte tests pin the order.
- Text codings: the scripted response codes the GSM default alphabet via the
GSM 03.38 codec ('00' packed, '04' 8-bit) instead of latin-1, keeps '08'
UCS2 and sends the spec's null text string for an empty answer; the packing
helper is shared with the interactive path (`testscript.pack_gsm7`). A
UCS2 GET INPUT answer is documented and tested.
- docs/test-scripts.md gained the handoff brief for the applet agent: the
value-collection table, fill-in worksheets for the two applet scenarios, a
menu-by-text example, the validation checklist and a format stamp; help
EN/RU and docs/api.md updated.
Tests: 809 frontend + 705 Python; sw cache simple-v380.
This commit is contained in:
+9
-4
@@ -667,8 +667,11 @@ keysets, TARs and counters. The inline `preset` form stays for external
|
||||
callers.
|
||||
|
||||
**Action steps** (`type: "action"`): `kind` is `envelope` (`event`, `data`),
|
||||
`menu-select` (`item_id` 1-255), `file-write` (`path`, `data`, `mode`
|
||||
`auto`/`binary`/`record`, `record`), `file-read` (same, verifies `check.data`),
|
||||
`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
|
||||
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`),
|
||||
`apdu` (raw transport, no auto-handler), `scp80` (`apdu` or `sp`, selected by
|
||||
the optional `source` field when both are present; optional `kvn` (keyset
|
||||
number 1-15), `tar`/`spi1`/`spi2` overrides - KIc/KID and the counter always
|
||||
@@ -691,8 +694,10 @@ preceding `scp80` step - PoR-in-submit, SPI2 bit `20`: `status` (the response
|
||||
status name, `ok` = `por_ok`), `sw` (the R-APDU status word, exact/mask) and
|
||||
`data` (the R-APDU response data, exact/mask)) or `files` (the File List of a
|
||||
REFRESH, TS 102 223 8.18; `files` is a list of path hex strings, matched
|
||||
order-insensitively); `respond` is the TERMINAL RESPONSE (`result` name or
|
||||
value, `item_id`, `text`+`dcs`, raw TLVs).
|
||||
order-insensitively); `respond` is the TERMINAL RESPONSE in the TS 102 223
|
||||
6.8.0 object order (`result` name or value; `text`+`dcs` for GET INKEY/GET
|
||||
INPUT, `dcs` `00` packed / `04` 8-bit / `08` UCS2; `item_id`; extra `raw`
|
||||
TLVs appended after the standard objects).
|
||||
|
||||
The response is the initial state (`running: true`), the final counter
|
||||
(`scp80_counter`) and the step list; poll `/api/test/status`. The PWA writes
|
||||
|
||||
+117
-5
@@ -12,6 +12,86 @@ localStorage export imports as-is), and any API client can create or run them
|
||||
(`POST /api/test/run` with an inline `script` or a stored `script_id` - see
|
||||
`docs/api.md`). While a run is active the card belongs to the script.
|
||||
|
||||
**Format**: server ≥ 3.15.0 (`kvn`, `por` and `files` checks), scripts stored
|
||||
server-side since 3.16.0, `menu-select` by text and the spec-order TERMINAL
|
||||
RESPONSE codings since 3.17.0.
|
||||
|
||||
## Writing a test for an applet
|
||||
|
||||
Collect these values before writing the JSON (from the applet's source/spec
|
||||
and the card's preset):
|
||||
|
||||
| What | Where it goes | Notes |
|
||||
|---|---|---|
|
||||
| Applet TAR (3 bytes) + MSL, or an explicit SPI1 | `scp80.params.tar` / `spi1` | the card preset must carry the TAR with its MSL, or the step passes `spi1` |
|
||||
| Keyset number | `scp80.params.kvn` (1–15) | only when the applet's keys are not the preset's first keyset |
|
||||
| Trigger C-APDUs | `scp80.params.apdu` | one secured packet per `scp80` step; the preset provides keys and counter |
|
||||
| Expected file paths + contents | `file-read.params.path` + `check.data` | a mask must have **exactly** the actual value's length |
|
||||
| Expected proactive command(s) | `expect.command` + `qualifier`/`checks` | REFRESH `qualifier: "00"` = NAA init + full FCN, `files` asserts the changed EFs |
|
||||
| PoR transport + expected PoR | `scp80.check.por`/`check.data` (inline) or `expect SEND SHORT MESSAGE` + `por` check | pick per the SPI2 the applet expects |
|
||||
| Delivery timing | a `status` action when the card delivers on poll | the runner never polls by itself |
|
||||
|
||||
### Worksheet 1 - RFM update → file change → REFRESH (full FCN)
|
||||
|
||||
```json
|
||||
{"name": "applet RFM update",
|
||||
"steps": [
|
||||
{"type": "action", "kind": "scp80",
|
||||
"params": {"apdu": "<RFM C-APDU>", "tar": "<applet TAR>", "kvn": 1, "spi2": "01"},
|
||||
"check": {"sw": {"mode": "mask", "value": "91??"}, "por": "none"},
|
||||
"label": "RFM update"},
|
||||
{"type": "expect", "command": "REFRESH", "qualifier": "00",
|
||||
"checks": [{"kind": "files", "value": "<changed FIDs, e.g. 3F007F206F07>"}],
|
||||
"respond": {"result": "ok"}},
|
||||
{"type": "action", "kind": "file-read", "params": {"path": "<EF path>"},
|
||||
"check": {"data": {"mode": "mask", "value": "<expected bytes, ? = wildcard>"}}}
|
||||
]}
|
||||
```
|
||||
|
||||
### Worksheet 2 - incoming data → particular PoR, no other actions
|
||||
|
||||
Inline PoR:
|
||||
|
||||
```json
|
||||
{"name": "applet data (inline PoR)",
|
||||
"steps": [
|
||||
{"type": "action", "kind": "scp80",
|
||||
"params": {"apdu": "<data C-APDU>", "tar": "<applet TAR>", "spi2": "01"},
|
||||
"check": {"sw": "9000", "por": "ok",
|
||||
"data": {"mode": "mask", "value": "<expected PoR bytes>"}}}
|
||||
]}
|
||||
```
|
||||
|
||||
PoR-in-submit:
|
||||
|
||||
```json
|
||||
{"name": "applet data (SEND SM PoR)",
|
||||
"steps": [
|
||||
{"type": "action", "kind": "scp80",
|
||||
"params": {"apdu": "<data C-APDU>", "tar": "<applet TAR>", "spi2": "21"},
|
||||
"check": {"sw": {"mode": "mask", "value": "91??"}, "por": "none"}},
|
||||
{"type": "expect", "command": "SEND SHORT MESSAGE",
|
||||
"checks": [{"kind": "por", "status": "por_ok", "sw": "<R-APDU SW>",
|
||||
"data": {"mode": "mask", "value": "<expected response data>"}}],
|
||||
"respond": {"result": "ok"}}
|
||||
]}
|
||||
```
|
||||
|
||||
### Validation checklist
|
||||
|
||||
1. **Client-side**: the PWA import (or `testScriptProblem`) rejects structural
|
||||
mistakes; the server validates again with the same engine at run start.
|
||||
2. **Import** the JSON (PWA → Simulator → Test script → Import, or
|
||||
`POST /api/test/scripts`), select the matched preset, **Run**.
|
||||
3. **Read the report**: every action/expectation shows expected vs actual per
|
||||
check; ⚠ = warning (continues), ✗ = error (stops). A failed step names the
|
||||
reason (`note`).
|
||||
4. **Iterate**: a mismatch on `data` usually means the mask length differs from
|
||||
the actual value, or the value changed between runs - mask the varying
|
||||
bytes (counter, date-time) with `?`.
|
||||
5. **Counters**: the run starts from the preset's stored counter and persists
|
||||
each accepted packet's next value; consecutive runs continue correctly.
|
||||
|
||||
## Document shape
|
||||
|
||||
```json
|
||||
@@ -52,7 +132,7 @@ continue correctly.
|
||||
| kind | params | notes |
|
||||
|---|---|---|
|
||||
| `envelope` | `event` 0–255, `data` hex (optional) | ENVELOPE(Event Download) |
|
||||
| `menu-select` | `item_id` 1–255 | ENVELOPE(Menu Selection) |
|
||||
| `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 |
|
||||
| `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 |
|
||||
@@ -116,10 +196,26 @@ should use `raw` or no PoR check).
|
||||
|
||||
### `respond`
|
||||
|
||||
The TERMINAL RESPONSE for the fetched command: `result` (name or hex -
|
||||
`ok`, `partial`, `missing`, `refused`, `not_understood`, `modified`, `cancel`,
|
||||
`back`, `timeout`, `no_response`), `item_id`, `text`+`dcs`, `raw` (extra
|
||||
COMPREHENSION-TLVs before the result).
|
||||
The TERMINAL RESPONSE for the fetched command, in the TS 102 223 §6.8.0
|
||||
object order (Command details, Device identities, **Result**, Duration, Text
|
||||
string, Item identifier, …):
|
||||
|
||||
- `result` - name or hex (`ok`, `partial`, `missing`, `refused`,
|
||||
`not_understood`, `modified`, `cancel`, `back`, `timeout`, `no_response`).
|
||||
- `text` + `dcs` - the Text string for a GET INKEY / GET INPUT answer. `dcs`
|
||||
uses the spec's recommended codings: `"00"` GSM default alphabet 7 bits
|
||||
**packed**, `"04"` GSM default alphabet 8 bits (one octet per character),
|
||||
`"08"` **UCS2** (UTF-16-BE). An empty `text` sends the null text string
|
||||
(Length `00`).
|
||||
- `item_id` - the Item identifier for a successful SELECT ITEM answer.
|
||||
- `raw` - extra COMPREHENSION-TLVs appended after the standard objects.
|
||||
|
||||
UCS2 answer to a GET INPUT:
|
||||
|
||||
```json
|
||||
{"type": "expect", "command": "GET INPUT",
|
||||
"respond": {"result": "ok", "text": "Пароль", "dcs": "08"}}
|
||||
```
|
||||
|
||||
## The two PoR transports (SPI2)
|
||||
|
||||
@@ -149,6 +245,22 @@ pending command the next step does not expect.
|
||||
|
||||
## Worked examples
|
||||
|
||||
### Menu selection by text (the id varies with the install parameters)
|
||||
|
||||
```json
|
||||
{"name": "applet menu",
|
||||
"steps": [
|
||||
{"type": "action", "kind": "menu-select",
|
||||
"params": {"text": "Settings", "mode": "exact"}},
|
||||
{"type": "expect", "command": "ANY", "respond": {"result": "ok"}}
|
||||
]}
|
||||
```
|
||||
|
||||
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
|
||||
script must wait for the menu, add a `status` action and an
|
||||
`expect SET UP MENU` before the selection.
|
||||
|
||||
### RFM update → file change → REFRESH (NAA init + full FCN)
|
||||
|
||||
```json
|
||||
|
||||
Reference in New Issue
Block a user