Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions homeassistant/components/hue.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import os
import socket

import requests
import voluptuous as vol

from homeassistant.components.discovery import SERVICE_HUE
Expand All @@ -22,6 +23,7 @@

DOMAIN = "hue"
SERVICE_HUE_SCENE = "hue_activate_scene"
API_NUPNP = 'https://www.meethue.com/api/nupnp'

CONF_BRIDGES = "bridges"

Expand Down Expand Up @@ -49,7 +51,7 @@

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_BRIDGES, default=[]): BRIDGE_CONFIG_SCHEMA,
vol.Optional(CONF_BRIDGES): BRIDGE_CONFIG_SCHEMA,
}),
}, extra=vol.ALLOW_EXTRA)

Expand All @@ -69,9 +71,9 @@

def setup(hass, config):
"""Set up the Hue platform."""
config = config.get(DOMAIN)
if config is None:
config = {}
conf = config.get(DOMAIN)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an empty dictionary as default value.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I just copied that from the original code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn you're on a roll with good code reviews Martin!

if conf is None:
conf = {}

if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
Expand All @@ -82,7 +84,21 @@ def setup(hass, config):
lambda service, discovery_info:
bridge_discovered(hass, service, discovery_info))

bridges = config.get(CONF_BRIDGES, [])
# User has configured bridges
if CONF_BRIDGES in conf:
bridges = conf[CONF_BRIDGES]
# Component is part of config but no bridges specified, discover.
elif DOMAIN in config:
# discover from nupnp
hosts = requests.get(API_NUPNP).json()
bridges = [{
CONF_HOST: entry['internalipaddress'],
CONF_FILENAME: '.hue_{}.conf'.format(entry['id']),
} for entry in hosts]
else:
# Component not specified in config, we're loaded via discovery
bridges = []

for bridge in bridges:
filename = bridge.get(CONF_FILENAME)
allow_unreachable = bridge.get(CONF_ALLOW_UNREACHABLE)
Expand Down
33 changes: 21 additions & 12 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,27 +542,36 @@ def __init__(self, root, *args):
self.root = root
self.submodules = args

def __call__(self, func):
"""Apply decorator."""
from unittest.mock import MagicMock, patch

def __enter__(self):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewriting MockDependency so that it can be used as both a decorator and a context manager.

"""Start mocking."""
def resolve(mock, path):
"""Resolve a mock."""
if not path:
return mock

return resolve(getattr(mock, path[0]), path[1:])

base = MagicMock()
to_mock = {
"{}.{}".format(self.root, tom): resolve(base, tom.split('.'))
for tom in self.submodules
}
to_mock[self.root] = base

self.patcher = patch.dict('sys.modules', to_mock)
self.patcher.start()
return base

def __exit__(self, *exc):
"""Stop mocking."""
self.patcher.stop()
return False

def __call__(self, func):
"""Apply decorator."""
def run_mocked(*args, **kwargs):
"""Run with mocked dependencies."""
base = MagicMock()
to_mock = {
"{}.{}".format(self.root, tom): resolve(base, tom.split('.'))
for tom in self.submodules
}
to_mock[self.root] = base

with patch.dict('sys.modules', to_mock):
with self as base:
args = list(args) + [base]
func(*args, **kwargs)

Expand Down
27 changes: 16 additions & 11 deletions tests/components/test_hue.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Generic Philips Hue component tests."""

import asyncio
import logging
import unittest
from unittest.mock import call, MagicMock, patch

from homeassistant.components import configurator, hue
from homeassistant.const import CONF_FILENAME, CONF_HOST
from homeassistant.setup import setup_component
from homeassistant.setup import setup_component, async_setup_component

from tests.common import (
assert_setup_component, get_test_home_assistant, get_test_config_dir,
Expand Down Expand Up @@ -38,15 +38,6 @@ def test_setup_no_domain(self, mock_phue):
mock_phue.Bridge.assert_not_called()
self.assertEquals({}, self.hass.data[hue.DOMAIN])

@MockDependency('phue')
def test_setup_no_host(self, mock_phue):
"""No host specified in any way."""
with assert_setup_component(1):
self.assertTrue(setup_component(
self.hass, hue.DOMAIN, {hue.DOMAIN: {}}))
mock_phue.Bridge.assert_not_called()
self.assertEquals({}, self.hass.data[hue.DOMAIN])

@MockDependency('phue')
def test_setup_with_host(self, mock_phue):
"""Host specified in the config file."""
Expand Down Expand Up @@ -400,3 +391,17 @@ def test_hue_activate_scene(self, mock_phue):
{hue.ATTR_GROUP_NAME: 'group', hue.ATTR_SCENE_NAME: 'scene'},
blocking=True)
bridge.bridge.run_scene.assert_called_once_with('group', 'scene')


@asyncio.coroutine
def test_setup_no_host(hass, requests_mock):
"""No host specified in any way."""
requests_mock.get(hue.API_NUPNP, json=[])
with MockDependency('phue') as mock_phue:
result = yield from async_setup_component(
hass, hue.DOMAIN, {hue.DOMAIN: {}})
assert result

mock_phue.Bridge.assert_not_called()

assert hass.data[hue.DOMAIN] == {}