Repository: alandtse/alexa_media_player

Issue:
Additional automated testing
We should add some additional unit testing for the CI to increase coverage and cover major functionality.


Discussion:
@alandtse this issue was automatically closed because it did not follow the [issue template](https://github.com/alandtse/alexa_media_player/issues/new/choose) and this warning: **IMPORTANT: Please search the issues, including closed issues, and the [FAQ](https://github.com/alandtse/alexa_media_player/wiki/FAQ) before opening a new issue. The template is mandatory; failure to use it will result in issue closure.**



Patch:
diff --git a/custom_components/alexa_media/exceptions.py b/custom_components/alexa_media/exceptions.py
index f94f4dedc..a44b45c49 100644
--- a/custom_components/alexa_media/exceptions.py
+++ b/custom_components/alexa_media/exceptions.py
@@ -1,34 +1,34 @@
-"""Alexa Media Exceptions"""
-
-
-class EmptyDataException(Exception):
-    """Empty data exception"""
-
-
-class ForbiddenException(Exception):
-    """Forbidden exception"""
-
-
-class LoginForbiddenException(Exception):
-    """Login forbidden exception"""
-
-
-class LoginInvalidException(Exception):
-    """Invalid login exception"""
-
-    def __init__(self, attempts_remaining):
-        self.attempts_remaining = attempts_remaining
-        super().__init__(
-            f"Invalid login credentials. {attempts_remaining} attempts remaining."
-        )
-
-
-class TimeoutException(Exception):
-    """Timeout exception"""
-
-    def __init__(self, message=""):
-        super().__init__(f"Timeour exception: {message}")
-
-
-class UnexpectedApiException(Exception):
-    """Unexpected API exception"""
+"""Alexa Media Exceptions"""
+
+
+class EmptyDataException(Exception):
+    """Empty data exception"""
+
+
+class ForbiddenException(Exception):
+    """Forbidden exception"""
+
+
+class LoginForbiddenException(Exception):
+    """Login forbidden exception"""
+
+
+class LoginInvalidException(Exception):
+    """Invalid login exception"""
+
+    def __init__(self, attempts_remaining):
+        self.attempts_remaining = attempts_remaining
+        super().__init__(
+            f"Invalid login credentials. {attempts_remaining} attempts remaining."
+        )
+
+
+class TimeoutException(Exception):
+    """Timeout exception"""
+
+    def __init__(self, message=""):
+        super().__init__(f"Timeout exception: {message}")
+
+
+class UnexpectedApiException(Exception):
+    """Unexpected API exception"""


Test patch:
diff --git a/tests/test_alexa_entity.py b/tests/test_alexa_entity.py
new file mode 100644
index 000000000..944ecf96f
--- /dev/null
+++ b/tests/test_alexa_entity.py
@@ -0,0 +1,299 @@
+"""Test the alexa_entity module utility functions."""
+
+import pytest
+
+from custom_components.alexa_media.alexa_entity import (
+    has_capability,
+    is_hue_v1,
+    is_known_ha_bridge,
+    is_local,
+    is_skill,
+)
+
+
+class TestHasCapability:
+    """Test the has_capability function."""
+
+    def test_has_capability_with_valid_interface(self):
+        """Test has_capability returns True when interface and property match."""
+        appliance = {
+            "capabilities": [
+                {
+                    "interfaceName": "Alexa.PowerController",
+                    "properties": {
+                        "retrievable": True,
+                        "proactivelyReported": False,
+                        "supported": [{"name": "powerState"}],
+                    },
+                }
+            ]
+        }
+
+        assert has_capability(appliance, "Alexa.PowerController", "powerState")
+
+    def test_has_capability_with_proactively_reported(self):
+        """Test has_capability returns True when proactivelyReported is True."""
+        appliance = {
+            "capabilities": [
+                {
+                    "interfaceName": "Alexa.BrightnessController",
+                    "properties": {
+                        "retrievable": False,
+                        "proactivelyReported": True,
+                        "supported": [{"name": "brightness"}],
+                    },
+                }
+            ]
+        }
+
+        assert has_capability(appliance, "Alexa.BrightnessController", "brightness")
+
+    def test_has_capability_no_matching_interface(self):
+        """Test has_capability returns False when interface doesn't match."""
+        appliance = {
+            "capabilities": [
+                {
+                    "interfaceName": "Alexa.PowerController",
+                    "properties": {
+                        "retrievable": True,
+                        "proactivelyReported": False,
+                        "supported": [{"name": "powerState"}],
+                    },
+                }
+            ]
+        }
+
+        assert not has_capability(appliance, "Alexa.BrightnessController", "brightness")
+
+    def test_has_capability_no_matching_property(self):
+        """Test has_capability returns False when property doesn't match."""
+        appliance = {
+            "capabilities": [
+                {
+                    "interfaceName": "Alexa.PowerController",
+                    "properties": {
+                        "retrievable": True,
+                        "proactivelyReported": False,
+                        "supported": [{"name": "powerState"}],
+                    },
+                }
+            ]
+        }
+
+        assert not has_capability(appliance, "Alexa.PowerController", "brightness")
+
+    def test_has_capability_no_properties(self):
+        """Test has_capability returns False when properties are missing."""
+        appliance = {"capabilities": [{"interfaceName": "Alexa.PowerController"}]}
+
+        assert not has_capability(appliance, "Alexa.PowerController", "powerState")
+
+    def test_has_capability_not_retrievable_or_reported(self):
+        """Test has_capability returns False when not retrievable or proactively reported."""
+        appliance = {
+            "capabilities": [
+                {
+                    "interfaceName": "Alexa.PowerController",
+                    "properties": {
+                        "retrievable": False,
+                        "proactivelyReported": False,
+                        "supported": [{"name": "powerState"}],
+                    },
+                }
+            ]
+        }
+
+        assert not has_capability(appliance, "Alexa.PowerController", "powerState")
+
+    def test_has_capability_empty_capabilities(self):
+        """Test has_capability returns False when capabilities list is empty."""
+        appliance = {"capabilities": []}
+
+        assert not has_capability(appliance, "Alexa.PowerController", "powerState")
+
+    def test_has_capability_multiple_properties(self):
+        """Test has_capability with multiple supported properties."""
+        appliance = {
+            "capabilities": [
+                {
+                    "interfaceName": "Alexa.ColorController",
+                    "properties": {
+                        "retrievable": True,
+                        "proactivelyReported": False,
+                        "supported": [
+                            {"name": "color"},
+                            {"name": "colorTemperatureInKelvin"},
+                        ],
+                    },
+                }
+            ]
+        }
+
+        assert has_capability(appliance, "Alexa.ColorController", "color")
+        assert has_capability(
+            appliance, "Alexa.ColorController", "colorTemperatureInKelvin"
+        )
+        assert not has_capability(appliance, "Alexa.ColorController", "brightness")
+
+
+class TestIsHueV1:
+    """Test the is_hue_v1 function."""
+
+    def test_is_hue_v1_true(self):
+        """Test is_hue_v1 returns True for Royal Philips Electronics."""
+        appliance = {"manufacturerName": "Royal Philips Electronics"}
+        assert is_hue_v1(appliance)
+
+    def test_is_hue_v1_false_different_manufacturer(self):
+        """Test is_hue_v1 returns False for different manufacturer."""
+        appliance = {"manufacturerName": "Amazon"}
+        assert not is_hue_v1(appliance)
+
+    def test_is_hue_v1_false_no_manufacturer(self):
+        """Test is_hue_v1 returns False when manufacturerName is missing."""
+        appliance = {}
+        assert not is_hue_v1(appliance)
+
+    def test_is_hue_v1_false_none_manufacturer(self):
+        """Test is_hue_v1 returns False when manufacturerName is None."""
+        appliance = {"manufacturerName": None}
+        assert not is_hue_v1(appliance)
+
+
+class TestIsSkill:
+    """Test the is_skill function."""
+
+    def test_is_skill_true(self):
+        """Test is_skill returns True when namespace is SKILL."""
+        appliance = {"driverIdentity": {"namespace": "SKILL"}}
+        assert is_skill(appliance)
+
+    def test_is_skill_false_different_namespace(self):
+        """Test is_skill returns False for different namespace."""
+        appliance = {"driverIdentity": {"namespace": "OTHER"}}
+        assert not is_skill(appliance)
+
+    def test_is_skill_false_no_driver_identity(self):
+        """Test is_skill returns False when driverIdentity is missing."""
+        appliance = {}
+        assert not is_skill(appliance)
+
+    def test_is_skill_false_no_namespace(self):
+        """Test is_skill returns False when namespace is missing."""
+        appliance = {"driverIdentity": {}}
+        assert not is_skill(appliance)
+
+    def test_is_skill_false_empty_namespace(self):
+        """Test is_skill returns False when namespace is empty."""
+        appliance = {"driverIdentity": {"namespace": ""}}
+        assert not is_skill(appliance)
+
+
+class TestIsKnownHaBridge:
+    """Test the is_known_ha_bridge function."""
+
+    def test_is_known_ha_bridge_none(self):
+        """Test is_known_ha_bridge returns False for None input."""
+        assert not is_known_ha_bridge(None)
+
+    def test_is_known_ha_bridge_t0bst4r(self):
+        """Test is_known_ha_bridge returns True for t0bst4r manufacturer."""
+        appliance = {"manufacturerName": "t0bst4r"}
+        assert is_known_ha_bridge(appliance)
+
+    def test_is_known_ha_bridge_matterbridge(self):
+        """Test is_known_ha_bridge returns True for Matterbridge manufacturer."""
+        appliance = {"manufacturerName": "Matterbridge"}
+        assert is_known_ha_bridge(appliance)
+
+    def test_is_known_ha_bridge_false(self):
+        """Test is_known_ha_bridge returns False for unknown manufacturer."""
+        appliance = {"manufacturerName": "Amazon"}
+        assert not is_known_ha_bridge(appliance)
+
+    def test_is_known_ha_bridge_no_manufacturer(self):
+        """Test is_known_ha_bridge returns False when manufacturerName is missing."""
+        appliance = {}
+        assert not is_known_ha_bridge(appliance)
+
+
+class TestIsLocal:
+    """Test the is_local function."""
+
+    def test_is_local_with_connected_via(self):
+        """Test is_local returns True when connectedVia is present."""
+        appliance = {"connectedVia": "Echo Dot"}
+        assert is_local(appliance)
+
+    def test_is_local_voice_enabled_not_skill(self):
+        """Test is_local returns True for voice enabled device that's not a skill."""
+        appliance = {
+            "applianceTypes": ["ALEXA_VOICE_ENABLED"],
+            "driverIdentity": {"namespace": "OTHER"},
+        }
+        assert is_local(appliance)
+
+    def test_is_local_voice_enabled_skill(self):
+        """Test is_local returns False for voice enabled device that's a skill."""
+        appliance = {
+            "applianceTypes": ["ALEXA_VOICE_ENABLED"],
+            "driverIdentity": {"namespace": "SKILL"},
+        }
+        assert not is_local(appliance)
+
+    def test_is_local_ledvance_not_skill(self):
+        """Test is_local returns True for Ledvance device that's not a skill."""
+        appliance = {
+            "manufacturerName": "Ledvance",
+            "driverIdentity": {"namespace": "OTHER"},
+        }
+        assert is_local(appliance)
+
+    def test_is_local_sengled_not_skill(self):
+        """Test is_local returns True for Sengled device that's not a skill."""
+        appliance = {
+            "manufacturerName": "Sengled",
+            "driverIdentity": {"namespace": "OTHER"},
+        }
+        assert is_local(appliance)
+
+    def test_is_local_amazon_not_skill(self):
+        """Test is_local returns True for Amazon device that's not a skill."""
+        appliance = {
+            "manufacturerName": "Amazon",
+            "driverIdentity": {"namespace": "OTHER"},
+        }
+        assert is_local(appliance)
+
+    def test_is_local_ledvance_skill(self):
+        """Test is_local returns False for Ledvance device that's a skill."""
+        appliance = {
+            "manufacturerName": "Ledvance",
+            "driverIdentity": {"namespace": "SKILL"},
+        }
+        assert not is_local(appliance)
+
+    def test_is_local_unknown_manufacturer(self):
+        """Test is_local returns False for unknown manufacturer without other flags."""
+        appliance = {"manufacturerName": "Unknown"}
+        # This should return False as the final zigbee pattern check will fail
+        result = is_local(appliance)
+        assert result is False
+
+    def test_is_local_empty_appliance(self):
+        """Test is_local returns False for empty appliance."""
+        appliance = {}
+        result = is_local(appliance)
+        assert result is False
+
+    def test_is_local_zigbee_pattern_match(self):
+        """Test is_local returns True for valid zigbee pattern."""
+        appliance = {"applianceId": "AAA_SonarCloudService_AB:CD:EF:12:34:56:78:90"}
+        result = is_local(appliance)
+        assert result is True
+
+    def test_is_local_zigbee_pattern_no_match(self):
+        """Test is_local returns False for invalid zigbee pattern."""
+        appliance = {"applianceId": "invalid_pattern"}
+        result = is_local(appliance)
+        assert result is False
diff --git a/tests/test_const.py b/tests/test_const.py
new file mode 100644
index 000000000..fa062fd41
--- /dev/null
+++ b/tests/test_const.py
@@ -0,0 +1,173 @@
+"""Test the const module."""
+
+from datetime import timedelta
+
+import pytest
+
+from custom_components.alexa_media.const import (
+    ALEXA_COMPONENTS,
+    CONF_ACCOUNTS,
+    CONF_DEBUG,
+    CONF_EXCLUDE_DEVICES,
+    CONF_HASS_URL,
+    CONF_INCLUDE_DEVICES,
+    CONF_PUBLIC_URL,
+    CONF_QUEUE_DELAY,
+    DATA_ALEXAMEDIA,
+    DEPENDENT_ALEXA_COMPONENTS,
+    DOMAIN,
+    HTTP_COOKIE_HEADER,
+    ISSUE_URL,
+    MIN_TIME_BETWEEN_FORCED_SCANS,
+    MIN_TIME_BETWEEN_SCANS,
+    NOTIFY_URL,
+    PLAY_SCAN_INTERVAL,
+    PROJECT_URL,
+    SCAN_INTERVAL,
+    __version__,
+)
+
+
+class TestConstants:
+    """Test various constants and configuration values."""
+
+    def test_version_format(self):
+        """Test that version follows semantic versioning format."""
+        # Version should be in format X.Y.Z
+        version_parts = __version__.split(".")
+        assert len(version_parts) == 3
+        for part in version_parts:
+            assert part.isdigit()
+
+    def test_project_url_format(self):
+        """Test that project URL is properly formatted."""
+        assert PROJECT_URL.startswith("https://github.com/")
+        assert PROJECT_URL.endswith("/")
+
+    def test_issue_url_based_on_project(self):
+        """Test that issue URL is based on project URL."""
+        expected_issue_url = f"{PROJECT_URL}issues"
+        assert ISSUE_URL == expected_issue_url
+
+    def test_notify_url_based_on_project(self):
+        """Test that notify URL is based on project URL."""
+        expected_notify_url = f"{PROJECT_URL}wiki/Configuration%3A-Notification-Component#use-the-notifyalexa_media-service"
+        assert NOTIFY_URL == expected_notify_url
+
+    def test_domain_value(self):
+        """Test that domain is set correctly."""
+        assert DOMAIN == "alexa_media"
+
+    def test_data_alexamedia_value(self):
+        """Test that DATA_ALEXAMEDIA matches domain."""
+        assert DATA_ALEXAMEDIA == "alexa_media"
+        assert DATA_ALEXAMEDIA == DOMAIN
+
+    def test_play_scan_interval_type(self):
+        """Test that PLAY_SCAN_INTERVAL is an integer."""
+        assert isinstance(PLAY_SCAN_INTERVAL, int)
+        assert PLAY_SCAN_INTERVAL > 0
+
+    def test_scan_interval_type_and_value(self):
+        """Test that SCAN_INTERVAL is a timedelta and reasonable."""
+        assert isinstance(SCAN_INTERVAL, timedelta)
+        assert SCAN_INTERVAL.total_seconds() > 0
+        assert SCAN_INTERVAL == timedelta(seconds=60)
+
+    def test_min_time_between_scans_matches_scan_interval(self):
+        """Test that MIN_TIME_BETWEEN_SCANS equals SCAN_INTERVAL."""
+        assert MIN_TIME_BETWEEN_SCANS == SCAN_INTERVAL
+
+    def test_min_time_between_forced_scans_value(self):
+        """Test that MIN_TIME_BETWEEN_FORCED_SCANS is reasonable."""
+        assert isinstance(MIN_TIME_BETWEEN_FORCED_SCANS, timedelta)
+        assert MIN_TIME_BETWEEN_FORCED_SCANS == timedelta(seconds=1)
+        # Forced scans should be allowed more frequently than regular scans
+        assert MIN_TIME_BETWEEN_FORCED_SCANS < SCAN_INTERVAL
+
+    def test_alexa_components_list(self):
+        """Test that ALEXA_COMPONENTS contains expected components."""
+        assert isinstance(ALEXA_COMPONENTS, list)
+        assert "media_player" in ALEXA_COMPONENTS
+        # Ensure no duplicates
+        assert len(ALEXA_COMPONENTS) == len(set(ALEXA_COMPONENTS))
+
+    def test_dependent_alexa_components_list(self):
+        """Test that DEPENDENT_ALEXA_COMPONENTS contains expected components."""
+        assert isinstance(DEPENDENT_ALEXA_COMPONENTS, list)
+        expected_components = [
+            "notify",
+            "switch",
+            "sensor",
+            "alarm_control_panel",
+            "light",
+            "binary_sensor",
+        ]
+        for component in expected_components:
+            assert component in DEPENDENT_ALEXA_COMPONENTS
+        # Ensure no duplicates
+        assert len(DEPENDENT_ALEXA_COMPONENTS) == len(set(DEPENDENT_ALEXA_COMPONENTS))
+
+    def test_alexa_components_and_dependent_no_overlap(self):
+        """Test that main and dependent components don't overlap."""
+        main_set = set(ALEXA_COMPONENTS)
+        dependent_set = set(DEPENDENT_ALEXA_COMPONENTS)
+        assert main_set.isdisjoint(dependent_set)
+
+    def test_http_cookie_header_format(self):
+        """Test that HTTP_COOKIE_HEADER is correct."""
+        assert HTTP_COOKIE_HEADER == "# HTTP Cookie File"
+
+    def test_configuration_constants_are_strings(self):
+        """Test that configuration constants are strings."""
+        config_constants = [
+            CONF_ACCOUNTS,
+            CONF_DEBUG,
+            CONF_HASS_URL,
+            CONF_INCLUDE_DEVICES,
+            CONF_EXCLUDE_DEVICES,
+            CONF_QUEUE_DELAY,
+            CONF_PUBLIC_URL,
+        ]
+
+        for const in config_constants:
+            assert isinstance(const, str)
+            assert len(const) > 0
+
+    def test_specific_configuration_values(self):
+        """Test specific configuration constant values."""
+        assert CONF_ACCOUNTS == "accounts"
+        assert CONF_DEBUG == "debug"
+        assert CONF_HASS_URL == "hass_url"
+        assert CONF_INCLUDE_DEVICES == "include_devices"
+        assert CONF_EXCLUDE_DEVICES == "exclude_devices"
+        assert CONF_QUEUE_DELAY == "queue_delay"
+        assert CONF_PUBLIC_URL == "public_url"
+
+    def test_all_constants_exist(self):
+        """Test that all expected constants are defined and not None."""
+        constants_to_check = [
+            __version__,
+            PROJECT_URL,
+            ISSUE_URL,
+            NOTIFY_URL,
+            DOMAIN,
+            DATA_ALEXAMEDIA,
+            PLAY_SCAN_INTERVAL,
+            SCAN_INTERVAL,
+            MIN_TIME_BETWEEN_SCANS,
+            MIN_TIME_BETWEEN_FORCED_SCANS,
+            ALEXA_COMPONENTS,
+            DEPENDENT_ALEXA_COMPONENTS,
+            HTTP_COOKIE_HEADER,
+            CONF_ACCOUNTS,
+            CONF_DEBUG,
+            CONF_HASS_URL,
+            CONF_INCLUDE_DEVICES,
+            CONF_EXCLUDE_DEVICES,
+            CONF_QUEUE_DELAY,
+            CONF_PUBLIC_URL,
+        ]
+
+        for const in constants_to_check:
+            assert const is not None
diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py
new file mode 100644
index 000000000..658a8e362
--- /dev/null
+++ b/tests/test_exceptions.py
@@ -0,0 +1,91 @@
+"""Test the exceptions module."""
+
+import pytest
+
+from custom_components.alexa_media.exceptions import (
+    EmptyDataException,
+    ForbiddenException,
+    LoginForbiddenException,
+    LoginInvalidException,
+    TimeoutException,
+    UnexpectedApiException,
+)
+
+
+def test_empty_data_exception():
+    """Test EmptyDataException creation and inheritance."""
+    exception = EmptyDataException("Test message")
+    assert isinstance(exception, Exception)
+    assert str(exception) == "Test message"
+
+
+def test_forbidden_exception():
+    """Test ForbiddenException creation and inheritance."""
+    exception = ForbiddenException("Access denied")
+    assert isinstance(exception, Exception)
+    assert str(exception) == "Access denied"
+
+
+def test_login_forbidden_exception():
+    """Test LoginForbiddenException creation and inheritance."""
+    exception = LoginForbiddenException("Login blocked")
+    assert isinstance(exception, Exception)
+    assert str(exception) == "Login blocked"
+
+
+def test_login_invalid_exception():
+    """Test LoginInvalidException with attempts remaining."""
+    attempts = 3
+    exception = LoginInvalidException(attempts)
+
+    assert isinstance(exception, Exception)
+    assert exception.attempts_remaining == attempts
+    assert str(exception) == "Invalid login credentials. 3 attempts remaining."
+
+
+def test_login_invalid_exception_zero_attempts():
+    """Test LoginInvalidException with zero attempts remaining."""
+    exception = LoginInvalidException(0)
+
+    assert exception.attempts_remaining == 0
+    assert str(exception) == "Invalid login credentials. 0 attempts remaining."
+
+
+def test_timeout_exception_with_message():
+    """Test TimeoutException with custom message."""
+    message = "API call took too long"
+    exception = TimeoutException(message)
+
+    assert isinstance(exception, Exception)
+    assert str(exception) == f"Timeout exception: {message}"
+
+
+def test_timeout_exception_without_message():
+    """Test TimeoutException with default empty message."""
+    exception = TimeoutException()
+
+    assert isinstance(exception, Exception)
+    assert str(exception) == "Timeout exception: "
+
+
+def test_unexpected_api_exception():
+    """Test UnexpectedApiException creation and inheritance."""
+    exception = UnexpectedApiException("Unexpected response")
+    assert isinstance(exception, Exception)
+    assert str(exception) == "Unexpected response"
+
+
+def test_all_exceptions_are_raisable():
+    """Test that all custom exceptions can be raised and caught."""
+    exceptions_to_test = [
+        (EmptyDataException, "empty"),
+        (ForbiddenException, "forbidden"),
+        (LoginForbiddenException, "login forbidden"),
+        (LoginInvalidException, 2),
+        (TimeoutException, "timeout"),
+        (UnexpectedApiException, "unexpected"),
+    ]
+
+    for exception_class, arg in exceptions_to_test:
+        with pytest.raises(exception_class):
+            raise exception_class(arg)
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index 0842e92fd..86d99dbf2 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -1,9 +1,10 @@
 from unittest.mock import MagicMock, patch
 
+from homeassistant.exceptions import ConditionErrorMessage
 import pytest
 
 from custom_components.alexa_media.const import DATA_ALEXAMEDIA
-from custom_components.alexa_media.helpers import _existing_serials
+from custom_components.alexa_media.helpers import _existing_serials, add_devices
 
 
 def test_existing_serials_no_accounts():
@@ -108,3 +109,167 @@ def test_existing_serials_with_invalid_app_devices():
 
     result = _existing_serials(hass, login_obj)
     assert sorted(result) == ["app1", "device1"]
+
+
+class TestAddDevices:
+    """Test the add_devices function."""
+
+    @pytest.mark.asyncio
+    async def test_add_devices_success(self):
+        """Test successful device addition."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        device2 = MagicMock()
+        device2.name = "Device 2"
+        devices = [device1, device2]
+
+        add_devices_callback = MagicMock()
+
+        result = await add_devices("test_account", devices, add_devices_callback)
+
+        assert result is True
+        add_devices_callback.assert_called_once_with(devices, False)
+
+    @pytest.mark.asyncio
+    async def test_add_devices_empty_list(self):
+        """Test adding empty device list."""
+        devices = []
+        add_devices_callback = MagicMock()
+
+        result = await add_devices("test_account", devices, add_devices_callback)
+
+        assert result is True
+        add_devices_callback.assert_not_called()
+
+    @pytest.mark.asyncio
+    async def test_add_devices_with_include_filter(self):
+        """Test device filtering with include filter."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        device2 = MagicMock()
+        device2.name = "Device 2"
+        devices = [device1, device2]
+
+        add_devices_callback = MagicMock()
+        include_filter = ["Device 1"]
+
+        result = await add_devices(
+            "test_account", devices, add_devices_callback, include_filter=include_filter
+        )
+
+        assert result is True
+        # Only device1 should be added
+        add_devices_callback.assert_called_once_with([device1], False)
+
+    @pytest.mark.asyncio
+    async def test_add_devices_with_exclude_filter(self):
+        """Test device filtering with exclude filter."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        device2 = MagicMock()
+        device2.name = "Device 2"
+        devices = [device1, device2]
+
+        add_devices_callback = MagicMock()
+        exclude_filter = ["Device 2"]
+
+        result = await add_devices(
+            "test_account", devices, add_devices_callback, exclude_filter=exclude_filter
+        )
+
+        assert result is True
+        # Only device1 should be added
+        add_devices_callback.assert_called_once_with([device1], False)
+
+    @pytest.mark.asyncio
+    async def test_add_devices_filtered_to_empty(self):
+        """Test when filtering results in no devices to add."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        devices = [device1]
+
+        add_devices_callback = MagicMock()
+        exclude_filter = ["Device 1"]
+
+        result = await add_devices(
+            "test_account", devices, add_devices_callback, exclude_filter=exclude_filter
+        )
+
+        assert result is True
+        add_devices_callback.assert_not_called()
+
+    @pytest.mark.asyncio
+    async def test_add_devices_condition_error_entity_exists(self):
+        """Test handling of entity already exists error."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        devices = [device1]
+
+        add_devices_callback = MagicMock()
+        add_devices_callback.side_effect = ConditionErrorMessage(
+            "entity_exists", "Entity id already exists: device1"
+        )
+
+        result = await add_devices("test_account", devices, add_devices_callback)
+
+        assert result is False
+        add_devices_callback.assert_called_once_with([device1], False)
+
+    @pytest.mark.asyncio
+    async def test_add_devices_condition_error_other(self):
+        """Test handling of other condition errors."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        devices = [device1]
+
+        add_devices_callback = MagicMock()
+        add_devices_callback.side_effect = ConditionErrorMessage(
+            "other_error", "Some other error"
+        )
+
+        result = await add_devices("test_account", devices, add_devices_callback)
+
+        assert result is False
+        add_devices_callback.assert_called_once_with([device1], False)
+
+    @pytest.mark.asyncio
+    async def test_add_devices_base_exception(self):
+        """Test handling of base exceptions."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        devices = [device1]
+
+        add_devices_callback = MagicMock()
+        add_devices_callback.side_effect = ValueError("Some error")
+
+        result = await add_devices("test_account", devices, add_devices_callback)
+
+        assert result is False
+        add_devices_callback.assert_called_once_with([device1], False)
+
+    @pytest.mark.asyncio
+    async def test_add_devices_include_and_exclude_filters(self):
+        """Test device filtering with both include and exclude filters."""
+        device1 = MagicMock()
+        device1.name = "Device 1"
+        device2 = MagicMock()
+        device2.name = "Device 2"
+        device3 = MagicMock()
+        device3.name = "Device 3"
+        devices = [device1, device2, device3]
+
+        add_devices_callback = MagicMock()
+        include_filter = ["Device 1", "Device 2"]
+        exclude_filter = ["Device 2"]
+
+        result = await add_devices(
+            "test_account",
+            devices,
+            add_devices_callback,
+            include_filter=include_filter,
+            exclude_filter=exclude_filter,
+        )
+
+        assert result is True
+        # Only device1 should be added (included but not excluded)
+        add_devices_callback.assert_called_once_with([device1], False)