Merge branch 'dev' into mysensors-component-switch

This commit is contained in:
MartinHjelmare 2015-12-18 03:39:41 +01:00
commit eae07c070a
24 changed files with 278 additions and 117 deletions

View File

@ -55,6 +55,7 @@ omit =
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/ifttt.py
homeassistant/components/influxdb.py
homeassistant/components/keyboard.py
homeassistant/components/light/hue.py
homeassistant/components/light/mqtt.py

View File

@ -53,9 +53,10 @@ def setup(hass, config):
bootstrap.setup_component(hass, 'sun')
# Setup demo platforms
demo_config = config.copy()
for component in COMPONENTS_WITH_DEMO_PLATFORM:
bootstrap.setup_component(
hass, component, {component: {CONF_PLATFORM: 'demo'}})
demo_config[component] = {CONF_PLATFORM: 'demo'}
bootstrap.setup_component(hass, component, demo_config)
# Setup room groups
lights = sorted(hass.states.entity_ids('light'))

View File

@ -44,7 +44,7 @@ HOLD_TEMP = 'hold_temp'
REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/'
'5645f843b64ac4f6e59dfb96233a07083c5e10c1.zip#python-ecobee==0.0.3']
'92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4']
_LOGGER = logging.getLogger(__name__)

View File

@ -22,8 +22,7 @@ _LOGGER = logging.getLogger(__name__)
FRONTEND_URLS = [
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent', '/devInfo']
STATES_URL = re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)')
'/devEvent', '/devInfo', '/states']
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
@ -37,7 +36,8 @@ def setup(hass, config):
for url in FRONTEND_URLS:
hass.http.register_path('GET', url, _handle_get_root, False)
hass.http.register_path('GET', STATES_URL, _handle_get_root, False)
hass.http.register_path('GET', '/service_worker.js',
_handle_get_service_worker, False)
# Static files
hass.http.register_path(
@ -78,6 +78,17 @@ def _handle_get_root(handler, path_match, data):
handler.wfile.write(template_html.encode("UTF-8"))
def _handle_get_service_worker(handler, path_match, data):
""" Returns service worker for the frontend. """
if handler.server.development:
sw_path = "home-assistant-polymer/build/service_worker.js"
else:
sw_path = "service_worker.js"
handler.write_file(os.path.join(os.path.dirname(__file__), 'www_static',
sw_path))
def _handle_get_static(handler, path_match, data):
""" Returns a static file for the frontend. """
req_file = util.sanitize_path(path_match.group('file'))

View File

@ -1,5 +1,5 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home Assistant</title>
@ -31,11 +31,21 @@
margin-bottom: 123px;
}
</style>
<link rel='import' href='/static/{{ app_url }}' async>
</head>
<body fullbleed>
<div id='init'><img src='/static/favicon-192x192.png' height='192'></div>
<script src='/static/webcomponents-lite.min.js'></script>
<link rel='import' href='/static/{{ app_url }}' />
<script>
var webComponentsSupported = ('registerElement' in document &&
'import' in document.createElement('link') &&
'content' in document.createElement('template'))
if (!webComponentsSupported) {
var script = document.createElement('script')
script.async = true
script.src = '/static/webcomponents-lite.min.js'
document.head.appendChild(script)
}
</script>
<home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant>
</body>
</html>

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "33a9830ccda8000eb88700de9d4cd03b"
VERSION = "aac488c33cd4291cd0924e60a55bd309"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 2e8ad266eeb8cd0136df498b995f584e01338000
Subproject commit e51b8add369f9e81d22b25b4be2400675361afdb

View File

