mirror of https://github.com/home-assistant/core
249 lines
7.9 KiB
Python
249 lines
7.9 KiB
Python
"""The tests for the Google Pub/Sub component."""
|
|
|
|
from collections.abc import Generator
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
import os
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components import google_pubsub
|
|
from homeassistant.components.google_pubsub import DateTimeJSONEncoder as victim
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
GOOGLE_PUBSUB_PATH = "homeassistant.components.google_pubsub"
|
|
|
|
|
|
@dataclass
|
|
class FilterTest:
|
|
"""Class for capturing a filter test."""
|
|
|
|
id: str
|
|
should_pass: bool
|
|
|
|
|
|
async def test_datetime() -> None:
|
|
"""Test datetime encoding."""
|
|
time = datetime(2019, 1, 13, 12, 30, 5)
|
|
assert victim().encode(time) == '"2019-01-13T12:30:05"'
|
|
|
|
|
|
async def test_no_datetime() -> None:
|
|
"""Test integer encoding."""
|
|
assert victim().encode(42) == "42"
|
|
|
|
|
|
async def test_nested() -> None:
|
|
"""Test dictionary encoding."""
|
|
assert victim().encode({"foo": "bar"}) == '{"foo": "bar"}'
|
|
|
|
|
|
@pytest.fixture(autouse=True, name="mock_client")
|
|
def mock_client_fixture() -> Generator[MagicMock]:
|
|
"""Mock the pubsub client."""
|
|
with patch(f"{GOOGLE_PUBSUB_PATH}.PublisherClient") as client:
|
|
setattr(
|
|
client,
|
|
"from_service_account_json",
|
|
MagicMock(return_value=MagicMock()),
|
|
)
|
|
yield client
|
|
|
|
|
|
@pytest.fixture(autouse=True, name="mock_is_file")
|
|
def mock_is_file_fixture() -> Generator[MagicMock]:
|
|
"""Mock os.path.isfile."""
|
|
with patch(f"{GOOGLE_PUBSUB_PATH}.os.path.isfile") as is_file:
|
|
is_file.return_value = True
|
|
yield is_file
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_json(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Mock the event bus listener and os component."""
|
|
monkeypatch.setattr(
|
|
f"{GOOGLE_PUBSUB_PATH}.json.dumps", Mock(return_value=MagicMock())
|
|
)
|
|
|
|
|
|
async def test_minimal_config(hass: HomeAssistant, mock_client) -> None:
|
|
"""Test the minimal config and defaults of component."""
|
|
config = {
|
|
google_pubsub.DOMAIN: {
|
|
"project_id": "proj",
|
|
"topic_name": "topic",
|
|
"credentials_json": "creds",
|
|
"filter": {},
|
|
}
|
|
}
|
|
assert await async_setup_component(hass, google_pubsub.DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
assert mock_client.from_service_account_json.call_count == 1
|
|
assert mock_client.from_service_account_json.call_args[0][0] == os.path.join(
|
|
hass.config.config_dir, "creds"
|
|
)
|
|
|
|
|
|
async def test_full_config(hass: HomeAssistant, mock_client) -> None:
|
|
"""Test the full config of the component."""
|
|
config = {
|
|
google_pubsub.DOMAIN: {
|
|
"project_id": "proj",
|
|
"topic_name": "topic",
|
|
"credentials_json": "creds",
|
|
"filter": {
|
|
"include_domains": ["light"],
|
|
"include_entity_globs": ["sensor.included_*"],
|
|
"include_entities": ["binary_sensor.included"],
|
|
"exclude_domains": ["light"],
|
|
"exclude_entity_globs": ["sensor.excluded_*"],
|
|
"exclude_entities": ["binary_sensor.excluded"],
|
|
},
|
|
}
|
|
}
|
|
assert await async_setup_component(hass, google_pubsub.DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
assert mock_client.from_service_account_json.call_count == 1
|
|
assert mock_client.from_service_account_json.call_args[0][0] == os.path.join(
|
|
hass.config.config_dir, "creds"
|
|
)
|
|
|
|
|
|
async def _setup(hass: HomeAssistant, filter_config: dict[str, Any]) -> None:
|
|
"""Shared set up for filtering tests."""
|
|
config = {
|
|
google_pubsub.DOMAIN: {
|
|
"project_id": "proj",
|
|
"topic_name": "topic",
|
|
"credentials_json": "creds",
|
|
"filter": filter_config,
|
|
}
|
|
}
|
|
assert await async_setup_component(hass, google_pubsub.DOMAIN, config)
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
async def test_allowlist(hass: HomeAssistant, mock_client) -> None:
|
|
"""Test an allowlist only config."""
|
|
await _setup(
|
|
hass,
|
|
{
|
|
"include_domains": ["light"],
|
|
"include_entity_globs": ["sensor.included_*"],
|
|
"include_entities": ["binary_sensor.included"],
|
|
},
|
|
)
|
|
publish_client = mock_client.from_service_account_json("path")
|
|
|
|
tests = [
|
|
FilterTest("climate.excluded", False),
|
|
FilterTest("light.included", True),
|
|
FilterTest("sensor.excluded_test", False),
|
|
FilterTest("sensor.included_test", True),
|
|
FilterTest("binary_sensor.included", True),
|
|
FilterTest("binary_sensor.excluded", False),
|
|
]
|
|
|
|
for test in tests:
|
|
hass.states.async_set(test.id, "on")
|
|
await hass.async_block_till_done()
|
|
|
|
was_called = publish_client.publish.call_count == 1
|
|
assert test.should_pass == was_called
|
|
publish_client.publish.reset_mock()
|
|
|
|
|
|
async def test_denylist(hass: HomeAssistant, mock_client) -> None:
|
|
"""Test a denylist only config."""
|
|
await _setup(
|
|
hass,
|
|
{
|
|
"exclude_domains": ["climate"],
|
|
"exclude_entity_globs": ["sensor.excluded_*"],
|
|
"exclude_entities": ["binary_sensor.excluded"],
|
|
},
|
|
)
|
|
publish_client = mock_client.from_service_account_json("path")
|
|
|
|
tests = [
|
|
FilterTest("climate.excluded", False),
|
|
FilterTest("light.included", True),
|
|
FilterTest("sensor.excluded_test", False),
|
|
FilterTest("sensor.included_test", True),
|
|
FilterTest("binary_sensor.included", True),
|
|
FilterTest("binary_sensor.excluded", False),
|
|
]
|
|
|
|
for test in tests:
|
|
hass.states.async_set(test.id, "on")
|
|
await hass.async_block_till_done()
|
|
|
|
was_called = publish_client.publish.call_count == 1
|
|
assert test.should_pass == was_called
|
|
publish_client.publish.reset_mock()
|
|
|
|
|
|
async def test_filtered_allowlist(hass: HomeAssistant, mock_client) -> None:
|
|
"""Test an allowlist config with a filtering denylist."""
|
|
await _setup(
|
|
hass,
|
|
{
|
|
"include_domains": ["light"],
|
|
"include_entity_globs": ["*.included_*"],
|
|
"exclude_domains": ["climate"],
|
|
"exclude_entity_globs": ["*.excluded_*"],
|
|
"exclude_entities": ["light.excluded"],
|
|
},
|
|
)
|
|
publish_client = mock_client.from_service_account_json("path")
|
|
|
|
tests = [
|
|
FilterTest("light.included", True),
|
|
FilterTest("light.excluded_test", False),
|
|
FilterTest("light.excluded", False),
|
|
FilterTest("sensor.included_test", True),
|
|
FilterTest("climate.included_test", True),
|
|
]
|
|
|
|
for test in tests:
|
|
hass.states.async_set(test.id, "not blank")
|
|
await hass.async_block_till_done()
|
|
|
|
was_called = publish_client.publish.call_count == 1
|
|
assert test.should_pass == was_called
|
|
publish_client.publish.reset_mock()
|
|
|
|
|
|
async def test_filtered_denylist(hass: HomeAssistant, mock_client) -> None:
|
|
"""Test a denylist config with a filtering allowlist."""
|
|
await _setup(
|
|
hass,
|
|
{
|
|
"include_entities": ["climate.included", "sensor.excluded_test"],
|
|
"exclude_domains": ["climate"],
|
|
"exclude_entity_globs": ["*.excluded_*"],
|
|
"exclude_entities": ["light.excluded"],
|
|
},
|
|
)
|
|
publish_client = mock_client.from_service_account_json("path")
|
|
|
|
tests = [
|
|
FilterTest("climate.excluded", False),
|
|
FilterTest("climate.included", True),
|
|
FilterTest("switch.excluded_test", False),
|
|
FilterTest("sensor.excluded_test", True),
|
|
FilterTest("light.excluded", False),
|
|
FilterTest("light.included", True),
|
|
]
|
|
|
|
for test in tests:
|
|
hass.states.async_set(test.id, "not blank")
|
|
await hass.async_block_till_done()
|
|
|
|
was_called = publish_client.publish.call_count == 1
|
|
assert test.should_pass == was_called
|
|
publish_client.publish.reset_mock()
|