Move elevation to core config and clean up HTTP mocking in tests (#2378)

* Stick version numbers

* Move elevation to core config

* Migrate forecast test to requests-mock

* Migrate YR tests to requests-mock

* Add requests_mock to requirements_test.txt

* Move conf code from bootstrap to config

* More config fixes

* Fix some more issues

* Add test for set config and failing auto detect
This commit is contained in:
Paulus Schoutsen 2016-06-27 09:02:45 -07:00 committed by GitHub
parent dc75b28b90
commit 6714392e9c
26 changed files with 1779 additions and 337 deletions

View file

@ -3,7 +3,6 @@
import logging
import logging.handlers
import os
import shutil
import sys
from collections import defaultdict
from threading import RLock
@ -12,21 +11,15 @@ import voluptuous as vol
import homeassistant.components as core_components
from homeassistant.components import group, persistent_notification
import homeassistant.config as config_util
import homeassistant.config as conf_util
import homeassistant.core as core
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
import homeassistant.util.dt as date_util
import homeassistant.util.location as loc_util
import homeassistant.util.package as pkg_util
from homeassistant.const import (
CONF_CUSTOMIZE, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME,
CONF_TEMPERATURE_UNIT, CONF_TIME_ZONE, EVENT_COMPONENT_LOADED,
TEMP_CELSIUS, TEMP_FAHRENHEIT, PLATFORM_FORMAT, __version__)
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs,
entity)
event_decorators, service, config_per_platform, extract_domain_configs)
_LOGGER = logging.getLogger(__name__)
_SETUP_LOCK = RLock()
@ -208,11 +201,6 @@ def prepare_setup_platform(hass, config, domain, platform_name):
return platform
def mount_local_lib_path(config_dir):
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'deps'))
# pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, skip_pip=False,
@ -226,18 +214,17 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
_mount_local_lib_path(config_dir)
core_config = config.get(core.DOMAIN, {})
try:
process_ha_core_config(hass, config_util.CORE_CONFIG_SCHEMA(
core_config))
except vol.MultipleInvalid as ex:
conf_util.process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
return None
process_ha_config_upgrade(hass)
conf_util.process_ha_config_upgrade(hass)
if enable_log:
enable_logging(hass, verbose, log_rotate_days)
@ -292,12 +279,12 @@ def from_config_file(config_path, hass=None, verbose=False, skip_pip=True,
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
_mount_local_lib_path(config_dir)
enable_logging(hass, verbose, log_rotate_days)
try:
config_dict = config_util.load_yaml_config_file(config_path)
config_dict = conf_util.load_yaml_config_file(config_path)
except HomeAssistantError:
return None
@ -356,100 +343,12 @@ def enable_logging(hass, verbose=False, log_rotate_days=None):
'Unable to setup error log %s (access denied)', err_log_path)
def process_ha_config_upgrade(hass):
"""Upgrade config if necessary."""
version_path = hass.config.path('.HA_VERSION')
try:
with open(version_path, 'rt') as inp:
conf_version = inp.readline().strip()
except FileNotFoundError:
# Last version to not have this file
conf_version = '0.7.7'
if conf_version == __version__:
return
_LOGGER.info('Upgrading config directory from %s to %s', conf_version,
__version__)
# This was where dependencies were installed before v0.18
# Probably should keep this around until ~v0.20.
lib_path = hass.config.path('lib')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
lib_path = hass.config.path('deps')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
with open(version_path, 'wt') as outp:
outp.write(__version__)
def process_ha_core_config(hass, config):
"""Process the [homeassistant] section from the config."""
hac = hass.config
def set_time_zone(time_zone_str):
"""Helper method to set time zone."""
if time_zone_str is None:
return
time_zone = date_util.get_time_zone(time_zone_str)
if time_zone:
hac.time_zone = time_zone
date_util.set_default_time_zone(time_zone)
else:
_LOGGER.error('Received invalid time zone %s', time_zone_str)
for key, attr in ((CONF_LATITUDE, 'latitude'),
(CONF_LONGITUDE, 'longitude'),
(CONF_NAME, 'location_name')):
if key in config:
setattr(hac, attr, config[key])
if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
entity.set_customize(config.get(CONF_CUSTOMIZE))
if CONF_TEMPERATURE_UNIT in config:
hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]
# If we miss some of the needed values, auto detect them
if None not in (
hac.latitude, hac.longitude, hac.temperature_unit, hac.time_zone):
return
_LOGGER.warning('Incomplete core config. Auto detecting location and '
'temperature unit')
info = loc_util.detect_location_info()
if info is None:
_LOGGER.error('Could not detect location information')
return
if hac.latitude is None and hac.longitude is None:
hac.latitude = info.latitude
hac.longitude = info.longitude
if hac.temperature_unit is None:
if info.use_fahrenheit:
hac.temperature_unit = TEMP_FAHRENHEIT
else:
hac.temperature_unit = TEMP_CELSIUS
if hac.location_name is None:
hac.location_name = info.city
if hac.time_zone is None:
set_time_zone(info.time_zone)
def _ensure_loader_prepared(hass):
"""Ensure Home Assistant loader is prepared."""
if not loader.PREPARED:
loader.prepare(hass)
def _mount_local_lib_path(config_dir):
"""Add local library to Python Path."""
sys.path.insert(0, os.path.join(config_dir, 'deps'))

View file

