mirror of
https://github.com/home-assistant/core.git
synced 2026-02-15 07:36:16 +00:00
Add tests for hassfest conditions module (#151646)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1 +1,19 @@
|
||||
"""Tests for hassfest."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from script.hassfest.model import Config, Integration
|
||||
|
||||
|
||||
def get_integration(domain: str, config: Config):
|
||||
"""Helper function for creating hassfest integration model instances."""
|
||||
return Integration(
|
||||
Path(domain),
|
||||
_config=config,
|
||||
_manifest={
|
||||
"domain": domain,
|
||||
"name": domain,
|
||||
"documentation": "https://example.com",
|
||||
"codeowners": ["@awesome"],
|
||||
},
|
||||
)
|
||||
|
||||
26
tests/hassfest/conftest.py
Normal file
26
tests/hassfest/conftest.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""Fixtures for hassfest tests."""
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from script.hassfest.model import Config, Integration
|
||||
|
||||
|
||||
@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
|
||||
154
tests/hassfest/test_conditions.py
Normal file
154
tests/hassfest/test_conditions.py
Normal file
@@ -0,0 +1,154 @@
|
||||
"""Tests for hassfest conditions."""
|
||||
|
||||
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 conditions
|
||||
from script.hassfest.model import Config
|
||||
|
||||
from . import get_integration
|
||||
|
||||
CONDITION_DESCRIPTION_FILENAME = "conditions.yaml"
|
||||
CONDITION_ICONS_FILENAME = "icons.json"
|
||||
CONDITION_STRINGS_FILENAME = "strings.json"
|
||||
|
||||
CONDITION_DESCRIPTIONS = {
|
||||
"valid": {
|
||||
CONDITION_DESCRIPTION_FILENAME: """
|
||||
_:
|
||||
fields:
|
||||
after:
|
||||
example: sunrise
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- sunrise
|
||||
- sunset
|
||||
after_offset:
|
||||
selector:
|
||||
time: null
|
||||
""",
|
||||
CONDITION_ICONS_FILENAME: {"conditions": {"_": {"condition": "mdi:flash"}}},
|
||||
CONDITION_STRINGS_FILENAME: {
|
||||
"conditions": {
|
||||
"_": {
|
||||
"name": "Sun",
|
||||
"description": "When the sun is above/below the horizon",
|
||||
"description_configured": "When a the sun rises or sets.",
|
||||
"fields": {
|
||||
"after": {"name": "After event", "description": "The event."},
|
||||
"after_offset": {
|
||||
"name": "Offset",
|
||||
"description": "The offset.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": [],
|
||||
},
|
||||
"yaml_missing_colon": {
|
||||
CONDITION_DESCRIPTION_FILENAME: """
|
||||
test:
|
||||
fields
|
||||
entity:
|
||||
selector:
|
||||
entity:
|
||||
""",
|
||||
"errors": ["Invalid conditions.yaml"],
|
||||
},
|
||||
"invalid_conditions_schema": {
|
||||
CONDITION_DESCRIPTION_FILENAME: """
|
||||
invalid_condition:
|
||||
fields:
|
||||
entity:
|
||||
selector:
|
||||
invalid_selector: null
|
||||
""",
|
||||
"errors": ["Unknown selector type invalid_selector"],
|
||||
},
|
||||
"missing_strings_and_icons": {
|
||||
CONDITION_DESCRIPTION_FILENAME: """
|
||||
sun:
|
||||
fields:
|
||||
after:
|
||||
example: sunrise
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- sunrise
|
||||
- sunset
|
||||
translation_key: after
|
||||
after_offset:
|
||||
selector:
|
||||
time: null
|
||||
""",
|
||||
CONDITION_ICONS_FILENAME: {"conditions": {}},
|
||||
CONDITION_STRINGS_FILENAME: {
|
||||
"conditions": {
|
||||
"sun": {
|
||||
"fields": {
|
||||
"after_offset": {},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": [
|
||||
"has no icon",
|
||||
"has no name",
|
||||
"has no description",
|
||||
"field after with no name",
|
||||
"field after with no description",
|
||||
"field after with a selector with a translation key",
|
||||
"field after_offset with no name",
|
||||
"field after_offset with no description",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@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 == CONDITION_DESCRIPTION_FILENAME
|
||||
|
||||
condition_descriptions = CONDITION_DESCRIPTIONS[domain][yaml_file]
|
||||
with io.StringIO(condition_descriptions) as file:
|
||||
return parse_yaml(file)
|
||||
|
||||
def _patched_path_read_text(path: Path):
|
||||
domain = path.parent.name
|
||||
filename = path.name
|
||||
|
||||
return json.dumps(CONDITION_DESCRIPTIONS[domain][filename])
|
||||
|
||||
integrations = {
|
||||
domain: get_integration(domain, config) for domain in CONDITION_DESCRIPTIONS
|
||||
}
|
||||
|
||||
with (
|
||||
patch("script.hassfest.conditions.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),
|
||||
):
|
||||
conditions.validate(integrations, config)
|
||||
|
||||
assert not config.errors
|
||||
|
||||
for domain, description in CONDITION_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
|
||||
@@ -9,7 +9,9 @@ import pytest
|
||||
|
||||
from homeassistant.util.yaml.loader import parse_yaml
|
||||
from script.hassfest import triggers
|
||||
from script.hassfest.model import Config, Integration
|
||||
from script.hassfest.model import Config
|
||||
|
||||
from . import get_integration
|
||||
|
||||
TRIGGER_DESCRIPTION_FILENAME = "triggers.yaml"
|
||||
TRIGGER_ICONS_FILENAME = "icons.json"
|
||||
@@ -107,38 +109,6 @@ TRIGGER_DESCRIPTIONS = {
|
||||
}
|
||||
|
||||
|
||||
@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."""
|
||||
|
||||
Reference in New Issue
Block a user