import importlib.util import json from pathlib import Path import sys import tempfile import unittest TOOLS = Path(__file__).resolve().parents[1] / "tools" sys.path.insert(0, str(TOOLS)) import device_prepare as prepare class PrepareTests(unittest.TestCase): def setUp(self): self.directory = tempfile.TemporaryDirectory() self.addCleanup(self.directory.cleanup) self.root = Path(self.directory.name) self.credential = self.root / "credential.json" self.value = {"factory_id": "KP01-0123456789AB", "model": "ECHOEAR-S3", "origin": "https://example.com", "key_version": 1, "device_key_hex": "01" * 32} self.save() def save(self): self.credential.write_text(json.dumps(self.value), encoding="utf-8-sig") def test_accepts_actual_web_download(self): self.assertEqual(prepare.read_credential(self.credential)["device_id"], self.value["factory_id"]) def test_legacy_id(self): self.value["device_id"] = self.value.pop("factory_id") self.save() self.assertEqual(prepare.read_credential(self.credential)["device_id"], self.value["device_id"]) def test_rejects_conflicting_identity(self): self.value["device_id"] = "KP01-AAAAAAAAAAAA" self.save() with self.assertRaises(ValueError): prepare.read_credential(self.credential) def test_rejects_bad_configuration(self): for field, value in [("model", "different-board"), ("key_version", True), ("key_version", 0), ("device_key_hex", "bad"), ("origin", "http://example.com"), ("origin", "https://user:password@example.com"), ("origin", "https://example.com/path"), ("origin", "https://example.com:invalid")]: original = self.value[field] self.value[field] = value self.save() with self.subTest(field=field, value=value), self.assertRaises(ValueError): prepare.read_credential(self.credential) self.value[field] = original def test_mac_independent_of_factory_id(self): self.assertEqual(prepare.normalize_mac("B4-3A-45-16-34-A8"), "b4:3a:45:16:34:a8") @unittest.skipUnless(importlib.util.find_spec("esp_idf_nvs_partition_gen"), "NVS generator unavailable") def test_real_nvs_generation_and_no_overwrite(self): output = self.root / "private" manifest = prepare.prepare(self.credential, output, "B4:3A:45:16:34:A8") image = (output / "ble_factory.bin").read_bytes() self.assertEqual(len(image), 0x6000) self.assertIn(bytes.fromhex(self.value["device_key_hex"]), image) self.assertEqual(manifest["expected_mac"], "b4:3a:45:16:34:a8") self.assertNotIn("device_key_hex", json.dumps(manifest)) self.assertNotIn(self.value["device_key_hex"], (output / "setup-label.json").read_text()) if (TOOLS.parent / "build" / "flasher_args.json").is_file(): import device_flash plan = device_flash.build_plan(TOOLS.parent / "build", output, identity_only=True, reset_runtime=True, expect_mac="B4:3A:45:16:34:A8") self.assertEqual({item.name for item in plan.images}, {"reset-runtime", "ble_factory"}) self.assertEqual(next(item.data for item in plan.images if item.name == "ble_factory"), image) with self.assertRaises(FileExistsError): prepare.prepare(self.credential, output, "B4:3A:45:16:34:A8") if __name__ == "__main__": unittest.main()