Skip to content

Commit

Permalink
Bugfix group order (home-assistant#3323)
Browse files Browse the repository at this point in the history
* Add ordered dict config validator

* Have group component use ordered dict config validator

* Improve config_validation testing

* update doc string config_validation.ordered_dict

* validate full dict entries

* Further simplify ordered_dict validator.

* Lint fix
  • Loading branch information
balloob authored Sep 12, 2016
1 parent fa4b253 commit 838b09b
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 9 deletions.
4 changes: 2 additions & 2 deletions homeassistant/components/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ def _conf_preprocess(value):


CONFIG_SCHEMA = vol.Schema({
DOMAIN: {cv.match_all: vol.Schema(vol.All(_conf_preprocess, {
DOMAIN: cv.ordered_dict(vol.All(_conf_preprocess, {
vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids, None),
CONF_VIEW: cv.boolean,
CONF_NAME: cv.string,
CONF_ICON: cv.icon,
}))}
}, cv.match_all))
}, extra=vol.ALLOW_EXTRA)

# List of ON/OFF state tuples for groupable states
Expand Down
22 changes: 22 additions & 0 deletions homeassistant/helpers/config_validation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Helpers for config validation using voluptuous."""
from collections import OrderedDict
from datetime import timedelta
import os
from urllib.parse import urlparse
Expand Down Expand Up @@ -290,6 +291,27 @@ def url(value: Any) -> str:
raise vol.Invalid('invalid url')


def ordered_dict(value_validator, key_validator=match_all):
"""Validate an ordered dict validator that maintains ordering.
value_validator will be applied to each value of the dictionary.
key_validator (optional) will be applied to each key of the dictionary.
"""
item_validator = vol.Schema({key_validator: value_validator})

def validator(value):
"""Validate ordered dict."""
config = OrderedDict()

for key, val in value.items():
v_res = item_validator({key: val})
config.update(v_res)

return config

return validator


# Validator helpers

def key_dependency(key, dependency):
Expand Down
17 changes: 10 additions & 7 deletions tests/components/test_group.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""The tests for the Group components."""
# pylint: disable=protected-access,too-many-public-methods
from collections import OrderedDict
import unittest
from unittest.mock import patch

Expand Down Expand Up @@ -220,16 +221,16 @@ def test_setup(self):
test_group = group.Group(
self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False)

_setup_component(self.hass, 'group', {'group': {
'second_group': {
group_conf = OrderedDict()
group_conf['second_group'] = {
'entities': 'light.Bowl, ' + test_group.entity_id,
'icon': 'mdi:work',
'view': True,
},
'test_group': 'hello.world,sensor.happy',
'empty_group': {'name': 'Empty Group', 'entities': None},
}
})
}
group_conf['test_group'] = 'hello.world,sensor.happy'
group_conf['empty_group'] = {'name': 'Empty Group', 'entities': None}

_setup_component(self.hass, 'group', {'group': group_conf})

group_state = self.hass.states.get(
group.ENTITY_ID_FORMAT.format('second_group'))
Expand All @@ -241,6 +242,7 @@ def test_setup(self):
group_state.attributes.get(ATTR_ICON))
self.assertTrue(group_state.attributes.get(group.ATTR_VIEW))
self.assertTrue(group_state.attributes.get(ATTR_HIDDEN))
self.assertEqual(1, group_state.attributes.get(group.ATTR_ORDER))

group_state = self.hass.states.get(
group.ENTITY_ID_FORMAT.format('test_group'))
Expand All @@ -251,6 +253,7 @@ def test_setup(self):
self.assertIsNone(group_state.attributes.get(ATTR_ICON))
self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW))
self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN))
self.assertEqual(2, group_state.attributes.get(group.ATTR_ORDER))

def test_groups_get_unique_names(self):
"""Two groups with same name should both have a unique entity id."""
Expand Down
50 changes: 50 additions & 0 deletions tests/helpers/test_config_validation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Test config validators."""
from collections import OrderedDict
from datetime import timedelta
import os
import tempfile
Expand Down Expand Up @@ -367,3 +369,51 @@ def test_has_at_least_one_key():

for value in ({'beer': None}, {'soda': None}):
schema(value)


def test_ordered_dict_order():
"""Test ordered_dict validator."""
schema = vol.Schema(cv.ordered_dict(int, cv.string))

val = OrderedDict()
val['first'] = 1
val['second'] = 2

validated = schema(val)

assert isinstance(validated, OrderedDict)
assert ['first', 'second'] == list(validated.keys())


def test_ordered_dict_key_validator():
"""Test ordered_dict key validator."""
schema = vol.Schema(cv.ordered_dict(cv.match_all, cv.string))

with pytest.raises(vol.Invalid):
schema({None: 1})

schema({'hello': 'world'})

schema = vol.Schema(cv.ordered_dict(cv.match_all, int))

with pytest.raises(vol.Invalid):
schema({'hello': 1})

schema({1: 'works'})


def test_ordered_dict_value_validator():
"""Test ordered_dict validator."""
schema = vol.Schema(cv.ordered_dict(cv.string))

with pytest.raises(vol.Invalid):
schema({'hello': None})

schema({'hello': 'world'})

schema = vol.Schema(cv.ordered_dict(int))

with pytest.raises(vol.Invalid):
schema({'hello': 'world'})

schema({'hello': 5})

0 comments on commit 838b09b

Please sign in to comment.