Add REG3 frame parser and fixtures
This commit is contained in:
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
# Fixtures REG3
|
||||
|
||||
Les fichiers binaires de ce dossier sont chacun un exemplaire réel extrait de
|
||||
`captures/stream_normal.bin` :
|
||||
|
||||
- `metadata_95.bin` : 95 octets ;
|
||||
- `frigo_163.bin` : 163 octets ;
|
||||
- `regulation_227.bin` : 227 octets.
|
||||
|
||||
Ils permettent d'exécuter les tests du parseur sans versionner ni charger la
|
||||
capture complète. Les limites de lectures TCP ne sont pas représentées par ces
|
||||
fixtures.
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,163 @@
|
||||
"""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 == {}
|
||||
Reference in New Issue
Block a user