@ -121,16 +121,16 @@ def setup(hass, config):
def handle_reload_config(call):
"""Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError
from homeassistant import config, bootstrap
from homeassistant import config as conf_util
try:
path = config.find_config_file(hass.config.config_dir)
conf = config.load_yaml_config_file(path)
path = conf_util.find_config_file(hass.config.config_dir)
conf = conf_util.load_yaml_config_file(path)
except HomeAssistantError as err:
_LOGGER.error(err)
return
bootstrap.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
conf_util.process_ha_core_config(hass, conf.get(ha.DOMAIN) or {})
hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
handle_reload_config)

View file

@ -17,7 +17,7 @@ from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
CONF_PORT)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pycmus>=0.1.0']
REQUIREMENTS = ['pycmus==0.1.0']
SUPPORT_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \

View file

@ -15,7 +15,6 @@ from homeassistant.const import (
)
from homeassistant.helpers.entity import Entity
from homeassistant.util import dt as dt_util
from homeassistant.util import location
_LOGGER = logging.getLogger(__name__)
@ -54,16 +53,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Yr.no sensor."""
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
elevation = config.get(CONF_ELEVATION)
elevation = config.get(CONF_ELEVATION, hass.config.elevation or 0)
if None in (latitude, longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False
if elevation is None:
elevation = location.elevation(latitude,
longitude)
coordinates = dict(lat=latitude,
lon=longitude,
msl=elevation)

View file

@ -12,7 +12,6 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
track_point_in_utc_time, track_utc_time_change)
from homeassistant.util import dt as dt_util
from homeassistant.util import location as location_util
from homeassistant.const import CONF_ELEVATION
REQUIREMENTS = ['astral==1.2']
@ -108,7 +107,7 @@ def setup(hass, config):
elevation = platform_config.get(CONF_ELEVATION)
if elevation is None:
elevation = location_util.elevation(latitude, longitude)
elevation = hass.config.elevation or 0
from astral import Location

View file

@ -10,7 +10,7 @@ from homeassistant.components.thermostat import ThermostatDevice
from homeassistant.const import TEMP_CELCIUS
from homeassistant.helpers.temperature import convert
REQUIREMENTS = ['bluepy_devices>=0.2.0']
REQUIREMENTS = ['bluepy_devices==0.2.0']
CONF_MAC = 'mac'
CONF_DEVICES = 'devices'

View file

