From b49cbe71e64c316f19fb10187987c833e6844978 Mon Sep 17 00:00:00 2001 From: santiagosalamandri Date: Wed, 19 Apr 2023 10:25:02 +0100 Subject: [PATCH] feat: add optional param for passing an unique_id to call method. (#438) * feat: add optional param for passing an unique_id to call method. * feat: add tests for call method using optional unique_id param * chore: remove duplicated * chore: remove duplicated * fix: asynmock for python3.7 --- ocpp/charge_point.py | 8 +++-- tests/v16/conftest.py | 40 +++++++++++++++++++-- tests/v16/test_v16_charge_point.py | 35 +++++++++++++++++++ tests/v20/conftest.py | 52 ++++++++++++++++++++++++---- tests/v20/test_v20_charge_point.py | 35 +++++++++++++++++++ tests/v201/conftest.py | 52 ++++++++++++++++++++++++---- tests/v201/test_v201_charge_point.py | 35 +++++++++++++++++++ 7 files changed, 239 insertions(+), 18 deletions(-) diff --git a/ocpp/charge_point.py b/ocpp/charge_point.py index 6e1f45d2d..bdd75ae6e 100644 --- a/ocpp/charge_point.py +++ b/ocpp/charge_point.py @@ -237,7 +237,7 @@ async def _handle_call(self, msg): # when no '_on_after' hook is installed. pass - async def call(self, payload, suppress=True): + async def call(self, payload, suppress=True, unique_id=None): """ Send Call message to client and return payload of response. @@ -261,8 +261,12 @@ async def call(self, payload, suppress=True): """ camel_case_payload = snake_to_camel_case(asdict(payload)) + unique_id = ( + unique_id if unique_id is not None else str(self._unique_id_generator()) + ) + call = Call( - unique_id=str(self._unique_id_generator()), + unique_id=unique_id, action=payload.__class__.__name__[:-7], payload=remove_nones(camel_case_payload), ) diff --git a/tests/v16/conftest.py b/tests/v16/conftest.py index 78a44a8d2..d747bf420 100644 --- a/tests/v16/conftest.py +++ b/tests/v16/conftest.py @@ -1,7 +1,14 @@ +try: + from unittest.mock import AsyncMock +except ImportError: + # Python 3.7 and below don't include unittest.mock.AsyncMock. Hence, + # we need to resolve to a package on pypi. + from asynctest import CoroutineMock as AsyncMock + import pytest -from ocpp.messages import Call -from ocpp.v16 import ChargePoint +from ocpp.messages import Call, CallResult +from ocpp.v16 import ChargePoint, call from ocpp.v16.enums import Action @@ -33,3 +40,32 @@ def base_central_system(connection): cs._unique_id_generator = lambda: 1337 return cs + + +@pytest.fixture +def mock_boot_request(): + return call.BootNotificationPayload( + charge_point_vendor="dummy_vendor", + charge_point_model="dummy_model", + ) + + +@pytest.fixture +def mock_base_central_system(base_central_system): + mock_result_call = CallResult( + unique_id=str(base_central_system._unique_id_generator()), + action="BootNotification", + payload={ + "currentTime": "2018-05-29T17:37:05.495259", + "interval": 350, + "status": "Accepted", + }, + ) + + base_central_system._send = AsyncMock() + + mock_response = AsyncMock() + mock_response.return_value = mock_result_call + base_central_system._get_specific_response = mock_response + + return base_central_system diff --git a/tests/v16/test_v16_charge_point.py b/tests/v16/test_v16_charge_point.py index 89ef097cd..5471e1618 100644 --- a/tests/v16/test_v16_charge_point.py +++ b/tests/v16/test_v16_charge_point.py @@ -191,3 +191,38 @@ async def test_suppress_call_error(base_central_system): payload = call.ClearCachePayload() await base_central_system.call(payload) + + +@pytest.mark.asyncio +async def test_call_with_unique_id_should_return_same_id( + mock_boot_request, mock_base_central_system +): + + expected_unique_id = "12345" + # Call the method being tested with a unique_id as a parameter + await mock_base_central_system.call(mock_boot_request, unique_id=expected_unique_id) + ( + actual_unique_id, + _, + ) = mock_base_central_system._get_specific_response.call_args_list[0][0] + + # Check the actual unique id is equals to the one passed to the call method + assert actual_unique_id == expected_unique_id + + +@pytest.mark.asyncio +async def test_call_without_unique_id_should_return_a_random_value( + mock_boot_request, mock_base_central_system +): + + expected_unique_id = str(mock_base_central_system._unique_id_generator()) + + # Call the method being tested without passing a unique_id as a parameter + await mock_base_central_system.call(mock_boot_request) + + ( + actual_unique_id, + _, + ) = mock_base_central_system._get_specific_response.call_args_list[0][0] + # Check the actual unique id is equals to the one internally generated + assert actual_unique_id == expected_unique_id diff --git a/tests/v20/conftest.py b/tests/v20/conftest.py index 8f6ecdcbf..927faef17 100644 --- a/tests/v20/conftest.py +++ b/tests/v20/conftest.py @@ -1,7 +1,20 @@ +try: + from unittest.mock import AsyncMock +except ImportError: + # Python 3.7 and below don't include unittest.mock.AsyncMock. Hence, + # we need to resolve to a package on pypi. + from asynctest import CoroutineMock as AsyncMock + import pytest -from ocpp.messages import Call -from ocpp.v20 import ChargePoint +from ocpp.messages import Call, CallResult +from ocpp.v20 import ChargePoint, call + +chargingStation = { + "vendorName": "ICU Eve Mini", + "firmwareVersion": "#1:3.4.0-2990#N:217H;1.0-223", + "model": "ICU Eve Mini", +} @pytest.fixture @@ -16,11 +29,7 @@ def boot_notification_call(): action="BootNotification", payload={ "reason": "PowerUp", - "chargingStation": { - "vendorName": "ICU Eve Mini", - "firmwareVersion": "#1:3.4.0-2990#N:217H;1.0-223", - "model": "ICU Eve Mini", - }, + "chargingStation": chargingStation, }, ).to_json() @@ -35,3 +44,32 @@ def base_central_system(connection): cs._unique_id_generator = lambda: 1337 return cs + + +@pytest.fixture +def mock_boot_request(): + return call.BootNotificationPayload( + reason="PowerUp", + charging_station=chargingStation, + ) + + +@pytest.fixture +def mock_base_central_system(base_central_system): + mock_result_call = CallResult( + unique_id=str(base_central_system._unique_id_generator()), + action="BootNotification", + payload={ + "currentTime": "2018-05-29T17:37:05.495259", + "interval": 350, + "status": "Accepted", + }, + ) + + base_central_system._send = AsyncMock() + + mock_response = AsyncMock() + mock_response.return_value = mock_result_call + base_central_system._get_specific_response = mock_response + + return base_central_system diff --git a/tests/v20/test_v20_charge_point.py b/tests/v20/test_v20_charge_point.py index f73f0aa41..9226b8ba4 100644 --- a/tests/v20/test_v20_charge_point.py +++ b/tests/v20/test_v20_charge_point.py @@ -83,3 +83,38 @@ async def test_route_message_with_no_route(base_central_system, heartbeat_call): separators=(",", ":"), ) ) + + +@pytest.mark.asyncio +async def test_call_with_unique_id_should_return_same_id( + mock_boot_request, mock_base_central_system +): + + expected_unique_id = "12345" + # Call the method being tested with a unique_id as a parameter + await mock_base_central_system.call(mock_boot_request, unique_id=expected_unique_id) + ( + actual_unique_id, + _, + ) = mock_base_central_system._get_specific_response.call_args_list[0][0] + + # Check the actual unique id is equals to the one passed to the call method + assert actual_unique_id == expected_unique_id + + +@pytest.mark.asyncio +async def test_call_without_unique_id_should_return_a_random_value( + mock_boot_request, mock_base_central_system +): + + expected_unique_id = str(mock_base_central_system._unique_id_generator()) + + # Call the method being tested without passing a unique_id as a parameter + await mock_base_central_system.call(mock_boot_request) + + ( + actual_unique_id, + _, + ) = mock_base_central_system._get_specific_response.call_args_list[0][0] + # Check the actual unique id is equals to the one internally generated + assert actual_unique_id == expected_unique_id diff --git a/tests/v201/conftest.py b/tests/v201/conftest.py index 11786afd9..ca71fc5b3 100644 --- a/tests/v201/conftest.py +++ b/tests/v201/conftest.py @@ -1,7 +1,20 @@ +try: + from unittest.mock import AsyncMock +except ImportError: + # Python 3.7 and below don't include unittest.mock.AsyncMock. Hence, + # we need to resolve to a package on pypi. + from asynctest import CoroutineMock as AsyncMock + import pytest -from ocpp.messages import Call -from ocpp.v201 import ChargePoint +from ocpp.messages import Call, CallResult +from ocpp.v201 import ChargePoint, call + +chargingStation = { + "vendorName": "ICU Eve Mini", + "firmwareVersion": "#1:3.4.0-2990#N:217H;1.0-223", + "model": "ICU Eve Mini", +} @pytest.fixture @@ -16,11 +29,7 @@ def boot_notification_call(): action="BootNotification", payload={ "reason": "PowerUp", - "chargingStation": { - "vendorName": "ICU Eve Mini", - "firmwareVersion": "#1:3.4.0-2990#N:217H;1.0-223", - "model": "ICU Eve Mini", - }, + "chargingStation": chargingStation, }, ).to_json() @@ -35,3 +44,32 @@ def base_central_system(connection): cs._unique_id_generator = lambda: 1337 return cs + + +@pytest.fixture +def mock_boot_request(): + return call.BootNotificationPayload( + reason="PowerUp", + charging_station=chargingStation, + ) + + +@pytest.fixture +def mock_base_central_system(base_central_system): + mock_result_call = CallResult( + unique_id=str(base_central_system._unique_id_generator()), + action="BootNotification", + payload={ + "currentTime": "2018-05-29T17:37:05.495259", + "interval": 350, + "status": "Accepted", + }, + ) + + base_central_system._send = AsyncMock() + + mock_response = AsyncMock() + mock_response.return_value = mock_result_call + base_central_system._get_specific_response = mock_response + + return base_central_system diff --git a/tests/v201/test_v201_charge_point.py b/tests/v201/test_v201_charge_point.py index f910c85cf..8624c90ca 100644 --- a/tests/v201/test_v201_charge_point.py +++ b/tests/v201/test_v201_charge_point.py @@ -83,3 +83,38 @@ async def test_route_message_with_no_route(base_central_system, heartbeat_call): separators=(",", ":"), ) ) + + +@pytest.mark.asyncio +async def test_call_with_unique_id_should_return_same_id( + mock_boot_request, mock_base_central_system +): + + expected_unique_id = "12345" + # Call the method being tested with a unique_id as a parameter + await mock_base_central_system.call(mock_boot_request, unique_id=expected_unique_id) + ( + actual_unique_id, + _, + ) = mock_base_central_system._get_specific_response.call_args_list[0][0] + + # Check the actual unique id is equals to the one passed to the call method + assert actual_unique_id == expected_unique_id + + +@pytest.mark.asyncio +async def test_call_without_unique_id_should_return_a_random_value( + mock_boot_request, mock_base_central_system +): + + expected_unique_id = str(mock_base_central_system._unique_id_generator()) + + # Call the method being tested without passing a unique_id as a parameter + await mock_base_central_system.call(mock_boot_request) + + ( + actual_unique_id, + _, + ) = mock_base_central_system._get_specific_response.call_args_list[0][0] + # Check the actual unique id is equals to the one internally generated + assert actual_unique_id == expected_unique_id