| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import pytest
- from unittest.mock import MagicMock, patch, ANY
- import asyncio
- from src.controllers.measurement_controller import MeasurementController
- from src.models.device import DeviceStatus
- from src.models.cell import Cell, CellLimits, MeasureValues
- from src.services.mqtt_service import InsertedCell
- import logging
- @pytest.fixture
- def mock_services():
- i2c_service = MagicMock()
- http_service = MagicMock()
- mqtt_service = MagicMock()
- return i2c_service, http_service, mqtt_service
- @pytest.fixture
- def config():
- return {
- 'devices': [
- {
- 'id': 1,
- 'i2c_address': 0x48,
- 'num_slots': 2
- }
- ],
- 'mqtt': {
- 'subscribe_prefix': 'test/'
- },
- 'i2c': {
- 'polling_interval_ms': 100
- },
- 'measurement': {
- 'min_voltage': 2.5,
- 'max_voltage': 4.2,
- 'c_rate': 0.5
- }
- }
- @pytest.fixture
- def controller(mock_services, config):
- i2c_service, http_service, mqtt_service = mock_services
- return MeasurementController(config, i2c_service, http_service, mqtt_service)
- @pytest.mark.asyncio
- async def test_device_polling(controller, mock_services):
- i2c_service, _, _ = mock_services
-
- # Setup mock responses
- i2c_service.request_status_list.return_value = [DeviceStatus.EMPTY, DeviceStatus.EMPTY]
-
- # Start polling
- await controller.start_polling()
- await asyncio.sleep(0.2) # Allow some polling cycles
- await controller.stop_polling()
-
- # Verify I2C service was called
- assert i2c_service.request_status_list.called
- assert i2c_service.request_status_list.call_count >= 1
- @pytest.mark.asyncio
- async def test_cell_insertion_flow(controller, mock_services):
- i2c_service, http_service, mqtt_service = mock_services
-
- # Setup mock responses
- http_service.fetch_cell_info.return_value = {
- 'cell_type': {
- 'min_voltage': 2.5,
- 'max_voltage': 4.2,
- 'capacity': 3000,
- 'name': 'Test Cell'
- }
- }
-
- # Simulate cell insertion
- insertion_info = InsertedCell(device_id=1, slot_id=0, cell_id=123)
- controller._update_inserted_cell(insertion_info)
-
- # Verify HTTP service was called to fetch cell info
- http_service.fetch_cell_info.assert_called_once_with(123)
-
- # Verify cell was inserted with correct parameters
- device = controller.devices[1]
- cell = device.slots[0].get_cell()
- assert cell is not None
- assert cell.id == 123
- assert cell.nom_capacity == 3000
- @pytest.mark.asyncio
- async def test_measurement_cycle(controller, mock_services):
- i2c_service, _, mqtt_service = mock_services
-
- capacity = 3000
- # Setup initial state
- device = controller.devices[1]
- cell = Cell(123, CellLimits(2.5, 4.2, 1.5), capacity)
- device.slots[0].insert_cell(cell)
-
- # Setup mock responses for state transitions
- i2c_service.request_status_list.side_effect = [
- [DeviceStatus.INSERTED, DeviceStatus.EMPTY], # Initial state
- [DeviceStatus.MEASURING, DeviceStatus.EMPTY], # Measuring
- [DeviceStatus.MEASURING, DeviceStatus.EMPTY], # Measuring
- [DeviceStatus.DONE, DeviceStatus.EMPTY], # Measurement complete
- ]
-
- i2c_service.request_measure_values.return_value = MeasureValues(4.2, 3.6, 1.5)
-
- # Start polling
- await controller.start_polling()
- await asyncio.sleep(1) # Allow state transitions
- await controller.stop_polling()
-
- # Verify measurement cycle
- assert i2c_service.send_cell_limits.called
- assert i2c_service.request_measure_values.called
- assert mqtt_service.cell_finished.called
-
- # Verify health calculation is called
- device_id, slot_id, cell_id, health, status = mqtt_service.cell_finished.call_args[0]
-
- # Verify health calculation and other parameters
- assert device_id == 1
- assert slot_id == 0
- assert cell_id == 123
- assert health != -1.0 # Verify that health was actually calculated
- assert status == DeviceStatus.DONE
- @pytest.mark.asyncio
- async def test_error_handling(controller, mock_services):
- i2c_service, _, mqtt_service = mock_services
-
- # Setup initial state
- device = controller.devices[1]
- cell = Cell(123, CellLimits(2.5, 4.2, 1.5), 3000)
- device.slots[0].insert_cell(cell)
-
- # Simulate error condition
- i2c_service.request_status_list.return_value = [DeviceStatus.ERROR, DeviceStatus.ERROR]
-
- # Start polling
- await controller.start_polling()
- await asyncio.sleep(0.5)
- await controller.stop_polling()
-
- # Verify error handling
- mqtt_service.cell_finished.assert_called_with(1, 0, 123, 0.0, DeviceStatus.ERROR)
- assert device.slots[0].get_cell() is None # Cell should be removed after error
- @pytest.mark.asyncio
- async def test_invalid_status_list(controller, mock_services, caplog):
- i2c_service, _, _ = mock_services
- caplog.set_level(logging.ERROR)
-
- # Setup mock to return invalid status list
- i2c_service.request_status_list.return_value = [DeviceStatus.EMPTY] # Too short
-
- # Start polling
- await controller.start_polling()
- await asyncio.sleep(0.2) # Allow some polling cycles
- await controller.stop_polling()
-
- # Verify error was logged
- assert "Invalid status list length" in caplog.text
- # Add pytest.ini file to handle asyncio
|