@ -1,31 +1,35 @@
"""Module to help with parsing and generating configuration files."""
import logging
import os
import shutil
from types import MappingProxyType
import voluptuous as vol
import homeassistant.util.location as loc_util
from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT,
CONF_TIME_ZONE, CONF_CUSTOMIZE)
CONF_TIME_ZONE, CONF_CUSTOMIZE, CONF_ELEVATION, TEMP_FAHRENHEIT,
TEMP_CELSIUS, __version__)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.yaml import load_yaml
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import valid_entity_id
from homeassistant.helpers.entity import valid_entity_id, set_customize
from homeassistant.util import dt as date_util, location as loc_util
_LOGGER = logging.getLogger(__name__)
YAML_CONFIG_FILE = 'configuration.yaml'
VERSION_FILE = '.HA_VERSION'
CONFIG_DIR_NAME = '.homeassistant'
DEFAULT_CONFIG = (
# Tuples (attribute, default, auto detect property, description)
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
'running'),
(CONF_LATITUDE, None, 'latitude', 'Location required to calculate the time'
(CONF_LATITUDE, 0, 'latitude', 'Location required to calculate the time'
' the sun rises and sets'),
(CONF_LONGITUDE, None, 'longitude', None),
(CONF_LONGITUDE, 0, 'longitude', None),
(CONF_ELEVATION, 0, None, 'Impacts weather/sunrise data'),
(CONF_TEMPERATURE_UNIT, 'C', None, 'C for Celsius, F for Fahrenheit'),
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
'pedia.org/wiki/List_of_tz_database_time_zones'),
@ -39,7 +43,7 @@ DEFAULT_COMPONENTS = {
'history:': 'Enables support for tracking state changes over time.',
'logbook:': 'View all events in a logbook',
'sun:': 'Track the sun',
'sensor:\n platform: yr': 'Prediction of weather',
'sensor:\n platform: yr': 'Weather Prediction',
}
@ -61,6 +65,7 @@ CORE_CONFIG_SCHEMA = vol.Schema({
CONF_NAME: vol.Coerce(str),
CONF_LATITUDE: cv.latitude,
CONF_LONGITUDE: cv.longitude,
CONF_ELEVATION: vol.Coerce(float),
CONF_TEMPERATURE_UNIT: cv.temperature_unit,
CONF_TIME_ZONE: cv.time_zone,
vol.Required(CONF_CUSTOMIZE,
@ -97,6 +102,7 @@ def create_default_config(config_dir, detect_location=True):
Return path to new config file if success, None if failed.
"""
config_path = os.path.join(config_dir, YAML_CONFIG_FILE)
version_path = os.path.join(config_dir, VERSION_FILE)
info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG}
@ -111,6 +117,10 @@ def create_default_config(config_dir, detect_location=True):
continue
info[attr] = getattr(location_info, prop) or default
if location_info.latitude and location_info.longitude:
info[CONF_ELEVATION] = loc_util.elevation(location_info.latitude,
location_info.longitude)
# Writing files with YAML does not create the most human readable results
# So we're hard coding a YAML template.
try:
@ -130,6 +140,9 @@ def create_default_config(config_dir, detect_location=True):
config_file.write("# {}\n".format(description))
config_file.write("{}\n\n".format(component))
with open(version_path, 'wt') as version_file:
version_file.write(__version__)
return config_path
except IOError:
@ -155,3 +168,112 @@ def load_yaml_config_file(config_path):
raise HomeAssistantError(msg)
return conf_dict
def process_ha_config_upgrade(hass):
"""Upgrade config if necessary."""
version_path = hass.config.path(VERSION_FILE)
try:
with open(version_path, 'rt') as inp:
conf_version = inp.readline().strip()
except FileNotFoundError:
# Last version to not have this file
conf_version = '0.7.7'
if conf_version == __version__:
return
_LOGGER.info('Upgrading config directory from %s to %s', conf_version,
__version__)
lib_path = hass.config.path('deps')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
with open(version_path, 'wt') as outp:
outp.write(__version__)
def process_ha_core_config(hass, config):
"""Process the [homeassistant] section from the config."""
# pylint: disable=too-many-branches
config = CORE_CONFIG_SCHEMA(config)
hac = hass.config
def set_time_zone(time_zone_str):
"""Helper method to set time zone."""
if time_zone_str is None:
return
time_zone = date_util.get_time_zone(time_zone_str)
if time_zone:
hac.time_zone = time_zone
date_util.set_default_time_zone(time_zone)
else:
_LOGGER.error('Received invalid time zone %s', time_zone_str)
for key, attr in ((CONF_LATITUDE, 'latitude'),
(CONF_LONGITUDE, 'longitude'),
(CONF_NAME, 'location_name'),
(CONF_ELEVATION, 'elevation')):
if key in config:
setattr(hac, attr, config[key])
if CONF_TIME_ZONE in config:
set_time_zone(config.get(CONF_TIME_ZONE))
set_customize(config.get(CONF_CUSTOMIZE) or {})
if CONF_TEMPERATURE_UNIT in config:
hac.temperature_unit = config[CONF_TEMPERATURE_UNIT]
# Shortcut if no auto-detection necessary
if None not in (hac.latitude, hac.longitude, hac.temperature_unit,
hac.time_zone, hac.elevation):
return
discovered = []
# If we miss some of the needed values, auto detect them
if None in (hac.latitude, hac.longitude, hac.temperature_unit,
hac.time_zone):
info = loc_util.detect_location_info()
if info is None:
_LOGGER.error('Could not detect location information')
return
if hac.latitude is None and hac.longitude is None:
hac.latitude = info.latitude
hac.longitude = info.longitude
discovered.append(('latitude', hac.latitude))
discovered.append(('longitude', hac.longitude))
if hac.temperature_unit is None:
if info.use_fahrenheit:
hac.temperature_unit = TEMP_FAHRENHEIT
discovered.append(('temperature_unit', 'F'))
else:
hac.temperature_unit = TEMP_CELSIUS
discovered.append(('temperature_unit', 'C'))
if hac.location_name is None:
hac.location_name = info.city
discovered.append(('name', info.city))
if hac.time_zone is None:
set_time_zone(info.time_zone)
discovered.append(('time_zone', info.time_zone))
if hac.elevation is None and hac.latitude is not None and \
hac.longitude is not None:
elevation = loc_util.elevation(hac.latitude, hac.longitude)
hac.elevation = elevation
discovered.append(('elevation', elevation))
if discovered:
_LOGGER.warning(
'Incomplete core config. Auto detected %s',
', '.join('{}: {}'.format(key, val) for key, val in discovered))

View file

@ -681,6 +681,7 @@ class Config(object):
"""Initialize a new config object."""
self.latitude = None
self.longitude = None
self.elevation = None
self.temperature_unit = None
self.location_name = None
self.time_zone = None

View file

@ -8,7 +8,8 @@ import math
import requests
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json'
DATA_SOURCE = ['https://freegeoip.io/json/', 'http://ip-api.com/json']
FREEGEO_API = 'https://freegeoip.io/json/'
IP_API = 'http://ip-api.com/json'
# Constants from https://github.com/maurycyp/vincenty
# Earth ellipsoid according to WGS 84
@ -32,30 +33,13 @@ LocationInfo = collections.namedtuple(
def detect_location_info():
"""Detect location information."""
success = None
data = _get_freegeoip()
for source in DATA_SOURCE:
try:
raw_info = requests.get(source, timeout=5).json()
success = source
break
except (requests.RequestException, ValueError):
success = False
if data is None:
data = _get_ip_api()
if success is False:
if data is None:
return None
else:
data = {key: raw_info.get(key) for key in LocationInfo._fields}
if success is DATA_SOURCE[1]:
data['ip'] = raw_info.get('query')
data['country_code'] = raw_info.get('countryCode')
data['country_name'] = raw_info.get('country')
data['region_code'] = raw_info.get('region')
data['region_name'] = raw_info.get('regionName')
data['zip_code'] = raw_info.get('zip')
data['time_zone'] = raw_info.get('timezone')
data['latitude'] = raw_info.get('lat')
data['longitude'] = raw_info.get('lon')
# From Wikipedia: Fahrenheit is used in the Bahamas, Belize,
# the Cayman Islands, Palau, and the United States and associated
@ -73,11 +57,16 @@ def distance(lat1, lon1, lat2, lon2):
def elevation(latitude, longitude):
"""Return elevation for given latitude and longitude."""
req = requests.get(ELEVATION_URL,
params={'locations': '{},{}'.format(latitude,
longitude),
'sensor': 'false'},
try:
req = requests.get(
ELEVATION_URL,
params={
'locations': '{},{}'.format(latitude, longitude),
'sensor': 'false',
},
timeout=10)
except requests.RequestException:
return 0
if req.status_code != 200:
return 0
@ -157,3 +146,45 @@ def vincenty(point1, point2, miles=False):
s *= MILES_PER_KILOMETER # kilometers to miles
return round(s, 6)
def _get_freegeoip():
"""Query freegeoip.io for location data."""
try:
raw_info = requests.get(FREEGEO_API, timeout=5).json()
except (requests.RequestException, ValueError):
return None
return {
'ip': raw_info.get('ip'),
'country_code': raw_info.get('country_code'),
'country_name': raw_info.get('country_name'),
'region_code': raw_info.get('region_code'),
'region_name': raw_info.get('region_name'),
'city': raw_info.get('city'),
'zip_code': raw_info.get('zip_code'),
'time_zone': raw_info.get('time_zone'),
'latitude': raw_info.get('latitude'),
'longitude': raw_info.get('longitude'),
}
def _get_ip_api():
"""Query ip-api.com for location data."""
try:
raw_info = requests.get(IP_API, timeout=5).json()
except (requests.RequestException, ValueError):
return None
return {
'ip': raw_info.get('query'),
'country_code': raw_info.get('countryCode'),
'country_name': raw_info.get('country'),
'region_code': raw_info.get('region'),
'region_name': raw_info.get('regionName'),
'city': raw_info.get('city'),
'zip_code': raw_info.get('zip'),
'time_zone': raw_info.get('timezone'),
'latitude': raw_info.get('lat'),
'longitude': raw_info.get('lon'),
}

View file

@ -41,7 +41,7 @@ blinkstick==1.1.7
blockchain==1.3.3
# homeassistant.components.thermostat.eq3btsmart
# bluepy_devices>=0.2.0
# bluepy_devices==0.2.0
# homeassistant.components.notify.aws_lambda
# homeassistant.components.notify.aws_sns
@ -245,7 +245,7 @@ pyasn1==0.1.9
pychromecast==0.7.2
# homeassistant.components.media_player.cmus
pycmus>=0.1.0
pycmus==0.1.0
# homeassistant.components.envisalink
# homeassistant.components.zwave

View file

@ -1,10 +1,9 @@
flake8>=2.5.4
pylint>=1.5.5
flake8>=2.6.0
pylint>=1.5.6
coveralls>=1.1
pytest>=2.9.1
pytest-cov>=2.2.0
pytest>=2.9.2
pytest-cov>=2.2.1
pytest-timeout>=1.0.0
pytest-capturelog>=0.7
betamax==0.7.0
pydocstyle>=1.0.0
httpretty==0.8.14
requests_mock>=1.0

View file

@ -1,27 +1,25 @@
"""Test the initialization."""
import betamax
"""Setup some common test helper things."""
import functools
from homeassistant import util
from homeassistant.util import location
with betamax.Betamax.configure() as config:
config.cassette_library_dir = 'tests/cassettes'
# Automatically called during different setups. Too often forgotten
# so mocked by default.
location.detect_location_info = lambda: location.LocationInfo(
ip='1.1.1.1',
country_code='US',
country_name='United States',
region_code='CA',
region_name='California',
city='San Diego',
zip_code='92122',
time_zone='America/Los_Angeles',
latitude='2.0',
longitude='1.0',
use_fahrenheit=True,
)
def test_real(func):
"""Force a function to require a keyword _test_real to be passed in."""
@functools.wraps(func)
def guard_func(*args, **kwargs):
real = kwargs.pop('_test_real', None)
location.elevation = lambda latitude, longitude: 0
if not real:
raise Exception('Forgot to mock or pass "_test_real=True" to %s',
func.__name__)
return func(*args, **kwargs)
return guard_func
# Guard a few functions that would make network connections
location.detect_location_info = test_real(location.detect_location_info)
location.elevation = test_real(location.elevation)
util.get_local_ip = lambda: '127.0.0.1'

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -35,6 +35,7 @@ def get_test_home_assistant(num_threads=None):
hass.config.config_dir = get_test_config_dir()
hass.config.latitude = 32.87336
hass.config.longitude = -117.22743
hass.config.elevation = 0
hass.config.time_zone = date_util.get_time_zone('US/Pacific')
hass.config.temperature_unit = TEMP_CELSIUS
@ -105,6 +106,13 @@ def ensure_sun_set(hass):
fire_time_changed(hass, sun.next_setting_utc(hass) + timedelta(seconds=10))
def load_fixture(filename):
"""Helper to load a fixture."""
path = os.path.join(os.path.dirname(__file__), 'fixtures', filename)
with open(path) as fp:
return fp.read()
def mock_state_change_event(hass, new_state, old_state=None):
"""Mock state change envent."""
event_data = {

View file

@ -1,17 +1,17 @@
"""The tests for the forecast.io platform."""
import json
import re
import os
import unittest
from unittest.mock import MagicMock, patch
import forecastio
import httpretty
from requests.exceptions import HTTPError
import requests_mock
from homeassistant.components.sensor import forecast
from homeassistant import core as ha
from tests.common import load_fixture
class TestForecastSetup(unittest.TestCase):
"""Test the forecast.io platform."""
@ -48,29 +48,14 @@ class TestForecastSetup(unittest.TestCase):
response = forecast.setup_platform(self.hass, self.config, MagicMock())
self.assertFalse(response)
@httpretty.activate
@requests_mock.Mocker()
@patch('forecastio.api.get_forecast', wraps=forecastio.api.get_forecast)
def test_setup(self, mock_get_forecast):
def test_setup(self, m, mock_get_forecast):
"""Test for successfully setting up the forecast.io platform."""
def load_fixture_from_json():
cwd = os.path.dirname(__file__)
fixture_path = os.path.join(cwd, '..', 'fixtures', 'forecast.json')
with open(fixture_path) as file:
content = json.load(file)
return json.dumps(content)
# Mock out any calls to the actual API and
# return the fixture json instead
uri = 'api.forecast.io\/forecast\/(\w+)\/(-?\d+\.?\d*),(-?\d+\.?\d*)'
httpretty.register_uri(
httpretty.GET,
re.compile(uri),
body=load_fixture_from_json(),
)
# The following will raise an error if the regex for the mock was
# incorrect and we actually try to go out to the internet.
httpretty.HTTPretty.allow_net_connect = False
uri = ('https://api.forecast.io\/forecast\/(\w+)\/'
'(-?\d+\.?\d*),(-?\d+\.?\d*)')
m.get(re.compile(uri),
text=load_fixture('forecast.json'))
forecast.setup_platform(self.hass, self.config, MagicMock())
self.assertTrue(mock_get_forecast.called)
self.assertEqual(mock_get_forecast.call_count, 1)

View file

@ -1,34 +1,35 @@
"""The tests for the Yr sensor platform."""
from datetime import datetime
from unittest import TestCase
from unittest.mock import patch
import pytest
import requests_mock
from homeassistant.bootstrap import _setup_component
import homeassistant.util.dt as dt_util
from tests.common import get_test_home_assistant
from tests.common import get_test_home_assistant, load_fixture
@pytest.mark.usefixtures('betamax_session')
class TestSensorYr:
class TestSensorYr(TestCase):
"""Test the Yr sensor."""
def setup_method(self, method):
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.latitude = 32.87336
self.hass.config.longitude = 117.22743
def teardown_method(self, method):
def tearDown(self):
"""Stop everything that was started."""
self.hass.stop()
def test_default_setup(self, betamax_session):
@requests_mock.Mocker()
def test_default_setup(self, m):
"""Test the default setup."""
m.get('http://api.yr.no/weatherapi/locationforecast/1.9/',
text=load_fixture('yr.no.json'))
now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.sensor.yr.requests.Session',
return_value=betamax_session):
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=now):
assert _setup_component(self.hass, 'sensor', {
@ -41,12 +42,13 @@ class TestSensorYr:
assert state.state.isnumeric()
assert state.attributes.get('unit_of_measurement') is None
def test_custom_setup(self, betamax_session):
@requests_mock.Mocker()
def test_custom_setup(self, m):
"""Test a custom setup."""
m.get('http://api.yr.no/weatherapi/locationforecast/1.9/',
text=load_fixture('yr.no.json'))
now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.sensor.yr.requests.Session',
return_value=betamax_session):
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=now):
assert _setup_component(self.hass, 'sensor', {

View file

@ -131,7 +131,7 @@ class TestComponentsCore(unittest.TestCase):
assert state.attributes.get('hello') == 'world'
@patch('homeassistant.components._LOGGER.error')
@patch('homeassistant.bootstrap.process_ha_core_config')
@patch('homeassistant.config.process_ha_core_config')
def test_reload_core_with_wrong_conf(self, mock_process, mock_error):
"""Test reload core conf service."""
with TemporaryDirectory() as conf_dir:

13
tests/fixtures/freegeoip.io.json vendored Normal file
View file

@ -0,0 +1,13 @@
{
"ip": "1.2.3.4",
"country_code": "US",
"country_name": "United States",
"region_code": "CA",
"region_name": "California",
"city": "San Diego",
"zip_code": "92122",
"time_zone": "America\/Los_Angeles",
"latitude": 32.8594,
"longitude": -117.2073,
"metro_code": 825
}

View file

@ -0,0 +1,13 @@
{
"results" : [
{
"elevation" : 101.5,
"location" : {
"lat" : 32.54321,
"lng" : -117.12345
},
"resolution" : 4.8
}
],
"status" : "OK"
}

16
tests/fixtures/ip-api.com.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"as": "AS20001 Time Warner Cable Internet LLC",
"city": "San Diego",
"country": "United States",
"countryCode": "US",
"isp": "Time Warner Cable",
"lat": 32.8594,
"lon": -117.2073,
"org": "Time Warner Cable",
"query": "1.2.3.4",
"region": "CA",
"regionName": "California",
"status": "success",
"timezone": "America\/Los_Angeles",
"zip": "92122"
}

1184
tests/fixtures/yr.no.json vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
"""Test the bootstrapping."""
# pylint: disable=too-many-public-methods,protected-access
import os
import tempfile
from unittest import mock
import threading
@ -8,10 +7,7 @@ import threading
import voluptuous as vol
from homeassistant import bootstrap, loader
from homeassistant.const import (__version__, CONF_LATITUDE, CONF_LONGITUDE,
CONF_NAME, CONF_CUSTOMIZE)
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from tests.common import get_test_home_assistant, MockModule, MockPlatform
@ -24,23 +20,22 @@ class TestBootstrap:
def setup_method(self, method):
"""Setup the test."""
self.backup_cache = loader._COMPONENT_CACHE
if method == self.test_from_config_file:
return
self.hass = get_test_home_assistant()
self.backup_cache = loader._COMPONENT_CACHE
def teardown_method(self, method):
"""Clean up."""
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
if method == self.test_from_config_file:
return
self.hass.stop()
loader._COMPONENT_CACHE = self.backup_cache
def test_from_config_file(self):
@mock.patch('homeassistant.util.location.detect_location_info',
return_value=None)
def test_from_config_file(self, mock_detect):
"""Test with configuration file."""
components = ['browser', 'conversation', 'script']
with tempfile.NamedTemporaryFile() as fp:
@ -48,71 +43,10 @@ class TestBootstrap:
fp.write('{}:\n'.format(comp).encode('utf-8'))
fp.flush()
hass = bootstrap.from_config_file(fp.name)
self.hass = bootstrap.from_config_file(fp.name)
components.append('group')
assert sorted(components) == sorted(hass.config.components)
def test_remove_lib_on_upgrade(self):
"""Test removal of library on upgrade."""
with tempfile.TemporaryDirectory() as config_dir:
version_path = os.path.join(config_dir, '.HA_VERSION')
lib_dir = os.path.join(config_dir, 'deps')
check_file = os.path.join(lib_dir, 'check')
with open(version_path, 'wt') as outp:
outp.write('0.7.0')
os.mkdir(lib_dir)
with open(check_file, 'w'):
pass
self.hass.config.config_dir = config_dir
assert os.path.isfile(check_file)
bootstrap.process_ha_config_upgrade(self.hass)
assert not os.path.isfile(check_file)
def test_not_remove_lib_if_not_upgrade(self):
"""Test removal of library with no upgrade."""
with tempfile.TemporaryDirectory() as config_dir:
version_path = os.path.join(config_dir, '.HA_VERSION')
lib_dir = os.path.join(config_dir, 'deps')
check_file = os.path.join(lib_dir, 'check')
with open(version_path, 'wt') as outp:
outp.write(__version__)
os.mkdir(lib_dir)
with open(check_file, 'w'):
pass
self.hass.config.config_dir = config_dir
bootstrap.process_ha_config_upgrade(self.hass)
assert os.path.isfile(check_file)
def test_entity_customization(self):
"""Test entity customization through configuration."""
config = {CONF_LATITUDE: 50,
CONF_LONGITUDE: 50,
CONF_NAME: 'Test',
CONF_CUSTOMIZE: {'test.test': {'hidden': True}}}
bootstrap.process_ha_core_config(self.hass, config)
entity = Entity()
entity.entity_id = 'test.test'
entity.hass = self.hass
entity.update_ha_state()
state = self.hass.states.get('test.test')
assert state.attributes['hidden']
assert sorted(components) == sorted(self.hass.config.components)
def test_handle_setup_circular_dependency(self):
"""Test the setup of circular dependencies."""
@ -302,8 +236,7 @@ class TestBootstrap:
assert not bootstrap._setup_component(self.hass, 'comp', None)
assert 'comp' not in self.hass.config.components
@mock.patch('homeassistant.bootstrap.process_ha_core_config')
def test_home_assistant_core_config_validation(self, mock_process):
def test_home_assistant_core_config_validation(self):
"""Test if we pass in wrong information for HA conf."""
# Extensive HA conf validation testing is done in test_config.py
assert None is bootstrap.from_config_dict({
@ -311,7 +244,6 @@ class TestBootstrap:
'latitude': 'some string'
}
})
assert not mock_process.called
def test_component_setup_with_validation_and_dependency(self):
"""Test all config is passed to dependencies."""

View file

@ -1,22 +1,28 @@
"""Test config utils."""
# pylint: disable=too-many-public-methods,protected-access
import os
import tempfile
import unittest
import unittest.mock as mock
import os
import pytest
from voluptuous import MultipleInvalid
from homeassistant.core import DOMAIN, HomeAssistantError
from homeassistant.core import DOMAIN, HomeAssistantError, Config
import homeassistant.config as config_util
from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME,
CONF_TIME_ZONE)
CONF_TIME_ZONE, CONF_ELEVATION, CONF_CUSTOMIZE, __version__,
TEMP_FAHRENHEIT)
from homeassistant.util import location as location_util, dt as dt_util
from homeassistant.helpers.entity import Entity
from tests.common import get_test_config_dir
from tests.common import (
get_test_config_dir, get_test_home_assistant)
CONFIG_DIR = get_test_config_dir()
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
def create_file(path):
@ -30,9 +36,14 @@ class TestConfig(unittest.TestCase):
def tearDown(self): # pylint: disable=invalid-name
"""Clean up."""
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
if os.path.isfile(YAML_PATH):
os.remove(YAML_PATH)
if hasattr(self, 'hass'):
self.hass.stop()
def test_create_default_config(self):
"""Test creation of default config."""
config_util.create_default_config(CONFIG_DIR, False)
@ -108,8 +119,15 @@ class TestConfig(unittest.TestCase):
[('hello', 0), ('world', 1)],
list(config_util.load_yaml_config_file(YAML_PATH).items()))
@mock.patch('homeassistant.util.location.detect_location_info',
return_value=location_util.LocationInfo(
'0.0.0.0', 'US', 'United States', 'CA', 'California',
'San Diego', '92122', 'America/Los_Angeles', 32.8594,
-117.2073, True))
@mock.patch('homeassistant.util.location.elevation', return_value=101)
@mock.patch('builtins.print')
def test_create_default_config_detect_location(self, mock_print):
def test_create_default_config_detect_location(self, mock_detect,
mock_elev, mock_print):
"""Test that detect location sets the correct config keys."""
config_util.ensure_config_exists(CONFIG_DIR)
@ -120,15 +138,16 @@ class TestConfig(unittest.TestCase):
ha_conf = config[DOMAIN]
expected_values = {
CONF_LATITUDE: 2.0,
CONF_LONGITUDE: 1.0,
CONF_LATITUDE: 32.8594,
CONF_LONGITUDE: -117.2073,
CONF_ELEVATION: 101,
CONF_TEMPERATURE_UNIT: 'F',
CONF_NAME: 'Home',
CONF_TIME_ZONE: 'America/Los_Angeles'
}
self.assertEqual(expected_values, ha_conf)
self.assertTrue(mock_print.called)
assert expected_values == ha_conf
assert mock_print.called
@mock.patch('builtins.print')
def test_create_default_config_returns_none_if_write_error(self,
@ -166,3 +185,127 @@ class TestConfig(unittest.TestCase):
},
},
})
def test_entity_customization(self):
"""Test entity customization through configuration."""
self.hass = get_test_home_assistant()
config = {CONF_LATITUDE: 50,
CONF_LONGITUDE: 50,
CONF_NAME: 'Test',
CONF_CUSTOMIZE: {'test.test': {'hidden': True}}}
config_util.process_ha_core_config(self.hass, config)
entity = Entity()
entity.entity_id = 'test.test'
entity.hass = self.hass
entity.update_ha_state()
state = self.hass.states.get('test.test')
assert state.attributes['hidden']
def test_remove_lib_on_upgrade(self):
"""Test removal of library on upgrade."""
with tempfile.TemporaryDirectory() as config_dir:
version_path = os.path.join(config_dir, '.HA_VERSION')
lib_dir = os.path.join(config_dir, 'deps')
check_file = os.path.join(lib_dir, 'check')
with open(version_path, 'wt') as outp:
outp.write('0.7.0')
os.mkdir(lib_dir)
with open(check_file, 'w'):
pass
self.hass = get_test_home_assistant()
self.hass.config.config_dir = config_dir
assert os.path.isfile(check_file)
config_util.process_ha_config_upgrade(self.hass)
assert not os.path.isfile(check_file)
def test_not_remove_lib_if_not_upgrade(self):
"""Test removal of library with no upgrade."""
with tempfile.TemporaryDirectory() as config_dir:
version_path = os.path.join(config_dir, '.HA_VERSION')
lib_dir = os.path.join(config_dir, 'deps')
check_file = os.path.join(lib_dir, 'check')
with open(version_path, 'wt') as outp:
outp.write(__version__)
os.mkdir(lib_dir)
with open(check_file, 'w'):
pass
self.hass = get_test_home_assistant()
self.hass.config.config_dir = config_dir
config_util.process_ha_config_upgrade(self.hass)
assert os.path.isfile(check_file)
def test_loading_configuration(self):
"""Test loading core config onto hass object."""
config = Config()
hass = mock.Mock(config=config)
config_util.process_ha_core_config(hass, {
'latitude': 60,
'longitude': 50,
'elevation': 25,
'name': 'Huis',
'temperature_unit': 'F',
'time_zone': 'America/New_York',
})
assert config.latitude == 60
assert config.longitude == 50
assert config.elevation == 25
assert config.location_name == 'Huis'
assert config.temperature_unit == TEMP_FAHRENHEIT
assert config.time_zone.zone == 'America/New_York'
@mock.patch('homeassistant.util.location.detect_location_info',
return_value=location_util.LocationInfo(
'0.0.0.0', 'US', 'United States', 'CA', 'California',
'San Diego', '92122', 'America/Los_Angeles', 32.8594,
-117.2073, True))
@mock.patch('homeassistant.util.location.elevation', return_value=101)
def test_discovering_configuration(self, mock_detect, mock_elevation):
"""Test auto discovery for missing core configs."""
config = Config()
hass = mock.Mock(config=config)
config_util.process_ha_core_config(hass, {})
assert config.latitude == 32.8594
assert config.longitude == -117.2073
assert config.elevation == 101
assert config.location_name == 'San Diego'
assert config.temperature_unit == TEMP_FAHRENHEIT
assert config.time_zone.zone == 'America/Los_Angeles'
@mock.patch('homeassistant.util.location.detect_location_info',
return_value=None)
@mock.patch('homeassistant.util.location.elevation', return_value=0)
def test_discovering_configuration_auto_detect_fails(self, mock_detect,
mock_elevation):
"""Test config remains unchanged if discovery fails."""
config = Config()
hass = mock.Mock(config=config)
config_util.process_ha_core_config(hass, {})
blankConfig = Config()
assert config.latitude == blankConfig.latitude
assert config.longitude == blankConfig.longitude
assert config.elevation == blankConfig.elevation
assert config.location_name == blankConfig.location_name
assert config.temperature_unit == blankConfig.temperature_unit
assert config.time_zone == blankConfig.time_zone

View file

@ -1,9 +1,15 @@
"""Test Home Assistant location util methods."""
# pylint: disable=too-many-public-methods
import unittest
from unittest import TestCase
from unittest.mock import patch
import requests
import requests_mock
import homeassistant.util.location as location_util
from tests.common import load_fixture
# Paris
COORDINATES_PARIS = (48.864716, 2.349014)
# New York
@ -20,26 +26,124 @@ DISTANCE_KM = 5846.39
DISTANCE_MILES = 3632.78
class TestLocationUtil(unittest.TestCase):
class TestLocationUtil(TestCase):
"""Test util location methods."""
def test_get_distance_to_same_place(self):
"""Test getting the distance."""
meters = location_util.distance(COORDINATES_PARIS[0],
COORDINATES_PARIS[1],
COORDINATES_PARIS[0],
COORDINATES_PARIS[1])
assert meters == 0
def test_get_distance(self):
"""Test getting the distance."""
meters = location_util.distance(COORDINATES_PARIS[0],
COORDINATES_PARIS[1],
COORDINATES_NEW_YORK[0],
COORDINATES_NEW_YORK[1])
self.assertAlmostEqual(meters / 1000, DISTANCE_KM, places=2)
assert meters/1000 - DISTANCE_KM < 0.01
def test_get_kilometers(self):
"""Test getting the distance between given coordinates in km."""
kilometers = location_util.vincenty(COORDINATES_PARIS,
COORDINATES_NEW_YORK)
self.assertEqual(round(kilometers, 2), DISTANCE_KM)
assert round(kilometers, 2) == DISTANCE_KM
def test_get_miles(self):
"""Test getting the distance between given coordinates in miles."""
miles = location_util.vincenty(COORDINATES_PARIS,
COORDINATES_NEW_YORK,
miles=True)
self.assertEqual(round(miles, 2), DISTANCE_MILES)
assert round(miles, 2) == DISTANCE_MILES
@requests_mock.Mocker()
def test_detect_location_info_freegeoip(self, m):
"""Test detect location info using freegeoip."""
m.get(location_util.FREEGEO_API,
text=load_fixture('freegeoip.io.json'))
info = location_util.detect_location_info(_test_real=True)
assert info is not None
assert info.ip == '1.2.3.4'
assert info.country_code == 'US'
assert info.country_name == 'United States'
assert info.region_code == 'CA'
assert info.region_name == 'California'
assert info.city == 'San Diego'
assert info.zip_code == '92122'
assert info.time_zone == 'America/Los_Angeles'
assert info.latitude == 32.8594
assert info.longitude == -117.2073
assert info.use_fahrenheit
@requests_mock.Mocker()
@patch('homeassistant.util.location._get_freegeoip', return_value=None)
def test_detect_location_info_ipapi(self, mock_req, mock_freegeoip):
"""Test detect location info using freegeoip."""
mock_req.get(location_util.IP_API,
text=load_fixture('ip-api.com.json'))
info = location_util.detect_location_info(_test_real=True)
assert info is not None
assert info.ip == '1.2.3.4'
assert info.country_code == 'US'
assert info.country_name == 'United States'
assert info.region_code == 'CA'
assert info.region_name == 'California'
assert info.city == 'San Diego'
assert info.zip_code == '92122'
assert info.time_zone == 'America/Los_Angeles'
assert info.latitude == 32.8594
assert info.longitude == -117.2073
assert info.use_fahrenheit
@patch('homeassistant.util.location.elevation', return_value=0)
@patch('homeassistant.util.location._get_freegeoip', return_value=None)
@patch('homeassistant.util.location._get_ip_api', return_value=None)
def test_detect_location_info_both_queries_fail(self, mock_ipapi,
mock_freegeoip,
mock_elevation):
"""Ensure we return None if both queries fail."""
info = location_util.detect_location_info(_test_real=True)
assert info is None
@patch('homeassistant.util.location.requests.get',
side_effect=requests.RequestException)
def test_freegeoip_query_raises(self, mock_get):
"""Test freegeoip query when the request to API fails."""
info = location_util._get_freegeoip()
assert info is None
@patch('homeassistant.util.location.requests.get',
side_effect=requests.RequestException)
def test_ip_api_query_raises(self, mock_get):
"""Test ip api query when the request to API fails."""
info = location_util._get_ip_api()
assert info is None
@patch('homeassistant.util.location.requests.get',
side_effect=requests.RequestException)
def test_elevation_query_raises(self, mock_get):
"""Test elevation when the request to API fails."""
elevation = location_util.elevation(10, 10, _test_real=True)
assert elevation == 0
@requests_mock.Mocker()
def test_elevation_query_fails(self, mock_req):
"""Test elevation when the request to API fails."""
mock_req.get(location_util.ELEVATION_URL, text='{}', status_code=401)
elevation = location_util.elevation(10, 10, _test_real=True)
assert elevation == 0
@requests_mock.Mocker()
def test_elevation_query_nonjson(self, mock_req):
"""Test if elevation API returns a non JSON value."""
mock_req.get(location_util.ELEVATION_URL, text='{ I am not JSON }')
elevation = location_util.elevation(10, 10, _test_real=True)
assert elevation == 0

View file

@ -49,7 +49,7 @@ class TestPackageUtil(unittest.TestCase):
self.assertTrue(package.check_package_exists(
TEST_NEW_REQ, self.lib_dir))
bootstrap.mount_local_lib_path(self.tmp_dir.name)
bootstrap._mount_local_lib_path(self.tmp_dir.name)
try:
import pyhelloworld3