Skip to content

Commit 814b98c

Browse files
authored
Add tests for hassfest triggers module (home-assistant#151318)
1 parent 243569f commit 814b98c

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

tests/hassfest/test_triggers.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"""Tests for hassfest triggers."""
2+
3+
import io
4+
import json
5+
from pathlib import Path
6+
from unittest.mock import patch
7+
8+
import pytest
9+
10+
from homeassistant.util.yaml.loader import parse_yaml
11+
from script.hassfest import triggers
12+
from script.hassfest.model import Config, Integration
13+
14+
TRIGGER_DESCRIPTION_FILENAME = "triggers.yaml"
15+
TRIGGER_ICONS_FILENAME = "icons.json"
16+
TRIGGER_STRINGS_FILENAME = "strings.json"
17+
18+
TRIGGER_DESCRIPTIONS = {
19+
"valid": {
20+
TRIGGER_DESCRIPTION_FILENAME: """
21+
_:
22+
fields:
23+
event:
24+
example: sunrise
25+
selector:
26+
select:
27+
options:
28+
- sunrise
29+
- sunset
30+
offset:
31+
selector:
32+
time: null
33+
""",
34+
TRIGGER_ICONS_FILENAME: {"triggers": {"_": {"trigger": "mdi:flash"}}},
35+
TRIGGER_STRINGS_FILENAME: {
36+
"triggers": {
37+
"_": {
38+
"name": "MQTT",
39+
"description": "When a specific message is received on a given MQTT topic.",
40+
"description_configured": "When an MQTT message has been received",
41+
"fields": {
42+
"event": {"name": "Event", "description": "The event."},
43+
"offset": {"name": "Offset", "description": "The offset."},
44+
},
45+
}
46+
}
47+
},
48+
"errors": [],
49+
},
50+
"yaml_missing_colon": {
51+
TRIGGER_DESCRIPTION_FILENAME: """
52+
test:
53+
fields
54+
entity:
55+
selector:
56+
entity:
57+
""",
58+
"errors": ["Invalid triggers.yaml"],
59+
},
60+
"invalid_triggers_schema": {
61+
TRIGGER_DESCRIPTION_FILENAME: """
62+
invalid_trigger:
63+
fields:
64+
entity:
65+
selector:
66+
invalid_selector: null
67+
""",
68+
"errors": ["Unknown selector type invalid_selector"],
69+
},
70+
"missing_strings_and_icons": {
71+
TRIGGER_DESCRIPTION_FILENAME: """
72+
sun:
73+
fields:
74+
event:
75+
example: sunrise
76+
selector:
77+
select:
78+
options:
79+
- sunrise
80+
- sunset
81+
translation_key: event
82+
offset:
83+
selector:
84+
time: null
85+
""",
86+
TRIGGER_ICONS_FILENAME: {"triggers": {}},
87+
TRIGGER_STRINGS_FILENAME: {
88+
"triggers": {
89+
"sun": {
90+
"fields": {
91+
"offset": {},
92+
},
93+
}
94+
}
95+
},
96+
"errors": [
97+
"has no icon",
98+
"has no name",
99+
"has no description",
100+
"field event with no name",
101+
"field event with no description",
102+
"field event with a selector with a translation key",
103+
"field offset with no name",
104+
"field offset with no description",
105+
],
106+
},
107+
}
108+
109+
110+
@pytest.fixture
111+
def config():
112+
"""Fixture for hassfest Config."""
113+
return Config(
114+
root=Path(".").absolute(),
115+
specific_integrations=None,
116+
action="validate",
117+
requirements=True,
118+
)
119+
120+
121+
@pytest.fixture
122+
def mock_core_integration():
123+
"""Mock Integration to be a core one."""
124+
with patch.object(Integration, "core", return_value=True):
125+
yield
126+
127+
128+
def get_integration(domain: str, config: Config):
129+
"""Fixture for hassfest integration model."""
130+
return Integration(
131+
Path(domain),
132+
_config=config,
133+
_manifest={
134+
"domain": domain,
135+
"name": domain,
136+
"documentation": "https://example.com",
137+
"codeowners": ["@awesome"],
138+
},
139+
)
140+
141+
142+
@pytest.mark.usefixtures("mock_core_integration")
143+
def test_validate(config: Config) -> None:
144+
"""Test validate version with no key."""
145+
146+
def _load_yaml(fname, secrets=None):
147+
domain, yaml_file = fname.split("/")
148+
assert yaml_file == TRIGGER_DESCRIPTION_FILENAME
149+
150+
trigger_descriptions = TRIGGER_DESCRIPTIONS[domain][yaml_file]
151+
with io.StringIO(trigger_descriptions) as file:
152+
return parse_yaml(file)
153+
154+
def _patched_path_read_text(path: Path):
155+
domain = path.parent.name
156+
filename = path.name
157+
158+
return json.dumps(TRIGGER_DESCRIPTIONS[domain][filename])
159+
160+
integrations = {
161+
domain: get_integration(domain, config) for domain in TRIGGER_DESCRIPTIONS
162+
}
163+
164+
with (
165+
patch("script.hassfest.triggers.grep_dir", return_value=True),
166+
patch("pathlib.Path.is_file", return_value=True),
167+
patch("pathlib.Path.read_text", _patched_path_read_text),
168+
patch("annotatedyaml.loader.load_yaml", side_effect=_load_yaml),
169+
):
170+
triggers.validate(integrations, config)
171+
172+
assert not config.errors
173+
174+
for domain, description in TRIGGER_DESCRIPTIONS.items():
175+
assert len(integrations[domain].errors) == len(description["errors"]), (
176+
f"Domain '{domain}' has unexpected errors: {integrations[domain].errors}"
177+
)
178+
for error, expected_error in zip(
179+
integrations[domain].errors, description["errors"], strict=True
180+
):
181+
assert expected_error in error.error

0 commit comments

Comments
 (0)