diff --git a/.coveragerc b/.coveragerc index 7594d2d2d98..0ef8c5dfe29 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1470,12 +1470,6 @@ omit = homeassistant/components/traccar_server/entity.py homeassistant/components/traccar_server/helpers.py homeassistant/components/traccar_server/sensor.py - homeassistant/components/tractive/__init__.py - homeassistant/components/tractive/binary_sensor.py - homeassistant/components/tractive/device_tracker.py - homeassistant/components/tractive/entity.py - homeassistant/components/tractive/sensor.py - homeassistant/components/tractive/switch.py homeassistant/components/tradfri/__init__.py homeassistant/components/tradfri/base_class.py homeassistant/components/tradfri/coordinator.py diff --git a/tests/components/tractive/__init__.py b/tests/components/tractive/__init__.py index dcde4b87436..48254a80f37 100644 --- a/tests/components/tractive/__init__.py +++ b/tests/components/tractive/__init__.py @@ -1 +1,18 @@ """Tests for the tractive integration.""" + +from unittest.mock import patch + +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + + +async def init_integration( + hass: HomeAssistant, entry: MockConfigEntry +) -> MockConfigEntry: + """Set up the Tractive integration in Home Assistant.""" + entry.add_to_hass(hass) + + with patch("homeassistant.components.tractive.TractiveClient._listen"): + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() diff --git a/tests/components/tractive/conftest.py b/tests/components/tractive/conftest.py index 2137919ce98..5492f58b2ba 100644 --- a/tests/components/tractive/conftest.py +++ b/tests/components/tractive/conftest.py @@ -1,14 +1,16 @@ """Common fixtures for the Tractive tests.""" from collections.abc import Generator +from typing import Any from unittest.mock import AsyncMock, Mock, patch from aiotractive.trackable_object import TrackableObject from aiotractive.tracker import Tracker import pytest -from homeassistant.components.tractive.const import DOMAIN +from homeassistant.components.tractive.const import DOMAIN, SERVER_UNAVAILABLE from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.helpers.dispatcher import async_dispatcher_send from tests.common import MockConfigEntry, load_json_object_fixture @@ -17,7 +19,72 @@ from tests.common import MockConfigEntry, load_json_object_fixture def mock_tractive_client() -> Generator[AsyncMock, None, None]: """Mock a Tractive client.""" - trackable_object = load_json_object_fixture("tractive/trackable_object.json") + def send_hardware_event( + entry: MockConfigEntry, event: dict[str, Any] | None = None + ): + """Send hardware event.""" + if event is None: + event = { + "tracker_id": "device_id_123", + "hardware": {"battery_level": 88}, + "tracker_state": "operational", + "charging_state": "CHARGING", + } + entry.runtime_data.client._send_hardware_update(event) + + def send_wellness_event( + entry: MockConfigEntry, event: dict[str, Any] | None = None + ): + """Send wellness event.""" + if event is None: + event = { + "pet_id": "pet_id_123", + "sleep": {"minutes_day_sleep": 100, "minutes_night_sleep": 300}, + "wellness": {"activity_label": "ok", "sleep_label": "good"}, + "activity": { + "calories": 999, + "minutes_goal": 200, + "minutes_active": 150, + "minutes_rest": 122, + }, + } + entry.runtime_data.client._send_wellness_update(event) + + def send_position_event( + entry: MockConfigEntry, event: dict[str, Any] | None = None + ): + """Send position event.""" + if event is None: + event = { + "tracker_id": "device_id_123", + "position": { + "latlong": [22.333, 44.555], + "accuracy": 99, + "sensor_used": "GPS", + }, + } + entry.runtime_data.client._send_position_update(event) + + def send_switch_event(entry: MockConfigEntry, event: dict[str, Any] | None = None): + """Send switch event.""" + if event is None: + event = { + "tracker_id": "device_id_123", + "buzzer_control": {"active": True}, + "led_control": {"active": False}, + "live_tracking": {"active": True}, + } + entry.runtime_data.client._send_switch_update(event) + + def send_server_unavailable_event(hass): + """Send server unavailable event.""" + async_dispatcher_send(hass, f"{SERVER_UNAVAILABLE}-12345") + + trackable_object = load_json_object_fixture("trackable_object.json", DOMAIN) + tracker_details = load_json_object_fixture("tracker_details.json", DOMAIN) + tracker_hw_info = load_json_object_fixture("tracker_hw_info.json", DOMAIN) + tracker_pos_report = load_json_object_fixture("tracker_pos_report.json", DOMAIN) + with ( patch( "homeassistant.components.tractive.aiotractive.Tractive", autospec=True @@ -33,7 +100,21 @@ def mock_tractive_client() -> Generator[AsyncMock, None, None]: details=AsyncMock(return_value=trackable_object), ), ] - client.tracker.return_value = Mock(spec=Tracker) + client.tracker.return_value = AsyncMock( + spec=Tracker, + details=AsyncMock(return_value=tracker_details), + hw_info=AsyncMock(return_value=tracker_hw_info), + pos_report=AsyncMock(return_value=tracker_pos_report), + set_live_tracking_active=AsyncMock(return_value={"pending": True}), + set_buzzer_active=AsyncMock(return_value={"pending": True}), + set_led_active=AsyncMock(return_value={"pending": True}), + ) + + client.send_hardware_event = send_hardware_event + client.send_wellness_event = send_wellness_event + client.send_position_event = send_position_event + client.send_switch_event = send_switch_event + client.send_server_unavailable_event = send_server_unavailable_event yield client diff --git a/tests/components/tractive/fixtures/trackable_object.json b/tests/components/tractive/fixtures/trackable_object.json index 066cc613a80..a33dd314bff 100644 --- a/tests/components/tractive/fixtures/trackable_object.json +++ b/tests/components/tractive/fixtures/trackable_object.json @@ -1,7 +1,8 @@ { - "device_id": "54321", + "device_id": "device_id_123", + "_id": "pet_id_123", "details": { - "_id": "xyz123", + "_id": "pet_id_123", "_version": "123abc", "name": "Test Pet", "pet_type": "DOG", diff --git a/tests/components/tractive/fixtures/tracker_details.json b/tests/components/tractive/fixtures/tracker_details.json new file mode 100644 index 00000000000..0acde4b991a --- /dev/null +++ b/tests/components/tractive/fixtures/tracker_details.json @@ -0,0 +1,38 @@ +{ + "_id": "device_id_123", + "_version": "abcd-123-efgh-456", + "hw_id": "device_id_123", + "model_number": "TG4422", + "hw_edition": "BLUE-WHITE", + "bluetooth_mac": null, + "geofence_sensitivity": "HIGH", + "battery_save_mode": null, + "read_only": false, + "demo": false, + "self_test_available": false, + "capabilities": [ + "LT", + "BUZZER", + "LT_BLE", + "LED_BLE", + "BUZZER_BLE", + "HW_REPORTS_BLE", + "WIFI_SCAN_REPORTS_BLE", + "LED", + "ACTIVITY_TRACKING", + "WIFI_ZONE", + "SLEEP_TRACKING" + ], + "supported_geofence_types": ["CIRCLE", "RECTANGLE", "POLYGON"], + "fw_version": "123.456", + "state": "OPERATIONAL", + "state_reason": "POWER_SAVING", + "charging_state": "NOT_CHARGING", + "battery_state": "FULL", + "power_saving_zone_id": "abcdef12345", + "prioritized_zone_id": "098765", + "prioritized_zone_type": "POWER_SAVING", + "prioritized_zone_last_seen_at": 1716106551, + "prioritized_zone_entered_at": 1716105066, + "_type": "tracker" +} diff --git a/tests/components/tractive/fixtures/tracker_hw_info.json b/tests/components/tractive/fixtures/tracker_hw_info.json new file mode 100644 index 00000000000..1f2929b328a --- /dev/null +++ b/tests/components/tractive/fixtures/tracker_hw_info.json @@ -0,0 +1,11 @@ +{ + "time": 1716105966, + "battery_level": 96, + "clip_mounted_state": null, + "_id": "device_id_123", + "_type": "device_hw_report", + "_version": "e87646946", + "report_id": "098123", + "power_saving_zone_id": "abcdef12345", + "hw_status": null +} diff --git a/tests/components/tractive/fixtures/tracker_pos_report.json b/tests/components/tractive/fixtures/tracker_pos_report.json new file mode 100644 index 00000000000..2fafd960ee8 --- /dev/null +++ b/tests/components/tractive/fixtures/tracker_pos_report.json @@ -0,0 +1,16 @@ +{ + "time": 1716106551, + "time_rcvd": 1716106561, + "pos_status": null, + "latlong": [33.222222, 44.555555], + "speed": null, + "pos_uncertainty": 30, + "_id": "device_id_123", + "_type": "device_pos_report", + "_version": "b7422b930", + "altitude": 85, + "report_id": "098123", + "sensor_used": "KNOWN_WIFI", + "nearby_user_id": null, + "power_saving_zone_id": "abcdef12345" +} diff --git a/tests/components/tractive/snapshots/test_binary_sensor.ambr b/tests/components/tractive/snapshots/test_binary_sensor.ambr new file mode 100644 index 00000000000..c6d50fb0fbb --- /dev/null +++ b/tests/components/tractive/snapshots/test_binary_sensor.ambr @@ -0,0 +1,95 @@ +# serializer version: 1 +# name: test_binary_sensor[binary_sensor.test_pet_tracker_battery_charging-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.test_pet_tracker_battery_charging', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tracker battery charging', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_battery_charging', + 'unique_id': 'pet_id_123_battery_charging', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensor[binary_sensor.test_pet_tracker_battery_charging-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery_charging', + 'friendly_name': 'Test Pet Tracker battery charging', + }), + 'context': , + 'entity_id': 'binary_sensor.test_pet_tracker_battery_charging', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_sensor[binary_sensor.test_pet_tracker_battery_charging-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.test_pet_tracker_battery_charging', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tracker battery charging', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_battery_charging', + 'unique_id': 'pet_id_123_battery_charging', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[binary_sensor.test_pet_tracker_battery_charging-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery_charging', + 'friendly_name': 'Test Pet Tracker battery charging', + }), + 'context': , + 'entity_id': 'binary_sensor.test_pet_tracker_battery_charging', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/tractive/snapshots/test_device_tracker.ambr b/tests/components/tractive/snapshots/test_device_tracker.ambr new file mode 100644 index 00000000000..3a145a48b5a --- /dev/null +++ b/tests/components/tractive/snapshots/test_device_tracker.ambr @@ -0,0 +1,103 @@ +# serializer version: 1 +# name: test_device_tracker[device_tracker.test_pet_tracker-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'device_tracker', + 'entity_category': , + 'entity_id': 'device_tracker.test_pet_tracker', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Tracker', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker', + 'unique_id': 'pet_id_123', + 'unit_of_measurement': None, + }) +# --- +# name: test_device_tracker[device_tracker.test_pet_tracker-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'battery_level': 88, + 'friendly_name': 'Test Pet Tracker', + 'gps_accuracy': 99, + 'latitude': 22.333, + 'longitude': 44.555, + 'source_type': , + }), + 'context': , + 'entity_id': 'device_tracker.test_pet_tracker', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'not_home', + }) +# --- +# name: test_sensor[device_tracker.test_pet_tracker-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'device_tracker', + 'entity_category': , + 'entity_id': 'device_tracker.test_pet_tracker', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Tracker', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker', + 'unique_id': 'pet_id_123', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[device_tracker.test_pet_tracker-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'battery_level': 88, + 'friendly_name': 'Test Pet Tracker', + 'gps_accuracy': 99, + 'latitude': 22.333, + 'longitude': 44.555, + 'source_type': , + }), + 'context': , + 'entity_id': 'device_tracker.test_pet_tracker', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'not_home', + }) +# --- diff --git a/tests/components/tractive/snapshots/test_diagnostics.ambr b/tests/components/tractive/snapshots/test_diagnostics.ambr index 11bf7bae2a3..a66247749b7 100644 --- a/tests/components/tractive/snapshots/test_diagnostics.ambr +++ b/tests/components/tractive/snapshots/test_diagnostics.ambr @@ -21,6 +21,7 @@ }), 'trackables': list([ dict({ + '_id': '**REDACTED**', 'details': dict({ '_id': '**REDACTED**', '_type': 'pet_detail', @@ -64,7 +65,7 @@ 'weight': 23700, 'weight_is_default': None, }), - 'device_id': '54321', + 'device_id': 'device_id_123', }), ]), }) diff --git a/tests/components/tractive/snapshots/test_sensor.ambr b/tests/components/tractive/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..f1ed397450e --- /dev/null +++ b/tests/components/tractive/snapshots/test_sensor.ambr @@ -0,0 +1,524 @@ +# serializer version: 1 +# name: test_sensor[sensor.test_pet_activity-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'good', + 'low', + 'ok', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_activity', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Activity', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'activity', + 'unique_id': 'pet_id_123_activity_label', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.test_pet_activity-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Test Pet Activity', + 'options': list([ + 'good', + 'low', + 'ok', + ]), + }), + 'context': , + 'entity_id': 'sensor.test_pet_activity', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'ok', + }) +# --- +# name: test_sensor[sensor.test_pet_activity_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_activity_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Activity time', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'activity_time', + 'unique_id': 'pet_id_123_minutes_active', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.test_pet_activity_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Activity time', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_pet_activity_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '150', + }) +# --- +# name: test_sensor[sensor.test_pet_calories_burned-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_calories_burned', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Calories burned', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'calories', + 'unique_id': 'pet_id_123_calories', + 'unit_of_measurement': 'kcal', + }) +# --- +# name: test_sensor[sensor.test_pet_calories_burned-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Calories burned', + 'state_class': , + 'unit_of_measurement': 'kcal', + }), + 'context': , + 'entity_id': 'sensor.test_pet_calories_burned', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '999', + }) +# --- +# name: test_sensor[sensor.test_pet_daily_goal-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_daily_goal', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Daily goal', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'daily_goal', + 'unique_id': 'pet_id_123_daily_goal', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.test_pet_daily_goal-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Daily goal', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_pet_daily_goal', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '200', + }) +# --- +# name: test_sensor[sensor.test_pet_day_sleep-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_day_sleep', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Day sleep', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'minutes_day_sleep', + 'unique_id': 'pet_id_123_minutes_day_sleep', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.test_pet_day_sleep-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Day sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_pet_day_sleep', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100', + }) +# --- +# name: test_sensor[sensor.test_pet_night_sleep-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_night_sleep', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Night sleep', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'minutes_night_sleep', + 'unique_id': 'pet_id_123_minutes_night_sleep', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.test_pet_night_sleep-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Night sleep', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_pet_night_sleep', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '300', + }) +# --- +# name: test_sensor[sensor.test_pet_rest_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_rest_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Rest time', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'rest_time', + 'unique_id': 'pet_id_123_minutes_rest', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor[sensor.test_pet_rest_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Rest time', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_pet_rest_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '122', + }) +# --- +# name: test_sensor[sensor.test_pet_sleep-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'good', + 'low', + 'ok', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_pet_sleep', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Sleep', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'sleep', + 'unique_id': 'pet_id_123_sleep_label', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.test_pet_sleep-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Test Pet Sleep', + 'options': list([ + 'good', + 'low', + 'ok', + ]), + }), + 'context': , + 'entity_id': 'sensor.test_pet_sleep', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'good', + }) +# --- +# name: test_sensor[sensor.test_pet_tracker_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_pet_tracker_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tracker battery', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_battery_level', + 'unique_id': 'pet_id_123_battery_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor[sensor.test_pet_tracker_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Test Pet Tracker battery', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.test_pet_tracker_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '88', + }) +# --- +# name: test_sensor[sensor.test_pet_tracker_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'inaccurate_position', + 'not_reporting', + 'operational', + 'system_shutdown_user', + 'system_startup', + ]), + }), + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.test_pet_tracker_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Tracker state', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_state', + 'unique_id': 'pet_id_123_tracker_state', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[sensor.test_pet_tracker_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Test Pet Tracker state', + 'options': list([ + 'inaccurate_position', + 'not_reporting', + 'operational', + 'system_shutdown_user', + 'system_startup', + ]), + }), + 'context': , + 'entity_id': 'sensor.test_pet_tracker_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'operational', + }) +# --- diff --git a/tests/components/tractive/snapshots/test_switch.ambr b/tests/components/tractive/snapshots/test_switch.ambr new file mode 100644 index 00000000000..ea9ea9d9e48 --- /dev/null +++ b/tests/components/tractive/snapshots/test_switch.ambr @@ -0,0 +1,277 @@ +# serializer version: 1 +# name: test_sensor[switch.test_pet_live_tracking-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_pet_live_tracking', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Live tracking', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'live_tracking', + 'unique_id': 'pet_id_123_live_tracking', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[switch.test_pet_live_tracking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Live tracking', + }), + 'context': , + 'entity_id': 'switch.test_pet_live_tracking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_sensor[switch.test_pet_tracker_buzzer-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_pet_tracker_buzzer', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Tracker buzzer', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_buzzer', + 'unique_id': 'pet_id_123_buzzer', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[switch.test_pet_tracker_buzzer-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Tracker buzzer', + }), + 'context': , + 'entity_id': 'switch.test_pet_tracker_buzzer', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_sensor[switch.test_pet_tracker_led-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_pet_tracker_led', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Tracker LED', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_led', + 'unique_id': 'pet_id_123_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor[switch.test_pet_tracker_led-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Tracker LED', + }), + 'context': , + 'entity_id': 'switch.test_pet_tracker_led', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_switch[switch.test_pet_live_tracking-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_pet_live_tracking', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Live tracking', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'live_tracking', + 'unique_id': 'pet_id_123_live_tracking', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.test_pet_live_tracking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Live tracking', + }), + 'context': , + 'entity_id': 'switch.test_pet_live_tracking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[switch.test_pet_tracker_buzzer-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_pet_tracker_buzzer', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Tracker buzzer', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_buzzer', + 'unique_id': 'pet_id_123_buzzer', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.test_pet_tracker_buzzer-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Tracker buzzer', + }), + 'context': , + 'entity_id': 'switch.test_pet_tracker_buzzer', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_switch[switch.test_pet_tracker_led-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.test_pet_tracker_led', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Tracker LED', + 'platform': 'tractive', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'tracker_led', + 'unique_id': 'pet_id_123_led', + 'unit_of_measurement': None, + }) +# --- +# name: test_switch[switch.test_pet_tracker_led-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test Pet Tracker LED', + }), + 'context': , + 'entity_id': 'switch.test_pet_tracker_led', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/tractive/test_binary_sensor.py b/tests/components/tractive/test_binary_sensor.py new file mode 100644 index 00000000000..cd7ffbc3da3 --- /dev/null +++ b/tests/components/tractive/test_binary_sensor.py @@ -0,0 +1,29 @@ +"""Test the Tractive binary sensor platform.""" + +from unittest.mock import AsyncMock, patch + +from syrupy import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import init_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_binary_sensor( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test states of the binary sensor.""" + with patch("homeassistant.components.tractive.PLATFORMS", [Platform.BINARY_SENSOR]): + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_hardware_event(mock_config_entry) + await hass.async_block_till_done() + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) diff --git a/tests/components/tractive/test_device_tracker.py b/tests/components/tractive/test_device_tracker.py new file mode 100644 index 00000000000..ff78173ef7b --- /dev/null +++ b/tests/components/tractive/test_device_tracker.py @@ -0,0 +1,61 @@ +"""Test the Tractive device tracker platform.""" + +from unittest.mock import AsyncMock, patch + +from syrupy import SnapshotAssertion + +from homeassistant.components.device_tracker import SourceType +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import init_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_device_tracker( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test states of the device_tracker.""" + with patch( + "homeassistant.components.tractive.PLATFORMS", [Platform.DEVICE_TRACKER] + ): + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_position_event(mock_config_entry) + mock_tractive_client.send_hardware_event(mock_config_entry) + await hass.async_block_till_done() + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +async def test_source_type_phone( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the device tracker with source type phone.""" + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_position_event( + mock_config_entry, + { + "tracker_id": "device_id_123", + "position": { + "latlong": [22.333, 44.555], + "accuracy": 99, + "sensor_used": "PHONE", + }, + }, + ) + mock_tractive_client.send_hardware_event(mock_config_entry) + await hass.async_block_till_done() + + assert ( + hass.states.get("device_tracker.test_pet_tracker").attributes["source_type"] + is SourceType.BLUETOOTH + ) diff --git a/tests/components/tractive/test_diagnostics.py b/tests/components/tractive/test_diagnostics.py index acf4a3ed151..cc4fcdeba15 100644 --- a/tests/components/tractive/test_diagnostics.py +++ b/tests/components/tractive/test_diagnostics.py @@ -1,12 +1,12 @@ """Test the Tractive diagnostics.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock from syrupy import SnapshotAssertion -from homeassistant.components.tractive.const import DOMAIN from homeassistant.core import HomeAssistant -from homeassistant.setup import async_setup_component + +from . import init_integration from tests.common import MockConfigEntry from tests.components.diagnostics import get_diagnostics_for_config_entry @@ -21,9 +21,8 @@ async def test_entry_diagnostics( mock_config_entry: MockConfigEntry, ) -> None: """Test config entry diagnostics.""" - mock_config_entry.add_to_hass(hass) - with patch("homeassistant.components.tractive.PLATFORMS", []): - assert await async_setup_component(hass, DOMAIN, {}) + await init_integration(hass, mock_config_entry) + result = await get_diagnostics_for_config_entry( hass, hass_client, mock_config_entry ) diff --git a/tests/components/tractive/test_init.py b/tests/components/tractive/test_init.py new file mode 100644 index 00000000000..3387232b231 --- /dev/null +++ b/tests/components/tractive/test_init.py @@ -0,0 +1,163 @@ +"""Test init of Tractive integration.""" + +from unittest.mock import AsyncMock, patch + +from aiotractive.exceptions import TractiveError, UnauthorizedError +import pytest + +from homeassistant.components.tractive.const import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant + +from . import init_integration + +from tests.common import MockConfigEntry + + +async def test_setup_entry( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test a successful setup entry.""" + await init_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.LOADED + + +async def test_unload_entry( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test successful unload of entry.""" + await init_integration(hass, mock_config_entry) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert mock_config_entry.state is ConfigEntryState.LOADED + + with patch("homeassistant.components.tractive.TractiveClient.unsubscribe"): + assert await hass.config_entries.async_unload(mock_config_entry.entry_id) + await hass.async_block_till_done() + + assert mock_config_entry.state is ConfigEntryState.NOT_LOADED + assert not hass.data.get(DOMAIN) + + +@pytest.mark.parametrize( + ("method", "exc", "entry_state"), + [ + ("authenticate", UnauthorizedError, ConfigEntryState.SETUP_ERROR), + ("authenticate", TractiveError, ConfigEntryState.SETUP_RETRY), + ("trackable_objects", TractiveError, ConfigEntryState.SETUP_RETRY), + ], +) +async def test_setup_failed( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, + method: str, + exc: Exception, + entry_state: ConfigEntryState, +) -> None: + """Test for setup failure.""" + getattr(mock_tractive_client, method).side_effect = exc + + await init_integration(hass, mock_config_entry) + + assert mock_config_entry.state is entry_state + + +async def test_config_not_ready( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test for setup failure if the tracker_details doesn't contain '_id'.""" + mock_tractive_client.tracker.return_value.details.return_value.pop("_id") + + await init_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_trackable_without_details( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test a successful setup entry.""" + mock_tractive_client.trackable_objects.return_value[0].details.return_value = { + "device_id": "xyz098" + } + + await init_integration(hass, mock_config_entry) + + assert ( + "Tracker xyz098 has no details and will be skipped. This happens for shared trackers" + in caplog.text + ) + assert mock_config_entry.state is ConfigEntryState.LOADED + + +async def test_trackable_without_device_id( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test a successful setup entry.""" + mock_tractive_client.trackable_objects.return_value[0].details.return_value = { + "device_id": None + } + + await init_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.LOADED + + +async def test_unsubscribe_on_ha_stop( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test unsuscribe when HA stops.""" + await init_integration(hass, mock_config_entry) + + with patch( + "homeassistant.components.tractive.TractiveClient.unsubscribe" + ) as mock_unsuscribe: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + + assert mock_unsuscribe.called + + +async def test_server_unavailable( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test states of the sensor.""" + entity_id = "sensor.test_pet_tracker_battery" + + await init_integration(hass, mock_config_entry) + + # send event to make the entity available + mock_tractive_client.send_hardware_event(mock_config_entry) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state != STATE_UNAVAILABLE + + # send server unavailable event, the entity should be unavailable + mock_tractive_client.send_server_unavailable_event(hass) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == STATE_UNAVAILABLE + + # send event to make the entity available once again + mock_tractive_client.send_hardware_event(mock_config_entry) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state != STATE_UNAVAILABLE diff --git a/tests/components/tractive/test_sensor.py b/tests/components/tractive/test_sensor.py new file mode 100644 index 00000000000..b53cc3c4d64 --- /dev/null +++ b/tests/components/tractive/test_sensor.py @@ -0,0 +1,30 @@ +"""Test the Tractive sensor platform.""" + +from unittest.mock import AsyncMock, patch + +from syrupy import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import init_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_sensor( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test states of the sensor.""" + with patch("homeassistant.components.tractive.PLATFORMS", [Platform.SENSOR]): + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_hardware_event(mock_config_entry) + mock_tractive_client.send_wellness_event(mock_config_entry) + await hass.async_block_till_done() + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) diff --git a/tests/components/tractive/test_switch.py b/tests/components/tractive/test_switch.py new file mode 100644 index 00000000000..cc7ce6cf81f --- /dev/null +++ b/tests/components/tractive/test_switch.py @@ -0,0 +1,228 @@ +"""Test the Tractive switch platform.""" + +from unittest.mock import AsyncMock, patch + +from aiotractive.exceptions import TractiveError +from syrupy import SnapshotAssertion + +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import init_integration + +from tests.common import MockConfigEntry, snapshot_platform + + +async def test_switch( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test states of the switch.""" + with patch("homeassistant.components.tractive.PLATFORMS", [Platform.SWITCH]): + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_switch_event(mock_config_entry) + await hass.async_block_till_done() + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +async def test_switch_on( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the switch can be turned on.""" + entity_id = "switch.test_pet_tracker_led" + + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_switch_event(mock_config_entry) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + mock_tractive_client.send_switch_event( + mock_config_entry, + {"tracker_id": "device_id_123", "led_control": {"active": True}}, + ) + await hass.async_block_till_done() + + assert mock_tractive_client.tracker.return_value.set_led_active.call_count == 1 + assert ( + mock_tractive_client.tracker.return_value.set_led_active.call_args[0][0] is True + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_ON + + +async def test_switch_off( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the switch can be turned off.""" + entity_id = "switch.test_pet_tracker_buzzer" + + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_switch_event(mock_config_entry) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_ON + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + mock_tractive_client.send_switch_event( + mock_config_entry, + {"tracker_id": "device_id_123", "buzzer_control": {"active": False}}, + ) + await hass.async_block_till_done() + + assert mock_tractive_client.tracker.return_value.set_buzzer_active.call_count == 1 + assert ( + mock_tractive_client.tracker.return_value.set_buzzer_active.call_args[0][0] + is False + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + + +async def test_live_tracking_switch( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the live_tracking switch.""" + entity_id = "switch.test_pet_live_tracking" + + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_switch_event(mock_config_entry) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_ON + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + mock_tractive_client.send_switch_event( + mock_config_entry, + {"tracker_id": "device_id_123", "live_tracking": {"active": False}}, + ) + await hass.async_block_till_done() + + assert ( + mock_tractive_client.tracker.return_value.set_live_tracking_active.call_count + == 1 + ) + assert ( + mock_tractive_client.tracker.return_value.set_live_tracking_active.call_args[0][ + 0 + ] + is False + ) + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + + +async def test_switch_on_with_exception( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the switch turn on with exception.""" + entity_id = "switch.test_pet_tracker_led" + + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_switch_event(mock_config_entry) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + + mock_tractive_client.tracker.return_value.set_led_active.side_effect = TractiveError + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_OFF + + +async def test_switch_off_with_exception( + hass: HomeAssistant, + mock_tractive_client: AsyncMock, + mock_config_entry: MockConfigEntry, +) -> None: + """Test the switch turn off with exception.""" + entity_id = "switch.test_pet_tracker_buzzer" + + await init_integration(hass, mock_config_entry) + + mock_tractive_client.send_switch_event(mock_config_entry) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_ON + + mock_tractive_client.tracker.return_value.set_buzzer_active.side_effect = ( + TractiveError + ) + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_ON