from __future__ import annotations import copy import importlib.util from pathlib import Path import sys import unittest SOURCE_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(SOURCE_ROOT / "src")) from matrix_image_editor.config import ( # noqa: E402 HEADER_BYTES, SLOT_BYTES, ImageConfigError, create_config_file, read_config_file, update_config_file, validate_config, ) def sample_config() -> dict: return { "schema_version": 1, "product": "matrix-screen-controller-walnutpi", "software_version": "1.0.3", "account": {"username": "matrix", "password": "password123"}, "wifi": {"ssid": "测试 WiFi", "password": "wifi-pass-123"}, "ipv4": {"mode": "dhcp", "address": "", "prefix": 0, "gateway": "", "dns": []}, } class ConfigTests(unittest.TestCase): def test_round_trip_and_update_inactive_slot(self) -> None: original = create_config_file(sample_config()) config, generation, slot = read_config_file(original) self.assertEqual((generation, slot), (1, 0)) config["wifi"]["ssid"] = "新网络" updated = update_config_file(original, config) verified, generation, slot = read_config_file(updated) self.assertEqual((generation, slot, verified["wifi"]["ssid"]), (2, 1, "新网络")) self.assertEqual(updated[HEADER_BYTES : HEADER_BYTES + SLOT_BYTES], original[HEADER_BYTES : HEADER_BYTES + SLOT_BYTES]) def test_corrupt_active_slot_falls_back_and_both_corrupt_fail(self) -> None: original = create_config_file(sample_config()) updated = bytearray(update_config_file(original, sample_config())) updated[HEADER_BYTES + SLOT_BYTES + 60] ^= 0x01 _config, generation, slot = read_config_file(bytes(updated)) self.assertEqual((generation, slot), (1, 0)) updated[HEADER_BYTES + 60] ^= 0x01 with self.assertRaises(ImageConfigError): read_config_file(bytes(updated)) def test_rejects_unknown_product_schema_and_fields(self) -> None: for field, value in (("product", "wrong"), ("schema_version", 2)): config = sample_config() config[field] = value with self.assertRaises(ImageConfigError): validate_config(config) config = sample_config() config["unexpected"] = True with self.assertRaises(ImageConfigError): validate_config(config) def test_utf8_ssid_and_utf16_secret_lengths_match_contract(self) -> None: config = sample_config() config["wifi"]["ssid"] = "中" * 10 validate_config(config) config["wifi"]["ssid"] = "中" * 11 with self.assertRaises(ImageConfigError): validate_config(config) config = sample_config() config["account"]["password"] = "😀" * 4 validate_config(config) def test_static_ipv4_requires_same_subnet_and_valid_dns(self) -> None: config = sample_config() config["ipv4"] = { "mode": "static", "address": "192.168.10.20", "prefix": 24, "gateway": "192.168.10.1", "dns": ["1.1.1.1", "8.8.8.8"], } validate_config(config) config["ipv4"]["gateway"] = "192.168.11.1" with self.assertRaises(ImageConfigError): validate_config(config) def test_project_codec_cross_compatibility_when_repository_is_present(self) -> None: project_codec = SOURCE_ROOT.parents[2] / "核桃派软件源代码" / "scripts" / "image_config.py" if not project_codec.is_file(): self.skipTest("独立源码副本中没有主工程交叉校验器") spec = importlib.util.spec_from_file_location("project_image_config", project_codec) assert spec is not None and spec.loader is not None module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) ours = create_config_file(copy.deepcopy(sample_config())) theirs = module.create_config_file(copy.deepcopy(sample_config())) self.assertEqual(module.read_config_file(ours)[0], read_config_file(theirs)[0]) if __name__ == "__main__": unittest.main()