aboutsummaryrefslogtreecommitdiff
path: root/tools/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tools/tests')
-rw-r--r--tools/tests/test_gen_fixture_wrap.py33
-rw-r--r--tools/tests/test_ui_flow.py73
2 files changed, 106 insertions, 0 deletions
diff --git a/tools/tests/test_gen_fixture_wrap.py b/tools/tests/test_gen_fixture_wrap.py
new file mode 100644
index 0000000..1c72e66
--- /dev/null
+++ b/tools/tests/test_gen_fixture_wrap.py
@@ -0,0 +1,33 @@
+# Author: cirakg & claude ai
+# Summary: Tests that the fixture PIN wrap generator matches the constants in source/app/Fixtures.mc and RFC 5869.
+from __future__ import annotations
+
+import re
+import shutil
+from pathlib import Path
+
+import gen_fixture_wrap
+import pytest
+
+ROOT = Path(__file__).resolve().parents[2]
+
+
+def fixture_constant(name: str) -> str:
+ text = (ROOT / "source" / "app" / "Fixtures.mc").read_text(encoding="utf-8")
+ match = re.search(rf'const {name} = "([0-9a-f]+)"', text)
+ assert match, f"{name} not found in Fixtures.mc"
+ return match.group(1)
+
+
+def test_hkdf_matches_rfc5869_case_3() -> None:
+ # RFC 5869 A.3: empty salt and info.
+ okm = gen_fixture_wrap.hkdf(bytes([0x0B] * 22), b"", 42)
+ assert okm.hex() == (
+ "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8" # RFC 5869 A.3
+ )
+
+
+@pytest.mark.skipif(shutil.which("openssl") is None, reason="needs openssl for AES")
+def test_generated_wrap_matches_the_fixture() -> None:
+ assert gen_fixture_wrap.fixture_wrap().hex() == fixture_constant("FIXTURE_WRAP")
+ assert gen_fixture_wrap.MASTER_KEY.hex() == fixture_constant("FIXTURE_MASTER_KEY")
diff --git a/tools/tests/test_ui_flow.py b/tools/tests/test_ui_flow.py
new file mode 100644
index 0000000..7e0d336
--- /dev/null
+++ b/tools/tests/test_ui_flow.py
@@ -0,0 +1,73 @@
+# Author: cirakg & claude ai
+# Summary: Tests for the UI flow driver's pure parts: device key layout, JSON lookup and the app-log reader.
+from __future__ import annotations
+
+import json
+import threading
+import time
+from pathlib import Path
+
+import pytest
+import ui_flow
+
+
+def write_sim(tmp_path: Path, config: dict[str, object]) -> Path:
+ (tmp_path / "simulator.json").write_text(json.dumps(config), encoding="utf-8")
+ return tmp_path
+
+
+def test_load_keys_gives_button_centres(tmp_path: Path) -> None:
+ device = write_sim(
+ tmp_path,
+ {
+ "keys": [
+ {"id": "enter", "behavior": "onSelect", "location": {"x": 10, "y": 20, "width": 30, "height": 40}},
+ {
+ "id": "menu",
+ "behavior": "onMenu",
+ "isHold": True,
+ "location": {"x": 0, "y": 0, "width": 2, "height": 2},
+ },
+ {"id": "start", "location": {"x": 5, "y": 5, "width": 10, "height": 10}},
+ ]
+ },
+ )
+ keys = ui_flow.load_keys(device)
+ assert keys["enter"] == ui_flow.Key(25, 40, "onSelect", False)
+ assert keys["menu"].hold
+ assert keys["start"].behavior == ""
+
+
+def test_find_key_searches_nested_structures() -> None:
+ data = {"display": {"isTouch": True}, "glance": [{"contentArea": {"x": 1}}]}
+ assert ui_flow.find_key(data, "isTouch") is True
+ assert ui_flow.find_key(data, "contentArea") == {"x": 1}
+ assert ui_flow.find_key(data, "missing") is None
+
+
+def test_app_log_waits_only_for_lines_after_the_mark(tmp_path: Path) -> None:
+ log_path = tmp_path / "app.log"
+ log_path.write_text("DIAG shown list\n", encoding="utf-8")
+ log = ui_flow.AppLog(log_path)
+ assert log.wait_for(r"DIAG shown list\s*$", 1).group(0).startswith("DIAG shown list")
+ log.set_mark()
+
+ def append_later() -> None:
+ time.sleep(0.2)
+ with log_path.open("a", encoding="utf-8") as f:
+ f.write("DIAG shown otp\n")
+
+ writer = threading.Thread(target=append_later)
+ writer.start()
+ assert log.wait_for(r"DIAG shown otp", 3)
+ writer.join()
+ with pytest.raises(ui_flow.FlowError):
+ log.wait_for(r"DIAG shown list", 0.3) # only before the mark
+
+
+def test_app_log_latest_returns_the_last_match(tmp_path: Path) -> None:
+ log_path = tmp_path / "app.log"
+ log_path.write_text("DIAG list_focus y=10\nother\nDIAG list_focus y=42\n", encoding="utf-8")
+ match = ui_flow.AppLog(log_path).latest(r"DIAG list_focus y=(\d+)")
+ assert match is not None and match.group(1) == "42"
+ assert ui_flow.AppLog(tmp_path / "missing.log").latest("x") is None