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
import logging.handlers import logging.handlers
import os import os
import shutil
import sys import sys
from collections import defaultdict from collections import defaultdict
from threading import RLock from threading import RLock
@ -12,21 +11,15 @@ import voluptuous as vol
import homeassistant.components as core_components import homeassistant.components as core_components
from homeassistant.components import group, persistent_notification 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.core as core
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader 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 import homeassistant.util.package as pkg_util
from homeassistant.const import ( from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
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.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import ( from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs, event_decorators, service, config_per_platform, extract_domain_configs)
entity)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_SETUP_LOCK = RLock() _SETUP_LOCK = RLock()
@ -208,11 +201,6 @@ def prepare_setup_platform(hass, config, domain, platform_name):
return platform 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 # pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True, def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, skip_pip=False, 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: if config_dir is not None:
config_dir = os.path.abspath(config_dir) config_dir = os.path.abspath(config_dir)
hass.config.config_dir = 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, {}) core_config = config.get(core.DOMAIN, {})
try: try:
process_ha_core_config(hass, config_util.CORE_CONFIG_SCHEMA( conf_util.process_ha_core_config(hass, core_config)
core_config)) except vol.Invalid as ex:
except vol.MultipleInvalid as ex:
cv.log_exception(_LOGGER, ex, 'homeassistant', core_config) cv.log_exception(_LOGGER, ex, 'homeassistant', core_config)
return None return None
process_ha_config_upgrade(hass) conf_util.process_ha_config_upgrade(hass)
if enable_log: if enable_log:
enable_logging(hass, verbose, log_rotate_days) 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 # Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path)) config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir 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) enable_logging(hass, verbose, log_rotate_days)
try: try:
config_dict = config_util.load_yaml_config_file(config_path) config_dict = conf_util.load_yaml_config_file(config_path)
except HomeAssistantError: except HomeAssistantError:
return None 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) '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): def _ensure_loader_prepared(hass):
"""Ensure Home Assistant loader is prepared.""" """Ensure Home Assistant loader is prepared."""
if not loader.PREPARED: if not loader.PREPARED:
loader.prepare(hass) 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): def handle_reload_config(call):
"""Service handler for reloading core config.""" """Service handler for reloading core config."""
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant import config, bootstrap from homeassistant import config as conf_util
try: try:
path = config.find_config_file(hass.config.config_dir) path = conf_util.find_config_file(hass.config.config_dir)
conf = config.load_yaml_config_file(path) conf = conf_util.load_yaml_config_file(path)
except HomeAssistantError as err: except HomeAssistantError as err:
_LOGGER.error(err) _LOGGER.error(err)
return 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, hass.services.register(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG,
handle_reload_config) handle_reload_config)

View File

