IonizationChamber/Software/MeasurementAcquisition/tests_ut/test_application_layer.py

89 wiersze
4.0 KiB
Python

2025-06-27 06:15:49 +00:00
"""Unit tests for :pyclass:`measurement_acquisition.application_layer.ApplicationLayer`."""
# pylint: disable=protected-access
from __future__ import annotations
from unittest.mock import MagicMock, create_autospec
import pytest
from measurement_acquisition.application_layer import ApplicationLayer, MCP3425Converter
from measurement_acquisition.interfaces import ITransportLayer
class TestApplicationLayer: # pylint: disable=too-few-public-methods
"""Behaviour and input-validation tests for :class:`ApplicationLayer`."""
# ------------------------------------------------------------------ #
# Fixtures #
# ------------------------------------------------------------------ #
@pytest.fixture()
def mock_transport(self): # noqa: D401
"""Autospecced transport-layer mock."""
return create_autospec(ITransportLayer)
@pytest.fixture()
def mock_converter(self): # noqa: D401
"""Generic mock converter implementing :class:`IADCConverter`."""
return MagicMock()
@pytest.fixture()
def app_layer(self, mock_transport, mock_converter): # noqa: D401
"""Default-configured :class:`ApplicationLayer`."""
return ApplicationLayer(mock_transport, converter=mock_converter)
# ------------------------------------------------------------------ #
# Constructor behaviour #
# ------------------------------------------------------------------ #
def test_initialization(self, mock_transport, mock_converter):
"""Verify default and custom initialisation paths."""
# Defaults -----------------------------------------------------
app = ApplicationLayer(mock_transport)
assert isinstance(
app._converter, MCP3425Converter
) # pylint: disable=protected-access
# Custom converter --------------------------------------------
app = ApplicationLayer(mock_transport, converter=mock_converter)
assert app._converter is mock_converter # pylint: disable=protected-access
# ------------------------------------------------------------------ #
# Transport connection #
# ------------------------------------------------------------------ #
def test_connect(self, app_layer, mock_transport):
"""``connect`` should delegate to the transport layer."""
app_layer.connect()
mock_transport.connect.assert_called_once()
# ------------------------------------------------------------------ #
# Happy-path measurement retrieval #
# ------------------------------------------------------------------ #
def test_get_measurement_success(self, app_layer, mock_transport, mock_converter):
"""Successful frame retrieval and conversion."""
# Frame layout from DataFrameConfig:
# byte 0 : header
# byte 1 : adc_conf (e.g. 0xA0 : R12, gain×1, etc.)
# byte 2 : adc_msb
# byte 3 : adc_lsb
# byte 4 : crc (ignored by ApplicationLayer)
mock_frame = bytes([0x00, 0xA0, 0xAB, 0xCD, 0x00])
mock_transport.get_frame.return_value = mock_frame
mock_converter.convert.return_value = 3.1415
result = app_layer.get_measurement()
assert result == 3.1415
mock_transport.get_frame.assert_called_once()
mock_converter.convert.assert_called_with(0xAB, 0xCD, 0xA0)
# ------------------------------------------------------------------ #
# Error handling – incomplete frames #
# ------------------------------------------------------------------ #
def test_invalid_frame(self, app_layer, mock_transport):
"""An incomplete frame must raise ``ValueError``."""
# Too short (only header + conf)
mock_transport.get_frame.return_value = bytes([0x00, 0x00])
with pytest.raises(ValueError, match="Incomplete frame received"):
app_layer.get_measurement()