From 7621c450c7efda3c854d2d204a64cfc7f84f315d Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 10 Jan 2023 17:31:47 +0100 Subject: [PATCH] Add kitchen_sink integration (#85592) --- CODEOWNERS | 2 + homeassistant/components/demo/__init__.py | 190 ------------ homeassistant/components/demo/manifest.json | 1 - .../components/kitchen_sink/__init__.py | 212 +++++++++++++ .../components/kitchen_sink/manifest.json | 9 + .../{demo => kitchen_sink}/repairs.py | 0 homeassistant/generated/integrations.json | 6 + tests/components/demo/test_init.py | 276 +---------------- tests/components/kitchen_sink/__init__.py | 1 + tests/components/kitchen_sink/test_init.py | 287 ++++++++++++++++++ 10 files changed, 518 insertions(+), 466 deletions(-) create mode 100644 homeassistant/components/kitchen_sink/__init__.py create mode 100644 homeassistant/components/kitchen_sink/manifest.json rename homeassistant/components/{demo => kitchen_sink}/repairs.py (100%) create mode 100644 tests/components/kitchen_sink/__init__.py create mode 100644 tests/components/kitchen_sink/test_init.py diff --git a/CODEOWNERS b/CODEOWNERS index 42c0186520a..95c0e8cecca 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -605,6 +605,8 @@ build.json @home-assistant/supervisor /homeassistant/components/keyboard_remote/ @bendavid @lanrat /homeassistant/components/keymitt_ble/ @spycle /tests/components/keymitt_ble/ @spycle +/homeassistant/components/kitchen_sink/ @home-assistant/core +/tests/components/kitchen_sink/ @home-assistant/core /homeassistant/components/kmtronic/ @dgomes /tests/components/kmtronic/ @dgomes /homeassistant/components/knx/ @Julius2342 @farmio @marvin-w diff --git a/homeassistant/components/demo/__init__.py b/homeassistant/components/demo/__init__.py index d4c07cfa730..13e8e135394 100644 --- a/homeassistant/components/demo/__init__.py +++ b/homeassistant/components/demo/__init__.py @@ -2,33 +2,20 @@ from __future__ import annotations import asyncio -import datetime -from random import random from homeassistant import config_entries, setup from homeassistant.components import persistent_notification -from homeassistant.components.recorder import get_instance -from homeassistant.components.recorder.models import StatisticData, StatisticMetaData -from homeassistant.components.recorder.statistics import ( - async_add_external_statistics, - get_last_statistics, -) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, Platform, - UnitOfEnergy, UnitOfSoundPressure, - UnitOfTemperature, - UnitOfVolume, ) import homeassistant.core as ha from homeassistant.core import Event, HomeAssistant from homeassistant.helpers.discovery import async_load_platform -from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.typing import ConfigType -import homeassistant.util.dt as dt_util DOMAIN = "demo" @@ -186,192 +173,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.bus.async_listen(EVENT_HOMEASSISTANT_START, demo_start_listener) - # Create issues - async_create_issue( - hass, - DOMAIN, - "transmogrifier_deprecated", - breaks_in_ha_version="2023.1.1", - is_fixable=False, - learn_more_url="https://en.wiktionary.org/wiki/transmogrifier", - severity=IssueSeverity.WARNING, - translation_key="transmogrifier_deprecated", - ) - - async_create_issue( - hass, - DOMAIN, - "out_of_blinker_fluid", - breaks_in_ha_version="2023.1.1", - is_fixable=True, - learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU", - severity=IssueSeverity.CRITICAL, - translation_key="out_of_blinker_fluid", - ) - - async_create_issue( - hass, - DOMAIN, - "unfixable_problem", - is_fixable=False, - learn_more_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", - severity=IssueSeverity.WARNING, - translation_key="unfixable_problem", - ) - - async_create_issue( - hass, - DOMAIN, - "bad_psu", - is_fixable=True, - learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU", - severity=IssueSeverity.CRITICAL, - translation_key="bad_psu", - ) - - async_create_issue( - hass, - DOMAIN, - "cold_tea", - is_fixable=True, - severity=IssueSeverity.WARNING, - translation_key="cold_tea", - ) - return True -def _generate_mean_statistics( - start: datetime.datetime, end: datetime.datetime, init_value: float, max_diff: float -) -> list[StatisticData]: - statistics: list[StatisticData] = [] - mean = init_value - now = start - while now < end: - mean = mean + random() * max_diff - max_diff / 2 - statistics.append( - { - "start": now, - "mean": mean, - "min": mean - random() * max_diff, - "max": mean + random() * max_diff, - } - ) - now = now + datetime.timedelta(hours=1) - - return statistics - - -async def _insert_sum_statistics( - hass: HomeAssistant, - metadata: StatisticMetaData, - start: datetime.datetime, - end: datetime.datetime, - max_diff: float, -) -> None: - statistics: list[StatisticData] = [] - now = start - sum_ = 0.0 - statistic_id = metadata["statistic_id"] - - last_stats = await get_instance(hass).async_add_executor_job( - get_last_statistics, hass, 1, statistic_id, False, {"sum"} - ) - if statistic_id in last_stats: - sum_ = last_stats[statistic_id][0]["sum"] or 0 - while now < end: - sum_ = sum_ + random() * max_diff - statistics.append( - { - "start": now, - "sum": sum_, - } - ) - now = now + datetime.timedelta(hours=1) - - async_add_external_statistics(hass, metadata, statistics) - - -async def _insert_statistics(hass: HomeAssistant) -> None: - """Insert some fake statistics.""" - now = dt_util.now() - yesterday = now - datetime.timedelta(days=1) - yesterday_midnight = yesterday.replace(hour=0, minute=0, second=0, microsecond=0) - today_midnight = yesterday_midnight + datetime.timedelta(days=1) - - # Fake yesterday's temperatures - metadata: StatisticMetaData = { - "source": DOMAIN, - "name": "Outdoor temperature", - "statistic_id": f"{DOMAIN}:temperature_outdoor", - "unit_of_measurement": UnitOfTemperature.CELSIUS, - "has_mean": True, - "has_sum": False, - } - statistics = _generate_mean_statistics(yesterday_midnight, today_midnight, 15, 1) - async_add_external_statistics(hass, metadata, statistics) - - # Add external energy consumption in kWh, ~ 12 kWh / day - # This should be possible to pick for the energy dashboard - metadata = { - "source": DOMAIN, - "name": "Energy consumption 1", - "statistic_id": f"{DOMAIN}:energy_consumption_kwh", - "unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR, - "has_mean": False, - "has_sum": True, - } - await _insert_sum_statistics(hass, metadata, yesterday_midnight, today_midnight, 1) - - # Add external energy consumption in MWh, ~ 12 kWh / day - # This should not be possible to pick for the energy dashboard - metadata = { - "source": DOMAIN, - "name": "Energy consumption 2", - "statistic_id": f"{DOMAIN}:energy_consumption_mwh", - "unit_of_measurement": UnitOfEnergy.MEGA_WATT_HOUR, - "has_mean": False, - "has_sum": True, - } - await _insert_sum_statistics( - hass, metadata, yesterday_midnight, today_midnight, 0.001 - ) - - # Add external gas consumption in m³, ~6 m3/day - # This should be possible to pick for the energy dashboard - metadata = { - "source": DOMAIN, - "name": "Gas consumption 1", - "statistic_id": f"{DOMAIN}:gas_consumption_m3", - "unit_of_measurement": UnitOfVolume.CUBIC_METERS, - "has_mean": False, - "has_sum": True, - } - await _insert_sum_statistics( - hass, metadata, yesterday_midnight, today_midnight, 0.5 - ) - - # Add external gas consumption in ft³, ~180 ft3/day - # This should not be possible to pick for the energy dashboard - metadata = { - "source": DOMAIN, - "name": "Gas consumption 2", - "statistic_id": f"{DOMAIN}:gas_consumption_ft3", - "unit_of_measurement": UnitOfVolume.CUBIC_FEET, - "has_mean": False, - "has_sum": True, - } - await _insert_sum_statistics(hass, metadata, yesterday_midnight, today_midnight, 15) - - async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Set the config entry up.""" # Set up demo platforms with config entry await hass.config_entries.async_forward_entry_setups( config_entry, COMPONENTS_WITH_CONFIG_ENTRY_DEMO_PLATFORM ) - if "recorder" in hass.config.components: - await _insert_statistics(hass) return True diff --git a/homeassistant/components/demo/manifest.json b/homeassistant/components/demo/manifest.json index df6fa494079..bce79f11881 100644 --- a/homeassistant/components/demo/manifest.json +++ b/homeassistant/components/demo/manifest.json @@ -2,7 +2,6 @@ "domain": "demo", "name": "Demo", "documentation": "https://www.home-assistant.io/integrations/demo", - "after_dependencies": ["recorder"], "dependencies": ["conversation", "group", "zone"], "codeowners": ["@home-assistant/core"], "quality_scale": "internal", diff --git a/homeassistant/components/kitchen_sink/__init__.py b/homeassistant/components/kitchen_sink/__init__.py new file mode 100644 index 00000000000..f934a96f4d6 --- /dev/null +++ b/homeassistant/components/kitchen_sink/__init__.py @@ -0,0 +1,212 @@ +"""The Kitchen Sink integration contains demonstrations of various odds and ends. + +This sets up a demo environment of features which are obscure or which represent +incorrect behavior, and are thus not wanted in the demo integration. +""" +from __future__ import annotations + +import datetime +from random import random + +from homeassistant.components.recorder import get_instance +from homeassistant.components.recorder.models import StatisticData, StatisticMetaData +from homeassistant.components.recorder.statistics import ( + async_add_external_statistics, + get_last_statistics, +) +from homeassistant.const import UnitOfEnergy, UnitOfTemperature, UnitOfVolume +from homeassistant.core import HomeAssistant +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from homeassistant.helpers.typing import ConfigType +import homeassistant.util.dt as dt_util + +DOMAIN = "kitchen_sink" + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the demo environment.""" + # Create issues + _create_issues(hass) + + # Insert some external statistics + if "recorder" in hass.config.components: + await _insert_statistics(hass) + + return True + + +def _create_issues(hass): + """Create some issue registry issues.""" + async_create_issue( + hass, + DOMAIN, + "transmogrifier_deprecated", + breaks_in_ha_version="2023.1.1", + is_fixable=False, + learn_more_url="https://en.wiktionary.org/wiki/transmogrifier", + severity=IssueSeverity.WARNING, + translation_key="transmogrifier_deprecated", + ) + + async_create_issue( + hass, + DOMAIN, + "out_of_blinker_fluid", + breaks_in_ha_version="2023.1.1", + is_fixable=True, + learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU", + severity=IssueSeverity.CRITICAL, + translation_key="out_of_blinker_fluid", + ) + + async_create_issue( + hass, + DOMAIN, + "unfixable_problem", + is_fixable=False, + learn_more_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", + severity=IssueSeverity.WARNING, + translation_key="unfixable_problem", + ) + + async_create_issue( + hass, + DOMAIN, + "bad_psu", + is_fixable=True, + learn_more_url="https://www.youtube.com/watch?v=b9rntRxLlbU", + severity=IssueSeverity.CRITICAL, + translation_key="bad_psu", + ) + + async_create_issue( + hass, + DOMAIN, + "cold_tea", + is_fixable=True, + severity=IssueSeverity.WARNING, + translation_key="cold_tea", + ) + + +def _generate_mean_statistics( + start: datetime.datetime, end: datetime.datetime, init_value: float, max_diff: float +) -> list[StatisticData]: + statistics: list[StatisticData] = [] + mean = init_value + now = start + while now < end: + mean = mean + random() * max_diff - max_diff / 2 + statistics.append( + { + "start": now, + "mean": mean, + "min": mean - random() * max_diff, + "max": mean + random() * max_diff, + } + ) + now = now + datetime.timedelta(hours=1) + + return statistics + + +async def _insert_sum_statistics( + hass: HomeAssistant, + metadata: StatisticMetaData, + start: datetime.datetime, + end: datetime.datetime, + max_diff: float, +) -> None: + statistics: list[StatisticData] = [] + now = start + sum_ = 0.0 + statistic_id = metadata["statistic_id"] + + last_stats = await get_instance(hass).async_add_executor_job( + get_last_statistics, hass, 1, statistic_id, False, {"sum"} + ) + if statistic_id in last_stats: + sum_ = last_stats[statistic_id][0]["sum"] or 0 + while now < end: + sum_ = sum_ + random() * max_diff + statistics.append( + { + "start": now, + "sum": sum_, + } + ) + now = now + datetime.timedelta(hours=1) + + async_add_external_statistics(hass, metadata, statistics) + + +async def _insert_statistics(hass: HomeAssistant) -> None: + """Insert some fake statistics.""" + now = dt_util.now() + yesterday = now - datetime.timedelta(days=1) + yesterday_midnight = yesterday.replace(hour=0, minute=0, second=0, microsecond=0) + today_midnight = yesterday_midnight + datetime.timedelta(days=1) + + # Fake yesterday's temperatures + metadata: StatisticMetaData = { + "source": DOMAIN, + "name": "Outdoor temperature", + "statistic_id": f"{DOMAIN}:temperature_outdoor", + "unit_of_measurement": UnitOfTemperature.CELSIUS, + "has_mean": True, + "has_sum": False, + } + statistics = _generate_mean_statistics(yesterday_midnight, today_midnight, 15, 1) + async_add_external_statistics(hass, metadata, statistics) + + # Add external energy consumption in kWh, ~ 12 kWh / day + # This should be possible to pick for the energy dashboard + metadata = { + "source": DOMAIN, + "name": "Energy consumption 1", + "statistic_id": f"{DOMAIN}:energy_consumption_kwh", + "unit_of_measurement": UnitOfEnergy.KILO_WATT_HOUR, + "has_mean": False, + "has_sum": True, + } + await _insert_sum_statistics(hass, metadata, yesterday_midnight, today_midnight, 1) + + # Add external energy consumption in MWh, ~ 12 kWh / day + # This should not be possible to pick for the energy dashboard + metadata = { + "source": DOMAIN, + "name": "Energy consumption 2", + "statistic_id": f"{DOMAIN}:energy_consumption_mwh", + "unit_of_measurement": UnitOfEnergy.MEGA_WATT_HOUR, + "has_mean": False, + "has_sum": True, + } + await _insert_sum_statistics( + hass, metadata, yesterday_midnight, today_midnight, 0.001 + ) + + # Add external gas consumption in m³, ~6 m3/day + # This should be possible to pick for the energy dashboard + metadata = { + "source": DOMAIN, + "name": "Gas consumption 1", + "statistic_id": f"{DOMAIN}:gas_consumption_m3", + "unit_of_measurement": UnitOfVolume.CUBIC_METERS, + "has_mean": False, + "has_sum": True, + } + await _insert_sum_statistics( + hass, metadata, yesterday_midnight, today_midnight, 0.5 + ) + + # Add external gas consumption in ft³, ~180 ft3/day + # This should not be possible to pick for the energy dashboard + metadata = { + "source": DOMAIN, + "name": "Gas consumption 2", + "statistic_id": f"{DOMAIN}:gas_consumption_ft3", + "unit_of_measurement": UnitOfVolume.CUBIC_FEET, + "has_mean": False, + "has_sum": True, + } + await _insert_sum_statistics(hass, metadata, yesterday_midnight, today_midnight, 15) diff --git a/homeassistant/components/kitchen_sink/manifest.json b/homeassistant/components/kitchen_sink/manifest.json new file mode 100644 index 00000000000..04a4b550b58 --- /dev/null +++ b/homeassistant/components/kitchen_sink/manifest.json @@ -0,0 +1,9 @@ +{ + "after_dependencies": ["recorder"], + "codeowners": ["@home-assistant/core"], + "documentation": "https://www.home-assistant.io/integrations/kitchen_sink", + "domain": "kitchen_sink", + "iot_class": "calculated", + "name": "Everything but the Kitchen Sink", + "quality_scale": "internal" +} diff --git a/homeassistant/components/demo/repairs.py b/homeassistant/components/kitchen_sink/repairs.py similarity index 100% rename from homeassistant/components/demo/repairs.py rename to homeassistant/components/kitchen_sink/repairs.py diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index fdc9d12ad00..ee3bbcb3bb9 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -2673,6 +2673,12 @@ "config_flow": false, "iot_class": "local_push" }, + "kitchen_sink": { + "name": "Everything but the Kitchen Sink", + "integration_type": "hub", + "config_flow": false, + "iot_class": "calculated" + }, "kiwi": { "name": "KIWI", "integration_type": "hub", diff --git a/tests/components/demo/test_init.py b/tests/components/demo/test_init.py index 45fa602b143..78d5e047b0b 100644 --- a/tests/components/demo/test_init.py +++ b/tests/components/demo/test_init.py @@ -1,25 +1,12 @@ """The tests for the Demo component.""" -import datetime -from http import HTTPStatus import json -from unittest.mock import ANY, patch +from unittest.mock import patch import pytest from homeassistant.components.demo import DOMAIN -from homeassistant.components.recorder import get_instance -from homeassistant.components.recorder.statistics import ( - async_add_external_statistics, - get_last_statistics, - list_statistic_ids, -) -from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN from homeassistant.helpers.json import JSONEncoder from homeassistant.setup import async_setup_component -import homeassistant.util.dt as dt_util -from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM - -from tests.components.recorder.common import async_wait_recording_done @pytest.fixture @@ -50,264 +37,3 @@ async def test_setting_up_demo(mock_history, hass): "Unable to convert all demo entities to JSON. " "Wrong data in state machine!" ) - - -async def test_demo_statistics(recorder_mock, mock_history, hass): - """Test that the demo components makes some statistics available.""" - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() - await hass.async_start() - await async_wait_recording_done(hass) - - statistic_ids = await get_instance(hass).async_add_executor_job( - list_statistic_ids, hass - ) - assert { - "display_unit_of_measurement": "°C", - "has_mean": True, - "has_sum": False, - "name": "Outdoor temperature", - "source": "demo", - "statistic_id": "demo:temperature_outdoor", - "statistics_unit_of_measurement": "°C", - "unit_class": "temperature", - } in statistic_ids - assert { - "display_unit_of_measurement": "kWh", - "has_mean": False, - "has_sum": True, - "name": "Energy consumption 1", - "source": "demo", - "statistic_id": "demo:energy_consumption_kwh", - "statistics_unit_of_measurement": "kWh", - "unit_class": "energy", - } in statistic_ids - - -async def test_demo_statistics_growth(recorder_mock, mock_history, hass): - """Test that the demo sum statistics adds to the previous state.""" - hass.config.units = US_CUSTOMARY_SYSTEM - - now = dt_util.now() - last_week = now - datetime.timedelta(days=7) - last_week_midnight = last_week.replace(hour=0, minute=0, second=0, microsecond=0) - - statistic_id = f"{DOMAIN}:energy_consumption_kwh" - metadata = { - "source": DOMAIN, - "name": "Energy consumption 1", - "statistic_id": statistic_id, - "unit_of_measurement": "m³", - "has_mean": False, - "has_sum": True, - } - statistics = [ - { - "start": last_week_midnight, - "sum": 2**20, - } - ] - async_add_external_statistics(hass, metadata, statistics) - await async_wait_recording_done(hass) - - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() - await hass.async_start() - await async_wait_recording_done(hass) - - statistics = await get_instance(hass).async_add_executor_job( - get_last_statistics, hass, 1, statistic_id, False, {"sum"} - ) - assert statistics[statistic_id][0]["sum"] > 2**20 - assert statistics[statistic_id][0]["sum"] <= (2**20 + 24) - - -async def test_issues_created(mock_history, hass, hass_client, hass_ws_client): - """Test issues are created and can be fixed.""" - assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}}) - assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) - await hass.async_block_till_done() - await hass.async_start() - - ws_client = await hass_ws_client(hass) - client = await hass_client() - - await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) - msg = await ws_client.receive_json() - - assert msg["success"] - assert msg["result"] == { - "issues": [ - { - "breaks_in_ha_version": "2023.1.1", - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "ignored": False, - "is_fixable": False, - "issue_id": "transmogrifier_deprecated", - "issue_domain": None, - "learn_more_url": "https://en.wiktionary.org/wiki/transmogrifier", - "severity": "warning", - "translation_key": "transmogrifier_deprecated", - "translation_placeholders": None, - }, - { - "breaks_in_ha_version": "2023.1.1", - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "ignored": False, - "is_fixable": True, - "issue_id": "out_of_blinker_fluid", - "issue_domain": None, - "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", - "severity": "critical", - "translation_key": "out_of_blinker_fluid", - "translation_placeholders": None, - }, - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "ignored": False, - "is_fixable": False, - "issue_id": "unfixable_problem", - "issue_domain": None, - "learn_more_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "severity": "warning", - "translation_key": "unfixable_problem", - "translation_placeholders": None, - }, - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "ignored": False, - "is_fixable": True, - "issue_domain": None, - "issue_id": "bad_psu", - "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", - "severity": "critical", - "translation_key": "bad_psu", - "translation_placeholders": None, - }, - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "is_fixable": True, - "issue_domain": None, - "issue_id": "cold_tea", - "learn_more_url": None, - "severity": "warning", - "translation_key": "cold_tea", - "translation_placeholders": None, - "ignored": False, - }, - ] - } - - url = "/api/repairs/issues/fix" - resp = await client.post( - url, json={"handler": "demo", "issue_id": "out_of_blinker_fluid"} - ) - - assert resp.status == HTTPStatus.OK - data = await resp.json() - - flow_id = data["flow_id"] - assert data == { - "data_schema": [], - "description_placeholders": None, - "errors": None, - "flow_id": ANY, - "handler": "demo", - "last_step": None, - "step_id": "confirm", - "type": "form", - } - - url = f"/api/repairs/issues/fix/{flow_id}" - resp = await client.post(url) - - assert resp.status == HTTPStatus.OK - data = await resp.json() - - flow_id = data["flow_id"] - assert data == { - "description": None, - "description_placeholders": None, - "flow_id": flow_id, - "handler": "demo", - "type": "create_entry", - "version": 1, - } - - await ws_client.send_json({"id": 4, "type": "repairs/list_issues"}) - msg = await ws_client.receive_json() - - assert msg["success"] - assert msg["result"] == { - "issues": [ - { - "breaks_in_ha_version": "2023.1.1", - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "ignored": False, - "is_fixable": False, - "issue_id": "transmogrifier_deprecated", - "issue_domain": None, - "learn_more_url": "https://en.wiktionary.org/wiki/transmogrifier", - "severity": "warning", - "translation_key": "transmogrifier_deprecated", - "translation_placeholders": None, - }, - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "ignored": False, - "is_fixable": False, - "issue_id": "unfixable_problem", - "issue_domain": None, - "learn_more_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "severity": "warning", - "translation_key": "unfixable_problem", - "translation_placeholders": None, - }, - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "ignored": False, - "is_fixable": True, - "issue_domain": None, - "issue_id": "bad_psu", - "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", - "severity": "critical", - "translation_key": "bad_psu", - "translation_placeholders": None, - }, - { - "breaks_in_ha_version": None, - "created": ANY, - "dismissed_version": None, - "domain": "demo", - "is_fixable": True, - "issue_domain": None, - "issue_id": "cold_tea", - "learn_more_url": None, - "severity": "warning", - "translation_key": "cold_tea", - "translation_placeholders": None, - "ignored": False, - }, - ] - } diff --git a/tests/components/kitchen_sink/__init__.py b/tests/components/kitchen_sink/__init__.py new file mode 100644 index 00000000000..0b6af465c4c --- /dev/null +++ b/tests/components/kitchen_sink/__init__.py @@ -0,0 +1 @@ +"""Tests for the Everything but the Kitchen Sink integration.""" diff --git a/tests/components/kitchen_sink/test_init.py b/tests/components/kitchen_sink/test_init.py new file mode 100644 index 00000000000..fcdf5ab8f54 --- /dev/null +++ b/tests/components/kitchen_sink/test_init.py @@ -0,0 +1,287 @@ +"""The tests for the Everything but the Kitchen Sink integration.""" +import datetime +from http import HTTPStatus +from unittest.mock import ANY + +import pytest + +from homeassistant.components.kitchen_sink import DOMAIN +from homeassistant.components.recorder import get_instance +from homeassistant.components.recorder.statistics import ( + async_add_external_statistics, + get_last_statistics, + list_statistic_ids, +) +from homeassistant.components.repairs import DOMAIN as REPAIRS_DOMAIN +from homeassistant.setup import async_setup_component +import homeassistant.util.dt as dt_util +from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM + +from tests.components.recorder.common import async_wait_recording_done + + +@pytest.fixture +def mock_history(hass): + """Mock history component loaded.""" + hass.config.components.add("history") + + +async def test_demo_statistics(recorder_mock, mock_history, hass): + """Test that the kitchen sink component makes some statistics available.""" + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + await hass.async_start() + await async_wait_recording_done(hass) + + statistic_ids = await get_instance(hass).async_add_executor_job( + list_statistic_ids, hass + ) + assert { + "display_unit_of_measurement": "°C", + "has_mean": True, + "has_sum": False, + "name": "Outdoor temperature", + "source": DOMAIN, + "statistic_id": f"{DOMAIN}:temperature_outdoor", + "statistics_unit_of_measurement": "°C", + "unit_class": "temperature", + } in statistic_ids + assert { + "display_unit_of_measurement": "kWh", + "has_mean": False, + "has_sum": True, + "name": "Energy consumption 1", + "source": DOMAIN, + "statistic_id": f"{DOMAIN}:energy_consumption_kwh", + "statistics_unit_of_measurement": "kWh", + "unit_class": "energy", + } in statistic_ids + + +async def test_demo_statistics_growth(recorder_mock, mock_history, hass): + """Test that the kitchen sink sum statistics adds to the previous state.""" + hass.config.units = US_CUSTOMARY_SYSTEM + + now = dt_util.now() + last_week = now - datetime.timedelta(days=7) + last_week_midnight = last_week.replace(hour=0, minute=0, second=0, microsecond=0) + + statistic_id = f"{DOMAIN}:energy_consumption_kwh" + metadata = { + "source": DOMAIN, + "name": "Energy consumption 1", + "statistic_id": statistic_id, + "unit_of_measurement": "m³", + "has_mean": False, + "has_sum": True, + } + statistics = [ + { + "start": last_week_midnight, + "sum": 2**20, + } + ] + async_add_external_statistics(hass, metadata, statistics) + await async_wait_recording_done(hass) + + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + await hass.async_start() + await async_wait_recording_done(hass) + + statistics = await get_instance(hass).async_add_executor_job( + get_last_statistics, hass, 1, statistic_id, False, {"sum"} + ) + assert statistics[statistic_id][0]["sum"] > 2**20 + assert statistics[statistic_id][0]["sum"] <= (2**20 + 24) + + +async def test_issues_created(mock_history, hass, hass_client, hass_ws_client): + """Test issues are created and can be fixed.""" + assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}}) + assert await async_setup_component(hass, DOMAIN, {DOMAIN: {}}) + await hass.async_block_till_done() + await hass.async_start() + + ws_client = await hass_ws_client(hass) + client = await hass_client() + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + assert msg["result"] == { + "issues": [ + { + "breaks_in_ha_version": "2023.1.1", + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "ignored": False, + "is_fixable": False, + "issue_id": "transmogrifier_deprecated", + "issue_domain": None, + "learn_more_url": "https://en.wiktionary.org/wiki/transmogrifier", + "severity": "warning", + "translation_key": "transmogrifier_deprecated", + "translation_placeholders": None, + }, + { + "breaks_in_ha_version": "2023.1.1", + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "ignored": False, + "is_fixable": True, + "issue_id": "out_of_blinker_fluid", + "issue_domain": None, + "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", + "severity": "critical", + "translation_key": "out_of_blinker_fluid", + "translation_placeholders": None, + }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "ignored": False, + "is_fixable": False, + "issue_id": "unfixable_problem", + "issue_domain": None, + "learn_more_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "severity": "warning", + "translation_key": "unfixable_problem", + "translation_placeholders": None, + }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "ignored": False, + "is_fixable": True, + "issue_domain": None, + "issue_id": "bad_psu", + "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", + "severity": "critical", + "translation_key": "bad_psu", + "translation_placeholders": None, + }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "is_fixable": True, + "issue_domain": None, + "issue_id": "cold_tea", + "learn_more_url": None, + "severity": "warning", + "translation_key": "cold_tea", + "translation_placeholders": None, + "ignored": False, + }, + ] + } + + url = "/api/repairs/issues/fix" + resp = await client.post( + url, json={"handler": DOMAIN, "issue_id": "out_of_blinker_fluid"} + ) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data == { + "data_schema": [], + "description_placeholders": None, + "errors": None, + "flow_id": ANY, + "handler": DOMAIN, + "last_step": None, + "step_id": "confirm", + "type": "form", + } + + url = f"/api/repairs/issues/fix/{flow_id}" + resp = await client.post(url) + + assert resp.status == HTTPStatus.OK + data = await resp.json() + + flow_id = data["flow_id"] + assert data == { + "description": None, + "description_placeholders": None, + "flow_id": flow_id, + "handler": DOMAIN, + "type": "create_entry", + "version": 1, + } + + await ws_client.send_json({"id": 4, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + assert msg["result"] == { + "issues": [ + { + "breaks_in_ha_version": "2023.1.1", + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "ignored": False, + "is_fixable": False, + "issue_id": "transmogrifier_deprecated", + "issue_domain": None, + "learn_more_url": "https://en.wiktionary.org/wiki/transmogrifier", + "severity": "warning", + "translation_key": "transmogrifier_deprecated", + "translation_placeholders": None, + }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "ignored": False, + "is_fixable": False, + "issue_id": "unfixable_problem", + "issue_domain": None, + "learn_more_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "severity": "warning", + "translation_key": "unfixable_problem", + "translation_placeholders": None, + }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "ignored": False, + "is_fixable": True, + "issue_domain": None, + "issue_id": "bad_psu", + "learn_more_url": "https://www.youtube.com/watch?v=b9rntRxLlbU", + "severity": "critical", + "translation_key": "bad_psu", + "translation_placeholders": None, + }, + { + "breaks_in_ha_version": None, + "created": ANY, + "dismissed_version": None, + "domain": DOMAIN, + "is_fixable": True, + "issue_domain": None, + "issue_id": "cold_tea", + "learn_more_url": None, + "severity": "warning", + "translation_key": "cold_tea", + "translation_placeholders": None, + "ignored": False, + }, + ] + }