"""Tests du parseur REG3 sans hypothèse sur les lectures TCP.""" from dataclasses import asdict, replace from pathlib import Path import pytest from custom_components.arkteos.parser import ( BufferBoundsError, FrameHeaderError, FrameLengthError, filter_values, parse_frame, parse_frigo_frame, parse_metadata_frame, parse_regulation_frame, read_i8, read_i16_le, read_u8, read_u16_le, ) FIXTURES_PATH = Path(__file__).with_name("fixtures") FIXTURE_FILES = { 95: "metadata_95.bin", 163: "frigo_163.bin", 227: "regulation_227.bin", } @pytest.fixture(scope="module") def frames() -> dict[int, bytes]: """Charge les exemplaires réels versionnés, indépendamment du flux complet.""" loaded_frames = { size: (FIXTURES_PATH / filename).read_bytes() for size, filename in FIXTURE_FILES.items() } for expected_size, frame in loaded_frames.items(): assert len(frame) == expected_size return loaded_frames def test_read_u8() -> None: assert read_u8(b"\x12", 0) == 18 def test_read_i8_positive() -> None: assert read_i8(b"\x7f", 0) == 127 def test_read_i8_negative() -> None: assert read_i8(b"\x80", 0) == -128 def test_read_u16_little_endian() -> None: assert read_u16_le(b"\x34\x12", 0) == 0x1234 def test_read_i16_little_endian_positive() -> None: assert read_i16_le(b"\xff\x7f", 0) == 32767 def test_read_i16_little_endian_negative() -> None: assert read_i16_le(b"\x00\x80", 0) == -32768 def test_buffer_overrun_and_short_frame_are_controlled() -> None: with pytest.raises(BufferBoundsError): read_u16_le(b"\x00", 0) with pytest.raises(FrameLengthError): parse_frigo_frame(b"\x55\x00") def test_bad_signature(frames: dict[int, bytes]) -> None: frame = bytearray(frames[163]) frame[0] = 0 with pytest.raises(FrameHeaderError, match="signature"): parse_frame(bytes(frame)) def test_type_and_length_inconsistency(frames: dict[int, bytes]) -> None: frame = bytearray(frames[163]) frame[8] = 0x0B with pytest.raises(FrameHeaderError, match="type"): parse_frame(bytes(frame)) def test_header_length_inconsistency(frames: dict[int, bytes]) -> None: frame = bytearray(frames[227]) frame[10:12] = (80).to_bytes(2, "little") with pytest.raises(FrameHeaderError, match="longueur"): parse_frame(bytes(frame)) def test_unknown_frame_length() -> None: with pytest.raises(FrameLengthError, match="inconnue"): parse_frame(b"\x00" * 96) def test_parse_real_metadata_frame(frames: dict[int, bytes]) -> None: frame = parse_metadata_frame(frames[95]) assert frame.frame_type == "metadata" def test_parse_real_frigo_frame(frames: dict[int, bytes]) -> None: frame = parse_frigo_frame(frames[163]) assert frame.frame_type == "frigo" assert frame.exterieur_temp == 32.0 def test_parse_real_regulation_frame(frames: dict[int, bytes]) -> None: frame = parse_regulation_frame(frames[227]) assert frame.frame_type == "regulation" assert frame.modele_pac_s == "BAGUIO_ZURAN_IV" def test_values_match_node_red_calculations(frames: dict[int, bytes]) -> None: frigo = parse_frigo_frame(frames[163]) regulation = parse_regulation_frame(frames[227]) assert frigo.temps_compresseur == 22.4 assert frigo.nb_cycles_compresseur == 8300 assert frigo.dc_voltage == 327 assert regulation.temps_mise_sous_tension == 43 assert regulation.primaire_temp_eau_aller == 54.9 assert regulation.ecs_temp_eau_milieu == 60.5 assert regulation.nb_cycles_compresseur_reg == 7827 def test_filter_keeps_valid_value(frames: dict[int, bytes]) -> None: result = filter_values(parse_frigo_frame(frames[163])) assert result.accepted["exterieur_temp"] == 32.0 assert result.rejected == {} def test_filter_rejects_aberrant_temperature(frames: dict[int, bytes]) -> None: frame = replace(parse_frigo_frame(frames[163]), exterieur_temp=150.1) result = filter_values(frame) assert "exterieur_temp" not in result.accepted assert result.rejected["exterieur_temp"] == 150.1 def test_filter_rejects_aberrant_pressure(frames: dict[int, bytes]) -> None: frame = replace(parse_regulation_frame(frames[227]), primaire_pression=10.1) result = filter_values(frame) assert "primaire_pression" not in result.accepted assert result.rejected["primaire_pression"] == 10.1 def test_filter_keeps_text_statuses(frames: dict[int, bytes]) -> None: frigo_result = filter_values(parse_frigo_frame(frames[163])) regulation_result = filter_values(parse_regulation_frame(frames[227])) assert frigo_result.accepted["statut_frigo_s"] == "Arret" assert regulation_result.accepted["statut_pac_s"] == "Attente" def test_metadata_has_no_invented_fields(frames: dict[int, bytes]) -> None: frame = parse_metadata_frame(frames[95]) result = filter_values(frame) assert asdict(frame) == {"frame_type": "metadata"} assert result.accepted == {"frame_type": "metadata"} assert result.rejected == {}