|
|
@@ -406,11 +406,11 @@ def check_entity(entity, cfg, mocker):
|
|
|
fname = f"custom_components/tuya_local/devices/{cfg}"
|
|
|
line = entity._config.__line__
|
|
|
assert entity._config.get("entity") is not None, (
|
|
|
- f"\n::error file={fname},line={line}::entity type missing in {cfg}",
|
|
|
+ f"\n::error file={fname},line={line}::entity type missing in {cfg}"
|
|
|
)
|
|
|
e = entity.config_id
|
|
|
assert entity._config.get("dps") is not None, (
|
|
|
- f"\n::error file={fname},line={line}::dps missing from {e} in {cfg}",
|
|
|
+ f"\n::error file={fname},line={line}::dps missing from {e} in {cfg}"
|
|
|
)
|
|
|
functions = set()
|
|
|
extra = set()
|
|
|
@@ -422,24 +422,24 @@ def check_entity(entity, cfg, mocker):
|
|
|
for dp in entity.dps():
|
|
|
line = dp._config.__line__
|
|
|
assert dp._config.get("id") is not None, (
|
|
|
- f"\n::error file={fname},line={line}::dp id missing from {e} in {cfg}",
|
|
|
+ f"\n::error file={fname},line={line}::dp id missing from {e} in {cfg}"
|
|
|
)
|
|
|
assert dp._config.get("type") is not None, (
|
|
|
- f"\n::error file={fname},line~{line}::dp type missing from {e} in {cfg}",
|
|
|
+ f"\n::error file={fname},line~{line}::dp type missing from {e} in {cfg}"
|
|
|
)
|
|
|
assert dp._config.get("name") is not None, (
|
|
|
- f"\n::error file={fname},line={line}::dp name missing from {e} in {cfg}",
|
|
|
+ f"\n::error file={fname},line={line}::dp name missing from {e} in {cfg}"
|
|
|
)
|
|
|
extra.add(dp.name)
|
|
|
mappings = dp._config.get("mapping", [])
|
|
|
assert isinstance(mappings, list), (
|
|
|
- f"\n::error file={fname},line={line}::mapping is not a list in {cfg}; entity {e}, dp {dp.name}",
|
|
|
+ f"\n::error file={fname},line={line}::mapping is not a list in {cfg}; entity {e}, dp {dp.name}"
|
|
|
)
|
|
|
for m in mappings:
|
|
|
line = m.__line__
|
|
|
conditions = m.get("conditions", [])
|
|
|
assert isinstance(conditions, list), (
|
|
|
- f"\n::error file={fname},line={line}::conditions is not a list in {cfg}; entity {e}, dp {dp.name}",
|
|
|
+ f"\n::error file={fname},line={line}::conditions is not a list in {cfg}; entity {e}, dp {dp.name}"
|
|
|
)
|
|
|
for c in conditions:
|
|
|
if c.get("value_redirect"):
|
|
|
@@ -455,19 +455,19 @@ def check_entity(entity, cfg, mocker):
|
|
|
# Check redirects all exist
|
|
|
for redirect in redirects:
|
|
|
assert redirect in extra, (
|
|
|
- f"\n::error file={fname},line={line}::dp {redirect} missing from {e} in {cfg}",
|
|
|
+ f"\n::error file={fname},line={line}::dp {redirect} missing from {e} in {cfg}"
|
|
|
)
|
|
|
|
|
|
# Check dps that are required for this entity type all exist
|
|
|
expected = KNOWN_DPS.get(entity.entity)
|
|
|
for rule in expected["required"]:
|
|
|
assert dp_match(rule, functions, extra, known, True), (
|
|
|
- f"\n::error file={fname},line={line}::{cfg} missing required {rule_broken_msg(rule)} in {e}",
|
|
|
+ f"\n::error file={fname},line={line}::{cfg} missing required {rule_broken_msg(rule)} in {e}"
|
|
|
)
|
|
|
|
|
|
for rule in expected["optional"]:
|
|
|
assert dp_match(rule, functions, extra, known, False), (
|
|
|
- f"\n::error file={fname},line={line}::{cfg} expecting {rule_broken_msg(rule)} in {e}",
|
|
|
+ f"\n::error file={fname},line={line}::{cfg} expecting {rule_broken_msg(rule)} in {e}"
|
|
|
)
|
|
|
|
|
|
# Check for potential typos in extra attributes
|
|
|
@@ -475,7 +475,7 @@ def check_entity(entity, cfg, mocker):
|
|
|
for attr in extra:
|
|
|
for dp in known_extra:
|
|
|
assert fuzz.ratio(attr, dp) < 85, (
|
|
|
- f"\n::error file={fname},line={line}::Probable typo {attr} is too similar to {dp} in {cfg} {e}",
|
|
|
+ f"\n::error file={fname},line={line}::Probable typo {attr} is too similar to {dp} in {cfg} {e}"
|
|
|
)
|
|
|
|
|
|
# Check that sensors with mapped values are of class enum and vice versa
|
|
|
@@ -484,11 +484,11 @@ def check_entity(entity, cfg, mocker):
|
|
|
sensor = TuyaLocalSensor(mock_device, entity)
|
|
|
if sensor.options:
|
|
|
assert entity.device_class == SensorDeviceClass.ENUM, (
|
|
|
- f"\n::error file={fname},line={line}::{cfg} {e} has mapped values but does not have a device class of enum",
|
|
|
+ f"\n::error file={fname},line={line}::{cfg} {e} has mapped values but does not have a device class of enum"
|
|
|
)
|
|
|
if entity.device_class == SensorDeviceClass.ENUM:
|
|
|
assert sensor.options is not None, (
|
|
|
- f"\n::error file={fname},line={line}::{cfg} {e} has a device class of enum, but has no mapped values",
|
|
|
+ f"\n::error file={fname},line={line}::{cfg} {e} has a device class of enum, but has no mapped values"
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -515,7 +515,7 @@ def test_config_files_parse(mocker):
|
|
|
pytest.fail(f"\n::error file={fname},line=1::Validation error: {messages}")
|
|
|
|
|
|
assert parsed._config.get("name") is not None, (
|
|
|
- f"\n::error file={fname},line=1::name missing from {cfg}",
|
|
|
+ f"\n::error file={fname},line=1::name missing from {cfg}"
|
|
|
)
|
|
|
count = 0
|
|
|
for entity in parsed.all_entities():
|
|
|
@@ -526,7 +526,7 @@ def test_config_files_parse(mocker):
|
|
|
|
|
|
# check entities are unique
|
|
|
assert len(entities) == len(set(entities)), (
|
|
|
- f"\n::error file={fname},line=1::Duplicate entities in {cfg}",
|
|
|
+ f"\n::error file={fname},line=1::Duplicate entities in {cfg}"
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -560,7 +560,7 @@ def test_configs_can_be_matched():
|
|
|
|
|
|
for dp in required:
|
|
|
assert dp not in optional, (
|
|
|
- f"\n::error file={fname},line=1::Optional dp {dp} is required in {cfg}",
|
|
|
+ f"\n::error file={fname},line=1::Optional dp {dp} is required in {cfg}"
|
|
|
)
|
|
|
|
|
|
|