@ -0,0 +1,5 @@
!function(e){function t(r){if(n[r])return n[r].exports;var s=n[r]={exports:{},id:r,loaded:!1};return e[r].call(s.exports,s,s.exports,t),s.loaded=!0,s.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([/*!*************************************!*\
!*** ./src/service-worker/index.js ***!
\*************************************/
function(e,t,n){"use strict";var r="0.10",s="/",c=["/","/logbook","/history","/map","/devService","/devState","/devEvent","/devInfo","/states"],i=["/static/favicon-192x192.png"];self.addEventListener("install",function(e){e.waitUntil(caches.open(r).then(function(e){return e.addAll(i.concat(s))}))}),self.addEventListener("activate",function(e){}),self.addEventListener("message",function(e){}),self.addEventListener("fetch",function(e){var t=e.request.url.substr(e.request.url.indexOf("/",8));i.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(e.request)})),c.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(s).then(function(n){var r=fetch(e.request).then(function(e){return t.put(s,e.clone()),e});return n||r})}))})}]);
//# sourceMappingURL=service_worker.js.map

View File

@ -6,16 +6,17 @@ This module provides an API and a HTTP interface for debug purposes.
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api/
"""
import json
import threading
import logging
import time
import gzip
import os
from datetime import timedelta
from http.server import SimpleHTTPRequestHandler, HTTPServer
import gzip
from http import cookies
from http.server import SimpleHTTPRequestHandler, HTTPServer
import json
import logging
import os
from socketserver import ThreadingMixIn
import ssl
import threading
import time
from urllib.parse import urlparse, parse_qs
import homeassistant.core as ha
@ -36,7 +37,8 @@ CONF_API_PASSWORD = "api_password"
CONF_SERVER_HOST = "server_host"
CONF_SERVER_PORT = "server_port"
CONF_DEVELOPMENT = "development"
CONF_SESSIONS_ENABLED = "sessions_enabled"
CONF_SSL_CERTIFICATE = 'ssl_certificate'
CONF_SSL_KEY = 'ssl_key'
DATA_API_PASSWORD = 'api_password'
@ -58,11 +60,13 @@ def setup(hass, config):
server_host = conf.get(CONF_SERVER_HOST, '0.0.0.0')
server_port = conf.get(CONF_SERVER_PORT, SERVER_PORT)
development = str(conf.get(CONF_DEVELOPMENT, "")) == "1"
ssl_certificate = conf.get(CONF_SSL_CERTIFICATE)
ssl_key = conf.get(CONF_SSL_KEY)
try:
server = HomeAssistantHTTPServer(
(server_host, server_port), RequestHandler, hass, api_password,
development)
development, ssl_certificate, ssl_key)
except OSError:
# If address already in use
_LOGGER.exception("Error setting up HTTP server")
@ -74,7 +78,8 @@ def setup(hass, config):
threading.Thread(target=server.start, daemon=True).start())
hass.http = server
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port)
hass.config.api = rem.API(util.get_local_ip(), api_password, server_port,
ssl_certificate is not None)
return True
@ -89,7 +94,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
# pylint: disable=too-many-arguments
def __init__(self, server_address, request_handler_class,
hass, api_password, development):
hass, api_password, development, ssl_certificate, ssl_key):
super().__init__(server_address, request_handler_class)
self.server_address = server_address
@ -98,6 +103,7 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.development = development
self.paths = []
self.sessions = SessionStore()
self.use_ssl = ssl_certificate is not None
# We will lazy init this one if needed
self.event_forwarder = None
@ -105,6 +111,12 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
if development:
_LOGGER.info("running http in development mode")
if ssl_certificate is not None:
wrap_kwargs = {'certfile': ssl_certificate}
if ssl_key is not None:
wrap_kwargs['keyfile'] = ssl_key
self.socket = ssl.wrap_socket(self.socket, **wrap_kwargs)
def start(self):
""" Starts the HTTP server. """
def stop_http(event):
@ -113,8 +125,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
self.hass.bus.listen_once(ha.EVENT_HOMEASSISTANT_STOP, stop_http)
protocol = 'https' if self.use_ssl else 'http'
_LOGGER.info(
"Starting web interface at http://%s:%d", *self.server_address)
"Starting web interface at %s://%s:%d",
protocol, self.server_address[0], self.server_address[1])
# 31-1-2015: Refactored frontend/api components out of this component
# To prevent stuff from breaking, load the two extracted components
@ -187,17 +202,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return
if self.server.api_password is None:
self.authenticated = True
elif HTTP_HEADER_HA_AUTH in self.headers:
api_password = self.headers.get(HTTP_HEADER_HA_AUTH)
if not api_password and DATA_API_PASSWORD in data:
api_password = data[DATA_API_PASSWORD]
self.authenticated = api_password == self.server.api_password
else:
self.authenticated = self.verify_session()
self.authenticated = (self.server.api_password is None
or self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password
or data.get(DATA_API_PASSWORD) ==
self.server.api_password
or self.verify_session())
if '_METHOD' in data:
method = data.pop('_METHOD')

View File

@ -0,0 +1,104 @@
"""
homeassistant.components.influxdb
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
InfluxDB component which allows you to send data to an Influx database.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/influxdb/
"""
import logging
import homeassistant.util as util
from homeassistant.helpers import validate_config
from homeassistant.const import (EVENT_STATE_CHANGED, STATE_ON, STATE_OFF,
STATE_UNLOCKED, STATE_LOCKED, STATE_UNKNOWN)
from homeassistant.components.sun import (STATE_ABOVE_HORIZON,
STATE_BELOW_HORIZON)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "influxdb"
DEPENDENCIES = []
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8086
DEFAULT_DATABASE = 'home_assistant'
REQUIREMENTS = ['influxdb==2.10.0']
CONF_HOST = 'host'
CONF_PORT = 'port'
CONF_DB_NAME = 'database'
CONF_USERNAME = 'username'
CONF_PASSWORD = 'password'
def setup(hass, config):
""" Setup the InfluxDB component. """
from influxdb import InfluxDBClient, exceptions
if not validate_config(config, {DOMAIN: ['host']}, _LOGGER):
return False
conf = config[DOMAIN]
host = conf[CONF_HOST]
port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT)
database = util.convert(conf.get(CONF_DB_NAME), str, DEFAULT_DATABASE)
username = util.convert(conf.get(CONF_USERNAME), str)
password = util.convert(conf.get(CONF_PASSWORD), str)
try:
influx = InfluxDBClient(host=host, port=port, username=username,
password=password, database=database)
databases = [i['name'] for i in influx.get_list_database()]
except exceptions.InfluxDBClientError:
_LOGGER.error("Database host is not accessible. "
"Please check your entries in the configuration file.")
return False
if database not in databases:
_LOGGER.error("Database %s doesn't exist", database)
return False
def influx_event_listener(event):
""" Listen for new messages on the bus and sends them to Influx. """
state = event.data.get('new_state')
if state is None:
return
if state.state in (STATE_ON, STATE_LOCKED, STATE_ABOVE_HORIZON):
_state = 1
elif state.state in (STATE_OFF, STATE_UNLOCKED, STATE_UNKNOWN,
STATE_BELOW_HORIZON):
_state = 0
else:
_state = state.state
measurement = state.attributes.get('unit_of_measurement', state.domain)
json_body = [
{
'measurement': measurement,
'tags': {
'domain': state.domain,
'entity_id': state.object_id,
},
'time': event.time_fired,
'fields': {
'value': _state,
}
}
]
try:
influx.write_points(json_body)
except exceptions.InfluxDBClientError:
_LOGGER.exception('Error saving event to InfluxDB')
hass.bus.listen(EVENT_STATE_CHANGED, influx_event_listener)
return True

View File

@ -1,7 +1,7 @@
"""
homeassistant.components.rollershutter.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform for rollorshutter component.
Demo platform for the rollorshutter component.
"""
from homeassistant.const import EVENT_TIME_CHANGED
from homeassistant.helpers.event import track_utc_time_change
@ -9,7 +9,7 @@ from homeassistant.components.rollershutter import RollershutterDevice
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Demo binary sensors. """
""" Sets up the Demo rollershutters. """
add_devices([
DemoRollershutter(hass, 'Kitchen Window', 0),
DemoRollershutter(hass, 'Living Room Window', 100),
@ -17,7 +17,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoRollershutter(RollershutterDevice):
""" Represents a rollershutter within Home Assistant. """
""" Represents a rollershutter.. """
# pylint: disable=no-self-use
def __init__(self, hass, name, position):
@ -29,14 +29,17 @@ class DemoRollershutter(RollershutterDevice):
@property
def name(self):
""" Returns the name of the rollershutter. """
return self._name
@property
def should_poll(self):
""" No polling needed for a demo rollershutter. """
return False
@property
def current_position(self):
""" Returns the current position of the rollershutter. """
return self._position
def move_up(self, **kwargs):
@ -62,11 +65,13 @@ class DemoRollershutter(RollershutterDevice):
self._listener = None
def _listen(self):
""" Listens for changes. """
if self._listener is None:
self._listener = track_utc_time_change(self.hass,
self._time_changed)
def _time_changed(self, now):
""" Track time changes. """
if self._moving_up:
self._position -= 10
else:

View File

@ -201,7 +201,7 @@ class Script(ToggleEntity):
self._last_action)
domain, service = split_entity_id(conf_service)
data = action.get(CONF_SERVICE_DATA, {})
self.hass.services.call(domain, service, data)
self.hass.services.call(domain, service, data, True)
def _fire_event(self, action):
""" Fires an event. """

View File

@ -127,15 +127,14 @@ class ArestSensor(Entity):
if 'error' in values:
return values['error']
elif 'value' in values:
if self._corr_factor is not None \
and self._decimal_places is not None:
return round((float(values['value']) *
float(self._corr_factor)), self._decimal_places)
elif self._corr_factor is not None \
and self._decimal_places is None:
return round(float(values['value']) * float(self._corr_factor))
else:
return values['value']
value = values['value']
if self._corr_factor is not None:
value = float(value) * float(self._corr_factor)
if self._decimal_places is not None:
value = round(value, self._decimal_places)
if self._decimal_places == 0:
value = int(value)
return value
else:
return values.get(self._variable, 'n/a')

View File

@ -35,8 +35,8 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
data,
config.get('name', DEFAULT_NAME),
config.get('unit_of_measurement'),
config.get('correction_factor', 1.0),
config.get('decimal_places', 0)
config.get('correction_factor', None),
config.get('decimal_places', None)
)])
@ -49,7 +49,7 @@ class CommandSensor(Entity):
self._name = name
self._state = False
self._unit_of_measurement = unit_of_measurement
self._corr_factor = float(corr_factor)
self._corr_factor = corr_factor
self._decimal_places = decimal_places
self.update()
@ -76,9 +76,11 @@ class CommandSensor(Entity):
try:
if value is not None:
if self._corr_factor is not None:
self._state = round((float(value) * self._corr_factor),
self._decimal_places)
else:
value = float(value) * float(self._corr_factor)
if self._decimal_places is not None:
value = round(value, self._decimal_places)
if self._decimal_places == 0:
value = int(value)
self._state = value
except ValueError:
self._state = value

