"""Tests for the firmware pin + needs-reflash detection (no hardware).""" import asyncio import pytest from pm3py._firmware import FirmwarePin, FIRMWARE_PIN, matches from pm3py.core.client import Proxmark3 from pm3py import flash_cli def run(coro): return asyncio.get_event_loop().run_until_complete(coro) # --- matches() --- def test_matches_sha_substring(): pin = FirmwarePin(sha="303bd5873") assert matches("Iceman/main/303bd5873-dirty", pin) is True assert matches("Iceman/main/303bd58-clean", pin) is True # 7-char prefix def test_matches_rejects_other_build(): pin = FirmwarePin(sha="303bd5873") assert matches("Iceman/master/deadbeef", pin) is False def test_matches_empty_is_false(): assert matches("", FirmwarePin(sha="303bd5873")) is False def test_matches_tag(): pin = FirmwarePin(sha="303bd5873", tag="fw-v0.1.0") assert matches("pm3py fw-v0.1.0 build", pin) is True def test_default_pin_is_present(): assert FIRMWARE_PIN.sha # a pin is baked in # --- _probe_firmware wiring --- def _fake_probe(pm3, version_string): async def fake_ping(length=32): return {"success": True} async def fake_version(): return {"version_string": version_string, "chip_id": "0x1", "section_size": 0} pm3.hw.ping = fake_ping pm3.hw.version = fake_version def test_probe_sets_matches_pin_true(): pm3 = Proxmark3("/dev/ttyFAKE") _fake_probe(pm3, f"Iceman/main/{FIRMWARE_PIN.sha}-dirty") run(pm3._probe_firmware()) assert pm3.firmware.matches_pin is True assert pm3.firmware.expected_firmware == FIRMWARE_PIN.sha def test_probe_sets_matches_pin_false_for_stock(): pm3 = Proxmark3("/dev/ttyFAKE") _fake_probe(pm3, "Iceman/master/00000000") run(pm3._probe_firmware()) assert pm3.firmware.matches_pin is False # --- CLI wiring --- def test_cli_help_exits_clean(capsys): try: flash_cli.main(["--help"]) except SystemExit as e: assert e.code == 0 out = capsys.readouterr().out assert "pm3flash" in out def test_cli_build_and_image_mutually_exclusive(capsys): with pytest.raises(SystemExit): flash_cli.main(["--build", "PM3GENERIC", "--image", "x.elf"]) def test_cli_build_invokes_builder(monkeypatch, tmp_path): elf = tmp_path / "fullimage.elf" elf.write_bytes(b"\x7fELF") monkeypatch.setattr(flash_cli, "build_firmware", lambda platform: elf) seen = {} async def fake_run(args): seen["image"] = args.image seen["force"] = args.force return 0 monkeypatch.setattr(flash_cli, "_run", fake_run) # main() uses asyncio.run(); route it through the suite's shared loop so it # doesn't close/null the global loop and break later get_event_loop() tests. monkeypatch.setattr(flash_cli.asyncio, "run", lambda coro: asyncio.get_event_loop().run_until_complete(coro)) rc = flash_cli.main(["--build", "PM3GENERIC"]) assert rc == 0 assert seen["image"] == str(elf) assert seen["force"] is True # --build flashes what it built, regardless of the pin