1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-18 07:56:03 +01:00

Add hassfest check to make sure new integrations have an integration type (#164001)

This commit is contained in:
Joost Lekkerkerker
2026-03-16 18:10:30 +01:00
committed by GitHub
parent 56b4d2c015
commit b1578a0c8c
3 changed files with 194 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ from . import (
docker,
icons,
integration_info,
integration_type,
json,
labs,
manifest,
@@ -48,6 +49,7 @@ INTEGRATION_PLUGINS = [
dhcp,
icons,
integration_info,
integration_type,
json,
labs,
manifest,

View File

@@ -0,0 +1,100 @@
"""Validate integration type is set for config flow integrations."""
from __future__ import annotations
from .model import Config, Integration
# Integrations with config_flow that are missing integration_type.
# These need to be fixed; do not add new entries to this list.
MISSING_INTEGRATION_TYPE = {
"abode",
"acmeda",
"adax",
"awair",
"bluetooth",
"bthome",
"chacon_dio",
"color_extractor",
"crownstone",
"deako",
"dialogflow",
"dynalite",
"elmax",
"emulated_roku",
"ezviz",
"file",
"filesize",
"fluss",
"flux_led",
"folder_watcher",
"forked_daapd",
"geniushub",
"gentex_homelink",
"geofency",
"govee_light_local",
"gpsd",
"gpslogger",
"gree",
"holiday",
"homekit",
"html5",
"ifttt",
"influxdb",
"ios",
"jewish_calendar",
"local_calendar",
"local_file",
"local_ip",
"local_todo",
"locative",
"mcp",
"media_extractor",
"mill",
"mjpeg",
"modern_forms",
"ness_alarm",
"nmap_tracker",
"otp",
"orvibo",
"profiler",
"proximity",
"rhasspy",
"risco",
"rpi_power",
"scrape",
"shopping_list",
"sql",
"sunweg",
"systemmonitor",
"tasmota",
"traccar",
"traccar_server",
"upb",
"version",
"volvooncall",
"wemo",
"zodiac",
}
def validate(integrations: dict[str, Integration], config: Config) -> None:
"""Validate that all config flow integrations declare an integration type."""
for integration in integrations.values():
if not integration.config_flow or not integration.core:
continue
if "integration_type" in integration.manifest:
if integration.domain in MISSING_INTEGRATION_TYPE:
integration.add_error(
"integration_type",
"Integration has an `integration_type` in the manifest but is still listed in MISSING_INTEGRATION_TYPE",
)
continue
if integration.domain in MISSING_INTEGRATION_TYPE:
continue
integration.add_error(
"integration_type",
"Integration has a config flow but is missing an `integration_type` in the manifest",
)

View File

@@ -0,0 +1,92 @@
"""Tests for hassfest integration_type."""
import pytest
from script.hassfest import integration_type
from script.hassfest.model import Config, Integration
from . import get_integration
def _get_integration(domain: str, config: Config, manifest_extra: dict) -> Integration:
"""Helper to create an integration with extra manifest keys."""
integration = get_integration(domain, config)
integration.manifest.update(manifest_extra)
return integration
@pytest.mark.usefixtures("mock_core_integration")
def test_integration_with_config_flow_and_integration_type(config: Config) -> None:
"""Integration with config_flow and integration_type should pass without errors."""
integrations = {
"test": _get_integration(
"test",
config,
{"config_flow": True, "integration_type": "device"},
)
}
integration_type.validate(integrations, config)
assert integrations["test"].errors == []
@pytest.mark.usefixtures("mock_core_integration")
def test_integration_with_config_flow_missing_integration_type(config: Config) -> None:
"""Integration with config_flow but no integration_type and not in allowlist should error."""
integrations = {
"test": _get_integration(
"test",
config,
{"config_flow": True},
)
}
integration_type.validate(integrations, config)
assert len(integrations["test"].errors) == 1
assert "missing an `integration_type`" in integrations["test"].errors[0].error
@pytest.mark.usefixtures("mock_core_integration")
def test_integration_with_config_flow_in_allowlist(config: Config) -> None:
"""Integration with config_flow but no integration_type and in allowlist should pass."""
domain = next(iter(integration_type.MISSING_INTEGRATION_TYPE))
integrations = {
domain: _get_integration(
domain,
config,
{"config_flow": True},
)
}
integration_type.validate(integrations, config)
assert integrations[domain].errors == []
@pytest.mark.usefixtures("mock_core_integration")
def test_integration_with_integration_type_still_in_allowlist(config: Config) -> None:
"""Integration with integration_type but still in allowlist should error."""
domain = next(iter(integration_type.MISSING_INTEGRATION_TYPE))
integrations = {
domain: _get_integration(
domain,
config,
{"config_flow": True, "integration_type": "device"},
)
}
integration_type.validate(integrations, config)
assert len(integrations[domain].errors) == 1
assert (
"still listed in MISSING_INTEGRATION_TYPE"
in integrations[domain].errors[0].error
)
@pytest.mark.usefixtures("mock_core_integration")
def test_integration_without_config_flow_skipped(config: Config) -> None:
"""Integration without config_flow should be skipped regardless of integration_type."""
integrations = {
"test": _get_integration(
"test",
config,
{},
)
}
integration_type.validate(integrations, config)
assert integrations["test"].errors == []