View File

@ -147,16 +147,7 @@ class ForeCastSensor(Entity):
self._state = data.nearestStormBearing
elif self.type == 'precip_intensity':
self._state = data.precipIntensity
if data.precipIntensity == 0:
self._state = 'None'
self._unit_of_measurement = ''
else:
self._state = data.precipIntensity
elif self.type == 'precip_type':
if data.precipType is None:
self._state = 'None'
self._unit_of_measurement = ''
else:
self._state = data.precipType
elif self.type == 'precip_probability':
self._state = round(data.precipProbability * 100, 1)

View File

@ -136,17 +136,12 @@ class RestSensor(Entity):
try:
if value is not None:
value = RestSensor.extract_value(value, self._variable)
if self._corr_factor is not None \
and self._decimal_places is not None:
self._state = round(
(float(value) *
float(self._corr_factor)),
self._decimal_places)
elif self._corr_factor is not None \
and self._decimal_places is None:
self._state = round(float(value) *
float(self._corr_factor))
else:
if self._corr_factor is not None:
value = float(value) * float(self._corr_factor)
if self._decimal_places is not None:
value = round(value, self._decimal_places)
if self._decimal_places == 0:
value = int(value)
self._state = value
except ValueError:
self._state = RestSensor.extract_value(value, self._variable)

