141 lines
3.7 KiB
Python
141 lines
3.7 KiB
Python
"""Unit tests for the hameter.discovery module."""
|
|
|
|
import threading
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from hameter.discovery import run_discovery_for_web
|
|
from hameter.config import GeneralConfig
|
|
from hameter.state import AppState, PipelineStatus
|
|
|
|
|
|
VALID_LINE_1 = '{"Time":"2026-03-05T10:00:00Z","Type":"SCM","Message":{"ID":12345,"Consumption":50000}}'
|
|
VALID_LINE_2 = '{"Time":"2026-03-05T10:00:05Z","Type":"SCM","Message":{"ID":67890,"Consumption":12000}}'
|
|
VALID_LINE_3 = '{"Time":"2026-03-05T10:00:10Z","Type":"SCM","Message":{"ID":12345,"Consumption":50100}}'
|
|
|
|
|
|
@pytest.fixture
|
|
def config():
|
|
return GeneralConfig()
|
|
|
|
|
|
@pytest.fixture
|
|
def shutdown_event():
|
|
return threading.Event()
|
|
|
|
|
|
@pytest.fixture
|
|
def app_state():
|
|
return AppState()
|
|
|
|
|
|
@patch("hameter.discovery.SubprocessManager")
|
|
def test_run_discovery_for_web_records_results(
|
|
mock_spm_cls, config, shutdown_event, app_state
|
|
):
|
|
"""Parsed meter readings are recorded via app_state."""
|
|
mock_proc = MagicMock()
|
|
mock_spm_cls.return_value = mock_proc
|
|
mock_proc.start_discovery_mode.return_value = True
|
|
|
|
calls = [0]
|
|
|
|
def get_line(timeout=1.0):
|
|
calls[0] += 1
|
|
lines = [VALID_LINE_1, VALID_LINE_2, VALID_LINE_3]
|
|
if calls[0] <= len(lines):
|
|
return lines[calls[0] - 1]
|
|
shutdown_event.set()
|
|
return None
|
|
|
|
mock_proc.get_line.side_effect = get_line
|
|
|
|
run_discovery_for_web(
|
|
config, shutdown_event, app_state,
|
|
duration=300, stop_event=None,
|
|
)
|
|
|
|
results = app_state.get_discovery_results()
|
|
assert 12345 in results
|
|
assert 67890 in results
|
|
assert results[12345]["count"] >= 2
|
|
mock_proc.stop.assert_called_once()
|
|
|
|
|
|
@patch("hameter.discovery.SubprocessManager")
|
|
def test_run_discovery_for_web_start_failure(
|
|
mock_spm_cls, config, shutdown_event, app_state
|
|
):
|
|
"""Start failure sets ERROR status."""
|
|
mock_proc = MagicMock()
|
|
mock_spm_cls.return_value = mock_proc
|
|
mock_proc.start_discovery_mode.return_value = False
|
|
|
|
run_discovery_for_web(
|
|
config, shutdown_event, app_state,
|
|
duration=120, stop_event=None,
|
|
)
|
|
|
|
assert app_state.status == PipelineStatus.ERROR
|
|
mock_proc.get_line.assert_not_called()
|
|
|
|
|
|
@patch("hameter.discovery.SubprocessManager")
|
|
def test_run_discovery_for_web_respects_stop_event(
|
|
mock_spm_cls, config, shutdown_event, app_state
|
|
):
|
|
"""stop_event causes early exit."""
|
|
mock_proc = MagicMock()
|
|
mock_spm_cls.return_value = mock_proc
|
|
mock_proc.start_discovery_mode.return_value = True
|
|
|
|
stop_event = threading.Event()
|
|
stop_event.set()
|
|
|
|
mock_proc.get_line.return_value = VALID_LINE_1
|
|
|
|
run_discovery_for_web(
|
|
config, shutdown_event, app_state,
|
|
duration=300, stop_event=stop_event,
|
|
)
|
|
|
|
mock_proc.stop.assert_called_once()
|
|
|
|
|
|
@patch("hameter.discovery.SubprocessManager")
|
|
def test_run_discovery_for_web_handles_invalid_json(
|
|
mock_spm_cls, config, shutdown_event, app_state
|
|
):
|
|
"""Invalid lines are skipped without crashing."""
|
|
mock_proc = MagicMock()
|
|
mock_spm_cls.return_value = mock_proc
|
|
mock_proc.start_discovery_mode.return_value = True
|
|
|
|
calls = [0]
|
|
lines = [
|
|
"not json",
|
|
"{malformed{{",
|
|
"",
|
|
VALID_LINE_1,
|
|
]
|
|
|
|
def get_line(timeout=1.0):
|
|
calls[0] += 1
|
|
if calls[0] <= len(lines):
|
|
return lines[calls[0] - 1]
|
|
shutdown_event.set()
|
|
return None
|
|
|
|
mock_proc.get_line.side_effect = get_line
|
|
|
|
run_discovery_for_web(
|
|
config, shutdown_event, app_state,
|
|
duration=300, stop_event=None,
|
|
)
|
|
|
|
results = app_state.get_discovery_results()
|
|
assert 12345 in results
|
|
assert len(results) == 1
|
|
mock_proc.stop.assert_called_once()
|