Skip to content

Commit

Permalink
Validate zone_id's, check for duplicates & orphans
Browse files Browse the repository at this point in the history
  • Loading branch information
rgc99 committed Apr 24, 2022
1 parent 6079195 commit e854305
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ The zone object manages a collection of schedules. There must be at least one zo
| Name | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| `schedules` | list | _[Schedule Objects](#schedule-objects)_ | Schedule details (Must have at least one) |
| `zone_id` | string | _N_ | Zone reference. Used for sequencing. |
| `zone_id` | string | _N_ | Zone reference. Used for sequencing. This should be in [snake_case](https://en.wikipedia.org/wiki/Snake_case) style |
| `name` | string | Zone _N_ | Friendly name for the zone |
| `enabled` | bool | true | Enable/disable the zone |
| `minimum` | time | '00:01' | The minimum run time |
Expand Down
78 changes: 78 additions & 0 deletions custom_components/irrigation_unlimited/irrigation_unlimited.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import uuid
import time as tm
import json
import re
from homeassistant.core import HomeAssistant, CALLBACK_TYPE, DOMAIN as HADOMAIN
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
Expand Down Expand Up @@ -2580,6 +2581,8 @@ def load(self, config: OrderedDict) -> "IUController":
sequence_config
)

self.check_links()

self._dirty = True
return self

Expand Down Expand Up @@ -2842,6 +2845,34 @@ def check_run(self, stime: datetime) -> bool:

return state_changed

def check_links(self) -> bool:
"""Check inter object links"""

def check_id(zone_id: str) -> bool:
return bool(re.match(r"^[a-z0-9]+(_[a-z0-9]+)*$", zone_id))

result = True
zone_ids = set()
for zone in self._zones:
if not check_id(zone.zone_id):
self._coordinator.logger.log_invalid_id(self, zone)
result = False
if zone.zone_id in zone_ids:
self._coordinator.logger.log_duplicate_id(self, zone)
result = False
else:
zone_ids.add(zone.zone_id)

for sequence in self._sequences:
for sequence_zone in sequence.zones:
for zone_id in sequence_zone.zone_ids:
if zone_id not in zone_ids:
self._coordinator.logger.log_orphan_id(
self, sequence, sequence_zone, zone_id
)
result = False
return result

def request_update(self, deep: bool) -> None:
"""Flag the sensor needs an update. The actual update is done
in update_sensor"""
Expand Down Expand Up @@ -3671,6 +3702,53 @@ def log_sync_error(
),
)

def log_invalid_id(
self, controller: IUController, zone: IUZone, level=WARNING
) -> None:
"""Warn that the zone_id is not valid"""
idl = IUBase.idl([controller, zone], "0", 1)
self._output(
level,
f"INVALID_ID Invalid ID (use snake_case format): "
f"controller: {idl[0]}, "
f"zone: {idl[1]}, "
f"zone_id: {zone.zone_id}",
)

def log_duplicate_id(
self, controller: IUController, zone: IUZone, level=WARNING
) -> None:
"""Warn a zone has a duplicate zone_id"""
idl = IUBase.idl([controller, zone], "0", 1)
self._output(
level,
f"DUPLICATE_ID Duplicate ID: "
f"controller: {idl[0]}, "
f"zone: {idl[1]}, "
f"zone_id: {zone.zone_id}",
)

def log_orphan_id(
self,
controller: IUController,
sequence: IUSequence,
sequence_zone: IUSequenceZone,
zone_id: str,
level=WARNING,
) -> None:
# pylint: disable=too-many-arguments
# pylint: disable=line-too-long
"""Warn a zone_id reference is orphaned"""
idl = IUBase.idl([controller, sequence, sequence_zone], "0", 1)
self._output(
level,
f"ORPHAN_ID Invalid reference ID: "
f"controller: {idl[0]}, "
f"sequence: {idl[1]}, "
f"sequence_zone: {idl[2]}, "
f"zone_id: {zone_id}",
)


class IUCoordinator:
"""Irrigation Unlimited Coordinator class"""
Expand Down
24 changes: 24 additions & 0 deletions tests/configs/test_ids.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
default_config:

irrigation_unlimited:
controllers:
zones:
- name: "Zone 1"
- name: "Zone 2"
zone_id: "1"
- name: "Zone 3"
zone_id: "zone_3"
- name: "Zone 4"
zone_id: "zone_3"
- name: "Zone 5"
zone_id: "ABC"
sequences:
- name: "Sequence 1"
schedules:
- name: Never
time: "21:00"
month: [feb]
day: [31]
zones:
- zone_id: 1
- zone_id: [1, "no_zone"]
23 changes: 23 additions & 0 deletions tests/test_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""irrigation_unlimited test for logging"""
# pylint: disable=unused-import
from unittest.mock import patch
import homeassistant.core as ha
from custom_components.irrigation_unlimited.irrigation_unlimited import (
IULogger,
)
from tests.iu_test_support import IUExam

IUExam.quiet_mode()


async def test_link_ids(hass: ha.HomeAssistant, skip_dependencies, skip_history):
"""Test invalid, duplicate and orphaned ids."""
# pylint: disable=unused-argument

with patch.object(IULogger, "log_duplicate_id") as mock_duplicate:
with patch.object(IULogger, "log_invalid_id") as mock_invalid:
with patch.object(IULogger, "log_orphan_id") as mock_orphan:
async with IUExam(hass, "test_ids.yaml"):
assert mock_duplicate.call_count == 2
assert mock_invalid.call_count == 1
assert mock_orphan.call_count == 1

0 comments on commit e854305

Please sign in to comment.