Add some basic tests for doorbird (#122135)

* basic tests

* basic tests

* basic tests

* basic tests

* cover

* cover

* Update tests/components/doorbird/test_init.py
This commit is contained in:
J. Nick Koston 2024-07-18 15:36:54 -05:00 committed by GitHub
parent d2cc25cee6
commit a0a5f640dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 297 additions and 68 deletions

View File

@ -1 +1,40 @@
"""Tests for the DoorBird integration."""
from typing import Any
from unittest.mock import AsyncMock, MagicMock, Mock
import aiohttp
from doorbirdpy import DoorBird, DoorBirdScheduleEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME
VALID_CONFIG = {
CONF_HOST: "1.2.3.4",
CONF_USERNAME: "friend",
CONF_PASSWORD: "password",
CONF_NAME: "mydoorbird",
}
def mock_unauthorized_exception() -> aiohttp.ClientResponseError:
"""Return a mock unauthorized exception."""
return aiohttp.ClientResponseError(request_info=Mock(), history=Mock(), status=401)
def get_mock_doorbird_api(
info: dict[str, Any] | None = None,
info_side_effect: Exception | None = None,
schedule: list[DoorBirdScheduleEntry] | None = None,
) -> DoorBird:
"""Return a mock DoorBirdAPI object with return values."""
doorbirdapi_mock = MagicMock(spec_set=DoorBird)
type(doorbirdapi_mock).info = AsyncMock(
side_effect=info_side_effect, return_value=info
)
type(doorbirdapi_mock).favorites = AsyncMock(return_value={})
type(doorbirdapi_mock).change_favorite = AsyncMock(return_value=True)
type(doorbirdapi_mock).schedule = AsyncMock(return_value=schedule)
type(doorbirdapi_mock).doorbell_state = AsyncMock(
side_effect=mock_unauthorized_exception()
)
return doorbirdapi_mock

View File

@ -0,0 +1,101 @@
"""Test configuration for DoorBird tests."""
from collections.abc import Generator
from contextlib import contextmanager
from dataclasses import dataclass
from typing import Any
from unittest.mock import MagicMock, patch
from doorbirdpy import DoorBird, DoorBirdScheduleEntry
import pytest
from homeassistant.components.doorbird.const import CONF_EVENTS, DOMAIN
from homeassistant.core import HomeAssistant
from . import VALID_CONFIG, get_mock_doorbird_api
from tests.common import MockConfigEntry, load_json_value_fixture
@dataclass
class MockDoorbirdEntry:
"""Mock DoorBird config entry."""
entry: MockConfigEntry
api: MagicMock
@pytest.fixture(scope="session")
def doorbird_info() -> dict[str, Any]:
"""Return a loaded DoorBird info fixture."""
return load_json_value_fixture("info.json", "doorbird")["BHA"]["VERSION"][0]
@pytest.fixture(scope="session")
def doorbird_schedule() -> list[DoorBirdScheduleEntry]:
"""Return a loaded DoorBird schedule fixture."""
return DoorBirdScheduleEntry.parse_all(
load_json_value_fixture("schedule.json", "doorbird")
)
@pytest.fixture
def doorbird_api(
doorbird_info: dict[str, Any], doorbird_schedule: dict[str, Any]
) -> Generator[Any, Any, DoorBird]:
"""Mock the DoorBirdAPI."""
api = get_mock_doorbird_api(info=doorbird_info, schedule=doorbird_schedule)
with patch_doorbird_api_entry_points(api):
yield api
@contextmanager
def patch_doorbird_api_entry_points(api: MagicMock) -> Generator[Any, Any, DoorBird]:
"""Mock the DoorBirdAPI."""
with (
patch(
"homeassistant.components.doorbird.DoorBird",
return_value=api,
),
patch(
"homeassistant.components.doorbird.config_flow.DoorBird",
return_value=api,
),
):
yield api
@pytest.fixture
async def doorbird_mocker(
hass: HomeAssistant,
doorbird_info: dict[str, Any],
doorbird_schedule: dict[str, Any],
) -> MockDoorbirdEntry:
"""Create a MockDoorbirdEntry."""
async def _async_mock(
entry: MockConfigEntry | None = None,
api: DoorBird | None = None,
info: dict[str, Any] | None = None,
info_side_effect: Exception | None = None,
schedule: list[DoorBirdScheduleEntry] | None = None,
) -> None:
"""Create a MockDoorbirdEntry from defaults or specific values."""
entry = entry or MockConfigEntry(
domain=DOMAIN,
unique_id="1CCAE3AAAAAA",
data=VALID_CONFIG,
options={CONF_EVENTS: ["event1", "event2", "event3"]},
)
api = api or get_mock_doorbird_api(
info=info or doorbird_info,
info_side_effect=info_side_effect,
schedule=schedule or doorbird_schedule,
)
entry.add_to_hass(hass)
with patch_doorbird_api_entry_points(api):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
return MockDoorbirdEntry(entry=entry, api=api)
return _async_mock

View File

@ -0,0 +1,23 @@
{
"BHA": {
"RETURNCODE": "1",
"VERSION": [
{
"FIRMWARE": "000125",
"BUILD_NUMBER": "15870439",
"WIFI_MAC_ADDR": "1234ABCD",
"RELAYS": [
"1",
"2",
"ghchdi@1",
"ghchdi@2",
"ghchdi@3",
"ghdwkh@1",
"ghdwkh@2",
"ghdwkh@3"
],
"DEVICE-TYPE": "DoorBird D2101V"
}
]
}
}

View File

@ -0,0 +1,67 @@
[
{
"input": "doorbell",
"param": "1",
"output": [
{
"event": "notify",
"param": "",
"schedule": {
"weekdays": [
{
"to": "107999",
"from": "108000"
}
]
}
},
{
"event": "http",
"param": "0",
"schedule": {
"weekdays": [
{
"to": "107999",
"from": "108000"
}
]
}
}
]
},
{
"input": "motion",
"param": "",
"output": [
{
"event": "notify",
"param": "",
"schedule": {
"weekdays": [
{
"to": "107999",
"from": "108000"
}
]
}
},
{
"event": "http",
"param": "5",
"schedule": {
"weekdays": [
{
"to": "107999",
"from": "108000"
}
]
}
}
]
},
{
"input": "relay",
"param": "1",
"output": []
}
]

View File

@ -1,9 +1,10 @@
"""Test the DoorBird config flow."""
from ipaddress import ip_address
from unittest.mock import AsyncMock, MagicMock, Mock, patch
from unittest.mock import AsyncMock, Mock, patch
import aiohttp
from doorbirdpy import DoorBird
import pytest
from homeassistant import config_entries
@ -18,35 +19,12 @@ from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNA
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from . import VALID_CONFIG, get_mock_doorbird_api, mock_unauthorized_exception
from tests.common import MockConfigEntry
VALID_CONFIG = {
CONF_HOST: "1.2.3.4",
CONF_USERNAME: "friend",
CONF_PASSWORD: "password",
CONF_NAME: "mydoorbird",
}
def _get_mock_doorbirdapi_return_values(info=None):
doorbirdapi_mock = MagicMock()
type(doorbirdapi_mock).info = AsyncMock(return_value=info)
type(doorbirdapi_mock).doorbell_state = AsyncMock(
side_effect=aiohttp.ClientResponseError(
request_info=Mock(), history=Mock(), status=401
)
)
return doorbirdapi_mock
def _get_mock_doorbirdapi_side_effects(info=None):
doorbirdapi_mock = MagicMock()
type(doorbirdapi_mock).info = AsyncMock(side_effect=info)
return doorbirdapi_mock
async def test_user_form(hass: HomeAssistant) -> None:
async def test_user_form(hass: HomeAssistant, doorbird_api: DoorBird) -> None:
"""Test we get the user form."""
result = await hass.config_entries.flow.async_init(
@ -55,12 +33,7 @@ async def test_user_form(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
doorbirdapi = _get_mock_doorbirdapi_return_values(info={"WIFI_MAC_ADDR": "macaddr"})
with (
patch(
"homeassistant.components.doorbird.config_flow.DoorBird",
return_value=doorbirdapi,
),
patch(
"homeassistant.components.doorbird.async_setup", return_value=True
) as mock_setup,
@ -178,37 +151,30 @@ async def test_form_zeroconf_non_ipv4_ignored(hass: HomeAssistant) -> None:
assert result["reason"] == "not_ipv4_address"
async def test_form_zeroconf_correct_oui(hass: HomeAssistant) -> None:
async def test_form_zeroconf_correct_oui(
hass: HomeAssistant, doorbird_api: DoorBird
) -> None:
"""Test we can setup from zeroconf with the correct OUI source."""
doorbirdapi = _get_mock_doorbirdapi_return_values(info={"WIFI_MAC_ADDR": "macaddr"})
with patch(
"homeassistant.components.doorbird.config_flow.DoorBird",
return_value=doorbirdapi,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
ip_address=ip_address("192.168.1.5"),
ip_addresses=[ip_address("192.168.1.5")],
hostname="mock_hostname",
name="Doorstation - abc123._axis-video._tcp.local.",
port=None,
properties={"macaddress": "1CCAE3DOORBIRD"},
type="mock_type",
),
)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
ip_address=ip_address("192.168.1.5"),
ip_addresses=[ip_address("192.168.1.5")],
hostname="mock_hostname",
name="Doorstation - abc123._axis-video._tcp.local.",
port=None,
properties={"macaddress": "1CCAE3DOORBIRD"},
type="mock_type",
),
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with (
patch(
"homeassistant.components.doorbird.config_flow.DoorBird",
return_value=doorbirdapi,
),
patch("homeassistant.components.logbook.async_setup", return_value=True),
patch(
"homeassistant.components.doorbird.async_setup", return_value=True
@ -244,10 +210,12 @@ async def test_form_zeroconf_correct_oui(hass: HomeAssistant) -> None:
],
)
async def test_form_zeroconf_correct_oui_wrong_device(
hass: HomeAssistant, doorbell_state_side_effect
hass: HomeAssistant,
doorbird_api: DoorBird,
doorbell_state_side_effect: Exception | None,
) -> None:
"""Test we can setup from zeroconf with the correct OUI source but not a doorstation."""
doorbirdapi = _get_mock_doorbirdapi_return_values(info={"WIFI_MAC_ADDR": "macaddr"})
doorbirdapi = get_mock_doorbird_api(info={"WIFI_MAC_ADDR": "macaddr"})
type(doorbirdapi).doorbell_state = AsyncMock(side_effect=doorbell_state_side_effect)
with patch(
@ -278,7 +246,7 @@ async def test_form_user_cannot_connect(hass: HomeAssistant) -> None:
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
doorbirdapi = _get_mock_doorbirdapi_side_effects(info=OSError)
doorbirdapi = get_mock_doorbird_api(info_side_effect=OSError)
with patch(
"homeassistant.components.doorbird.config_flow.DoorBird",
return_value=doorbirdapi,
@ -298,10 +266,8 @@ async def test_form_user_invalid_auth(hass: HomeAssistant) -> None:
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
mock_error = aiohttp.ClientResponseError(
request_info=Mock(), history=Mock(), status=401
)
doorbirdapi = _get_mock_doorbirdapi_side_effects(info=mock_error)
mock_error = mock_unauthorized_exception()
doorbirdapi = get_mock_doorbird_api(info_side_effect=mock_error)
with patch(
"homeassistant.components.doorbird.config_flow.DoorBird",
return_value=doorbirdapi,
@ -360,10 +326,8 @@ async def test_reauth(hass: HomeAssistant) -> None:
assert len(flows) == 1
flow = flows[0]
mock_error = aiohttp.ClientResponseError(
request_info=Mock(), history=Mock(), status=401
)
doorbirdapi = _get_mock_doorbirdapi_side_effects(info=mock_error)
mock_error = mock_unauthorized_exception()
doorbirdapi = get_mock_doorbird_api(info_side_effect=mock_error)
with patch(
"homeassistant.components.doorbird.config_flow.DoorBird",
return_value=doorbirdapi,
@ -379,7 +343,7 @@ async def test_reauth(hass: HomeAssistant) -> None:
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
doorbirdapi = _get_mock_doorbirdapi_return_values(info={"WIFI_MAC_ADDR": "macaddr"})
doorbirdapi = get_mock_doorbird_api(info={"WIFI_MAC_ADDR": "macaddr"})
with (
patch(
"homeassistant.components.doorbird.config_flow.DoorBird",

View File

@ -0,0 +1,35 @@
"""Test DoorBird init."""
from collections.abc import Callable, Coroutine
from typing import Any
from homeassistant.components.doorbird.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from . import mock_unauthorized_exception
from .conftest import MockDoorbirdEntry
async def test_basic_setup(
doorbird_mocker: Callable[[], Coroutine[Any, Any, MockDoorbirdEntry]],
) -> None:
"""Test basic setup."""
doorbird_entry = await doorbird_mocker()
entry = doorbird_entry.entry
assert entry.state is ConfigEntryState.LOADED
async def test_auth_fails(
hass: HomeAssistant,
doorbird_mocker: Callable[[], Coroutine[Any, Any, MockDoorbirdEntry]],
) -> None:
"""Test basic setup with an auth failure."""
doorbird_entry = await doorbird_mocker(
info_side_effect=mock_unauthorized_exception()
)
entry = doorbird_entry.entry
assert entry.state is ConfigEntryState.SETUP_ERROR
flows = hass.config_entries.flow.async_progress(DOMAIN)
assert len(flows) == 1
assert flows[0]["step_id"] == "reauth_confirm"