Add integration type (#68349)

This commit is contained in:
Paulus Schoutsen 2022-03-20 20:38:13 -07:00 committed by GitHub
parent 4f9df1fd0f
commit 3213091b8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 608 additions and 499 deletions

View File

@ -1,13 +1,14 @@
"""Http views to control the config manager."""
from __future__ import annotations
import asyncio
from http import HTTPStatus
from aiohttp import web
import aiohttp.web_exceptions
import voluptuous as vol
from homeassistant import config_entries, data_entry_flow
from homeassistant import config_entries, data_entry_flow, loader
from homeassistant.auth.permissions.const import CAT_CONFIG_ENTRIES, POLICY_EDIT
from homeassistant.components import websocket_api
from homeassistant.components.http import HomeAssistantView
@ -48,11 +49,36 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
async def get(self, request):
"""List available config entries."""
hass = request.app["hass"]
hass: HomeAssistant = request.app["hass"]
return self.json(
[entry_json(entry) for entry in hass.config_entries.async_entries()]
)
kwargs = {}
if "domain" in request.query:
kwargs["domain"] = request.query["domain"]
entries = hass.config_entries.async_entries(**kwargs)
if "type" not in request.query:
return self.json([entry_json(entry) for entry in entries])
integrations = {}
type_filter = request.query["type"]
# Fetch all the integrations so we can check their type
for integration in await asyncio.gather(
*(
loader.async_get_integration(hass, domain)
for domain in {entry.domain for entry in entries}
)
):
integrations[integration.domain] = integration
entries = [
entry
for entry in entries
if integrations[entry.domain].integration_type == type_filter
]
return self.json([entry_json(entry) for entry in entries])
class ConfigManagerEntryResourceView(HomeAssistantView):
@ -179,7 +205,10 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
async def get(self, request):
"""List available flow handlers."""
hass = request.app["hass"]
return self.json(await async_get_config_flows(hass))
kwargs = {}
if "type" in request.query:
kwargs["type_filter"] = request.query["type"]
return self.json(await async_get_config_flows(hass, **kwargs))
class OptionManagerFlowIndexView(FlowManagerIndexView):

View File

@ -1,5 +1,6 @@
{
"domain": "derivative",
"integration_type": "helper",
"name": "Derivative",
"documentation": "https://www.home-assistant.io/integrations/derivative",
"codeowners": [

View File

@ -5,394 +5,398 @@ To update, run python3 -m script.hassfest
# fmt: off
FLOWS = [
"abode",
"accuweather",
"acmeda",
"adax",
"adguard",
"advantage_air",
"aemet",
"agent_dvr",
"airly",
"airnow",
"airthings",
"airtouch4",
"airvisual",
"airzone",
"alarmdecoder",
"almond",
"ambee",
"amberelectric",
"ambiclimate",
"ambient_station",
"androidtv",
"apple_tv",
"arcam_fmj",
"aseko_pool_live",
"asuswrt",
"atag",
"august",
"aurora",
"aurora_abb_powerone",
"aussie_broadband",
"awair",
"axis",
"azure_devops",
"azure_event_hub",
"balboa",
"blebox",
"blink",
"bmw_connected_drive",
"bond",
"bosch_shc",
"braviatv",
"broadlink",
"brother",
"brunt",
"bsblan",
"buienradar",
"canary",
"cast",
"cert_expiry",
"cloudflare",
"co2signal",
"coinbase",
"control4",
"coolmaster",
"coronavirus",
"cpuspeed",
"crownstone",
"daikin",
"deconz",
"denonavr",
"derivative",
"devolo_home_control",
"devolo_home_network",
"dexcom",
"dialogflow",
"directv",
"dlna_dmr",
"dlna_dms",
"dnsip",
"doorbird",
"dsmr",
"dunehd",
"dynalite",
"eafm",
"ecobee",
"econet",
"efergy",
"elgato",
"elkm1",
"elmax",
"emonitor",
"emulated_roku",
"enocean",
"enphase_envoy",
"environment_canada",
"epson",
"esphome",
"evil_genius_labs",
"ezviz",
"faa_delays",
"fireservicerota",
"fivem",
"fjaraskupan",
"flick_electric",
"flipr",
"flo",
"flume",
"flunearyou",
"flux_led",
"forecast_solar",
"forked_daapd",
"foscam",
"freebox",
"freedompro",
"fritz",
"fritzbox",
"fritzbox_callmonitor",
"fronius",
"garages_amsterdam",
"gdacs",
"geofency",
"geonetnz_quakes",
"geonetnz_volcano",
"gios",
"github",
"glances",
"goalzero",
"gogogate2",
"goodwe",
"google",
"google_travel_time",
"gpslogger",
"gree",
"group",
"growatt_server",
"guardian",
"habitica",
"hangouts",
"harmony",
"heos",
"hisense_aehw4a1",
"hive",
"hlk_sw16",
"home_connect",
"home_plus_control",
"homekit",
"homekit_controller",
"homematicip_cloud",
"homewizard",
"honeywell",
"huawei_lte",
"hue",
"huisbaasje",
"hunterdouglas_powerview",
"hvv_departures",
"hyperion",
"ialarm",
"iaqualink",
"icloud",
"ifttt",
"insteon",
"integration",
"intellifire",
"ios",
"iotawatt",
"ipma",
"ipp",
"iqvia",
"islamic_prayer_times",
"iss",
"isy994",
"izone",
"jellyfin",
"juicenet",
"kaleidescape",
"keenetic_ndms2",
"kmtronic",
"knx",
"kodi",
"konnected",
"kostal_plenticore",
"kraken",
"kulersky",
"launch_library",
"life360",
"lifx",
"litejet",
"litterrobot",
"local_ip",
"locative",
"logi_circle",
"lookin",
"luftdaten",
"lutron_caseta",
"lyric",
"mailgun",
"mazda",
"melcloud",
"met",
"met_eireann",
"meteo_france",
"meteoclimatic",
"metoffice",
"mikrotik",
"mill",
"minecraft_server",
"mjpeg",
"mobile_app",
"modem_callerid",
"modern_forms",
"moehlenhoff_alpha2",
"monoprice",
"moon",
"motion_blinds",
"motioneye",
"mqtt",
"mullvad",
"mutesync",
"myq",
"mysensors",
"nam",
"nanoleaf",
"neato",
"nest",
"netatmo",
"netgear",
"nexia",
"nfandroidtv",
"nightscout",
"nina",
"nmap_tracker",
"notion",
"nuheat",
"nuki",
"nut",
"nws",
"nzbget",
"octoprint",
"omnilogic",
"oncue",
"ondilo_ico",
"onewire",
"onvif",
"open_meteo",
"opengarage",
"opentherm_gw",
"openuv",
"openweathermap",
"overkiz",
"ovo_energy",
"owntracks",
"p1_monitor",
"panasonic_viera",
"philips_js",
"pi_hole",
"picnic",
"plaato",
"plex",
"plugwise",
"plum_lightpad",
"point",
"poolsense",
"powerwall",
"profiler",
"progettihwsw",
"prosegur",
"ps4",
"pure_energie",
"pvoutput",
"pvpc_hourly_pricing",
"rachio",
"radio_browser",
"rainforest_eagle",
"rainmachine",
"rdw",
"recollect_waste",
"renault",
"rfxtrx",
"ridwell",
"ring",
"risco",
"rituals_perfume_genie",
"roku",
"roomba",
"roon",
"rpi_power",
"rtsp_to_webrtc",
"ruckus_unleashed",
"samsungtv",
"screenlogic",
"season",
"sense",
"senseme",
"sensibo",
"sentry",
"sharkiq",
"shelly",
"shopping_list",
"sia",
"simplisafe",
"sleepiq",
"sma",
"smappee",
"smart_meter_texas",
"smartthings",
"smarttub",
"smhi",
"sms",
"solaredge",
"solarlog",
"solax",
"soma",
"somfy",
"somfy_mylink",
"sonarr",
"songpal",
"sonos",
"speedtestdotnet",
"spider",
"spotify",
"squeezebox",
"srp_energy",
"starline",
"steamist",
"stookalert",
"subaru",
"sun",
"surepetcare",
"switch_as_x",
"switchbot",
"switcher_kis",
"syncthing",
"syncthru",
"synology_dsm",
"system_bridge",
"tado",
"tailscale",
"tasmota",
"tellduslive",
"tesla_wall_connector",
"tibber",
"tile",
"tolo",
"tomorrowio",
"toon",
"totalconnect",
"tplink",
"traccar",
"tractive",
"tradfri",
"trafikverket_weatherstation",
"transmission",
"tuya",
"twentemilieu",
"twilio",
"twinkly",
"unifi",
"unifiprotect",
"upb",
"upcloud",
"upnp",
"uptime",
"uptimerobot",
"vallox",
"velbus",
"venstar",
"vera",
"verisure",
"version",
"vesync",
"vicare",
"vilfo",
"vizio",
"vlc_telnet",
"volumio",
"wallbox",
"watttime",
"waze_travel_time",
"webostv",
"wemo",
"whirlpool",
"whois",
"wiffi",
"wilight",
"withings",
"wiz",
"wled",
"wolflink",
"xbox",
"xiaomi_aqara",
"xiaomi_miio",
"yale_smart_alarm",
"yamaha_musiccast",
"yeelight",
"youless",
"zerproc",
"zha",
"zwave_js",
"zwave_me"
]
FLOWS = {
"integration": [
"abode",
"accuweather",
"acmeda",
"adax",
"adguard",
"advantage_air",
"aemet",
"agent_dvr",
"airly",
"airnow",
"airthings",
"airtouch4",
"airvisual",
"airzone",
"alarmdecoder",
"almond",
"ambee",
"amberelectric",
"ambiclimate",
"ambient_station",
"androidtv",
"apple_tv",
"arcam_fmj",
"aseko_pool_live",
"asuswrt",
"atag",
"august",
"aurora",
"aurora_abb_powerone",
"aussie_broadband",
"awair",
"axis",
"azure_devops",
"azure_event_hub",
"balboa",
"blebox",
"blink",
"bmw_connected_drive",
"bond",
"bosch_shc",
"braviatv",
"broadlink",
"brother",
"brunt",
"bsblan",
"buienradar",
"canary",
"cast",
"cert_expiry",
"cloudflare",
"co2signal",
"coinbase",
"control4",
"coolmaster",
"coronavirus",
"cpuspeed",
"crownstone",
"daikin",
"deconz",
"denonavr",
"devolo_home_control",
"devolo_home_network",
"dexcom",
"dialogflow",
"directv",
"dlna_dmr",
"dlna_dms",
"dnsip",
"doorbird",
"dsmr",
"dunehd",
"dynalite",
"eafm",
"ecobee",
"econet",
"efergy",
"elgato",
"elkm1",
"elmax",
"emonitor",
"emulated_roku",
"enocean",
"enphase_envoy",
"environment_canada",
"epson",
"esphome",
"evil_genius_labs",
"ezviz",
"faa_delays",
"fireservicerota",
"fivem",
"fjaraskupan",
"flick_electric",
"flipr",
"flo",
"flume",
"flunearyou",
"flux_led",
"forecast_solar",
"forked_daapd",
"foscam",
"freebox",
"freedompro",
"fritz",
"fritzbox",
"fritzbox_callmonitor",
"fronius",
"garages_amsterdam",
"gdacs",
"geofency",
"geonetnz_quakes",
"geonetnz_volcano",
"gios",
"github",
"glances",
"goalzero",
"gogogate2",
"goodwe",
"google",
"google_travel_time",
"gpslogger",
"gree",
"group",
"growatt_server",
"guardian",
"habitica",
"hangouts",
"harmony",
"heos",
"hisense_aehw4a1",
"hive",
"hlk_sw16",
"home_connect",
"home_plus_control",
"homekit",
"homekit_controller",
"homematicip_cloud",
"homewizard",
"honeywell",
"huawei_lte",
"hue",
"huisbaasje",
"hunterdouglas_powerview",
"hvv_departures",
"hyperion",
"ialarm",
"iaqualink",
"icloud",
"ifttt",
"insteon",
"integration",
"intellifire",
"ios",
"iotawatt",
"ipma",
"ipp",
"iqvia",
"islamic_prayer_times",
"iss",
"isy994",
"izone",
"jellyfin",
"juicenet",
"kaleidescape",
"keenetic_ndms2",
"kmtronic",
"knx",
"kodi",
"konnected",
"kostal_plenticore",
"kraken",
"kulersky",
"launch_library",
"life360",
"lifx",
"litejet",
"litterrobot",
"local_ip",
"locative",
"logi_circle",
"lookin",
"luftdaten",
"lutron_caseta",
"lyric",
"mailgun",
"mazda",
"melcloud",
"met",
"met_eireann",
"meteo_france",
"meteoclimatic",
"metoffice",
"mikrotik",
"mill",
"minecraft_server",
"mjpeg",
"mobile_app",
"modem_callerid",
"modern_forms",
"moehlenhoff_alpha2",
"monoprice",
"moon",
"motion_blinds",
"motioneye",
"mqtt",
"mullvad",
"mutesync",
"myq",
"mysensors",
"nam",
"nanoleaf",
"neato",
"nest",
"netatmo",
"netgear",
"nexia",
"nfandroidtv",
"nightscout",
"nina",
"nmap_tracker",
"notion",
"nuheat",
"nuki",
"nut",
"nws",
"nzbget",
"octoprint",
"omnilogic",
"oncue",
"ondilo_ico",
"onewire",
"onvif",
"open_meteo",
"opengarage",
"opentherm_gw",
"openuv",
"openweathermap",
"overkiz",
"ovo_energy",
"owntracks",
"p1_monitor",
"panasonic_viera",
"philips_js",
"pi_hole",
"picnic",
"plaato",
"plex",
"plugwise",
"plum_lightpad",
"point",
"poolsense",
"powerwall",
"profiler",
"progettihwsw",
"prosegur",
"ps4",
"pure_energie",
"pvoutput",
"pvpc_hourly_pricing",
"rachio",
"radio_browser",
"rainforest_eagle",
"rainmachine",
"rdw",
"recollect_waste",
"renault",
"rfxtrx",
"ridwell",
"ring",
"risco",
"rituals_perfume_genie",
"roku",
"roomba",
"roon",
"rpi_power",
"rtsp_to_webrtc",
"ruckus_unleashed",
"samsungtv",
"screenlogic",
"season",
"sense",
"senseme",
"sensibo",
"sentry",
"sharkiq",
"shelly",
"shopping_list",
"sia",
"simplisafe",
"sleepiq",
"sma",
"smappee",
"smart_meter_texas",
"smartthings",
"smarttub",
"smhi",
"sms",
"solaredge",
"solarlog",
"solax",
"soma",
"somfy",
"somfy_mylink",
"sonarr",
"songpal",
"sonos",
"speedtestdotnet",
"spider",
"spotify",
"squeezebox",
"srp_energy",
"starline",
"steamist",
"stookalert",
"subaru",
"sun",
"surepetcare",
"switch_as_x",
"switchbot",
"switcher_kis",
"syncthing",
"syncthru",
"synology_dsm",
"system_bridge",
"tado",
"tailscale",
"tasmota",
"tellduslive",
"tesla_wall_connector",
"tibber",
"tile",
"tolo",
"tomorrowio",
"toon",
"totalconnect",
"tplink",
"traccar",
"tractive",
"tradfri",
"trafikverket_weatherstation",
"transmission",
"tuya",
"twentemilieu",
"twilio",
"twinkly",
"unifi",
"unifiprotect",
"upb",
"upcloud",
"upnp",
"uptime",
"uptimerobot",
"vallox",
"velbus",
"venstar",
"vera",
"verisure",
"version",
"vesync",
"vicare",
"vilfo",
"vizio",
"vlc_telnet",
"volumio",
"wallbox",
"watttime",
"waze_travel_time",
"webostv",
"wemo",
"whirlpool",
"whois",
"wiffi",
"wilight",
"withings",
"wiz",
"wled",
"wolflink",
"xbox",
"xiaomi_aqara",
"xiaomi_miio",
"yale_smart_alarm",
"yamaha_musiccast",
"yeelight",
"youless",
"zerproc",
"zha",
"zwave_js",
"zwave_me"
],
"helper": [
"derivative"
]
}

View File

@ -16,7 +16,7 @@ import logging
import pathlib
import sys
from types import ModuleType
from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, cast
from typing import TYPE_CHECKING, Any, Literal, TypedDict, TypeVar, cast
from awesomeversion import (
AwesomeVersion,
@ -87,6 +87,7 @@ class Manifest(TypedDict, total=False):
name: str
disabled: str
domain: str
integration_type: Literal["integration", "helper"]
dependencies: list[str]
after_dependencies: list[str]
requirements: list[str]
@ -180,20 +181,29 @@ async def async_get_custom_components(
return cast(dict[str, "Integration"], reg_or_evt)
async def async_get_config_flows(hass: HomeAssistant) -> set[str]:
async def async_get_config_flows(
hass: HomeAssistant,
type_filter: Literal["helper", "integration"] | None = None,
) -> set[str]:
"""Return cached list of config flows."""
# pylint: disable=import-outside-toplevel
from .generated.config_flows import FLOWS
flows: set[str] = set()
flows.update(FLOWS)
integrations = await async_get_custom_components(hass)
flows: set[str] = set()
if type_filter is not None:
flows.update(FLOWS[type_filter])
else:
for type_flows in FLOWS.values():
flows.update(type_flows)
flows.update(
[
integration.domain
for integration in integrations.values()
if integration.config_flow
and (type_filter is None or integration.integration_type == type_filter)
]
)
@ -474,6 +484,11 @@ class Integration:
"""Return the integration IoT Class."""
return self.manifest.get("iot_class")
@property
def integration_type(self) -> Literal["integration", "helper"]:
"""Return the integration type."""
return self.manifest.get("integration_type", "integration")
@property
def mqtt(self) -> list[str] | None:
"""Return Integration MQTT entries."""

View File

@ -69,7 +69,10 @@ def validate_integration(config: Config, integration: Integration):
def generate_and_validate(integrations: dict[str, Integration], config: Config):
"""Validate and generate config flow data."""
domains = []
domains = {
"integration": [],
"helper": [],
}
for domain in sorted(integrations):
integration = integrations[domain]
@ -79,7 +82,7 @@ def generate_and_validate(integrations: dict[str, Integration], config: Config):
validate_integration(config, integration)
domains.append(domain)
domains[integration.integration_type].append(domain)
return BASE.format(json.dumps(domains, indent=4))

View File

@ -152,6 +152,7 @@ MANIFEST_SCHEMA = vol.Schema(
{
vol.Required("domain"): str,
vol.Required("name"): str,
vol.Optional("integration_type"): "helper",
vol.Optional("config_flow"): bool,
vol.Optional("mqtt"): [str],
vol.Optional("zeroconf"): [

View File

@ -112,6 +112,11 @@ class Integration:
"""List of dependencies."""
return self.manifest.get("dependencies", [])
@property
def integration_type(self) -> str:
"""Get integration_type."""
return self.manifest.get("integration_type", "integration")
def add_error(self, *args: Any, **kwargs: Any) -> None:
"""Add an error."""
self.errors.append(Error(*args, **kwargs))

View File

@ -23,6 +23,13 @@ from tests.common import (
)
@pytest.fixture
def clear_handlers():
"""Clear config entry handlers."""
with patch.dict(HANDLERS, clear=True):
yield
@pytest.fixture(autouse=True)
def mock_test_component(hass):
"""Ensure a component called 'test' exists."""
@ -30,104 +37,133 @@ def mock_test_component(hass):
@pytest.fixture
def client(hass, hass_client):
async def client(hass, hass_client):
"""Fixture that can interact with the config manager API."""
hass.loop.run_until_complete(async_setup_component(hass, "http", {}))
hass.loop.run_until_complete(config_entries.async_setup(hass))
yield hass.loop.run_until_complete(hass_client())
await async_setup_component(hass, "http", {})
await config_entries.async_setup(hass)
return await hass_client()
async def test_get_entries(hass, client):
async def test_get_entries(hass, client, clear_handlers):
"""Test get entries."""
with patch.dict(HANDLERS, clear=True):
mock_integration(hass, MockModule("comp1"))
mock_integration(
hass, MockModule("comp2", partial_manifest={"integration_type": "helper"})
)
mock_integration(hass, MockModule("comp3"))
@HANDLERS.register("comp1")
class Comp1ConfigFlow:
"""Config flow with options flow."""
@HANDLERS.register("comp1")
class Comp1ConfigFlow:
"""Config flow with options flow."""
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get options flow."""
pass
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get options flow."""
pass
@classmethod
@callback
def async_supports_options_flow(cls, config_entry):
"""Return options flow support for this handler."""
return True
@classmethod
@callback
def async_supports_options_flow(cls, config_entry):
"""Return options flow support for this handler."""
return True
hass.helpers.config_entry_flow.register_discovery_flow(
"comp2", "Comp 2", lambda: None
)
hass.helpers.config_entry_flow.register_discovery_flow(
"comp2", "Comp 2", lambda: None
)
entry = MockConfigEntry(
domain="comp1",
title="Test 1",
source="bla",
)
entry.supports_unload = True
entry.add_to_hass(hass)
MockConfigEntry(
domain="comp2",
title="Test 2",
source="bla2",
state=core_ce.ConfigEntryState.SETUP_ERROR,
reason="Unsupported API",
).add_to_hass(hass)
MockConfigEntry(
domain="comp3",
title="Test 3",
source="bla3",
disabled_by=core_ce.ConfigEntryDisabler.USER,
).add_to_hass(hass)
entry = MockConfigEntry(
domain="comp1",
title="Test 1",
source="bla",
)
entry.supports_unload = True
entry.add_to_hass(hass)
MockConfigEntry(
domain="comp2",
title="Test 2",
source="bla2",
state=core_ce.ConfigEntryState.SETUP_ERROR,
reason="Unsupported API",
).add_to_hass(hass)
MockConfigEntry(
domain="comp3",
title="Test 3",
source="bla3",
disabled_by=core_ce.ConfigEntryDisabler.USER,
).add_to_hass(hass)
resp = await client.get("/api/config/config_entries/entry")
assert resp.status == HTTPStatus.OK
data = await resp.json()
for entry in data:
entry.pop("entry_id")
assert data == [
{
"domain": "comp1",
"title": "Test 1",
"source": "bla",
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": True,
"supports_remove_device": False,
"supports_unload": True,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": None,
"reason": None,
},
{
"domain": "comp2",
"title": "Test 2",
"source": "bla2",
"state": core_ce.ConfigEntryState.SETUP_ERROR.value,
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": None,
"reason": "Unsupported API",
},
{
"domain": "comp3",
"title": "Test 3",
"source": "bla3",
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": core_ce.ConfigEntryDisabler.USER,
"reason": None,
},
]
resp = await client.get("/api/config/config_entries/entry")
assert resp.status == HTTPStatus.OK
data = await resp.json()
for entry in data:
entry.pop("entry_id")
assert data == [
{
"domain": "comp1",
"title": "Test 1",
"source": "bla",
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": True,
"supports_remove_device": False,
"supports_unload": True,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": None,
"reason": None,
},
{
"domain": "comp2",
"title": "Test 2",
"source": "bla2",
"state": core_ce.ConfigEntryState.SETUP_ERROR.value,
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": None,
"reason": "Unsupported API",
},
{
"domain": "comp3",
"title": "Test 3",
"source": "bla3",
"state": core_ce.ConfigEntryState.NOT_LOADED.value,
"supports_options": False,
"supports_remove_device": False,
"supports_unload": False,
"pref_disable_new_entities": False,
"pref_disable_polling": False,
"disabled_by": core_ce.ConfigEntryDisabler.USER,
"reason": None,
},
]
resp = await client.get("/api/config/config_entries/entry?domain=comp3")
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert len(data) == 1
assert data[0]["domain"] == "comp3"
resp = await client.get("/api/config/config_entries/entry?domain=comp3&type=helper")
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert len(data) == 0
resp = await client.get(
"/api/config/config_entries/entry?domain=comp3&type=integration"
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert len(data) == 1
resp = await client.get("/api/config/config_entries/entry?type=integration")
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert len(data) == 2
assert data[0]["domain"] == "comp1"
assert data[1]["domain"] == "comp3"
async def test_remove_entry(hass, client):
@ -224,13 +260,28 @@ async def test_reload_entry_in_setup_retry(hass, client, hass_admin_user):
assert len(hass.config_entries.async_entries()) == 1
async def test_available_flows(hass, client):
@pytest.mark.parametrize(
"type_filter,result",
(
(None, {"hello", "another", "world"}),
("integration", {"hello", "another"}),
("helper", {"world"}),
),
)
async def test_available_flows(hass, client, type_filter, result):
"""Test querying the available flows."""
with patch.object(config_flows, "FLOWS", ["hello", "world"]):
resp = await client.get("/api/config/config_entries/flow_handlers")
with patch.object(
config_flows,
"FLOWS",
{"integration": ["hello", "another"], "helper": ["world"]},
):
resp = await client.get(
"/api/config/config_entries/flow_handlers",
params={"type": type_filter} if type_filter else {},
)
assert resp.status == HTTPStatus.OK
data = await resp.json()
assert set(data) == {"hello", "world"}
assert set(data) == result
############################

View File

@ -15,7 +15,7 @@ from homeassistant.setup import async_setup_component
@pytest.fixture
def mock_config_flows():
"""Mock the config flows."""
flows = []
flows = {"integration": [], "helper": {}}
with patch.object(config_flows, "FLOWS", flows):
yield flows
@ -124,7 +124,7 @@ async def test_get_translations(hass, mock_config_flows, enable_custom_integrati
async def test_get_translations_loads_config_flows(hass, mock_config_flows):
"""Test the get translations helper loads config flow translations."""
mock_config_flows.append("component1")
mock_config_flows["integration"].append("component1")
integration = Mock(file_path=pathlib.Path(__file__))
integration.name = "Component 1"
@ -153,7 +153,7 @@ async def test_get_translations_loads_config_flows(hass, mock_config_flows):
assert "component1" not in hass.config.components
mock_config_flows.append("component2")
mock_config_flows["integration"].append("component2")
integration = Mock(file_path=pathlib.Path(__file__))
integration.name = "Component 2"