@ -17,7 +17,7 @@ from homeassistant.const import (STATE_OFF, STATE_PAUSED, STATE_PLAYING,
CONF_PORT) CONF_PORT)
_LOGGER = logging.getLogger(__name__) _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_CMUS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \
SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ 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.helpers.entity import Entity
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.util import location
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,16 +53,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Yr.no sensor.""" """Setup the Yr.no sensor."""
latitude = config.get(CONF_LATITUDE, hass.config.latitude) latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude) 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): if None in (latitude, longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config") _LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False return False
if elevation is None:
elevation = location.elevation(latitude,
longitude)
coordinates = dict(lat=latitude, coordinates = dict(lat=latitude,
lon=longitude, lon=longitude,
msl=elevation) msl=elevation)

View File

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

View File

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

View File

@ -1,31 +1,35 @@
"""Module to help with parsing and generating configuration files.""" """Module to help with parsing and generating configuration files."""
import logging import logging
import os import os
import shutil
from types import MappingProxyType from types import MappingProxyType
import voluptuous as vol import voluptuous as vol
import homeassistant.util.location as loc_util
from homeassistant.const import ( from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_TEMPERATURE_UNIT, 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.exceptions import HomeAssistantError
from homeassistant.util.yaml import load_yaml from homeassistant.util.yaml import load_yaml
import homeassistant.helpers.config_validation as cv 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__) _LOGGER = logging.getLogger(__name__)
YAML_CONFIG_FILE = 'configuration.yaml' YAML_CONFIG_FILE = 'configuration.yaml'
VERSION_FILE = '.HA_VERSION'
CONFIG_DIR_NAME = '.homeassistant' CONFIG_DIR_NAME = '.homeassistant'
DEFAULT_CONFIG = ( DEFAULT_CONFIG = (
# Tuples (attribute, default, auto detect property, description) # Tuples (attribute, default, auto detect property, description)
(CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is ' (CONF_NAME, 'Home', None, 'Name of the location where Home Assistant is '
'running'), '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'), ' 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_TEMPERATURE_UNIT, 'C', None, 'C for Celsius, F for Fahrenheit'),
(CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki' (CONF_TIME_ZONE, 'UTC', 'time_zone', 'Pick yours from here: http://en.wiki'
'pedia.org/wiki/List_of_tz_database_time_zones'), 'pedia.org/wiki/List_of_tz_database_time_zones'),
@ -39,7 +43,7 @@ DEFAULT_COMPONENTS = {
'history:': 'Enables support for tracking state changes over time.', 'history:': 'Enables support for tracking state changes over time.',
'logbook:': 'View all events in a logbook', 'logbook:': 'View all events in a logbook',
'sun:': 'Track the sun', '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_NAME: vol.Coerce(str),
CONF_LATITUDE: cv.latitude, CONF_LATITUDE: cv.latitude,
CONF_LONGITUDE: cv.longitude, CONF_LONGITUDE: cv.longitude,
CONF_ELEVATION: vol.Coerce(float),
CONF_TEMPERATURE_UNIT: cv.temperature_unit, CONF_TEMPERATURE_UNIT: cv.temperature_unit,
CONF_TIME_ZONE: cv.time_zone, CONF_TIME_ZONE: cv.time_zone,
vol.Required(CONF_CUSTOMIZE, 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. Return path to new config file if success, None if failed.
""" """
config_path = os.path.join(config_dir, YAML_CONFIG_FILE) 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} info = {attr: default for attr, default, _, _ in DEFAULT_CONFIG}
@ -111,6 +117,10 @@ def create_default_config(config_dir, detect_location=True):
continue continue
info[attr] = getattr(location_info, prop) or default 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 # Writing files with YAML does not create the most human readable results
# So we're hard coding a YAML template. # So we're hard coding a YAML template.
try: try:
@ -130,6 +140,9 @@ def create_default_config(config_dir, detect_location=True):
config_file.write("# {}\n".format(description)) config_file.write("# {}\n".format(description))
config_file.write("{}\n\n".format(component)) config_file.write("{}\n\n".format(component))
with open(version_path, 'wt') as version_file:
version_file.write(__version__)
return config_path return config_path
except IOError: except IOError:
@ -155,3 +168,112 @@ def load_yaml_config_file(config_path):
raise HomeAssistantError(msg) raise HomeAssistantError(msg)
return conf_dict 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.""" """Initialize a new config object."""
self.latitude = None self.latitude = None
self.longitude = None self.longitude = None
self.elevation = None
self.temperature_unit = None self.temperature_unit = None
self.location_name = None self.location_name = None
self.time_zone = None self.time_zone = None

View File

