From 4ffcd1d2341ba3cade99ca68da2686537f08e295 Mon Sep 17 00:00:00 2001 From: hobbe Date: Mon, 23 Mar 2020 10:58:23 +0100 Subject: [PATCH 01/19] initialize pytest framework --- Makefile | 6 ++---- tests/__init__.py | 7 +++++++ tests/test_sample.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/test_sample.py diff --git a/Makefile b/Makefile index 7f823c05..5ea994c6 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,7 @@ black: pipenv run black teslajsonpy coverage: - #Not implemented yet - #pipenv run py.test -s --verbose --cov-report term-missing --cov-report xml --cov=teslajsonpy tests + pipenv run pytest -s --verbose --cov-report term-missing --cov-report xml --cov=teslajsonpy tests clean: rm -rf dist/ build/ .egg teslajsonpy.egg-info/ init: @@ -23,7 +22,6 @@ publish: pipenv run twine upload dist/* rm -rf dist/ build/ .egg teslajsonpy.egg-info/ test: - #Not implemented yet - #pipenv run py.test + pipenv run pytest typing: pipenv run mypy --ignore-missing-imports teslajsonpy diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..2a978c16 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 +""" +Python Package for controlling Tesla API. + +For more details about this api, please refer to the documentation at +https://github.com/zabuldon/teslajsonpy +""" diff --git a/tests/test_sample.py b/tests/test_sample.py new file mode 100644 index 00000000..8b79b3df --- /dev/null +++ b/tests/test_sample.py @@ -0,0 +1,30 @@ +"""Sample tests.""" + +import pytest + + +def system_exit(): + """Raise system exit.""" + raise SystemExit(1) + + +def add_one(x_value): + """Add 1 to x_value. + Args: + x_value (float): A number. + """ + return x_value + 1 + + +def test_system_exit(): + """Test system_exit.""" + with pytest.raises(SystemExit): + system_exit() + + +def test_add_one(): + """Test add_one.""" + assert add_one(1) == 2 + assert add_one(2) == 3 + assert add_one(3) == 4 + assert add_one(4) == 5 From e34ff06cb41e6b50c39c1d2ca3de2ba44ff25477 Mon Sep 17 00:00:00 2001 From: hobbe Date: Mon, 23 Mar 2020 16:31:55 +0100 Subject: [PATCH 02/19] Fix initialization of _sentry_mode_available flag --- teslajsonpy/vehicle.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/teslajsonpy/vehicle.py b/teslajsonpy/vehicle.py index a593bf76..f7cf6d13 100644 --- a/teslajsonpy/vehicle.py +++ b/teslajsonpy/vehicle.py @@ -40,7 +40,11 @@ def __init__(self, data, controller): self._state = data["state"] self._car_type = f"Model {str(self._vin[3]).upper()}" self._car_version = "" - self._sentry_mode_available = False + self._sentry_mode_available = ( + "vehicle_state" in data + and "sentry_mode_available" in data["vehicle_state"] + and data["vehicle_state"]["sentry_mode_available"] + ) self._controller = controller self.should_poll = True self.type = "device" From 2952f877624b85d44cb5eb684e4a5236b50bcbbe Mon Sep 17 00:00:00 2001 From: hobbe Date: Mon, 23 Mar 2020 16:33:27 +0100 Subject: [PATCH 03/19] fix initialization of sentry_mode flag remove sentry_mode_available flag to use the property from vehicle --- teslajsonpy/sentry_mode.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/teslajsonpy/sentry_mode.py b/teslajsonpy/sentry_mode.py index 0997e3ff..54d46e34 100644 --- a/teslajsonpy/sentry_mode.py +++ b/teslajsonpy/sentry_mode.py @@ -31,12 +31,16 @@ def __init__(self, data, controller): """ super().__init__(data, controller) self.__manual_update_time = 0 - self.__sentry_mode_available = False - self.__sentry_mode = False self.type = "sentry mode switch" self.hass_type = "switch" self.name = self._name() self.uniq_name = self._uniq_name() + self.__sentry_mode = ( + self.sentry_mode_available + and "vehicle_state" in data + and "sentry_mode" in data["vehicle_state"] + and data["vehicle_state"]["sentry_mode"] + ) async def async_update(self, wake_if_asleep=False): """Update the sentry mode of the vehicle.""" @@ -44,21 +48,18 @@ async def async_update(self, wake_if_asleep=False): last_update = self._controller.get_last_update_time(self._id) if last_update >= self.__manual_update_time: data = self._controller.get_state_params(self._id) - if data and "sentry_mode_available" in data: - self.__sentry_mode_available = data["sentry_mode_available"] - if self.__sentry_mode_available and "sentry_mode" in data: - self.__sentry_mode = data["sentry_mode"] + if self.sentry_mode_available and "sentry_mode" in data: + self.__sentry_mode = data["sentry_mode"] else: - self.__sentry_mode_available = False self.__sentry_mode = False def available(self): """Return whether the sentry mode is available.""" - return self.__sentry_mode_available + return self.sentry_mode_available def is_on(self): """Return whether the sentry mode is enabled, always False if sentry mode is not available.""" - return self.__sentry_mode_available and self.__sentry_mode + return self.sentry_mode_available and self.__sentry_mode @staticmethod def has_battery(): @@ -67,7 +68,7 @@ def has_battery(): async def enable_sentry_mode(self): """Enable the sentry mode.""" - if self.__sentry_mode_available and not self.__sentry_mode: + if self.sentry_mode_available and not self.__sentry_mode: data = await self._controller.command( self._id, "set_sentry_mode", {"on": True}, wake_if_asleep=True ) @@ -77,7 +78,7 @@ async def enable_sentry_mode(self): async def disable_sentry_mode(self): """Disable the sentry mode.""" - if self.__sentry_mode_available and self.__sentry_mode: + if self.sentry_mode_available and self.__sentry_mode: data = await self._controller.command( self._id, "set_sentry_mode", {"on": False}, wake_if_asleep=True ) From f819f9738717e95091772900a46175f558036506 Mon Sep 17 00:00:00 2001 From: hobbe Date: Mon, 23 Mar 2020 16:34:28 +0100 Subject: [PATCH 04/19] add tests for sentry mode add a mock for Tesla server communications --- Pipfile | 1 + tests/tesla_mock.py | 283 +++++++++++++++++++++++++++ tests/test_sample.py | 30 --- tests/unit_tests/__init__.py | 7 + tests/unit_tests/test_sentry_mode.py | 86 ++++++++ 5 files changed, 377 insertions(+), 30 deletions(-) create mode 100644 tests/tesla_mock.py delete mode 100644 tests/test_sample.py create mode 100644 tests/unit_tests/__init__.py create mode 100644 tests/unit_tests/test_sentry_mode.py diff --git a/Pipfile b/Pipfile index fab00cfd..b7d5bc2b 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,7 @@ detox = "*" mypy = "*" pydocstyle = "*" pylint = "*" +pytest = "*" pytest-cov = "*" tox = "*" twine = "*" diff --git a/tests/tesla_mock.py b/tests/tesla_mock.py new file mode 100644 index 00000000..ded0526e --- /dev/null +++ b/tests/tesla_mock.py @@ -0,0 +1,283 @@ +""" +Tesla mock. +""" + +from teslajsonpy.connection import Connection +from teslajsonpy.controller import Controller + + +class TeslaMock: + """ + Tesla mock. + """ + + def __init__(self, monkeypatch) -> None: + """ + Initialize mock. + + Args: + monkeypatch (pytest.Monkeypatch): Monkeypatch. + """ + self._monkeypatch = monkeypatch + self._monkeypatch.setattr(Controller, "connect", self.mock_connect) + self._monkeypatch.setattr( + Connection, "generate_oauth", self.mock_generate_oauth + ) + + def mock_connect(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's connect method.""" + return self.controller_connect() + + def mock_generate_oauth(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock connection's generate_oauth method.""" + return self.connexion_generate_oauth() + + @staticmethod + def controller_connect(): + """ Monkeypatch for controller.connect().""" + return ("abc123", "cba321") + + @staticmethod + def connexion_generate_oauth(): + """ Monkeypatch for connexion.generate_oauth().""" + return + + @staticmethod + def data_request_vehicle(): + """ Simulates the result of vehicle data request. """ + return VEHICLE + + @staticmethod + def data_request_charge_state(): + """ Simulates the result of charge state data request. """ + return CHARGE_STATE + + @staticmethod + def data_request_climate_state(): + """ Simulates the result of climate state data request. """ + return CLIMATE_STATE + + @staticmethod + def data_request_vehicle_state(): + """ Simulates the result of vehicle state data request. """ + return VEHICLE_STATE + + @staticmethod + def command_ok(): + """ Simulates an OK result for a command. """ + return {"reason": "", "result": True} + + +DRIVE_STATE = { + "gps_as_of": 1538363883, + "heading": 5, + "latitude": 33.111111, + "longitude": -88.111111, + "native_latitude": 33.111111, + "native_location_supported": 1, + "native_longitude": -88.111111, + "native_type": "wgs", + "power": 0, + "shift_state": None, + "speed": None, + "timestamp": 1538364666096, +} + +CLIMATE_STATE = { + "battery_heater": False, + "battery_heater_no_power": False, + "climate_keeper_mode": "dog", + "defrost_mode": 0, + "driver_temp_setting": 21.6, + "fan_status": 0, + "inside_temp": None, + "is_auto_conditioning_on": None, + "is_climate_on": False, + "is_front_defroster_on": False, + "is_preconditioning": False, + "is_rear_defroster_on": False, + "left_temp_direction": None, + "max_avail_temp": 28.0, + "min_avail_temp": 15.0, + "outside_temp": None, + "passenger_temp_setting": 21.6, + "remote_heater_control_enabled": True, + "right_temp_direction": None, + "seat_heater_left": 3, + "seat_heater_rear_center": 0, + "seat_heater_rear_left": 1, + "seat_heater_rear_left_back": 0, + "seat_heater_rear_right": 1, + "seat_heater_rear_right_back": 0, + "seat_heater_right": 2, + "side_mirror_heaters": False, + "steering_wheel_heater": False, + "timestamp": 1543186971731, + "wiper_blade_heater": False, +} + +CHARGE_STATE = { + "battery_heater_on": False, + "battery_level": 64, + "battery_range": 167.96, + "charge_current_request": 48, + "charge_current_request_max": 48, + "charge_enable_request": True, + "charge_energy_added": 12.41, + "charge_limit_soc": 90, + "charge_limit_soc_max": 100, + "charge_limit_soc_min": 50, + "charge_limit_soc_std": 90, + "charge_miles_added_ideal": 50.0, + "charge_miles_added_rated": 40.0, + "charge_port_cold_weather_mode": False, + "charge_port_door_open": False, + "charge_port_latch": "Engaged", + "charge_rate": 0.0, + "charge_to_max_range": False, + "charger_actual_current": 0, + "charger_phases": None, + "charger_pilot_current": 48, + "charger_power": 0, + "charger_voltage": 0, + "charging_state": "Disconnected", + "conn_charge_cable": "", + "est_battery_range": 118.38, + "fast_charger_brand": "", + "fast_charger_present": False, + "fast_charger_type": "", + "ideal_battery_range": 209.95, + "managed_charging_active": False, + "managed_charging_start_time": None, + "managed_charging_user_canceled": False, + "max_range_charge_counter": 0, + "minutes_to_full_charge": 0, + "not_enough_power_to_heat": False, + "scheduled_charging_pending": False, + "scheduled_charging_start_time": None, + "time_to_full_charge": 0.0, + "timestamp": 1543186971727, + "trip_charging": False, + "usable_battery_level": 64, + "user_charge_enable_request": None, +} + +GUI_SETTINGS = { + "gui_24_hour_time": False, + "gui_charge_rate_units": "mi/hr", + "gui_distance_units": "mi/hr", + "gui_range_display": "Rated", + "gui_temperature_units": "F", + "show_range_units": True, + "timestamp": 1543186971728, +} + +VEHICLE_STATE = { + "api_version": 7, + "autopark_state_v2": "standby", + "autopark_style": "standard", + "calendar_supported": True, + "car_version": "2019.40.2.1 38f55d9f9205", + "center_display_state": 0, + "df": 0, + "dr": 0, + "fd_window": 0, + "fp_window": 0, + "ft": 0, + "homelink_device_count": 0, + "homelink_nearby": True, + "is_user_present": False, + "last_autopark_error": "no_error", + "locked": True, + "media_state": {"remote_control_enabled": True}, + "notifications_supported": True, + "odometer": 33561.422505, + "parsed_calendar_supported": True, + "pf": 0, + "pr": 0, + "rd_window": 0, + "remote_start": False, + "remote_start_enabled": True, + "remote_start_supported": True, + "rp_window": 0, + "rt": 0, + "sentry_mode": True, + "sentry_mode_available": True, + "smart_summon_available": True, + "software_update": { + "download_perc": 100, + "expected_duration_sec": 2700, + "install_perc": 10, + "scheduled_time_ms": 1575689678432, + "status": "scheduled", + "version": "2019.40.2.1", + }, + "speed_limit_mode": { + "active": False, + "current_limit_mph": 75.0, + "max_limit_mph": 90, + "min_limit_mph": 50, + "pin_code_set": False, + }, + "summon_standby_mode_enabled": True, + "sun_roof_percent_open": 0, + "sun_roof_state": "unknown", + "timestamp": 1538364666096, + "valet_mode": False, + "valet_pin_needed": True, + "vehicle_name": "Nikola 2.0", +} + +VEHICLE_CONFIG = { + "can_accept_navigation_requests": True, + "can_actuate_trunks": True, + "car_special_type": "base", + "car_type": "models2", + "charge_port_type": "US", + "eu_vehicle": False, + "exterior_color": "White", + "has_air_suspension": True, + "has_ludicrous_mode": False, + "key_version": 1, + "motorized_charge_port": True, + "perf_config": "P2", + "plg": True, + "rear_seat_heaters": 0, + "rear_seat_type": 0, + "rhd": False, + "roof_color": "None", + "seat_type": 2, + "spoiler_type": "None", + "sun_roof_installed": 2, + "third_row_seats": "None", + "timestamp": 1538364666096, + "trim_badging": "p90d", + "use_range_badging": False, + "wheel_type": "AeroTurbine19", +} + +VEHICLE = { + "id": 12345678901234567, + "user_id": 123, + "vehicle_id": 1234567890, + "vin": "5YJSA11111111111", + "display_name": "Nikola 2.0", + "option_codes": "MDLS,RENA,AF02,APF1,APH2,APPB,AU01,BC0R,BP00,BR00,BS00,CDM0,CH05,PBCW,CW00,DCF0,DRLH,DSH7,DV4W,FG02,FR04,HP00,IDBA,IX01,LP01,ME02,MI01,PF01,PI01,PK00,PS01,PX00,PX4D,QTVB,RFP2,SC01,SP00,SR01,SU01,TM00,TP03,TR00,UTAB,WTAS,X001,X003,X007,X011,X013,X021,X024,X027,X028,X031,X037,X040,X044,YFFC,COUS", + "color": None, + "tokens": ["abcdef1234567890", "1234567890abcdef"], + "state": "online", + "in_service": False, + "id_s": "12345678901234567", + "calendar_enabled": True, + "api_version": 7, + "backseat_token": None, + "backseat_token_updated_at": None, + "drive_state": DRIVE_STATE, + "climate_state": CLIMATE_STATE, + "charge_state": CHARGE_STATE, + "gui_settings": GUI_SETTINGS, + "vehicle_state": VEHICLE_STATE, + "vehicle_config": VEHICLE_CONFIG, +} diff --git a/tests/test_sample.py b/tests/test_sample.py deleted file mode 100644 index 8b79b3df..00000000 --- a/tests/test_sample.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Sample tests.""" - -import pytest - - -def system_exit(): - """Raise system exit.""" - raise SystemExit(1) - - -def add_one(x_value): - """Add 1 to x_value. - Args: - x_value (float): A number. - """ - return x_value + 1 - - -def test_system_exit(): - """Test system_exit.""" - with pytest.raises(SystemExit): - system_exit() - - -def test_add_one(): - """Test add_one.""" - assert add_one(1) == 2 - assert add_one(2) == 3 - assert add_one(3) == 4 - assert add_one(4) == 5 diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py new file mode 100644 index 00000000..2a978c16 --- /dev/null +++ b/tests/unit_tests/__init__.py @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 +""" +Python Package for controlling Tesla API. + +For more details about this api, please refer to the documentation at +https://github.com/zabuldon/teslajsonpy +""" diff --git a/tests/unit_tests/test_sentry_mode.py b/tests/unit_tests/test_sentry_mode.py new file mode 100644 index 00000000..f5db671b --- /dev/null +++ b/tests/unit_tests/test_sentry_mode.py @@ -0,0 +1,86 @@ +"""Test sentry mode switch.""" + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.sentry_mode import SentryModeSwitch + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _switch = SentryModeSwitch(_data, _controller) + + assert not _switch.has_battery() + + +def test_available_true(monkeypatch): + """Test available() when flag sentry_mode_available is false.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _switch = SentryModeSwitch(_data, _controller) + + assert _switch.available() + + +def test_available_false(monkeypatch): + """Test available() when flag sentry_mode_available is false.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = False + _switch = SentryModeSwitch(_data, _controller) + + assert not _switch.available() + + +def test_is_on_false(monkeypatch): + """Test is_on() when flag sentry_mode is false.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + assert not _switch.is_on() + + +def test_is_on_true(monkeypatch): + """Test is_on() when flag sentry_mode is true.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = True + _switch = SentryModeSwitch(_data, _controller) + + assert _switch.is_on() + + +def test_is_on_unavailable(monkeypatch): + """Test is_on() when flag sentry_mode_available is false.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = False + _data["vehicle_state"]["sentry_mode"] = True + _switch = SentryModeSwitch(_data, _controller) + + assert not _switch.is_on() From ca2c10a43cd05671e768a67ac3bb4ec44087095a Mon Sep 17 00:00:00 2001 From: hobbe Date: Mon, 23 Mar 2020 16:36:54 +0100 Subject: [PATCH 05/19] add .vscode to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 709659d8..86feab15 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,6 @@ venv.bak/ # macOS files ._* .DS_Store + +# VS Code +.vscode/ From e293ce95fa9212136e69fd142bee3a3ac646b227 Mon Sep 17 00:00:00 2001 From: hobbe Date: Mon, 23 Mar 2020 18:54:37 +0100 Subject: [PATCH 06/19] add async tests for sentry mode switch --- Pipfile | 1 + tests/tesla_mock.py | 53 +++++++++- tests/unit_tests/test_sentry_mode.py | 152 +++++++++++++++++++++++++++ tox.ini | 3 + 4 files changed, 205 insertions(+), 4 deletions(-) diff --git a/Pipfile b/Pipfile index b7d5bc2b..1e25035d 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ mypy = "*" pydocstyle = "*" pylint = "*" pytest = "*" +pytest-asyncio = "*" pytest-cov = "*" tox = "*" twine = "*" diff --git a/tests/tesla_mock.py b/tests/tesla_mock.py index ded0526e..5f92c9c0 100644 --- a/tests/tesla_mock.py +++ b/tests/tesla_mock.py @@ -20,6 +20,14 @@ def __init__(self, monkeypatch) -> None: """ self._monkeypatch = monkeypatch self._monkeypatch.setattr(Controller, "connect", self.mock_connect) + self._monkeypatch.setattr(Controller, "command", self.mock_command) + self._monkeypatch.setattr( + Controller, "get_state_params", self.mock_get_state_params + ) + self._monkeypatch.setattr( + Controller, "get_last_update_time", self.mock_get_last_update_time + ) + self._monkeypatch.setattr(Controller, "update", self.mock_update) self._monkeypatch.setattr( Connection, "generate_oauth", self.mock_generate_oauth ) @@ -29,10 +37,30 @@ def mock_connect(self, *args, **kwargs): """ Mock controller's connect method.""" return self.controller_connect() + def mock_command(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's command method.""" + return self.controller_command() + + def mock_get_state_params(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's get_state_params method.""" + return self.controller_get_state_params() + + def mock_get_last_update_time(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's get_last_update_time method.""" + return 123 + + def mock_update(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's update method.""" + return self.controller_update() + def mock_generate_oauth(self, *args, **kwargs): # pylint: disable=unused-argument """ Mock connection's generate_oauth method.""" - return self.connexion_generate_oauth() + return self.connection_generate_oauth() @staticmethod def controller_connect(): @@ -40,8 +68,23 @@ def controller_connect(): return ("abc123", "cba321") @staticmethod - def connexion_generate_oauth(): - """ Monkeypatch for connexion.generate_oauth().""" + async def controller_command(): + """ Monkeypatch for controller.command().""" + return RESULT_OK + + @staticmethod + def controller_get_state_params(): + """ Monkeypatch for controller.get_state_params().""" + return VEHICLE_STATE + + @staticmethod + async def controller_update(): + """ Monkeypatch for controller.update().""" + return 123 + + @staticmethod + def connection_generate_oauth(): + """ Monkeypatch for connection.generate_oauth().""" return @staticmethod @@ -67,8 +110,10 @@ def data_request_vehicle_state(): @staticmethod def command_ok(): """ Simulates an OK result for a command. """ - return {"reason": "", "result": True} + return RESULT_OK + +RESULT_OK = {"response": {"reason": "", "result": True}} DRIVE_STATE = { "gps_as_of": 1538363883, diff --git a/tests/unit_tests/test_sentry_mode.py b/tests/unit_tests/test_sentry_mode.py index f5db671b..7c6c0879 100644 --- a/tests/unit_tests/test_sentry_mode.py +++ b/tests/unit_tests/test_sentry_mode.py @@ -1,5 +1,7 @@ """Test sentry mode switch.""" +import pytest + from tests.tesla_mock import TeslaMock from teslajsonpy.controller import Controller @@ -84,3 +86,153 @@ def test_is_on_unavailable(monkeypatch): _switch = SentryModeSwitch(_data, _controller) assert not _switch.is_on() + + +@pytest.mark.asyncio +async def test_enable_sentry_mode(monkeypatch): + """Test enable_sentry_mode().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + await _switch.enable_sentry_mode() + assert _switch.is_on() + + +@pytest.mark.asyncio +async def test_enable_sentry_mode_already_enabled(monkeypatch): + """Test enable_sentry_mode() when already enabled.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = True + _switch = SentryModeSwitch(_data, _controller) + + await _switch.enable_sentry_mode() + assert _switch.is_on() + + +@pytest.mark.asyncio +async def test_enable_sentry_mode_not_available(monkeypatch): + """Test enable_sentry_mode() when sentry mode is not available.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = False + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + await _switch.enable_sentry_mode() + assert not _switch.is_on() + + +@pytest.mark.asyncio +async def test_disable_sentry_mode(monkeypatch): + """Test disable_sentry_mode().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = True + _switch = SentryModeSwitch(_data, _controller) + + await _switch.disable_sentry_mode() + assert not _switch.is_on() + + +@pytest.mark.asyncio +async def test_disable_sentry_mode_already_disabled(monkeypatch): + """Test disable_sentry_mode() when already disabled.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + await _switch.disable_sentry_mode() + assert not _switch.is_on() + + +@pytest.mark.asyncio +async def test_disable_sentry_mode_not_available(monkeypatch): + """Test disable_sentry_mode() when sentry mode is not available.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = False + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + await _switch.enable_sentry_mode() + assert not _switch.is_on() + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + await _switch.async_update() + assert not _switch.is_on() + + +@pytest.mark.asyncio +async def test_async_update_with_change(monkeypatch): + """Test async_update() with a state change.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + # Change state value + _data["vehicle_state"]["sentry_mode"] = True + + await _switch.async_update() + assert _switch.is_on() + + +@pytest.mark.asyncio +async def test_async_update_with_change_but_not_available(monkeypatch): + """Test async_update() with a state change, but sentry mode is not available.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = False + _data["vehicle_state"]["sentry_mode"] = False + _switch = SentryModeSwitch(_data, _controller) + + # Change state value + _data["vehicle_state"]["sentry_mode"] = True + + await _switch.async_update() + assert not _switch.is_on() diff --git a/tox.ini b/tox.ini index 829e62d2..63d0b363 100644 --- a/tox.ini +++ b/tox.ini @@ -29,3 +29,6 @@ deps = pipenv commands= make init make typing + +[pytest] +junit_family=xunit1 From 7de9ccc5392f5df011ab9d08d260e07ec954d314 Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 12:13:18 +0100 Subject: [PATCH 07/19] add unit tests for gps and odometer --- tests/tesla_mock.py | 20 ++++++++ tests/unit_tests/test_gps.py | 83 +++++++++++++++++++++++++++++++ tests/unit_tests/test_odometer.py | 67 +++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 tests/unit_tests/test_gps.py create mode 100644 tests/unit_tests/test_odometer.py diff --git a/tests/tesla_mock.py b/tests/tesla_mock.py index 5f92c9c0..2121dd13 100644 --- a/tests/tesla_mock.py +++ b/tests/tesla_mock.py @@ -21,6 +21,9 @@ def __init__(self, monkeypatch) -> None: self._monkeypatch = monkeypatch self._monkeypatch.setattr(Controller, "connect", self.mock_connect) self._monkeypatch.setattr(Controller, "command", self.mock_command) + self._monkeypatch.setattr( + Controller, "get_drive_params", self.mock_get_drive_params + ) self._monkeypatch.setattr( Controller, "get_state_params", self.mock_get_state_params ) @@ -42,6 +45,11 @@ def mock_command(self, *args, **kwargs): """ Mock controller's command method.""" return self.controller_command() + def mock_get_drive_params(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's get_drive_params method.""" + return self.controller_get_drive_params() + def mock_get_state_params(self, *args, **kwargs): # pylint: disable=unused-argument """ Mock controller's get_state_params method.""" @@ -72,6 +80,11 @@ async def controller_command(): """ Monkeypatch for controller.command().""" return RESULT_OK + @staticmethod + def controller_get_drive_params(): + """ Monkeypatch for controller.get_drive_params().""" + return DRIVE_STATE + @staticmethod def controller_get_state_params(): """ Monkeypatch for controller.get_state_params().""" @@ -115,6 +128,13 @@ def command_ok(): RESULT_OK = {"response": {"reason": "", "result": True}} +# 408 - Request Timeout +RESULT_VEHICLE_UNAVAILABLE = { + "response": None, + "error": 'vehicle unavailable: {:error=>"vehicle unavailable:"}', + "error_description": "", +} + DRIVE_STATE = { "gps_as_of": 1538363883, "heading": 5, diff --git a/tests/unit_tests/test_gps.py b/tests/unit_tests/test_gps.py new file mode 100644 index 00000000..42166a3f --- /dev/null +++ b/tests/unit_tests/test_gps.py @@ -0,0 +1,83 @@ +"""Test GPS.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.gps import GPS + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _gps = GPS(_data, _controller) + + assert not _gps.has_battery() + + +def test_get_location_on_init(monkeypatch): + """Test get_location() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _gps = GPS(_data, _controller) + + _location = _gps.get_location() + assert not _location is None + assert not "longitude" in _location + assert not "latitude" in _location + assert not "heading" in _location + assert not "speed" in _location + + +@pytest.mark.asyncio +async def test_get_location_after_update(monkeypatch): + """Test get_location() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _gps = GPS(_data, _controller) + + await _gps.async_update() + _location = _gps.get_location() + + assert not _location is None + assert _location["longitude"] == -88.111111 + assert _location["latitude"] == 33.111111 + assert _location["heading"] == 5 + assert _location["speed"] == 0 + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["drive_state"]["longitude"] = 12.345 + _data["drive_state"]["native_longitude"] = 12.345 + _data["drive_state"]["latitude"] = 34.567 + _data["drive_state"]["native_latitude"] = 34.567 + _data["drive_state"]["heading"] = 12 + _data["drive_state"]["speed"] = 23.4 + _gps = GPS(_data, _controller) + + await _gps.async_update() + _location = _gps.get_location() + + assert not _location is None + assert _location["longitude"] == 12.345 + assert _location["latitude"] == 34.567 + assert _location["heading"] == 12 + assert _location["speed"] == 23.4 diff --git a/tests/unit_tests/test_odometer.py b/tests/unit_tests/test_odometer.py new file mode 100644 index 00000000..ff8a0cb7 --- /dev/null +++ b/tests/unit_tests/test_odometer.py @@ -0,0 +1,67 @@ +"""Test GPS.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.gps import Odometer + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _odo = Odometer(_data, _controller) + + assert not _odo.has_battery() + + +def test_get_value_on_init(monkeypatch): + """Test get_value() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _odo = Odometer(_data, _controller) + + assert not _odo is None + assert _odo.get_value() is None + + +@pytest.mark.asyncio +async def test_get_value_after_update(monkeypatch): + """Test get_value() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _odo = Odometer(_data, _controller) + + await _odo.async_update() + + assert not _odo is None + assert _odo.get_value() == 33561.4 + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["odometer"] = 12345.6789 + _odo = Odometer(_data, _controller) + + await _odo.async_update() + + assert not _odo is None + assert not _odo.get_value() is None + assert _odo.get_value() == 12345.7 From 3be416e5c2d16afbf3b6df7104e046da439d339c Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 14:19:07 +0100 Subject: [PATCH 08/19] increase gps test coverage --- tests/tesla_mock.py | 13 ++++++++ tests/unit_tests/test_gps.py | 59 ++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/tests/tesla_mock.py b/tests/tesla_mock.py index 2121dd13..189ae47f 100644 --- a/tests/tesla_mock.py +++ b/tests/tesla_mock.py @@ -27,6 +27,9 @@ def __init__(self, monkeypatch) -> None: self._monkeypatch.setattr( Controller, "get_state_params", self.mock_get_state_params ) + self._monkeypatch.setattr( + Controller, "get_gui_params", self.mock_get_gui_params + ) self._monkeypatch.setattr( Controller, "get_last_update_time", self.mock_get_last_update_time ) @@ -50,6 +53,11 @@ def mock_get_drive_params(self, *args, **kwargs): """ Mock controller's get_drive_params method.""" return self.controller_get_drive_params() + def mock_get_gui_params(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's get_gui_params method.""" + return self.controller_get_gui_params() + def mock_get_state_params(self, *args, **kwargs): # pylint: disable=unused-argument """ Mock controller's get_state_params method.""" @@ -85,6 +93,11 @@ def controller_get_drive_params(): """ Monkeypatch for controller.get_drive_params().""" return DRIVE_STATE + @staticmethod + def controller_get_gui_params(): + """ Monkeypatch for controller.get_gui_params().""" + return GUI_SETTINGS + @staticmethod def controller_get_state_params(): """ Monkeypatch for controller.get_state_params().""" diff --git a/tests/unit_tests/test_gps.py b/tests/unit_tests/test_gps.py index 42166a3f..17e28260 100644 --- a/tests/unit_tests/test_gps.py +++ b/tests/unit_tests/test_gps.py @@ -57,6 +57,64 @@ async def test_get_location_after_update(monkeypatch): assert _location["speed"] == 0 +@pytest.mark.asyncio +async def test_get_location_native_location(monkeypatch): + """Test get_location() with native location support.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["drive_state"]["native_location_supported"] = True + _data["drive_state"]["longitude"] = 12.345 + _data["drive_state"]["native_longitude"] = 23.456 + _data["drive_state"]["latitude"] = 34.567 + _data["drive_state"]["native_latitude"] = 45.678 + _data["drive_state"]["heading"] = 12 + _data["drive_state"]["native_heading"] = 23 + _data["drive_state"]["speed"] = 23.4 + + _gps = GPS(_data, _controller) + + await _gps.async_update() + _location = _gps.get_location() + + assert not _location is None + assert _location["longitude"] == 23.456 + assert _location["latitude"] == 45.678 + assert _location["heading"] == 23 + assert _location["speed"] == 23.4 + + +@pytest.mark.asyncio +async def test_get_location_no_native_location(monkeypatch): + """Test get_location() without native location support.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["drive_state"]["native_location_supported"] = False + _data["drive_state"]["longitude"] = 12.345 + _data["drive_state"]["native_longitude"] = 23.456 + _data["drive_state"]["latitude"] = 34.567 + _data["drive_state"]["native_latitude"] = 45.678 + _data["drive_state"]["heading"] = 12 + _data["drive_state"]["native_heading"] = 21 + _data["drive_state"]["speed"] = 23.4 + + _gps = GPS(_data, _controller) + + await _gps.async_update() + _location = _gps.get_location() + + assert not _location is None + assert _location["longitude"] == 12.345 + assert _location["latitude"] == 34.567 + assert _location["heading"] == 12 + assert _location["speed"] == 23.4 + + @pytest.mark.asyncio async def test_async_update(monkeypatch): """Test async_update().""" @@ -70,6 +128,7 @@ async def test_async_update(monkeypatch): _data["drive_state"]["latitude"] = 34.567 _data["drive_state"]["native_latitude"] = 34.567 _data["drive_state"]["heading"] = 12 + _data["drive_state"]["native_heading"] = 12 _data["drive_state"]["speed"] = 23.4 _gps = GPS(_data, _controller) From a5233e053beb279d899a5bd77d65e8446e40817f Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 15:09:38 +0100 Subject: [PATCH 09/19] add unit tests for battery and range sensors --- tests/tesla_mock.py | 17 +++- tests/unit_tests/test_battery.py | 131 ++++++++++++++++++++++++++++++ tests/unit_tests/test_odometer.py | 40 +++++---- tests/unit_tests/test_range.py | 123 ++++++++++++++++++++++++++++ 4 files changed, 295 insertions(+), 16 deletions(-) create mode 100644 tests/unit_tests/test_battery.py create mode 100644 tests/unit_tests/test_range.py diff --git a/tests/tesla_mock.py b/tests/tesla_mock.py index 189ae47f..a006c0b9 100644 --- a/tests/tesla_mock.py +++ b/tests/tesla_mock.py @@ -22,14 +22,17 @@ def __init__(self, monkeypatch) -> None: self._monkeypatch.setattr(Controller, "connect", self.mock_connect) self._monkeypatch.setattr(Controller, "command", self.mock_command) self._monkeypatch.setattr( - Controller, "get_drive_params", self.mock_get_drive_params + Controller, "get_charging_params", self.mock_get_charging_params ) self._monkeypatch.setattr( - Controller, "get_state_params", self.mock_get_state_params + Controller, "get_drive_params", self.mock_get_drive_params ) self._monkeypatch.setattr( Controller, "get_gui_params", self.mock_get_gui_params ) + self._monkeypatch.setattr( + Controller, "get_state_params", self.mock_get_state_params + ) self._monkeypatch.setattr( Controller, "get_last_update_time", self.mock_get_last_update_time ) @@ -48,6 +51,11 @@ def mock_command(self, *args, **kwargs): """ Mock controller's command method.""" return self.controller_command() + def mock_get_charging_params(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's get_charging_params method.""" + return self.controller_get_charging_params() + def mock_get_drive_params(self, *args, **kwargs): # pylint: disable=unused-argument """ Mock controller's get_drive_params method.""" @@ -88,6 +96,11 @@ async def controller_command(): """ Monkeypatch for controller.command().""" return RESULT_OK + @staticmethod + def controller_get_charging_params(): + """ Monkeypatch for controller.get_charging_params().""" + return CHARGE_STATE + @staticmethod def controller_get_drive_params(): """ Monkeypatch for controller.get_drive_params().""" diff --git a/tests/unit_tests/test_battery.py b/tests/unit_tests/test_battery.py new file mode 100644 index 00000000..c5033a37 --- /dev/null +++ b/tests/unit_tests/test_battery.py @@ -0,0 +1,131 @@ +"""Test GPS.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.battery_sensor import Battery + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _battery = Battery(_data, _controller) + + assert _battery.has_battery() + + +def test_device_class(monkeypatch): + """Test device_class().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _battery = Battery(_data, _controller) + + assert _battery.device_class == "battery" + + +def test_get_value_on_init(monkeypatch): + """Test get_value() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _battery = Battery(_data, _controller) + + assert not _battery is None + assert _battery.get_value() is None + + +@pytest.mark.asyncio +async def test_get_value_after_update(monkeypatch): + """Test get_value() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _battery = Battery(_data, _controller) + + await _battery.async_update() + + assert not _battery is None + assert not _battery.get_value() is None + assert _battery.get_value() == 64 + + +@pytest.mark.asyncio +async def test_battery_level(monkeypatch): + """Test battery_level().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _battery = Battery(_data, _controller) + + await _battery.async_update() + + assert not _battery is None + assert not _battery.get_value() is None + assert _battery.battery_level() == 64 + + +@pytest.mark.asyncio +async def test_battery_charging_off(monkeypatch): + """Test battery_charging() when not charging.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Disconnected" + _battery = Battery(_data, _controller) + + await _battery.async_update() + + assert not _battery is None + assert not _battery.battery_charging() + + +@pytest.mark.asyncio +async def test_battery_charging_on(monkeypatch): + """Test battery_charging() when charging.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Charging" + _battery = Battery(_data, _controller) + + await _battery.async_update() + + assert not _battery is None + assert _battery.battery_charging() + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["battery_level"] = 12.3 + _battery = Battery(_data, _controller) + + await _battery.async_update() + + assert not _battery is None + assert not _battery.get_value() is None + assert _battery.get_value() == 12.3 diff --git a/tests/unit_tests/test_odometer.py b/tests/unit_tests/test_odometer.py index ff8a0cb7..042de621 100644 --- a/tests/unit_tests/test_odometer.py +++ b/tests/unit_tests/test_odometer.py @@ -15,9 +15,21 @@ def test_has_battery(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _odo = Odometer(_data, _controller) + _odometer = Odometer(_data, _controller) - assert not _odo.has_battery() + assert not _odometer.has_battery() + + +def test_device_class(monkeypatch): + """Test device_class().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _odometer = Odometer(_data, _controller) + + assert _odometer.device_class is None def test_get_value_on_init(monkeypatch): @@ -27,10 +39,10 @@ def test_get_value_on_init(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _odo = Odometer(_data, _controller) + _odometer = Odometer(_data, _controller) - assert not _odo is None - assert _odo.get_value() is None + assert not _odometer is None + assert _odometer.get_value() is None @pytest.mark.asyncio @@ -41,12 +53,12 @@ async def test_get_value_after_update(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _odo = Odometer(_data, _controller) + _odometer = Odometer(_data, _controller) - await _odo.async_update() + await _odometer.async_update() - assert not _odo is None - assert _odo.get_value() == 33561.4 + assert not _odometer is None + assert _odometer.get_value() == 33561.4 @pytest.mark.asyncio @@ -58,10 +70,10 @@ async def test_async_update(monkeypatch): _data = _mock.data_request_vehicle() _data["vehicle_state"]["odometer"] = 12345.6789 - _odo = Odometer(_data, _controller) + _odometer = Odometer(_data, _controller) - await _odo.async_update() + await _odometer.async_update() - assert not _odo is None - assert not _odo.get_value() is None - assert _odo.get_value() == 12345.7 + assert not _odometer is None + assert not _odometer.get_value() is None + assert _odometer.get_value() == 12345.7 diff --git a/tests/unit_tests/test_range.py b/tests/unit_tests/test_range.py new file mode 100644 index 00000000..8959d022 --- /dev/null +++ b/tests/unit_tests/test_range.py @@ -0,0 +1,123 @@ +"""Test GPS.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.battery_sensor import Range + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + + assert not _range.has_battery() + + +def test_device_class(monkeypatch): + """Test device_class().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + + assert _range.device_class is None + + +def test_get_value_on_init(monkeypatch): + """Test get_value() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + + assert not _range is None + assert _range.get_value() is None + + +@pytest.mark.asyncio +async def test_get_value_after_update(monkeypatch): + """Test get_value() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + + await _range.async_update() + + assert not _range is None + assert not _range.get_value() is None + assert _range.get_value() == 167.96 + + +@pytest.mark.asyncio +async def test_get_value_rated_on(monkeypatch): + """Test get_value() for range display 'Rated'.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + _data["charge_state"]["gui_range_display"] = "Rated" + _data["charge_state"]["battery_range"] = 123.45 + _data["charge_state"]["est_battery_range"] = 234.56 + _data["charge_state"]["ideal_battery_range"] = 345.67 + await _range.async_update() + + assert not _range is None + assert not _range.get_value() is None + assert _range.get_value() == 123.45 + + +@pytest.mark.asyncio +async def test_get_value_rated_off(monkeypatch): + """Test get_value() for range display not 'Rated'.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + _data["gui_settings"]["gui_range_display"] = "Other" + _data["charge_state"]["battery_range"] = 123.45 + _data["charge_state"]["est_battery_range"] = 234.56 + _data["charge_state"]["ideal_battery_range"] = 345.67 + await _range.async_update() + + assert not _range is None + assert not _range.get_value() is None + assert _range.get_value() == 345.67 + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["gui_settings"]["gui_range_display"] = "Rated" + _data["charge_state"]["battery_range"] = 123.45 + _data["charge_state"]["est_battery_range"] = 234.56 + _data["charge_state"]["ideal_battery_range"] = 345.67 + _range = Range(_data, _controller) + + await _range.async_update() + + assert not _range is None + assert not _range.get_value() is None + assert _range.get_value() == 123.45 From 07ffae47a7eb092743e75b2b942553ac819c374e Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 17:07:52 +0100 Subject: [PATCH 10/19] fix test name in documentation --- tests/unit_tests/test_battery.py | 2 +- tests/unit_tests/test_odometer.py | 2 +- tests/unit_tests/test_range.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/test_battery.py b/tests/unit_tests/test_battery.py index c5033a37..76148ede 100644 --- a/tests/unit_tests/test_battery.py +++ b/tests/unit_tests/test_battery.py @@ -1,4 +1,4 @@ -"""Test GPS.""" +"""Test battery sensor.""" import pytest diff --git a/tests/unit_tests/test_odometer.py b/tests/unit_tests/test_odometer.py index 042de621..efa15473 100644 --- a/tests/unit_tests/test_odometer.py +++ b/tests/unit_tests/test_odometer.py @@ -1,4 +1,4 @@ -"""Test GPS.""" +"""Test odometer sensor.""" import pytest diff --git a/tests/unit_tests/test_range.py b/tests/unit_tests/test_range.py index 8959d022..5ee55e79 100644 --- a/tests/unit_tests/test_range.py +++ b/tests/unit_tests/test_range.py @@ -1,4 +1,4 @@ -"""Test GPS.""" +"""Test range sensor.""" import pytest From b1542c2a0a4e392c4a9aa75d07e2b52660b3b82d Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 17:08:40 +0100 Subject: [PATCH 11/19] add unit tests for binary sensors --- tests/tesla_mock.py | 28 ++++++ tests/unit_tests/test_charger_connection.py | 86 ++++++++++++++++++ tests/unit_tests/test_online.py | 98 +++++++++++++++++++++ tests/unit_tests/test_parking.py | 86 ++++++++++++++++++ 4 files changed, 298 insertions(+) create mode 100644 tests/unit_tests/test_charger_connection.py create mode 100644 tests/unit_tests/test_online.py create mode 100644 tests/unit_tests/test_parking.py diff --git a/tests/tesla_mock.py b/tests/tesla_mock.py index a006c0b9..935e5427 100644 --- a/tests/tesla_mock.py +++ b/tests/tesla_mock.py @@ -33,6 +33,7 @@ def __init__(self, monkeypatch) -> None: self._monkeypatch.setattr( Controller, "get_state_params", self.mock_get_state_params ) + self._monkeypatch.setattr(Controller, "get_vehicles", self.mock_get_vehicles) self._monkeypatch.setattr( Controller, "get_last_update_time", self.mock_get_last_update_time ) @@ -71,6 +72,11 @@ def mock_get_state_params(self, *args, **kwargs): """ Mock controller's get_state_params method.""" return self.controller_get_state_params() + def mock_get_vehicles(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's get_vehicles method.""" + return self.controller_get_vehicles() + def mock_get_last_update_time(self, *args, **kwargs): # pylint: disable=unused-argument """ Mock controller's get_last_update_time method.""" @@ -116,6 +122,11 @@ def controller_get_state_params(): """ Monkeypatch for controller.get_state_params().""" return VEHICLE_STATE + @staticmethod + def controller_get_vehicles(): + """ Monkeypatch for controller.get_vehicles().""" + return {SAMPLE_VEHICLE} + @staticmethod async def controller_update(): """ Monkeypatch for controller.update().""" @@ -161,6 +172,23 @@ def command_ok(): "error_description": "", } +SAMPLE_VEHICLE = { + "id": 12345678901234567, + "vehicle_id": 1234567890, + "vin": "5YJSA11111111111", + "display_name": "Nikola 2.0", + "option_codes": "MDLS,RENA,AF02,APF1,APH2,APPB,AU01,BC0R,BP00,BR00,BS00,CDM0,CH05,PBCW,CW00,DCF0,DRLH,DSH7,DV4W,FG02,FR04,HP00,IDBA,IX01,LP01,ME02,MI01,PF01,PI01,PK00,PS01,PX00,PX4D,QTVB,RFP2,SC01,SP00,SR01,SU01,TM00,TP03,TR00,UTAB,WTAS,X001,X003,X007,X011,X013,X021,X024,X027,X028,X031,X037,X040,X044,YFFC,COUS", + "color": None, + "tokens": ["abcdef1234567890", "1234567890abcdef"], + "state": "online", + "in_service": False, + "id_s": "12345678901234567", + "calendar_enabled": True, + "api_version": 7, + "backseat_token": None, + "backseat_token_updated_at": None, +} + DRIVE_STATE = { "gps_as_of": 1538363883, "heading": 5, diff --git a/tests/unit_tests/test_charger_connection.py b/tests/unit_tests/test_charger_connection.py new file mode 100644 index 00000000..8bfd069c --- /dev/null +++ b/tests/unit_tests/test_charger_connection.py @@ -0,0 +1,86 @@ +"""Test charger connection sensor.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.binary_sensor import ChargerConnectionSensor + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargerConnectionSensor(_data, _controller) + + assert not _sensor.has_battery() + + +def test_get_value_on_init(monkeypatch): + """Test get_value() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargerConnectionSensor(_data, _controller) + + assert not _sensor is None + assert _sensor.get_value() is None + + +@pytest.mark.asyncio +async def test_get_value_after_update(monkeypatch): + """Test get_value() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargerConnectionSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() + + +@pytest.mark.asyncio +async def test_get_value_on(monkeypatch): + """Test get_value() for charging state ON.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargerConnectionSensor(_data, _controller) + _data["charge_state"]["charging_state"] = "Charging" + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() + + +@pytest.mark.asyncio +async def test_get_value_off(monkeypatch): + """Test get_value() for charging state OFF.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargerConnectionSensor(_data, _controller) + _data["charge_state"]["charging_state"] = "Disconnected" + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert not _sensor.get_value() diff --git a/tests/unit_tests/test_online.py b/tests/unit_tests/test_online.py new file mode 100644 index 00000000..47ab795f --- /dev/null +++ b/tests/unit_tests/test_online.py @@ -0,0 +1,98 @@ +"""Test online sensor.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.binary_sensor import OnlineSensor + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = OnlineSensor(_data, _controller) + + assert not _sensor.has_battery() + + +def test_get_value_on_init(monkeypatch): + """Test get_value() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = OnlineSensor(_data, _controller) + + assert not _sensor is None + assert _sensor.get_value() is None + + +@pytest.mark.asyncio +async def test_get_value_after_update(monkeypatch): + """Test get_value() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + monkeypatch.setitem(_controller.car_online, "5YJSA11111111111", True) + monkeypatch.setitem( + _controller.car_state, "5YJSA11111111111", TeslaMock.data_request_vehicle() + ) + + _data = _mock.data_request_vehicle() + _sensor = OnlineSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() + + +@pytest.mark.asyncio +async def test_get_value_on(monkeypatch): + """Test get_value() for online mode.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + monkeypatch.setitem(_controller.car_online, "5YJSA11111111111", True) + monkeypatch.setitem( + _controller.car_state, "5YJSA11111111111", TeslaMock.data_request_vehicle() + ) + + _data = _mock.data_request_vehicle() + _sensor = OnlineSensor(_data, _controller) + _data["state"] = "online" + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() + + +@pytest.mark.asyncio +async def test_get_value_off(monkeypatch): + """Test get_value() for offline mode.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + monkeypatch.setitem(_controller.car_online, "5YJSA11111111111", False) + monkeypatch.setitem( + _controller.car_state, "5YJSA11111111111", TeslaMock.data_request_vehicle() + ) + + _data = _mock.data_request_vehicle() + _sensor = OnlineSensor(_data, _controller) + _data["state"] = "asleep" + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert not _sensor.get_value() diff --git a/tests/unit_tests/test_parking.py b/tests/unit_tests/test_parking.py new file mode 100644 index 00000000..bcd45c63 --- /dev/null +++ b/tests/unit_tests/test_parking.py @@ -0,0 +1,86 @@ +"""Test parking sensor.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.binary_sensor import ParkingSensor + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ParkingSensor(_data, _controller) + + assert not _sensor.has_battery() + + +def test_get_value_on_init(monkeypatch): + """Test get_value() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ParkingSensor(_data, _controller) + + assert not _sensor is None + assert _sensor.get_value() is None + + +@pytest.mark.asyncio +async def test_get_value_after_update(monkeypatch): + """Test get_value() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ParkingSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() + + +@pytest.mark.asyncio +async def test_get_value_on(monkeypatch): + """Test get_value() for parking mode ON.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ParkingSensor(_data, _controller) + _data["drive_state"]["shift_state"] = "P" + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() + + +@pytest.mark.asyncio +async def test_get_value_off(monkeypatch): + """Test get_value() for parking mode OFF.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ParkingSensor(_data, _controller) + _data["drive_state"]["shift_state"] = "N" + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_value() is None + assert not _sensor.get_value() From 69822a39d24fbede754acd989c660010bb6ae910 Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 18:57:04 +0100 Subject: [PATCH 12/19] add unit tests for charger switches and sensors --- tests/unit_tests/test_charger_connection.py | 1 + tests/unit_tests/test_charger_switch.py | 125 ++++++++++++++++++++ tests/unit_tests/test_charging_sensor.py | 95 +++++++++++++++ tests/unit_tests/test_range_switch.py | 125 ++++++++++++++++++++ 4 files changed, 346 insertions(+) create mode 100644 tests/unit_tests/test_charger_switch.py create mode 100644 tests/unit_tests/test_charging_sensor.py create mode 100644 tests/unit_tests/test_range_switch.py diff --git a/tests/unit_tests/test_charger_connection.py b/tests/unit_tests/test_charger_connection.py index 8bfd069c..7845060f 100644 --- a/tests/unit_tests/test_charger_connection.py +++ b/tests/unit_tests/test_charger_connection.py @@ -41,6 +41,7 @@ async def test_get_value_after_update(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Charging" _sensor = ChargerConnectionSensor(_data, _controller) await _sensor.async_update() diff --git a/tests/unit_tests/test_charger_switch.py b/tests/unit_tests/test_charger_switch.py new file mode 100644 index 00000000..63905420 --- /dev/null +++ b/tests/unit_tests/test_charger_switch.py @@ -0,0 +1,125 @@ +"""Test charger switch.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.charger import ChargerSwitch + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _switch = ChargerSwitch(_data, _controller) + + assert not _switch.has_battery() + + +def test_is_charging_on_init(monkeypatch): + """Test is_charging() when not charging.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _switch = ChargerSwitch(_data, _controller) + + assert not _switch.is_charging() + + +@pytest.mark.asyncio +async def test_is_charging_on(monkeypatch): + """Test is_charging() with charging state charging.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Charging" + _switch = ChargerSwitch(_data, _controller) + + await _switch.async_update() + assert _switch.is_charging() + + +@pytest.mark.asyncio +async def test_is_charging_off(monkeypatch): + """Test is_charging() with charging state disconnected.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Disconnected" + _switch = ChargerSwitch(_data, _controller) + + await _switch.async_update() + assert not _switch.is_charging() + + +@pytest.mark.asyncio +async def test_start_charge(monkeypatch): + """Test start_charge().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Disconnected" + _switch = ChargerSwitch(_data, _controller) + await _switch.async_update() + + await _switch.start_charge() + assert _switch.is_charging() + + +@pytest.mark.asyncio +async def test_stop_charge(monkeypatch): + """Test stop_charge().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Charging" + _switch = ChargerSwitch(_data, _controller) + await _switch.async_update() + + await _switch.stop_charge() + assert not _switch.is_charging() + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Charging" + _switch = ChargerSwitch(_data, _controller) + + await _switch.async_update() + assert _switch.is_charging() + + +@pytest.mark.asyncio +async def test_async_update_with_change(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charging_state"] = "Charging" + _switch = ChargerSwitch(_data, _controller) + + _data["charge_state"]["charging_state"] = "Disconnected" + await _switch.async_update() + assert not _switch.is_charging() diff --git a/tests/unit_tests/test_charging_sensor.py b/tests/unit_tests/test_charging_sensor.py new file mode 100644 index 00000000..f85bf32a --- /dev/null +++ b/tests/unit_tests/test_charging_sensor.py @@ -0,0 +1,95 @@ +"""Test charging sensor.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.charger import ChargingSensor + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargingSensor(_data, _controller) + + assert not _sensor.has_battery() + + +def test_device_class(monkeypatch): + """Test device_class().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargingSensor(_data, _controller) + + assert _sensor.device_class is None + + +def test_get_value_on_init(monkeypatch): + """Test get_value() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargingSensor(_data, _controller) + + assert not _sensor is None + assert _sensor.charging_rate is None + assert _sensor.time_left is None + assert _sensor.added_range is None + assert _sensor.charge_current_request is None + assert _sensor.charger_actual_current is None + assert _sensor.charger_voltage is None + assert _sensor.charge_energy_added is None + + +@pytest.mark.asyncio +async def test_get_value_after_update(monkeypatch): + """Test get_value() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargingSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert _sensor.charging_rate == 0 + assert _sensor.time_left == 0 + assert _sensor.added_range == 40 + assert _sensor.charge_current_request == 48 + assert _sensor.charger_actual_current == 0 + assert _sensor.charger_voltage == 0 + assert _sensor.charge_energy_added == 12.41 + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = ChargingSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert _sensor.charging_rate == 0 + assert _sensor.time_left == 0 + assert _sensor.added_range == 40 + assert _sensor.charge_current_request == 48 + assert _sensor.charger_actual_current == 0 + assert _sensor.charger_voltage == 0 + assert _sensor.charge_energy_added == 12.41 diff --git a/tests/unit_tests/test_range_switch.py b/tests/unit_tests/test_range_switch.py new file mode 100644 index 00000000..e8370880 --- /dev/null +++ b/tests/unit_tests/test_range_switch.py @@ -0,0 +1,125 @@ +"""Test range switch.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.charger import RangeSwitch + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _switch = RangeSwitch(_data, _controller) + + assert not _switch.has_battery() + + +def test_is_maxrange_on_init(monkeypatch): + """Test is_maxrange() when not charging.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _switch = RangeSwitch(_data, _controller) + + assert not _switch.is_maxrange() + + +@pytest.mark.asyncio +async def test_is_maxrange_on(monkeypatch): + """Test is_maxrange() with charging state charging.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_to_max_range"] = True + _switch = RangeSwitch(_data, _controller) + + await _switch.async_update() + assert _switch.is_maxrange() + + +@pytest.mark.asyncio +async def test_is_maxrange_off(monkeypatch): + """Test is_maxrange() with charging state disconnected.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_to_max_range"] = False + _switch = RangeSwitch(_data, _controller) + + await _switch.async_update() + assert not _switch.is_maxrange() + + +@pytest.mark.asyncio +async def test_set_max(monkeypatch): + """Test set_max().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_to_max_range"] = False + _switch = RangeSwitch(_data, _controller) + await _switch.async_update() + + await _switch.set_max() + assert _switch.is_maxrange() + + +@pytest.mark.asyncio +async def test_set_standard(monkeypatch): + """Test set_standard().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_to_max_range"] = True + _switch = RangeSwitch(_data, _controller) + await _switch.async_update() + + await _switch.set_standard() + assert not _switch.is_maxrange() + + +@pytest.mark.asyncio +async def test_async_update(monkeypatch): + """Test async_update().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_to_max_range"] = True + _switch = RangeSwitch(_data, _controller) + + await _switch.async_update() + assert _switch.is_maxrange() + + +@pytest.mark.asyncio +async def test_async_update_with_change(monkeypatch): + """Test async_update() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_to_max_range"] = True + _switch = RangeSwitch(_data, _controller) + + _data["charge_state"]["charge_to_max_range"] = False + await _switch.async_update() + assert not _switch.is_maxrange() From 508fdd23e3de811cbb8d640bb3bd38d06f13716f Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 19:00:33 +0100 Subject: [PATCH 13/19] rename test files --- tests/unit_tests/{test_battery.py => test_battery_sensor.py} | 0 ...st_charger_connection.py => test_charger_connection_sensor.py} | 0 tests/unit_tests/{test_gps.py => test_gps_tracker.py} | 0 tests/unit_tests/{test_odometer.py => test_odometer_sensor.py} | 0 tests/unit_tests/{test_online.py => test_online_sensor.py} | 0 tests/unit_tests/{test_parking.py => test_parking_sensor.py} | 0 tests/unit_tests/{test_range.py => test_range_sensor.py} | 0 .../{test_sentry_mode.py => test_sentry_mode_switch.py} | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename tests/unit_tests/{test_battery.py => test_battery_sensor.py} (100%) rename tests/unit_tests/{test_charger_connection.py => test_charger_connection_sensor.py} (100%) rename tests/unit_tests/{test_gps.py => test_gps_tracker.py} (100%) rename tests/unit_tests/{test_odometer.py => test_odometer_sensor.py} (100%) rename tests/unit_tests/{test_online.py => test_online_sensor.py} (100%) rename tests/unit_tests/{test_parking.py => test_parking_sensor.py} (100%) rename tests/unit_tests/{test_range.py => test_range_sensor.py} (100%) rename tests/unit_tests/{test_sentry_mode.py => test_sentry_mode_switch.py} (100%) diff --git a/tests/unit_tests/test_battery.py b/tests/unit_tests/test_battery_sensor.py similarity index 100% rename from tests/unit_tests/test_battery.py rename to tests/unit_tests/test_battery_sensor.py diff --git a/tests/unit_tests/test_charger_connection.py b/tests/unit_tests/test_charger_connection_sensor.py similarity index 100% rename from tests/unit_tests/test_charger_connection.py rename to tests/unit_tests/test_charger_connection_sensor.py diff --git a/tests/unit_tests/test_gps.py b/tests/unit_tests/test_gps_tracker.py similarity index 100% rename from tests/unit_tests/test_gps.py rename to tests/unit_tests/test_gps_tracker.py diff --git a/tests/unit_tests/test_odometer.py b/tests/unit_tests/test_odometer_sensor.py similarity index 100% rename from tests/unit_tests/test_odometer.py rename to tests/unit_tests/test_odometer_sensor.py diff --git a/tests/unit_tests/test_online.py b/tests/unit_tests/test_online_sensor.py similarity index 100% rename from tests/unit_tests/test_online.py rename to tests/unit_tests/test_online_sensor.py diff --git a/tests/unit_tests/test_parking.py b/tests/unit_tests/test_parking_sensor.py similarity index 100% rename from tests/unit_tests/test_parking.py rename to tests/unit_tests/test_parking_sensor.py diff --git a/tests/unit_tests/test_range.py b/tests/unit_tests/test_range_sensor.py similarity index 100% rename from tests/unit_tests/test_range.py rename to tests/unit_tests/test_range_sensor.py diff --git a/tests/unit_tests/test_sentry_mode.py b/tests/unit_tests/test_sentry_mode_switch.py similarity index 100% rename from tests/unit_tests/test_sentry_mode.py rename to tests/unit_tests/test_sentry_mode_switch.py From 7b7daea393806fed219616fbb0046e47a8c64e47 Mon Sep 17 00:00:00 2001 From: hobbe Date: Tue, 24 Mar 2020 19:04:22 +0100 Subject: [PATCH 14/19] refactor variable names --- tests/unit_tests/test_battery_sensor.py | 60 ++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/unit_tests/test_battery_sensor.py b/tests/unit_tests/test_battery_sensor.py index 76148ede..4b515454 100644 --- a/tests/unit_tests/test_battery_sensor.py +++ b/tests/unit_tests/test_battery_sensor.py @@ -15,9 +15,9 @@ def test_has_battery(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - assert _battery.has_battery() + assert _sensor.has_battery() def test_device_class(monkeypatch): @@ -27,9 +27,9 @@ def test_device_class(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - assert _battery.device_class == "battery" + assert _sensor.device_class == "battery" def test_get_value_on_init(monkeypatch): @@ -39,10 +39,10 @@ def test_get_value_on_init(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - assert not _battery is None - assert _battery.get_value() is None + assert not _sensor is None + assert _sensor.get_value() is None @pytest.mark.asyncio @@ -53,13 +53,13 @@ async def test_get_value_after_update(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - await _battery.async_update() + await _sensor.async_update() - assert not _battery is None - assert not _battery.get_value() is None - assert _battery.get_value() == 64 + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() == 64 @pytest.mark.asyncio @@ -70,13 +70,13 @@ async def test_battery_level(monkeypatch): _controller = Controller(None) _data = _mock.data_request_vehicle() - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - await _battery.async_update() + await _sensor.async_update() - assert not _battery is None - assert not _battery.get_value() is None - assert _battery.battery_level() == 64 + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.battery_level() == 64 @pytest.mark.asyncio @@ -88,12 +88,12 @@ async def test_battery_charging_off(monkeypatch): _data = _mock.data_request_vehicle() _data["charge_state"]["charging_state"] = "Disconnected" - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - await _battery.async_update() + await _sensor.async_update() - assert not _battery is None - assert not _battery.battery_charging() + assert not _sensor is None + assert not _sensor.battery_charging() @pytest.mark.asyncio @@ -105,12 +105,12 @@ async def test_battery_charging_on(monkeypatch): _data = _mock.data_request_vehicle() _data["charge_state"]["charging_state"] = "Charging" - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - await _battery.async_update() + await _sensor.async_update() - assert not _battery is None - assert _battery.battery_charging() + assert not _sensor is None + assert _sensor.battery_charging() @pytest.mark.asyncio @@ -122,10 +122,10 @@ async def test_async_update(monkeypatch): _data = _mock.data_request_vehicle() _data["charge_state"]["battery_level"] = 12.3 - _battery = Battery(_data, _controller) + _sensor = Battery(_data, _controller) - await _battery.async_update() + await _sensor.async_update() - assert not _battery is None - assert not _battery.get_value() is None - assert _battery.get_value() == 12.3 + assert not _sensor is None + assert not _sensor.get_value() is None + assert _sensor.get_value() == 12.3 From 771c95935ab92a6c20911e93d11c74c6a632e783 Mon Sep 17 00:00:00 2001 From: hobbe Date: Wed, 25 Mar 2020 10:45:44 +0100 Subject: [PATCH 15/19] add unit tests for climate and temp sensor --- tests/tesla_mock.py | 13 ++ tests/unit_tests/test_climate.py | 196 +++++++++++++++++++++++++++ tests/unit_tests/test_temp_sensor.py | 67 +++++++++ 3 files changed, 276 insertions(+) create mode 100644 tests/unit_tests/test_climate.py create mode 100644 tests/unit_tests/test_temp_sensor.py diff --git a/tests/tesla_mock.py b/tests/tesla_mock.py index 935e5427..c4b02aeb 100644 --- a/tests/tesla_mock.py +++ b/tests/tesla_mock.py @@ -24,6 +24,9 @@ def __init__(self, monkeypatch) -> None: self._monkeypatch.setattr( Controller, "get_charging_params", self.mock_get_charging_params ) + self._monkeypatch.setattr( + Controller, "get_climate_params", self.mock_get_climate_params + ) self._monkeypatch.setattr( Controller, "get_drive_params", self.mock_get_drive_params ) @@ -57,6 +60,11 @@ def mock_get_charging_params(self, *args, **kwargs): """ Mock controller's get_charging_params method.""" return self.controller_get_charging_params() + def mock_get_climate_params(self, *args, **kwargs): + # pylint: disable=unused-argument + """ Mock controller's get_climate_params method.""" + return self.controller_get_climate_params() + def mock_get_drive_params(self, *args, **kwargs): # pylint: disable=unused-argument """ Mock controller's get_drive_params method.""" @@ -107,6 +115,11 @@ def controller_get_charging_params(): """ Monkeypatch for controller.get_charging_params().""" return CHARGE_STATE + @staticmethod + def controller_get_climate_params(): + """ Monkeypatch for controller.get_climate_params().""" + return CLIMATE_STATE + @staticmethod def controller_get_drive_params(): """ Monkeypatch for controller.get_drive_params().""" diff --git a/tests/unit_tests/test_climate.py b/tests/unit_tests/test_climate.py new file mode 100644 index 00000000..9c60e9ff --- /dev/null +++ b/tests/unit_tests/test_climate.py @@ -0,0 +1,196 @@ +"""Test climate.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.climate import Climate + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _climate = Climate(_data, _controller) + + assert not _climate.has_battery() + + +def test_get_values_on_init(monkeypatch): + """Test values after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _climate = Climate(_data, _controller) + + assert not _climate is None + assert _climate.get_current_temp() is None + assert _climate.get_fan_status() is None + assert _climate.get_goal_temp() is None + assert _climate.is_hvac_enabled() is None + + +@pytest.mark.asyncio +async def test_get_values_after_update(monkeypatch): + """Test values after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _climate = Climate(_data, _controller) + + await _climate.async_update() + + assert not _climate is None + + assert _climate.get_current_temp() is None + assert not _climate.get_fan_status() is None + assert _climate.get_fan_status() == 0 + assert not _climate.get_goal_temp() is None + assert _climate.get_goal_temp() == 21.6 + assert not _climate.is_hvac_enabled() is None + assert not _climate.is_hvac_enabled() + + +@pytest.mark.asyncio +async def test_get_current_temp(monkeypatch): + """Test get_current_temp().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["climate_state"]["inside_temp"] = 18.8 + _climate = Climate(_data, _controller) + + await _climate.async_update() + + assert not _climate.get_current_temp() is None + assert _climate.get_current_temp() == 18.8 + + +@pytest.mark.asyncio +async def test_get_fan_status(monkeypatch): + """Test get_fan_status().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["climate_state"]["fan_status"] = 1 + _climate = Climate(_data, _controller) + + await _climate.async_update() + + assert not _climate.get_fan_status() is None + assert _climate.get_fan_status() == 1 + + +@pytest.mark.asyncio +async def test_get_goal_temp(monkeypatch): + """Test get_goal_temp().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["climate_state"]["driver_temp_setting"] = 23.4 + _climate = Climate(_data, _controller) + + await _climate.async_update() + + assert not _climate.get_goal_temp() is None + assert _climate.get_goal_temp() == 23.4 + + +@pytest.mark.asyncio +async def test_is_hvac_enabled_on(monkeypatch): + """Test is_hvac_enabled() when is_climate_on is True.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["climate_state"]["is_climate_on"] = True + _climate = Climate(_data, _controller) + + await _climate.async_update() + + assert not _climate.is_hvac_enabled() is None + assert _climate.is_hvac_enabled() + + +@pytest.mark.asyncio +async def test_is_hvac_enabled_off(monkeypatch): + """Test is_hvac_enabled() when is_climate_on is False.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["climate_state"]["is_climate_on"] = False + _climate = Climate(_data, _controller) + + await _climate.async_update() + + assert not _climate.is_hvac_enabled() is None + assert not _climate.is_hvac_enabled() + + +@pytest.mark.asyncio +async def test_set_status_on(monkeypatch): + """Test set_status() to enabled.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _climate = Climate(_data, _controller) + + await _climate.async_update() + await _climate.set_status(True) + + assert not _climate.is_hvac_enabled() is None + assert _climate.is_hvac_enabled() + + +@pytest.mark.asyncio +async def test_set_status_off(monkeypatch): + """Test set_status() to disabled.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _climate = Climate(_data, _controller) + + await _climate.async_update() + await _climate.set_status(False) + + assert not _climate.is_hvac_enabled() is None + assert not _climate.is_hvac_enabled() + + +@pytest.mark.asyncio +async def test_set_temperature(monkeypatch): + """Test set_temperature().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _climate = Climate(_data, _controller) + + await _climate.async_update() + + await _climate.set_temperature(12.3) + + assert not _climate.get_goal_temp() is None + assert _climate.get_goal_temp() == 12.3 diff --git a/tests/unit_tests/test_temp_sensor.py b/tests/unit_tests/test_temp_sensor.py new file mode 100644 index 00000000..4de9bc25 --- /dev/null +++ b/tests/unit_tests/test_temp_sensor.py @@ -0,0 +1,67 @@ +"""Test temp sensor.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.climate import TempSensor + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = TempSensor(_data, _controller) + + assert not _sensor.has_battery() + + +def test_device_class(monkeypatch): + """Test device_class().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = TempSensor(_data, _controller) + + assert _sensor.device_class == "temperature" + + +def test_get_temp_on_init(monkeypatch): + """Test get_inside_temp() and get_outside_temp() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _sensor = TempSensor(_data, _controller) + + assert not _sensor is None + assert _sensor.get_inside_temp() is None + assert _sensor.get_outside_temp() is None + + +@pytest.mark.asyncio +async def test_get_temp_after_update(monkeypatch): + """Test get_inside_temp() and get_outside_temp() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["climate_state"]["inside_temp"] = 18.8 + _data["climate_state"]["outside_temp"] = 22.2 + _sensor = TempSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert not _sensor.get_inside_temp() is None + assert not _sensor.get_outside_temp() is None + assert _sensor.get_inside_temp() == 18.8 + assert _sensor.get_outside_temp() == 22.2 From 86871ad1feb42be67bc1d8f7da72aef084d54cec Mon Sep 17 00:00:00 2001 From: hobbe Date: Wed, 25 Mar 2020 11:45:18 +0100 Subject: [PATCH 16/19] add unit tests for door lock and charge lock --- tests/unit_tests/test_charger_lock.py | 122 ++++++++++++++++++++++++++ tests/unit_tests/test_lock.py | 122 ++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 tests/unit_tests/test_charger_lock.py create mode 100644 tests/unit_tests/test_lock.py diff --git a/tests/unit_tests/test_charger_lock.py b/tests/unit_tests/test_charger_lock.py new file mode 100644 index 00000000..4c3545af --- /dev/null +++ b/tests/unit_tests/test_charger_lock.py @@ -0,0 +1,122 @@ +"""Test charger lock.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.lock import ChargerLock + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _lock = ChargerLock(_data, _controller) + + assert not _lock.has_battery() + + +def test_is_locked_on_init(monkeypatch): + """Test is_locked() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _lock = ChargerLock(_data, _controller) + + assert not _lock is None + assert not _lock.is_locked() + + +@pytest.mark.asyncio +async def test_is_locked_after_update(monkeypatch): + """Test is_locked() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_port_door_open"] = True + _lock = ChargerLock(_data, _controller) + + await _lock.async_update() + + assert not _lock is None + assert _lock.is_locked() + + +@pytest.mark.asyncio +async def test_lock(monkeypatch): + """Test lock().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_port_door_open"] = False + _lock = ChargerLock(_data, _controller) + + await _lock.async_update() + await _lock.lock() + + assert not _lock is None + assert _lock.is_locked() + + +@pytest.mark.asyncio +async def test_lock_already_locked(monkeypatch): + """Test lock() when already locked.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_port_door_open"] = True + _lock = ChargerLock(_data, _controller) + + await _lock.async_update() + await _lock.lock() + + assert not _lock is None + assert _lock.is_locked() + + +@pytest.mark.asyncio +async def test_unlock(monkeypatch): + """Test unlock().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_port_door_open"] = True + _lock = ChargerLock(_data, _controller) + + await _lock.async_update() + await _lock.unlock() + + assert not _lock is None + assert not _lock.is_locked() + + +@pytest.mark.asyncio +async def test_unlock_already_unlocked(monkeypatch): + """Test unlock() when already unlocked.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["charge_state"]["charge_port_door_open"] = False + _lock = ChargerLock(_data, _controller) + + await _lock.async_update() + await _lock.unlock() + + assert not _lock is None + assert not _lock.is_locked() diff --git a/tests/unit_tests/test_lock.py b/tests/unit_tests/test_lock.py new file mode 100644 index 00000000..e20f2ce8 --- /dev/null +++ b/tests/unit_tests/test_lock.py @@ -0,0 +1,122 @@ +"""Test door lock.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.lock import Lock + + +def test_has_battery(monkeypatch): + """Test has_battery().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _lock = Lock(_data, _controller) + + assert not _lock.has_battery() + + +def test_is_locked_on_init(monkeypatch): + """Test is_locked() after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _lock = Lock(_data, _controller) + + assert not _lock is None + assert not _lock.is_locked() + + +@pytest.mark.asyncio +async def test_is_locked_after_update(monkeypatch): + """Test is_locked() after an update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["locked"] = True + _lock = Lock(_data, _controller) + + await _lock.async_update() + + assert not _lock is None + assert _lock.is_locked() + + +@pytest.mark.asyncio +async def test_lock(monkeypatch): + """Test lock().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["locked"] = False + _lock = Lock(_data, _controller) + + await _lock.async_update() + await _lock.lock() + + assert not _lock is None + assert _lock.is_locked() + + +@pytest.mark.asyncio +async def test_lock_already_locked(monkeypatch): + """Test lock() when already locked.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["locked"] = True + _lock = Lock(_data, _controller) + + await _lock.async_update() + await _lock.lock() + + assert not _lock is None + assert _lock.is_locked() + + +@pytest.mark.asyncio +async def test_unlock(monkeypatch): + """Test unlock().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["locked"] = True + _lock = Lock(_data, _controller) + + await _lock.async_update() + await _lock.unlock() + + assert not _lock is None + assert not _lock.is_locked() + + +@pytest.mark.asyncio +async def test_unlock_already_unlocked(monkeypatch): + """Test unlock() when already unlocked.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["locked"] = False + _lock = Lock(_data, _controller) + + await _lock.async_update() + await _lock.unlock() + + assert not _lock is None + assert not _lock.is_locked() From bc4cb97482d69229126bbe65b61d06edb7206e67 Mon Sep 17 00:00:00 2001 From: hobbe Date: Wed, 25 Mar 2020 14:22:43 +0100 Subject: [PATCH 17/19] add unit tests for vehicle device --- tests/unit_tests/test_sentry_mode_switch.py | 19 +++ tests/unit_tests/test_vehicle_device.py | 141 ++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 tests/unit_tests/test_vehicle_device.py diff --git a/tests/unit_tests/test_sentry_mode_switch.py b/tests/unit_tests/test_sentry_mode_switch.py index 7c6c0879..0025c04b 100644 --- a/tests/unit_tests/test_sentry_mode_switch.py +++ b/tests/unit_tests/test_sentry_mode_switch.py @@ -236,3 +236,22 @@ async def test_async_update_with_change_but_not_available(monkeypatch): await _switch.async_update() assert not _switch.is_on() + + +@pytest.mark.asyncio +async def test_async_update_with_change_same_value(monkeypatch): + """Test async_update() with a state change, using same value.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["vehicle_state"]["sentry_mode_available"] = True + _data["vehicle_state"]["sentry_mode"] = True + _switch = SentryModeSwitch(_data, _controller) + + # Change state value + _data["vehicle_state"]["sentry_mode"] = True + + await _switch.async_update() + assert _switch.is_on() diff --git a/tests/unit_tests/test_vehicle_device.py b/tests/unit_tests/test_vehicle_device.py new file mode 100644 index 00000000..f51a0500 --- /dev/null +++ b/tests/unit_tests/test_vehicle_device.py @@ -0,0 +1,141 @@ +"""Test vehicle device.""" + +import pytest + +from tests.tesla_mock import TeslaMock + +from teslajsonpy.controller import Controller +from teslajsonpy.vehicle import VehicleDevice + + +def test_is_armable(monkeypatch): + """Test is_armable().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _device = VehicleDevice(_data, _controller) + + assert not _device.is_armable() + + +def test_is_armed(monkeypatch): + """Test is_armed().""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _device = VehicleDevice(_data, _controller) + + assert not _device.is_armed() + + +def test_values_on_init(monkeypatch): + """Test values after initialization.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _device = VehicleDevice(_data, _controller) + + assert not _device is None + + assert not _device.car_name() is None + assert _device.car_name() == "Nikola 2.0" + + assert not _device.car_type is None + assert _device.car_type == "Model S" + + assert not _device.car_version is None + assert _device.car_version == "" + + assert not _device.id() is None + assert _device.id() == 12345678901234567 + + assert not _device.sentry_mode_available is None + assert _device.sentry_mode_available + + assert not _device.vehicle_id is None + assert _device.vehicle_id() == 1234567890 + + +@pytest.mark.asyncio +async def test_values_after_update(monkeypatch): + """Test values after update.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _device = VehicleDevice(_data, _controller) + + await _device.async_update() + + assert not _device is None + + assert not _device.car_name() is None + assert _device.car_name() == "Nikola 2.0" + + assert not _device.car_type is None + assert _device.car_type == "Model S" + + assert not _device.car_version is None + assert _device.car_version == "2019.40.2.1 38f55d9f9205" + + assert not _device.id() is None + assert _device.id() == 12345678901234567 + + assert not _device.sentry_mode_available is None + assert _device.sentry_mode_available + + assert not _device.vehicle_id is None + assert _device.vehicle_id() == 1234567890 + + +@pytest.mark.asyncio +async def test_assumed_state_online(monkeypatch): + # pylint: disable=protected-access + """Test assumed_state() with online vehicle.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + # TODO: Should not have protected access here (see vehicle.py) + monkeypatch.setitem(_controller.car_online, 12345678901234567, True) + monkeypatch.setitem(_controller._last_update_time, 12345678901234567, 100) + monkeypatch.setitem(_controller._last_wake_up_time, 12345678901234567, 0) + + _data = _mock.data_request_vehicle() + _device = VehicleDevice(_data, _controller) + + await _device.async_update() + + assert not _device is None + assert not _device.assumed_state() is None + assert not _device.assumed_state() + + +@pytest.mark.asyncio +async def test_assumed_state_offline(monkeypatch): + # pylint: disable=protected-access + """Test assumed_state() with offline vehicle.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + # TODO: Should not have protected access here (see vehicle.py) + monkeypatch.setitem(_controller.car_online, 12345678901234567, False) + monkeypatch.setitem(_controller._last_update_time, 12345678901234567, 1000) + monkeypatch.setitem(_controller._last_wake_up_time, 12345678901234567, 0) + + _data = _mock.data_request_vehicle() + _device = VehicleDevice(_data, _controller) + + await _device.async_update() + + assert not _device is None + assert not _device.assumed_state() is None + assert _device.assumed_state() From 54b186e3f480f1349eba617feec526e640d5618d Mon Sep 17 00:00:00 2001 From: hobbe Date: Wed, 25 Mar 2020 17:27:43 +0100 Subject: [PATCH 18/19] increase test coverage for sensors --- tests/unit_tests/test_charging_sensor.py | 42 ++++++++++++++++++++++ tests/unit_tests/test_odometer_sensor.py | 38 ++++++++++++++++++++ tests/unit_tests/test_range_sensor.py | 44 +++++++++++++++++++++++- 3 files changed, 123 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/test_charging_sensor.py b/tests/unit_tests/test_charging_sensor.py index f85bf32a..1eba1da5 100644 --- a/tests/unit_tests/test_charging_sensor.py +++ b/tests/unit_tests/test_charging_sensor.py @@ -93,3 +93,45 @@ async def test_async_update(monkeypatch): assert _sensor.charger_actual_current == 0 assert _sensor.charger_voltage == 0 assert _sensor.charge_energy_added == 12.41 + + +@pytest.mark.asyncio +async def test_async_update_in_kmh(monkeypatch): + """Test async_update() for units in km/h.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["gui_settings"]["gui_distance_units"] = "km/hr" + _data["gui_settings"]["gui_range_display"] = "Rated" + _data["charge_state"]["charge_rate"] = 22 + _data["charge_state"]["charge_miles_added_rated"] = 44 + _sensor = ChargingSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert _sensor.charging_rate == 35.41 + assert _sensor.added_range == 70.81 + + +@pytest.mark.asyncio +async def test_async_update_in_mph(monkeypatch): + """Test async_update() for units in mph.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["gui_settings"]["gui_distance_units"] = "mi/hr" + _data["gui_settings"]["gui_range_display"] = "Rated" + _data["charge_state"]["charge_rate"] = 22 + _data["charge_state"]["charge_miles_added_rated"] = 44 + _sensor = ChargingSensor(_data, _controller) + + await _sensor.async_update() + + assert not _sensor is None + assert _sensor.charging_rate == 22 + assert _sensor.added_range == 44 diff --git a/tests/unit_tests/test_odometer_sensor.py b/tests/unit_tests/test_odometer_sensor.py index efa15473..637071b3 100644 --- a/tests/unit_tests/test_odometer_sensor.py +++ b/tests/unit_tests/test_odometer_sensor.py @@ -77,3 +77,41 @@ async def test_async_update(monkeypatch): assert not _odometer is None assert not _odometer.get_value() is None assert _odometer.get_value() == 12345.7 + + +@pytest.mark.asyncio +async def test_async_update_in_kmh(monkeypatch): + """Test async_update() for units in km/h.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["gui_settings"]["gui_distance_units"] = "km/hr" + _data["vehicle_state"]["odometer"] = 12345.6789 + _odometer = Odometer(_data, _controller) + + await _odometer.async_update() + + assert not _odometer is None + assert not _odometer.get_value() is None + assert _odometer.get_value() == 12345.7 + + +@pytest.mark.asyncio +async def test_async_update_in_mph(monkeypatch): + """Test async_update() for units in mph.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _data["gui_settings"]["gui_distance_units"] = "mi/hr" + _data["vehicle_state"]["odometer"] = 12345.6789 + _odometer = Odometer(_data, _controller) + + await _odometer.async_update() + + assert not _odometer is None + assert not _odometer.get_value() is None + assert _odometer.get_value() == 12345.7 diff --git a/tests/unit_tests/test_range_sensor.py b/tests/unit_tests/test_range_sensor.py index 5ee55e79..2f12130f 100644 --- a/tests/unit_tests/test_range_sensor.py +++ b/tests/unit_tests/test_range_sensor.py @@ -71,7 +71,7 @@ async def test_get_value_rated_on(monkeypatch): _data = _mock.data_request_vehicle() _range = Range(_data, _controller) - _data["charge_state"]["gui_range_display"] = "Rated" + _data["gui_settings"]["gui_range_display"] = "Rated" _data["charge_state"]["battery_range"] = 123.45 _data["charge_state"]["est_battery_range"] = 234.56 _data["charge_state"]["ideal_battery_range"] = 345.67 @@ -102,6 +102,48 @@ async def test_get_value_rated_off(monkeypatch): assert _range.get_value() == 345.67 +@pytest.mark.asyncio +async def test_get_value_in_kmh(monkeypatch): + """Test get_value() for units in km/h'.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + _data["gui_settings"]["gui_distance_units"] = "km/hr" + _data["gui_settings"]["gui_range_display"] = "Rated" + _data["charge_state"]["battery_range"] = 123.45 + _data["charge_state"]["est_battery_range"] = 234.56 + _data["charge_state"]["ideal_battery_range"] = 345.67 + await _range.async_update() + + assert not _range is None + assert not _range.get_value() is None + assert _range.get_value() == 123.45 + + +@pytest.mark.asyncio +async def test_get_value_in_mph(monkeypatch): + """Test get_value() for units in mph'.""" + + _mock = TeslaMock(monkeypatch) + _controller = Controller(None) + + _data = _mock.data_request_vehicle() + _range = Range(_data, _controller) + _data["gui_settings"]["gui_distance_units"] = "mi/hr" + _data["gui_settings"]["gui_range_display"] = "Rated" + _data["charge_state"]["battery_range"] = 123.45 + _data["charge_state"]["est_battery_range"] = 234.56 + _data["charge_state"]["ideal_battery_range"] = 345.67 + await _range.async_update() + + assert not _range is None + assert not _range.get_value() is None + assert _range.get_value() == 123.45 + + @pytest.mark.asyncio async def test_async_update(monkeypatch): """Test async_update().""" From 72359a58716c545d2540bae9d71eab54311b100c Mon Sep 17 00:00:00 2001 From: hobbe Date: Wed, 25 Mar 2020 17:47:51 +0100 Subject: [PATCH 19/19] add unit tests for tesla exception --- tests/unit_tests/test_tesla_exception.py | 153 +++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 tests/unit_tests/test_tesla_exception.py diff --git a/tests/unit_tests/test_tesla_exception.py b/tests/unit_tests/test_tesla_exception.py new file mode 100644 index 00000000..bed9224b --- /dev/null +++ b/tests/unit_tests/test_tesla_exception.py @@ -0,0 +1,153 @@ +"""Test Tesla exception.""" + +from teslajsonpy.exceptions import TeslaException + + +def test_code_as_string(): + """Test error with a string code.""" + + _err = TeslaException("dummy") + + assert not _err is None + assert _err.code == "dummy" + assert _err.message == "dummy" + + +def test_code_lt_300(): + """Test error with a code less than 300.""" + + _err = TeslaException(299) + + assert not _err is None + assert _err.code == 299 + assert _err.message == "" + + +def test_code_400(): + """Test error 400.""" + + _err = TeslaException(400) + + assert not _err is None + assert _err.code == 400 + assert _err.message == "UNKNOWN_ERROR_400" + + +def test_code_401(): + """Test error 401.""" + + _err = TeslaException(401) + + assert not _err is None + assert _err.code == 401 + assert _err.message == "UNAUTHORIZED" + + +def test_code_402(): + """Test error 402.""" + + _err = TeslaException(402) + + assert not _err is None + assert _err.code == 402 + assert _err.message == "UNKNOWN_ERROR_402" + + +def test_code_403(): + """Test error 403.""" + + _err = TeslaException(403) + + assert not _err is None + assert _err.code == 403 + assert _err.message == "UNKNOWN_ERROR_403" + + +def test_code_404(): + """Test error 404.""" + + _err = TeslaException(404) + + assert not _err is None + assert _err.code == 404 + assert _err.message == "NOT_FOUND" + + +def test_code_405(): + """Test error 405.""" + + _err = TeslaException(405) + + assert not _err is None + assert _err.code == 405 + assert _err.message == "MOBILE_ACCESS_DISABLED" + + +def test_code_408(): + """Test error 408.""" + + _err = TeslaException(408) + + assert not _err is None + assert _err.code == 408 + assert _err.message == "VEHICLE_UNAVAILABLE" + + +def test_code_423(): + """Test error 423.""" + + _err = TeslaException(423) + + assert not _err is None + assert _err.code == 423 + assert _err.message == "ACCOUNT_LOCKED" + + +def test_code_429(): + """Test error 429.""" + + _err = TeslaException(429) + + assert not _err is None + assert _err.code == 429 + assert _err.message == "TOO_MANY_REQUESTS" + + +def test_code_500(): + """Test error 500.""" + + _err = TeslaException(500) + + assert not _err is None + assert _err.code == 500 + assert _err.message == "SERVER_ERROR" + + +def test_code_503(): + """Test error 503.""" + + _err = TeslaException(503) + + assert not _err is None + assert _err.code == 503 + assert _err.message == "SERVICE_MAINTENANCE" + + +def test_code_504(): + """Test error 504.""" + + _err = TeslaException(504) + + assert not _err is None + assert _err.code == 504 + assert _err.message == "UPSTREAM_TIMEOUT" + + +def test_code_505(): + """Test error 505.""" + + _err = TeslaException(505) + + assert not _err is None + assert _err.code == 505 + assert _err.message == "UNKNOWN_ERROR_505"