View File

@ -1,7 +1,7 @@
# coding: utf-8
""" Constants used by Home Assistant components. """
__version__ = "0.9.0.dev0"
__version__ = "0.10.0.dev0"
# Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL = '*'

View File

@ -25,7 +25,7 @@ from homeassistant.const import (
from homeassistant.exceptions import (
HomeAssistantError, InvalidEntityFormatError)
import homeassistant.util as util
import homeassistant.util.dt as date_util
import homeassistant.util.dt as dt_util
import homeassistant.util.location as location
import homeassistant.helpers.temperature as temp_helper
from homeassistant.config import get_default_config_dir
@ -196,8 +196,8 @@ class Event(object):
self.event_type = event_type
self.data = data or {}
self.origin = origin
self.time_fired = date_util.strip_microseconds(
time_fired or date_util.utcnow())
self.time_fired = dt_util.strip_microseconds(
time_fired or dt_util.utcnow())
def as_dict(self):
""" Returns a dict representation of this Event. """
@ -205,7 +205,7 @@ class Event(object):
'event_type': self.event_type,
'data': dict(self.data),
'origin': str(self.origin),
'time_fired': date_util.datetime_to_str(self.time_fired),
'time_fired': dt_util.datetime_to_str(self.time_fired),
}
def __repr__(self):
@ -351,14 +351,14 @@ class State(object):
self.entity_id = entity_id.lower()
self.state = state
self.attributes = attributes or {}
self.last_updated = date_util.strip_microseconds(
last_updated or date_util.utcnow())
self.last_updated = dt_util.strip_microseconds(
last_updated or dt_util.utcnow())
# Strip microsecond from last_changed else we cannot guarantee
# state == State.from_dict(state.as_dict())
# This behavior occurs because to_dict uses datetime_to_str
# which does not preserve microseconds
self.last_changed = date_util.strip_microseconds(
self.last_changed = dt_util.strip_microseconds(
last_changed or self.last_updated)
@property
@ -381,7 +381,8 @@ class State(object):
def copy(self):
""" Creates a copy of itself. """
return State(self.entity_id, self.state,
dict(self.attributes), self.last_changed)
dict(self.attributes), self.last_changed,
self.last_updated)
def as_dict(self):
""" Converts State to a dict to be used within JSON.
@ -390,8 +391,8 @@ class State(object):
return {'entity_id': self.entity_id,
'state': self.state,
'attributes': self.attributes,
'last_changed': date_util.datetime_to_str(self.last_changed),
'last_updated': date_util.datetime_to_str(self.last_updated)}
'last_changed': dt_util.datetime_to_str(self.last_changed),
'last_updated': dt_util.datetime_to_str(self.last_updated)}
@classmethod
def from_dict(cls, json_dict):
@ -406,12 +407,12 @@ class State(object):
last_changed = json_dict.get('last_changed')
if last_changed:
last_changed = date_util.str_to_datetime(last_changed)
last_changed = dt_util.str_to_datetime(last_changed)
last_updated = json_dict.get('last_updated')
if last_updated:
last_updated = date_util.str_to_datetime(last_updated)
last_updated = dt_util.str_to_datetime(last_updated)
return cls(json_dict['entity_id'], json_dict['state'],
json_dict.get('attributes'), last_changed, last_updated)
@ -428,7 +429,7 @@ class State(object):
return "<state {}={}{} @ {}>".format(
self.entity_id, self.state, attr,
date_util.datetime_to_local_str(self.last_changed))
dt_util.datetime_to_local_str(self.last_changed))
class StateMachine(object):
@ -732,7 +733,7 @@ class Config(object):
def as_dict(self):
""" Converts config to a dictionary. """
time_zone = self.time_zone or date_util.UTC
time_zone = self.time_zone or dt_util.UTC
return {
'latitude': self.latitude,
@ -766,7 +767,7 @@ def create_timer(hass, interval=TIMER_INTERVAL):
last_fired_on_second = -1
calc_now = date_util.utcnow
calc_now = dt_util.utcnow
while not stop_event.isSet():
now = calc_now()
@ -832,6 +833,6 @@ def create_worker_pool(worker_count=None):
for start, job in current_jobs:
_LOGGER.warning("WorkerPool:Current job from %s: %s",
date_util.datetime_to_local_str(start), job)
dt_util.datetime_to_local_str(start), job)
return util.ThreadPool(job_handler, worker_count, busy_callback)

View File

@ -51,10 +51,13 @@ class API(object):
""" Object to pass around Home Assistant API location and credentials. """
# pylint: disable=too-few-public-methods
def __init__(self, host, api_password=None, port=None):
def __init__(self, host, api_password=None, port=None, use_ssl=False):
self.host = host
self.port = port or SERVER_PORT
self.api_password = api_password
if use_ssl:
self.base_url = "https://{}:{}".format(host, self.port)
else:
self.base_url = "http://{}:{}".format(host, self.port)
self.status = None
self._headers = {}

View File

@ -21,11 +21,14 @@ pysnmp==4.2.5
netdisco==0.5.2
# homeassistant.components.ecobee
https://github.com/nkgilley/python-ecobee-api/archive/5645f843b64ac4f6e59dfb96233a07083c5e10c1.zip#python-ecobee==0.0.3
https://github.com/nkgilley/python-ecobee-api/archive/92a2f330cbaf601d0618456fdd97e5a8c42c1c47.zip#python-ecobee==0.0.4
# homeassistant.components.ifttt
pyfttt==0.3
# homeassistant.components.influxdb
influxdb==2.10.0
# homeassistant.components.isy994
PyISY==1.0.5
@ -157,7 +160,7 @@ orvibo==1.0.1
pywemo==0.3.3
# homeassistant.components.thermostat.honeywell
evohomeclient==0.2.3
evohomeclient==0.2.4
# homeassistant.components.thermostat.nest
python-nest==2.6.0

View File

@ -7,6 +7,7 @@ npm run frontend_prod
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
cp build/frontend.html ..
cp build/service_worker.js ..
# Generate the MD5 hash of the new frontend
cd ../..

View File

@ -66,18 +66,31 @@ class TestAPI(unittest.TestCase):
# TODO move back to http component and test with use_auth.
def test_access_denied_without_password(self):
req = requests.get(
_url(const.URL_API_STATES_ENTITY.format("test")))
req = requests.get(_url(const.URL_API))
self.assertEqual(401, req.status_code)
def test_access_denied_with_wrong_password(self):
req = requests.get(
_url(const.URL_API_STATES_ENTITY.format("test")),
_url(const.URL_API),
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
self.assertEqual(401, req.status_code)
def test_access_with_password_in_url(self):
req = requests.get(
"{}?api_password={}".format(_url(const.URL_API), API_PASSWORD))
self.assertEqual(200, req.status_code)
def test_access_via_session(self):
session = requests.Session()
req = session.get(_url(const.URL_API), headers=HA_HEADERS)
self.assertEqual(200, req.status_code)
req = session.get(_url(const.URL_API))
self.assertEqual(200, req.status_code)
def test_api_list_state_entities(self):
""" Test if the debug interface allows us to list state entities. """
req = requests.get(_url(const.URL_API_STATES),

View File

@ -268,7 +268,15 @@ class TestState(unittest.TestCase):
def test_copy(self):
state = ha.State('domain.hello', 'world', {'some': 'attr'})
self.assertEqual(state, state.copy())
# Patch dt_util.utcnow() so we know last_updated got copied too
with patch('homeassistant.core.dt_util.utcnow',
return_value=dt_util.utcnow() + timedelta(seconds=10)):
copy = state.copy()
self.assertEqual(state.entity_id, copy.entity_id)
self.assertEqual(state.state, copy.state)
self.assertEqual(state.attributes, copy.attributes)
self.assertEqual(state.last_changed, copy.last_changed)
self.assertEqual(state.last_updated, copy.last_updated)
def test_dict_conversion(self):
state = ha.State('domain.hello', 'world', {'some': 'attr'})