From 8430dd16947f80f80a8388e570ddcf983954c62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sun, 4 Oct 2026 17:24:21 +0300 Subject: [PATCH] v3.16.0: test scripts move to a server-side store The scripts lived in the browser's localStorage although only the local server can run them. The new test_scripts.py store (~/.pysim-simple-server/test_scripts.json, --test-scripts PATH) sits next to the card presets: stable uuids and timestamps, atomic writes, and every mutation is validated with the runner's own engine (the submitted shape is kept - the normaliser's output is not re-normalisable: it fills defaults like an 'any' PoR check that the check normaliser then rejects), so a stored script cannot fail at run start for a validation reason. Import accepts the old localStorage export as-is (export/import remains the migration path; the PWA import also takes an exported array). - /api/test/scripts (GET card-free: path/count + scripts), POST create, /update, /delete and /import serve the store; the endpoints answer 503 without a store and 500 on a write failure, like the preset endpoints. - /api/test/run takes a stored script_id (resolved when no inline script is sent); the PWA sends the edited copy inline plus the id for the report, and the run snapshot carries script_id. - The PWA's Test script pill loads from the store, writes through on edits (debounced name, immediate step/new/copy/delete), reports load/save/delete failures inline, and exports the portable {name, steps} shape. Tests: 808 frontend + 701 Python; sw cache simple-v379. --- docs/api.md | 33 +++- docs/test-scripts.md | 10 +- frontend/help-ru.html | 2 +- frontend/help.html | 2 +- frontend/index.html | 129 +++++++++---- frontend/sw.js | 2 +- frontend/tests/testscripts.test.js | 73 +++++++- pyproject.toml | 2 +- pysim_simple_server/__main__.py | 15 +- pysim_simple_server/server.py | 146 ++++++++++++++- pysim_simple_server/test_scripts.py | 279 ++++++++++++++++++++++++++++ tests/test_fetch_routes.py | 131 +++++++++++++ tests/test_test_scripts.py | 197 ++++++++++++++++++++ tests/test_testscript.py | 33 ++++ 14 files changed, 1003 insertions(+), 51 deletions(-) create mode 100644 pysim_simple_server/test_scripts.py create mode 100644 tests/test_test_scripts.py diff --git a/docs/api.md b/docs/api.md index 6d22280..91bab2a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -48,10 +48,15 @@ a 3.x PWA). | `/api/ram-install-app` | POST | Single INSTALL [for install] / [for make selectable] for an already loaded package (no re-load) | | `/api/cap-info` | POST | Validate a `.cap` archive and estimate its code/NVRAM/RAM requirements (read-only) | | `/api/cap-compat` | POST | CAP compatibility test: LOAD only to the block completing the Import component (nothing committed) | -| `/api/test/run` | POST | Start a test script (actions + proactive expectations) | +| `/api/test/run` | POST | Start a test script (actions + proactive expectations; `script` inline or `script_id` from the store) | | `/api/test/status` | GET | Test script run state and per-step results | | `/api/test/stop` | POST | Request a running test script to stop | | `/api/test/clear` | POST | Clear the finished run report | +| `/api/test/scripts` | GET | Test script store (server-side `test_scripts.json`): all scripts + the store path | +| `/api/test/scripts` | POST | Create a test script (`{name, steps}` or `{script}`) | +| `/api/test/scripts/update` | POST | Update a test script (`{id, script}`) | +| `/api/test/scripts/delete` | POST | Delete a test script (`{id}`) | +| `/api/test/scripts/import` | POST | Import scripts (`{scripts: [...], mode: merge\|replace}`; accepts the old localStorage export shape) | | `/api/sp-verify` | POST | Verify secured packet against pySim reference | | `/api/menu` | GET | Current STK menu (title + items + active) | | `/api/menu-select` | POST | ENVELOPE(Menu Selection) with item_id | @@ -653,7 +658,10 @@ suspended; only `/api/test/*`, `/api/status`, `/api/poll-status`, {"role": "usimRfm", "tar": "B00001", "msl": "16"}]}} ``` -The PWA sends `preset_id` (the stored preset's id) instead of `preset`; the +The PWA sends the current script inline (a run always uses what the editor +shows) plus `script_id` when the script is stored; sending `script_id` alone +resolves the script from the store. The PWA sends `preset_id` (the stored +preset's id) instead of `preset`; the server resolves it from the preset store, so a run always uses the stored keysets, TARs and counters. The inline `preset` form stays for external callers. @@ -690,6 +698,27 @@ The response is the initial state (`running: true`), the final counter (`scp80_counter`) and the step list; poll `/api/test/status`. The PWA writes `scp80_counter` back to the card preset after the run. +### `GET /api/test/scripts` and the script store + +Test scripts live in a server-side store (`~/.pysim-simple-server/test_scripts.json`, +`--test-scripts PATH`) - the runner is server-side, so the store is the source +of truth and every browser manages the same set. The store validates with the +same engine the runner uses, so a stored script can never fail at run start for +a validation reason. + +```json +{"path": "/home/user/.pysim-simple-server/test_scripts.json", "count": 1, "version": 1, + "scripts": [{"id": "…", "name": "applet RFM update", "steps": [...], + "created": 1690000000.0, "updated": 1690000000.0}]} +``` + +`POST /api/test/scripts` creates one (`{name, steps}` or `{script: {...}}`), +`POST /api/test/scripts/update` replaces name/steps (`{id, script}`), +`POST /api/test/scripts/delete` removes (`{id}`) and +`POST /api/test/scripts/import` takes `{scripts: [...], mode: "merge"|"replace"}` +(the old localStorage export shape; invalid entries are reported in `errors` +instead of failing the whole import). + ### `GET /api/test/status` The current (or last) run: diff --git a/docs/test-scripts.md b/docs/test-scripts.md index 10ff9f9..427177d 100644 --- a/docs/test-scripts.md +++ b/docs/test-scripts.md @@ -5,10 +5,12 @@ the card over the local server: ordered **actions** (each sends something and checks the response) and **expectations** (each fetches the proactive command the card announced, checks it, and sends the scripted TERMINAL RESPONSE). -Scripts are imported into the PWA (Simulator → Test script → Import) and run -with **Run** against the equipped card, or POSTed directly to -`/api/test/run` (see `docs/api.md`). While a run is active the card belongs to -the script. +Scripts live in the **server-side store** +(`~/.pysim-simple-server/test_scripts.json`, `--test-scripts`): the PWA's Test +script pill manages them (Import/Export move JSON in and out, the old +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. ## Document shape diff --git a/frontend/help-ru.html b/frontend/help-ru.html index ac154ea..b59f05f 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -561,7 +561,7 @@

8.9 Тест-скрипт

-

Тест-скрипт — упорядоченный список действий и ожиданий проактивных команд, выполняемый на подключённой карте на стороне сервера. Пока запуск активен, карта принадлежит скрипту: остальные операции с картой отклоняются (HTTP 409), фоновый опрос STATUS приостанавливается; отчёт о запуске хранится в памяти (в том числе после перезагрузки страницы) до нажатия Очистить отчёт. Скрипты хранятся в localStorage (simple_tests) и экспортируются/импортируются как JSON.

+

Тест-скрипт — упорядоченный список действий и ожиданий проактивных команд, выполняемый на подключённой карте на стороне сервера. Пока запуск активен, карта принадлежит скрипту: остальные операции с картой отклоняются (HTTP 409), фоновый опрос STATUS приостанавливается; отчёт о запуске хранится в памяти (в том числе после перезагрузки страницы) до нажатия Очистить отчёт. Скрипты хранятся в хранилище на сервере (~/.pysim-simple-server/test_scripts.json, --test-scripts), поэтому все браузеры работают с одним набором; экспорт/импорт JSON остаётся для резервных копий и переноса скриптов между стендами (старый экспорт из localStorage импортируется как есть).