#!/usr/bin/env python3 """Unit tests for the pure CI-V codec (civ.py). Stdlib only: `python3 -m unittest`.""" import unittest import civ class TestFreqCodec(unittest.TestCase): # Real capture from the radio: VFO was 147.300000 MHz. REAL_BCD = bytes.fromhex("0000304701") REAL_HZ = 147_300_000 def test_bcd_to_freq_decodes_real_capture(self): self.assertEqual(civ.bcd_to_freq(self.REAL_BCD), self.REAL_HZ) def test_freq_to_bcd_matches_real_capture(self): self.assertEqual(civ.freq_to_bcd(self.REAL_HZ), self.REAL_BCD) def test_freq_roundtrip(self): for hz in (146_520_000, 446_000_000, 147_300_000, 1_240_000_000): self.assertEqual(civ.bcd_to_freq(civ.freq_to_bcd(hz)), hz) class TestLevelCodec(unittest.TestCase): # ICOM levels are 0..255 sent as 2-byte BCD "0000".."0255". def test_level_to_bcd_known(self): self.assertEqual(civ.level_to_bcd(0), b"\x00\x00") self.assertEqual(civ.level_to_bcd(128), b"\x01\x28") self.assertEqual(civ.level_to_bcd(255), b"\x02\x55") def test_bcd_to_level_known(self): self.assertEqual(civ.bcd_to_level(b"\x00\x00"), 0) self.assertEqual(civ.bcd_to_level(b"\x01\x28"), 128) self.assertEqual(civ.bcd_to_level(b"\x02\x55"), 255) def test_level_rejects_out_of_range(self): with self.assertRaises(ValueError): civ.level_to_bcd(256) with self.assertRaises(ValueError): civ.level_to_bcd(-1) class TestBuildFrame(unittest.TestCase): def test_read_freq_frame(self): # FE FE 03 FD self.assertEqual(civ.build_frame(0x03), bytes.fromhex("fefe8ce003fd")) def test_frame_with_data(self): # set freq 147.3 MHz: FE FE 8C E0 05 <5 BCD> FD frame = civ.build_frame(0x05, civ.freq_to_bcd(147_300_000)) self.assertEqual(frame, bytes.fromhex("fefe8ce005" + "0000304701" + "fd")) class TestParseFrames(unittest.TestCase): # Exact bytes the radio returned during the probe (echo + reply). REAL_RX = bytes.fromhex("fefe8ce003fd" + "fefee08c030000304701fd") def test_splits_echo_and_reply(self): frames = civ.parse_frames(self.REAL_RX) self.assertEqual(len(frames), 2) echo, reply = frames self.assertEqual((echo.to, echo.frm, echo.cn), (0x8C, 0xE0, 0x03)) self.assertEqual(echo.data, b"") self.assertEqual((reply.to, reply.frm, reply.cn), (0xE0, 0x8C, 0x03)) self.assertEqual(reply.data, bytes.fromhex("0000304701")) def test_ignores_leading_noise_and_partial_tail(self): noisy = b"\x00\xff" + self.REAL_RX + b"\xfe\xfe\x99" # partial frame at end frames = civ.parse_frames(noisy) self.assertEqual(len(frames), 2) def test_empty_buffer(self): self.assertEqual(civ.parse_frames(b""), []) if __name__ == "__main__": unittest.main()