from __future__ import annotations import tempfile import unittest from pathlib import Path from setprotocol.firmware_publish import ( FirmwarePublication, firmware_release_entry, firmware_release_tag, update_firmware_manifest, ) class FirmwarePublishTests(unittest.TestCase): def publication(self, path: Path, **overrides) -> FirmwarePublication: fields = { "path": path, "product": "F103DS18", "version_name": "1.1.0", "version_code": 0x00010100, "transport": "can", "base_address": 0x08003000, "notes": "Verified release", } fields.update(overrides) return FirmwarePublication(**fields) def test_publication_validates_file_and_metadata(self) -> None: with tempfile.TemporaryDirectory() as temporary: image = Path(temporary) / "image.hex" image.write_text(":00000001FF\n", encoding="ascii") self.publication(image).validate() with self.assertRaisesRegex(ValueError, "Unsupported"): self.publication(image, transport="unknown").validate() def test_entry_and_tag_are_deterministic(self) -> None: publication = self.publication(Path("image.hex")) entry = firmware_release_entry( publication, "https://example.test/image.hex", "ab" * 32 ) self.assertEqual( firmware_release_tag(publication), "firmware-F103DS18-v1.1.0" ) self.assertEqual(entry["baseAddress"], "0x08003000") def test_update_preserves_sections_and_replaces_same_release(self) -> None: first = { "product": "Device", "versionCode": 7, "versionName": "1.2.3", "imageUrl": "https://example.test/old.bin", "fileName": "old.bin", "sha256": "11" * 32, "transport": "rs485", } manifest = update_firmware_manifest( {"windows": {"versionCode": 8}}, first ) replacement = { **first, "imageUrl": "https://example.test/new.bin", "fileName": "new.bin", "sha256": "22" * 32, } updated = update_firmware_manifest(manifest, replacement) self.assertEqual(updated["windows"], {"versionCode": 8}) self.assertEqual(len(updated["firmware"]["releases"]), 1) self.assertEqual( updated["firmware"]["releases"][0]["sha256"], "22" * 32 ) self.assertEqual(updated["firmware"]["catalogVersion"], 2) def test_legacy_array_is_migrated_without_data_loss(self) -> None: legacy = { "product": "Legacy", "versionCode": 1, "versionName": "1.0", "imageUrl": "https://example.test/legacy.bin", "fileName": "legacy.bin", "sha256": "33" * 32, "transport": "rs485", } current = {**legacy, "product": "Current", "versionCode": 2} updated = update_firmware_manifest({"firmware": [legacy]}, current) self.assertCountEqual( [row["product"] for row in updated["firmware"]["releases"]], ["Legacy", "Current"], ) if __name__ == "__main__": unittest.main()