@ -8,7 +8,8 @@ import math
import requests import requests
ELEVATION_URL = 'http://maps.googleapis.com/maps/api/elevation/json' 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 # Constants from https://github.com/maurycyp/vincenty
# Earth ellipsoid according to WGS 84 # Earth ellipsoid according to WGS 84
@ -32,30 +33,13 @@ LocationInfo = collections.namedtuple(
def detect_location_info(): def detect_location_info():
"""Detect location information.""" """Detect location information."""
success = None data = _get_freegeoip()
for source in DATA_SOURCE: if data is None:
try: data = _get_ip_api()
raw_info = requests.get(source, timeout=5).json()
success = source
break
except (requests.RequestException, ValueError):
success = False
if success is False: if data is None:
return 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, # From Wikipedia: Fahrenheit is used in the Bahamas, Belize,
# the Cayman Islands, Palau, and the United States and associated # the Cayman Islands, Palau, and the United States and associated
@ -73,11 +57,16 @@ def distance(lat1, lon1, lat2, lon2):
def elevation(latitude, longitude): def elevation(latitude, longitude):
"""Return elevation for given latitude and longitude.""" """Return elevation for given latitude and longitude."""
req = requests.get(ELEVATION_URL, try:
params={'locations': '{},{}'.format(latitude, req = requests.get(
longitude), ELEVATION_URL,
'sensor': 'false'}, params={
timeout=10) 'locations': '{},{}'.format(latitude, longitude),
'sensor': 'false',
},
timeout=10)
except requests.RequestException:
return 0
if req.status_code != 200: if req.status_code != 200:
return 0 return 0
@ -157,3 +146,45 @@ def vincenty(point1, point2, miles=False):
s *= MILES_PER_KILOMETER # kilometers to miles s *= MILES_PER_KILOMETER # kilometers to miles
return round(s, 6) 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 blockchain==1.3.3
# homeassistant.components.thermostat.eq3btsmart # homeassistant.components.thermostat.eq3btsmart
# bluepy_devices>=0.2.0 # bluepy_devices==0.2.0
# homeassistant.components.notify.aws_lambda # homeassistant.components.notify.aws_lambda
# homeassistant.components.notify.aws_sns # homeassistant.components.notify.aws_sns
@ -245,7 +245,7 @@ pyasn1==0.1.9
pychromecast==0.7.2 pychromecast==0.7.2
# homeassistant.components.media_player.cmus # homeassistant.components.media_player.cmus
pycmus>=0.1.0 pycmus==0.1.0
# homeassistant.components.envisalink # homeassistant.components.envisalink
# homeassistant.components.zwave # homeassistant.components.zwave

View File

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

View File

@ -1,27 +1,25 @@
"""Test the initialization.""" """Setup some common test helper things."""
import betamax import functools
from homeassistant import util from homeassistant import util
from homeassistant.util import location 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 def test_real(func):
# so mocked by default. """Force a function to require a keyword _test_real to be passed in."""
location.detect_location_info = lambda: location.LocationInfo( @functools.wraps(func)
ip='1.1.1.1', def guard_func(*args, **kwargs):
country_code='US', real = kwargs.pop('_test_real', None)
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,
)
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' 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.config_dir = get_test_config_dir()
hass.config.latitude = 32.87336 hass.config.latitude = 32.87336
hass.config.longitude = -117.22743 hass.config.longitude = -117.22743
hass.config.elevation = 0
hass.config.time_zone = date_util.get_time_zone('US/Pacific') hass.config.time_zone = date_util.get_time_zone('US/Pacific')
hass.config.temperature_unit = TEMP_CELSIUS 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)) 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): def mock_state_change_event(hass, new_state, old_state=None):
"""Mock state change envent.""" """Mock state change envent."""
event_data = { event_data = {

View File

@ -1,17 +1,17 @@
"""The tests for the forecast.io platform.""" """The tests for the forecast.io platform."""
import json
import re import re
import os
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import forecastio import forecastio
import httpretty
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
import requests_mock
from homeassistant.components.sensor import forecast from homeassistant.components.sensor import forecast
from homeassistant import core as ha from homeassistant import core as ha
from tests.common import load_fixture
class TestForecastSetup(unittest.TestCase): class TestForecastSetup(unittest.TestCase):
"""Test the forecast.io platform.""" """Test the forecast.io platform."""
@ -48,29 +48,14 @@ class TestForecastSetup(unittest.TestCase):
response = forecast.setup_platform(self.hass, self.config, MagicMock()) response = forecast.setup_platform(self.hass, self.config, MagicMock())
self.assertFalse(response) self.assertFalse(response)
@httpretty.activate @requests_mock.Mocker()
@patch('forecastio.api.get_forecast', wraps=forecastio.api.get_forecast) @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.""" """Test for successfully setting up the forecast.io platform."""
def load_fixture_from_json(): uri = ('https://api.forecast.io\/forecast\/(\w+)\/'
cwd = os.path.dirname(__file__) '(-?\d+\.?\d*),(-?\d+\.?\d*)')
fixture_path = os.path.join(cwd, '..', 'fixtures', 'forecast.json') m.get(re.compile(uri),
with open(fixture_path) as file: text=load_fixture('forecast.json'))
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
forecast.setup_platform(self.hass, self.config, MagicMock()) forecast.setup_platform(self.hass, self.config, MagicMock())
self.assertTrue(mock_get_forecast.called) self.assertTrue(mock_get_forecast.called)
self.assertEqual(mock_get_forecast.call_count, 1) self.assertEqual(mock_get_forecast.call_count, 1)

View File

@ -1,39 +1,40 @@
"""The tests for the Yr sensor platform.""" """The tests for the Yr sensor platform."""
from datetime import datetime from datetime import datetime
from unittest import TestCase
from unittest.mock import patch from unittest.mock import patch
import pytest import requests_mock
from homeassistant.bootstrap import _setup_component from homeassistant.bootstrap import _setup_component
import homeassistant.util.dt as dt_util 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(TestCase):
class TestSensorYr:
"""Test the Yr sensor.""" """Test the Yr sensor."""
def setup_method(self, method): def setUp(self):
"""Setup things to be run when tests are started.""" """Setup things to be run when tests are started."""
self.hass = get_test_home_assistant() self.hass = get_test_home_assistant()
self.hass.config.latitude = 32.87336 self.hass.config.latitude = 32.87336
self.hass.config.longitude = 117.22743 self.hass.config.longitude = 117.22743
def teardown_method(self, method): def tearDown(self):
"""Stop everything that was started.""" """Stop everything that was started."""
self.hass.stop() self.hass.stop()
def test_default_setup(self, betamax_session): @requests_mock.Mocker()
def test_default_setup(self, m):
"""Test the default setup.""" """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) now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.sensor.yr.requests.Session', with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=betamax_session): return_value=now):
with patch('homeassistant.components.sensor.yr.dt_util.utcnow', assert _setup_component(self.hass, 'sensor', {
return_value=now): 'sensor': {'platform': 'yr',
assert _setup_component(self.hass, 'sensor', { 'elevation': 0}})
'sensor': {'platform': 'yr',
'elevation': 0}})
state = self.hass.states.get('sensor.yr_symbol') state = self.hass.states.get('sensor.yr_symbol')
@ -41,23 +42,24 @@ class TestSensorYr:
assert state.state.isnumeric() assert state.state.isnumeric()
assert state.attributes.get('unit_of_measurement') is None 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.""" """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) now = datetime(2016, 6, 9, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.sensor.yr.requests.Session', with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=betamax_session): return_value=now):
with patch('homeassistant.components.sensor.yr.dt_util.utcnow', assert _setup_component(self.hass, 'sensor', {
return_value=now): 'sensor': {'platform': 'yr',
assert _setup_component(self.hass, 'sensor', { 'elevation': 0,
'sensor': {'platform': 'yr', 'monitored_conditions': [
'elevation': 0, 'pressure',
'monitored_conditions': [ 'windDirection',
'pressure', 'humidity',
'windDirection', 'fog',
'humidity', 'windSpeed']}})
'fog',
'windSpeed']}})
state = self.hass.states.get('sensor.yr_pressure') state = self.hass.states.get('sensor.yr_pressure')
assert 'hPa' == state.attributes.get('unit_of_measurement') assert 'hPa' == state.attributes.get('unit_of_measurement')

View File

@ -131,7 +131,7 @@ class TestComponentsCore(unittest.TestCase):
assert state.attributes.get('hello') == 'world' assert state.attributes.get('hello') == 'world'
@patch('homeassistant.components._LOGGER.error') @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): def test_reload_core_with_wrong_conf(self, mock_process, mock_error):
"""Test reload core conf service.""" """Test reload core conf service."""
with TemporaryDirectory() as conf_dir: 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.""" """Test the bootstrapping."""
# pylint: disable=too-many-public-methods,protected-access # pylint: disable=too-many-public-methods,protected-access
import os
import tempfile import tempfile
from unittest import mock from unittest import mock
import threading import threading
@ -8,10 +7,7 @@ import threading
import voluptuous as vol import voluptuous as vol
from homeassistant import bootstrap, loader 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 import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from tests.common import get_test_home_assistant, MockModule, MockPlatform from tests.common import get_test_home_assistant, MockModule, MockPlatform
@ -24,23 +20,22 @@ class TestBootstrap:
def setup_method(self, method): def setup_method(self, method):
"""Setup the test.""" """Setup the test."""
self.backup_cache = loader._COMPONENT_CACHE
if method == self.test_from_config_file: if method == self.test_from_config_file:
return return
self.hass = get_test_home_assistant() self.hass = get_test_home_assistant()
self.backup_cache = loader._COMPONENT_CACHE
def teardown_method(self, method): def teardown_method(self, method):
"""Clean up.""" """Clean up."""
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
if method == self.test_from_config_file:
return
self.hass.stop() self.hass.stop()
loader._COMPONENT_CACHE = self.backup_cache 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.""" """Test with configuration file."""
components = ['browser', 'conversation', 'script'] components = ['browser', 'conversation', 'script']
with tempfile.NamedTemporaryFile() as fp: with tempfile.NamedTemporaryFile() as fp:
@ -48,71 +43,10 @@ class TestBootstrap:
fp.write('{}:\n'.format(comp).encode('utf-8')) fp.write('{}:\n'.format(comp).encode('utf-8'))
fp.flush() fp.flush()
hass = bootstrap.from_config_file(fp.name) self.hass = bootstrap.from_config_file(fp.name)
components.append('group') components.append('group')
assert sorted(components) == sorted(self.hass.config.components)
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']
def test_handle_setup_circular_dependency(self): def test_handle_setup_circular_dependency(self):
"""Test the setup of circular dependencies.""" """Test the setup of circular dependencies."""
@ -302,8 +236,7 @@ class TestBootstrap:
assert not bootstrap._setup_component(self.hass, 'comp', None) assert not bootstrap._setup_component(self.hass, 'comp', None)
assert 'comp' not in self.hass.config.components assert 'comp' not in self.hass.config.components
@mock.patch('homeassistant.bootstrap.process_ha_core_config') def test_home_assistant_core_config_validation(self):
def test_home_assistant_core_config_validation(self, mock_process):
"""Test if we pass in wrong information for HA conf.""" """Test if we pass in wrong information for HA conf."""
# Extensive HA conf validation testing is done in test_config.py # Extensive HA conf validation testing is done in test_config.py
assert None is bootstrap.from_config_dict({ assert None is bootstrap.from_config_dict({
@ -311,7 +244,6 @@ class TestBootstrap:
'latitude': 'some string' 'latitude': 'some string'
} }
}) })
assert not mock_process.called
def test_component_setup_with_validation_and_dependency(self): def test_component_setup_with_validation_and_dependency(self):
"""Test all config is passed to dependencies.""" """Test all config is passed to dependencies."""

