diff --git a/src/enapter/cli/http/api/site_location.py b/src/enapter/cli/http/api/site_location.py index 9504cbf..54a6304 100644 --- a/src/enapter/cli/http/api/site_location.py +++ b/src/enapter/cli/http/api/site_location.py @@ -4,7 +4,7 @@ def parse_site_location(location_str: str) -> tuple[str, float, float]: try: name, lat_str, lon_str = location_str.split(",") - return name, float(lat_str), float(lon_str) + return name.strip(), float(lat_str), float(lon_str) except ValueError: raise argparse.ArgumentTypeError( "Location must be in the format NAME,LATITUDE,LONGITUDE" diff --git a/tests/unit/test_cli/test_http/test_api/test_site_location.py b/tests/unit/test_cli/test_http/test_api/test_site_location.py new file mode 100644 index 0000000..b737e0c --- /dev/null +++ b/tests/unit/test_cli/test_http/test_api/test_site_location.py @@ -0,0 +1,58 @@ +import argparse + +import pytest + +from enapter.cli.http.api.site_location import parse_site_location + + +def test_parse_site_location_valid(): + assert parse_site_location("Berlin,52.52,13.405") == ("Berlin", 52.52, 13.405) + + +def test_parse_site_location_with_spaces(): + # Note: name strips whitespace, float() handles surrounding whitespace + assert parse_site_location(" Berlin , 52.52 , 13.405 ") == ( + "Berlin", + 52.52, + 13.405, + ) + + +def test_parse_site_location_too_few_parts(): + with pytest.raises(argparse.ArgumentTypeError) as exc_info: + parse_site_location("Berlin,52.52") + assert "Location must be in the format NAME,LATITUDE,LONGITUDE" in str( + exc_info.value + ) + + +def test_parse_site_location_too_many_parts(): + with pytest.raises(argparse.ArgumentTypeError) as exc_info: + parse_site_location("Berlin,52.52,13.405,extra") + assert "Location must be in the format NAME,LATITUDE,LONGITUDE" in str( + exc_info.value + ) + + +def test_parse_site_location_invalid_latitude(): + with pytest.raises(argparse.ArgumentTypeError) as exc_info: + parse_site_location("Berlin,invalid,13.405") + assert "Location must be in the format NAME,LATITUDE,LONGITUDE" in str( + exc_info.value + ) + + +def test_parse_site_location_invalid_longitude(): + with pytest.raises(argparse.ArgumentTypeError) as exc_info: + parse_site_location("Berlin,52.52,invalid") + assert "Location must be in the format NAME,LATITUDE,LONGITUDE" in str( + exc_info.value + ) + + +def test_parse_site_location_empty(): + with pytest.raises(argparse.ArgumentTypeError) as exc_info: + parse_site_location("") + assert "Location must be in the format NAME,LATITUDE,LONGITUDE" in str( + exc_info.value + ) diff --git a/tests/unit/test_standalone/test_mqtt_adapter.py b/tests/unit/test_standalone/test_mqtt_adapter.py index 53a8004..3edbfd1 100644 --- a/tests/unit/test_standalone/test_mqtt_adapter.py +++ b/tests/unit/test_standalone/test_mqtt_adapter.py @@ -50,6 +50,8 @@ async def test_publish_properties(): device = Device() mqtt_api_client = mock.AsyncMock(spec=enapter.mqtt.api.Client) device_channel = mock.AsyncMock(spec=enapter.mqtt.api.device.Channel) + event = asyncio.Event() + device_channel.publish_properties.side_effect = lambda *args, **kwargs: event.set() mqtt_api_client.device_channel.return_value = device_channel async with asyncio.TaskGroup() as tg: async with enapter.standalone.mqtt_adapter.MQTTAdapter( @@ -59,7 +61,7 @@ async def test_publish_properties(): device=device, task_group=tg, ): - await asyncio.sleep(0.02) + await asyncio.wait_for(event.wait(), timeout=1.0) device_channel.publish_properties.assert_called() last_call = device_channel.publish_properties.call_args published_properties = last_call.kwargs["properties"] @@ -71,6 +73,8 @@ async def test_publish_telemetry(): device = Device() mqtt_api_client = mock.AsyncMock(spec=enapter.mqtt.api.Client) device_channel = mock.AsyncMock(spec=enapter.mqtt.api.device.Channel) + event = asyncio.Event() + device_channel.publish_telemetry.side_effect = lambda *args, **kwargs: event.set() mqtt_api_client.device_channel.return_value = device_channel async with asyncio.TaskGroup() as tg: async with enapter.standalone.mqtt_adapter.MQTTAdapter( @@ -80,7 +84,7 @@ async def test_publish_telemetry(): device=device, task_group=tg, ): - await asyncio.sleep(0.02) + await asyncio.wait_for(event.wait(), timeout=1.0) device_channel.publish_telemetry.assert_called() last_call = device_channel.publish_telemetry.call_args published_telemetry = last_call.kwargs["telemetry"] @@ -94,6 +98,8 @@ async def test_publish_logs(log_severity, persist_logs) -> None: device = Device(log_severity=log_severity, persist_logs=persist_logs) mqtt_api_client = mock.AsyncMock(spec=enapter.mqtt.api.Client) device_channel = mock.AsyncMock(spec=enapter.mqtt.api.device.Channel) + event = asyncio.Event() + device_channel.publish_log.side_effect = lambda *args, **kwargs: event.set() mqtt_api_client.device_channel.return_value = device_channel async with asyncio.TaskGroup() as tg: async with enapter.standalone.mqtt_adapter.MQTTAdapter( @@ -103,7 +109,7 @@ async def test_publish_logs(log_severity, persist_logs) -> None: device=device, task_group=tg, ): - await asyncio.sleep(0.02) + await asyncio.wait_for(event.wait(), timeout=1.0) device_channel.publish_log.assert_called() last_call = device_channel.publish_log.call_args published_log = last_call.kwargs["log"] @@ -119,7 +125,13 @@ async def test_publish_properties_exception(): device = Device() mqtt_api_client = mock.AsyncMock(spec=enapter.mqtt.api.Client) device_channel = mock.AsyncMock(spec=enapter.mqtt.api.device.Channel) - device_channel.publish_properties.side_effect = RuntimeError("Publish error") + event = asyncio.Event() + + def publish_properties_mock(*args, **kwargs): + event.set() + raise RuntimeError("Publish error") + + device_channel.publish_properties.side_effect = publish_properties_mock mqtt_api_client.device_channel.return_value = device_channel async with asyncio.TaskGroup() as tg: async with enapter.standalone.mqtt_adapter.MQTTAdapter( @@ -129,7 +141,7 @@ async def test_publish_properties_exception(): device=device, task_group=tg, ): - await asyncio.sleep(0.02) + await asyncio.wait_for(event.wait(), timeout=1.0) device_channel.publish_properties.assert_called() @@ -137,7 +149,13 @@ async def test_publish_telemetry_exception(): device = Device() mqtt_api_client = mock.AsyncMock(spec=enapter.mqtt.api.Client) device_channel = mock.AsyncMock(spec=enapter.mqtt.api.device.Channel) - device_channel.publish_telemetry.side_effect = RuntimeError("Publish error") + event = asyncio.Event() + + def publish_telemetry_mock(*args, **kwargs): + event.set() + raise RuntimeError("Publish error") + + device_channel.publish_telemetry.side_effect = publish_telemetry_mock mqtt_api_client.device_channel.return_value = device_channel async with asyncio.TaskGroup() as tg: async with enapter.standalone.mqtt_adapter.MQTTAdapter( @@ -147,7 +165,7 @@ async def test_publish_telemetry_exception(): device=device, task_group=tg, ): - await asyncio.sleep(0.02) + await asyncio.wait_for(event.wait(), timeout=1.0) device_channel.publish_telemetry.assert_called() @@ -155,7 +173,13 @@ async def test_publish_logs_exception(): device = Device(log_severity="error") mqtt_api_client = mock.AsyncMock(spec=enapter.mqtt.api.Client) device_channel = mock.AsyncMock(spec=enapter.mqtt.api.device.Channel) - device_channel.publish_log.side_effect = RuntimeError("Publish error") + event = asyncio.Event() + + def publish_log_mock(*args, **kwargs): + event.set() + raise RuntimeError("Publish error") + + device_channel.publish_log.side_effect = publish_log_mock mqtt_api_client.device_channel.return_value = device_channel async with asyncio.TaskGroup() as tg: async with enapter.standalone.mqtt_adapter.MQTTAdapter( @@ -165,7 +189,7 @@ async def test_publish_logs_exception(): device=device, task_group=tg, ): - await asyncio.sleep(0.02) + await asyncio.wait_for(event.wait(), timeout=1.0) device_channel.publish_log.assert_called()