Add tests for hassfest triggers module

This commit is contained in:
abmantis
2025-08-27 22:45:08 +01:00
parent 8fc334b338
commit f125b137c5

View File

@@ -0,0 +1,181 @@
"""Tests for hassfest triggers."""
import io
import json
from pathlib import Path
from unittest.mock import patch
import pytest
from homeassistant.util.yaml.loader import parse_yaml
from script.hassfest import triggers
from script.hassfest.model import Config, Integration
TRIGGER_DESCPRITION_FILENAME = "triggers.yaml"
TRIGGER_ICONS_FILENAME = "icons.json"
TRIGGER_STRINGS_FILENAME = "strings.json"
TRIGGER_DESCRIPTIONS = {
"valid": {
TRIGGER_DESCPRITION_FILENAME: """
_:
fields:
event:
example: sunrise
selector:
select:
options:
- sunrise
- sunset
offset:
selector:
time: null
""",
TRIGGER_ICONS_FILENAME: {"triggers": {"_": {"trigger": "mdi:flash"}}},
TRIGGER_STRINGS_FILENAME: {
"triggers": {
"_": {
"name": "MQTT",
"description": "When a specific message is received on a given MQTT topic.",
"description_configured": "When an MQTT message has been received",
"fields": {
"event": {"name": "Event", "description": "The event."},
"offset": {"name": "Offset", "description": "The offset."},
},
}
}
},
"errors": [],
},
"yaml_missing_colon": {
TRIGGER_DESCPRITION_FILENAME: """
test:
fields
entity:
selector:
entity:
""",
"errors": ["Invalid triggers.yaml"],
},
"invalid_triggers_schema": {
TRIGGER_DESCPRITION_FILENAME: """
invalid_trigger:
fields:
entity:
selector:
invalid_selector: null
""",
"errors": ["Unknown selector type invalid_selector"],
},
"missing_strings_and_icons": {
TRIGGER_DESCPRITION_FILENAME: """
sun:
fields:
event:
example: sunrise
selector:
select:
options:
- sunrise
- sunset
translation_key: event
offset:
selector:
time: null
""",
TRIGGER_ICONS_FILENAME: {"triggers": {}},
TRIGGER_STRINGS_FILENAME: {
"triggers": {
"sun": {
"fields": {
"offset": {},
},
}
}
},
"errors": [
"has no icon",
"has no name",
"has no description",
"field event with no name",
"field event with no description",
"field event with a selector with a translation key",
"field offset with no name",
"field offset with no description",
],
},
}
@pytest.fixture
def config():
"""Fixture for hassfest Config."""
return Config(
root=Path(".").absolute(),
specific_integrations=None,
action="validate",
requirements=True,
)
@pytest.fixture
def mock_core_integration():
"""Mock Integration to be a core one."""
with patch.object(Integration, "core", return_value=True):
yield
def get_integration(domain: str, config: Config):
"""Fixture for hassfest integration model."""
return Integration(
Path(domain),
_config=config,
_manifest={
"domain": domain,
"name": domain,
"documentation": "https://example.com",
"codeowners": ["@awesome"],
},
)
@pytest.mark.usefixtures("mock_core_integration")
def test_validate(config: Config) -> None:
"""Test validate version with no key."""
def _load_yaml(fname, secrets=None):
domain, yaml_file = fname.split("/")
assert yaml_file == TRIGGER_DESCPRITION_FILENAME
trigger_descriptions = TRIGGER_DESCRIPTIONS[domain][yaml_file]
with io.StringIO(trigger_descriptions) as file:
return parse_yaml(file)
def _patched_path_read_text(path: Path):
domain = path.parent.name
filename = path.name
return json.dumps(TRIGGER_DESCRIPTIONS[domain][filename])
integrations = {
domain: get_integration(domain, config) for domain in TRIGGER_DESCRIPTIONS
}
with (
patch("script.hassfest.triggers.grep_dir", return_value=True),
patch("pathlib.Path.is_file", return_value=True),
patch("pathlib.Path.read_text", _patched_path_read_text),
patch("annotatedyaml.loader.load_yaml", side_effect=_load_yaml),
):
triggers.validate(integrations, config)
assert not config.errors
for domain, description in TRIGGER_DESCRIPTIONS.items():
assert len(integrations[domain].errors) == len(description["errors"]), (
f"Domain '{domain}' has unexpected errors: {integrations[domain].errors}"
)
for error, expected_error in zip(
integrations[domain].errors, description["errors"], strict=True
):
assert expected_error in error.error