View File

@ -1,22 +1,28 @@
"""Test config utils.""" """Test config utils."""
# pylint: disable=too-many-public-methods,protected-access # pylint: disable=too-many-public-methods,protected-access
import os
import tempfile
import unittest import unittest
import unittest.mock as mock import unittest.mock as mock
import os
import pytest import pytest
from voluptuous import MultipleInvalid from voluptuous import MultipleInvalid
from homeassistant.core import DOMAIN, HomeAssistantError from homeassistant.core import DOMAIN, HomeAssistantError, Config
import homeassistant.config as config_util import homeassistant.config as config_util
from homeassistant.const import ( from homeassistant.const import (
CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, 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() CONFIG_DIR = get_test_config_dir()
YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE)
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
def create_file(path): def create_file(path):
@ -30,9 +36,14 @@ class TestConfig(unittest.TestCase):
def tearDown(self): # pylint: disable=invalid-name def tearDown(self): # pylint: disable=invalid-name
"""Clean up.""" """Clean up."""
dt_util.DEFAULT_TIME_ZONE = ORIG_TIMEZONE
if os.path.isfile(YAML_PATH): if os.path.isfile(YAML_PATH):
os.remove(YAML_PATH) os.remove(YAML_PATH)
if hasattr(self, 'hass'):
self.hass.stop()
def test_create_default_config(self): def test_create_default_config(self):
"""Test creation of default config.""" """Test creation of default config."""
config_util.create_default_config(CONFIG_DIR, False) config_util.create_default_config(CONFIG_DIR, False)
@ -108,8 +119,15 @@ class TestConfig(unittest.TestCase):
[('hello', 0), ('world', 1)], [('hello', 0), ('world', 1)],
list(config_util.load_yaml_config_file(YAML_PATH).items())) 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') @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.""" """Test that detect location sets the correct config keys."""
config_util.ensure_config_exists(CONFIG_DIR) config_util.ensure_config_exists(CONFIG_DIR)
@ -120,15 +138,16 @@ class TestConfig(unittest.TestCase):
ha_conf = config[DOMAIN] ha_conf = config[DOMAIN]
expected_values = { expected_values = {
CONF_LATITUDE: 2.0, CONF_LATITUDE: 32.8594,
CONF_LONGITUDE: 1.0, CONF_LONGITUDE: -117.2073,
CONF_ELEVATION: 101,
CONF_TEMPERATURE_UNIT: 'F', CONF_TEMPERATURE_UNIT: 'F',
CONF_NAME: 'Home', CONF_NAME: 'Home',
CONF_TIME_ZONE: 'America/Los_Angeles' CONF_TIME_ZONE: 'America/Los_Angeles'
} }
self.assertEqual(expected_values, ha_conf) assert expected_values == ha_conf
self.assertTrue(mock_print.called) assert mock_print.called
@mock.patch('builtins.print') @mock.patch('builtins.print')
def test_create_default_config_returns_none_if_write_error(self, 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.""" """Test Home Assistant location util methods."""
# pylint: disable=too-many-public-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 import homeassistant.util.location as location_util
from tests.common import load_fixture
# Paris # Paris
COORDINATES_PARIS = (48.864716, 2.349014) COORDINATES_PARIS = (48.864716, 2.349014)
# New York # New York
@ -20,26 +26,124 @@ DISTANCE_KM = 5846.39
DISTANCE_MILES = 3632.78 DISTANCE_MILES = 3632.78
class TestLocationUtil(unittest.TestCase): class TestLocationUtil(TestCase):
"""Test util location methods.""" """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): def test_get_distance(self):
"""Test getting the distance.""" """Test getting the distance."""
meters = location_util.distance(COORDINATES_PARIS[0], meters = location_util.distance(COORDINATES_PARIS[0],
COORDINATES_PARIS[1], COORDINATES_PARIS[1],
COORDINATES_NEW_YORK[0], COORDINATES_NEW_YORK[0],
COORDINATES_NEW_YORK[1]) COORDINATES_NEW_YORK[1])
self.assertAlmostEqual(meters / 1000, DISTANCE_KM, places=2)
assert meters/1000 - DISTANCE_KM < 0.01
def test_get_kilometers(self): def test_get_kilometers(self):
"""Test getting the distance between given coordinates in km.""" """Test getting the distance between given coordinates in km."""
kilometers = location_util.vincenty(COORDINATES_PARIS, kilometers = location_util.vincenty(COORDINATES_PARIS,
COORDINATES_NEW_YORK) COORDINATES_NEW_YORK)
self.assertEqual(round(kilometers, 2), DISTANCE_KM) assert round(kilometers, 2) == DISTANCE_KM
def test_get_miles(self): def test_get_miles(self):
"""Test getting the distance between given coordinates in miles.""" """Test getting the distance between given coordinates in miles."""
miles = location_util.vincenty(COORDINATES_PARIS, miles = location_util.vincenty(COORDINATES_PARIS,
COORDINATES_NEW_YORK, COORDINATES_NEW_YORK,
miles=True) 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( self.assertTrue(package.check_package_exists(
TEST_NEW_REQ, self.lib_dir)) 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: try:
import pyhelloworld3 import pyhelloworld3