Merge remote-tracking branch 'origin/dev' into lirc
This commit is contained in:
commit
262d95b7b1
13 changed files with 167 additions and 91 deletions
|
@ -4,7 +4,6 @@ from __future__ import print_function
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import signal
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
@ -334,29 +333,6 @@ def try_to_restart():
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
sys.stderr.write("Failed to count non-daemonic threads.\n")
|
sys.stderr.write("Failed to count non-daemonic threads.\n")
|
||||||
|
|
||||||
# Send terminate signal to all processes in our process group which
|
|
||||||
# should be any children that have not themselves changed the process
|
|
||||||
# group id. Don't bother if couldn't even call setpgid.
|
|
||||||
if hasattr(os, 'setpgid'):
|
|
||||||
sys.stderr.write("Signalling child processes to terminate...\n")
|
|
||||||
os.kill(0, signal.SIGTERM)
|
|
||||||
|
|
||||||
# wait for child processes to terminate
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
time.sleep(1)
|
|
||||||
if os.waitpid(0, os.WNOHANG) == (0, 0):
|
|
||||||
break
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif os.name == 'nt':
|
|
||||||
# Maybe one of the following will work, but how do we indicate which
|
|
||||||
# processes are our children if there is no process group?
|
|
||||||
# os.kill(0, signal.CTRL_C_EVENT)
|
|
||||||
# os.kill(0, signal.CTRL_BREAK_EVENT)
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Try to not leave behind open filedescriptors with the emphasis on try.
|
# Try to not leave behind open filedescriptors with the emphasis on try.
|
||||||
try:
|
try:
|
||||||
max_fd = os.sysconf("SC_OPEN_MAX")
|
max_fd = os.sysconf("SC_OPEN_MAX")
|
||||||
|
@ -408,13 +384,6 @@ def main():
|
||||||
if args.pid_file:
|
if args.pid_file:
|
||||||
write_pid(args.pid_file)
|
write_pid(args.pid_file)
|
||||||
|
|
||||||
# Create new process group if we can
|
|
||||||
if hasattr(os, 'setpgid'):
|
|
||||||
try:
|
|
||||||
os.setpgid(0, 0)
|
|
||||||
except PermissionError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
exit_code = setup_and_run_hass(config_dir, args)
|
exit_code = setup_and_run_hass(config_dir, args)
|
||||||
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
||||||
try_to_restart()
|
try_to_restart()
|
||||||
|
|
|
@ -113,14 +113,13 @@ class APIEventStream(HomeAssistantView):
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
# Somehow our queue.get takes too long to
|
# Somehow our queue.get sometimes takes too long to
|
||||||
# be notified of arrival of object. Probably
|
# be notified of arrival of data. Probably
|
||||||
# because of our spawning on hub in other thread
|
# because of our spawning on hub in other thread
|
||||||
# hack. Because current goal is to get this out,
|
# hack. Because current goal is to get this out,
|
||||||
# We just timeout every second because it will
|
# We just timeout every second because it will
|
||||||
# return right away if qsize() > 0.
|
# return right away if qsize() > 0.
|
||||||
# So yes, we're basically polling :(
|
# So yes, we're basically polling :(
|
||||||
# socket.io anyone?
|
|
||||||
payload = to_write.get(timeout=1)
|
payload = to_write.get(timeout=1)
|
||||||
|
|
||||||
if payload is stop_obj:
|
if payload is stop_obj:
|
||||||
|
|
|
@ -8,12 +8,12 @@
|
||||||
{
|
{
|
||||||
"src": "/static/favicon-192x192.png",
|
"src": "/static/favicon-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png",
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/static/favicon-384x384.png",
|
"src": "/static/favicon-384x384.png",
|
||||||
"sizes": "384x384",
|
"sizes": "384x384",
|
||||||
"type": "image/png",
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.helpers.entity import split_entity_id
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
DOMAIN = "http"
|
DOMAIN = "http"
|
||||||
REQUIREMENTS = ("eventlet==0.18.4", "static3==0.7.0", "Werkzeug==0.11.5",)
|
REQUIREMENTS = ("eventlet==0.19.0", "static3==0.7.0", "Werkzeug==0.11.5",)
|
||||||
|
|
||||||
CONF_API_PASSWORD = "api_password"
|
CONF_API_PASSWORD = "api_password"
|
||||||
CONF_SERVER_HOST = "server_host"
|
CONF_SERVER_HOST = "server_host"
|
||||||
|
@ -31,8 +31,29 @@ _FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HideSensitiveFilter(logging.Filter):
|
||||||
|
"""Filter API password calls."""
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
def __init__(self, hass):
|
||||||
|
"""Initialize sensitive data filter."""
|
||||||
|
super().__init__()
|
||||||
|
self.hass = hass
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
"""Hide sensitive data in messages."""
|
||||||
|
if self.hass.wsgi.api_password is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
record.msg = record.msg.replace(self.hass.wsgi.api_password, '*******')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Set up the HTTP API and debug interface."""
|
"""Set up the HTTP API and debug interface."""
|
||||||
|
_LOGGER.addFilter(HideSensitiveFilter(hass))
|
||||||
|
|
||||||
conf = config.get(DOMAIN, {})
|
conf = config.get(DOMAIN, {})
|
||||||
|
|
||||||
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
|
api_password = util.convert(conf.get(CONF_API_PASSWORD), str)
|
||||||
|
@ -202,7 +223,7 @@ class HomeAssistantWSGI(object):
|
||||||
"""Register a redirect with the server.
|
"""Register a redirect with the server.
|
||||||
|
|
||||||
If given this must be either a string or callable. In case of a
|
If given this must be either a string or callable. In case of a
|
||||||
callable it’s called with the url adapter that triggered the match and
|
callable it's called with the url adapter that triggered the match and
|
||||||
the values of the URL as keyword arguments and has to return the target
|
the values of the URL as keyword arguments and has to return the target
|
||||||
for the redirect, otherwise it has to be a string with placeholders in
|
for the redirect, otherwise it has to be a string with placeholders in
|
||||||
rule syntax.
|
rule syntax.
|
||||||
|
@ -243,9 +264,9 @@ class HomeAssistantWSGI(object):
|
||||||
|
|
||||||
sock = eventlet.listen((self.server_host, self.server_port))
|
sock = eventlet.listen((self.server_host, self.server_port))
|
||||||
if self.ssl_certificate:
|
if self.ssl_certificate:
|
||||||
eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
|
sock = eventlet.wrap_ssl(sock, certfile=self.ssl_certificate,
|
||||||
keyfile=self.ssl_key, server_side=True)
|
keyfile=self.ssl_key, server_side=True)
|
||||||
wsgi.server(sock, self)
|
wsgi.server(sock, self, log=_LOGGER)
|
||||||
|
|
||||||
def dispatch_request(self, request):
|
def dispatch_request(self, request):
|
||||||
"""Handle incoming request."""
|
"""Handle incoming request."""
|
||||||
|
@ -318,9 +339,7 @@ class HomeAssistantView(object):
|
||||||
|
|
||||||
def handle_request(self, request, **values):
|
def handle_request(self, request, **values):
|
||||||
"""Handle request to url."""
|
"""Handle request to url."""
|
||||||
from werkzeug.exceptions import (
|
from werkzeug.exceptions import MethodNotAllowed, Unauthorized
|
||||||
MethodNotAllowed, Unauthorized, BadRequest,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler = getattr(self, request.method.lower())
|
handler = getattr(self, request.method.lower())
|
||||||
|
@ -342,18 +361,6 @@ class HomeAssistantView(object):
|
||||||
self.hass.wsgi.api_password):
|
self.hass.wsgi.api_password):
|
||||||
authenticated = True
|
authenticated = True
|
||||||
|
|
||||||
else:
|
|
||||||
# Do we still want to support passing it in as post data?
|
|
||||||
try:
|
|
||||||
json_data = request.json
|
|
||||||
if (json_data is not None and
|
|
||||||
hmac.compare_digest(
|
|
||||||
json_data.get(DATA_API_PASSWORD, ''),
|
|
||||||
self.hass.wsgi.api_password)):
|
|
||||||
authenticated = True
|
|
||||||
except BadRequest:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.requires_auth and not authenticated:
|
if self.requires_auth and not authenticated:
|
||||||
raise Unauthorized()
|
raise Unauthorized()
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
|
|
||||||
DOMAIN = "isy994"
|
DOMAIN = "isy994"
|
||||||
REQUIREMENTS = ['PyISY==1.0.5']
|
REQUIREMENTS = ['PyISY==1.0.6']
|
||||||
DISCOVER_LIGHTS = "isy994.lights"
|
DISCOVER_LIGHTS = "isy994.lights"
|
||||||
DISCOVER_SWITCHES = "isy994.switches"
|
DISCOVER_SWITCHES = "isy994.switches"
|
||||||
DISCOVER_SENSORS = "isy994.sensors"
|
DISCOVER_SENSORS = "isy994.sensors"
|
||||||
|
|
|
@ -37,7 +37,8 @@ PLATFORM_SCHEMA = vol.Schema({
|
||||||
vol.Required(CONF_PLATFORM): "lg_netcast",
|
vol.Required(CONF_PLATFORM): "lg_netcast",
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Required(CONF_HOST): cv.string,
|
vol.Required(CONF_HOST): cv.string,
|
||||||
vol.Optional(CONF_ACCESS_TOKEN): vol.All(cv.string, vol.Length(max=6)),
|
vol.Optional(CONF_ACCESS_TOKEN, default=None):
|
||||||
|
vol.All(cv.string, vol.Length(max=6)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -136,10 +136,11 @@ class GoogleTravelTimeSensor(Entity):
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
try:
|
_data = self._matrix['rows'][0]['elements'][0]
|
||||||
res = self._matrix['rows'][0]['elements'][0]['duration']['value']
|
if 'duration_in_traffic' in _data:
|
||||||
return round(res/60)
|
return round(_data['duration_in_traffic']['value']/60)
|
||||||
except KeyError:
|
if 'duration' in _data:
|
||||||
|
return round(_data['duration']['value']/60)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -175,9 +176,15 @@ class GoogleTravelTimeSensor(Entity):
|
||||||
atime = options_copy.get('arrival_time')
|
atime = options_copy.get('arrival_time')
|
||||||
if dtime is not None and ':' in dtime:
|
if dtime is not None and ':' in dtime:
|
||||||
options_copy['departure_time'] = convert_time_to_utc(dtime)
|
options_copy['departure_time'] = convert_time_to_utc(dtime)
|
||||||
|
elif dtime is not None:
|
||||||
|
options_copy['departure_time'] = dtime
|
||||||
|
else:
|
||||||
|
options_copy['departure_time'] = 'now'
|
||||||
|
|
||||||
if atime is not None and ':' in atime:
|
if atime is not None and ':' in atime:
|
||||||
options_copy['arrival_time'] = convert_time_to_utc(atime)
|
options_copy['arrival_time'] = convert_time_to_utc(atime)
|
||||||
|
elif atime is not None:
|
||||||
|
options_copy['arrival_time'] = atime
|
||||||
|
|
||||||
self._matrix = self._client.distance_matrix(self._origin,
|
self._matrix = self._client.distance_matrix(self._origin,
|
||||||
self._destination,
|
self._destination,
|
||||||
|
|
|
@ -93,6 +93,10 @@ class OctoPrintSensor(Entity):
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
|
sensor_unit = self.unit_of_measurement
|
||||||
|
if sensor_unit == TEMP_CELSIUS or sensor_unit == "%":
|
||||||
|
return round(self._state, 2)
|
||||||
|
else:
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.util import location as location_util
|
from homeassistant.util import location as location_util
|
||||||
from homeassistant.const import CONF_ELEVATION
|
from homeassistant.const import CONF_ELEVATION
|
||||||
|
|
||||||
REQUIREMENTS = ['astral==1.0']
|
REQUIREMENTS = ['astral==1.1']
|
||||||
DOMAIN = "sun"
|
DOMAIN = "sun"
|
||||||
ENTITY_ID = "sun.sun"
|
ENTITY_ID = "sun.sun"
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ voluptuous==0.8.9
|
||||||
webcolors==1.5
|
webcolors==1.5
|
||||||
|
|
||||||
# homeassistant.components.isy994
|
# homeassistant.components.isy994
|
||||||
PyISY==1.0.5
|
PyISY==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.arduino
|
# homeassistant.components.arduino
|
||||||
PyMata==2.12
|
PyMata==2.12
|
||||||
|
@ -30,7 +30,7 @@ Werkzeug==0.11.5
|
||||||
apcaccess==0.0.4
|
apcaccess==0.0.4
|
||||||
|
|
||||||
# homeassistant.components.sun
|
# homeassistant.components.sun
|
||||||
astral==1.0
|
astral==1.1
|
||||||
|
|
||||||
# homeassistant.components.light.blinksticklight
|
# homeassistant.components.light.blinksticklight
|
||||||
blinkstick==1.1.7
|
blinkstick==1.1.7
|
||||||
|
@ -57,7 +57,7 @@ dweepy==0.2.0
|
||||||
eliqonline==1.0.12
|
eliqonline==1.0.12
|
||||||
|
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
eventlet==0.18.4
|
eventlet==0.19.0
|
||||||
|
|
||||||
# homeassistant.components.thermostat.honeywell
|
# homeassistant.components.thermostat.honeywell
|
||||||
evohomeclient==0.2.5
|
evohomeclient==0.2.5
|
||||||
|
|
|
@ -4,5 +4,6 @@ coveralls>=1.1
|
||||||
pytest>=2.9.1
|
pytest>=2.9.1
|
||||||
pytest-cov>=2.2.0
|
pytest-cov>=2.2.0
|
||||||
pytest-timeout>=1.0.0
|
pytest-timeout>=1.0.0
|
||||||
|
pytest-capturelog>=0.7
|
||||||
betamax==0.5.1
|
betamax==0.5.1
|
||||||
pydocstyle>=1.0.0
|
pydocstyle>=1.0.0
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""The tests for the Home Assistant HTTP component."""
|
"""The tests for the Home Assistant API component."""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
# from contextlib import closing
|
# from contextlib import closing
|
||||||
import json
|
import json
|
||||||
|
@ -66,28 +66,6 @@ class TestAPI(unittest.TestCase):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
hass.pool.block_till_done()
|
hass.pool.block_till_done()
|
||||||
|
|
||||||
# TODO move back to http component and test with use_auth.
|
|
||||||
def test_access_denied_without_password(self):
|
|
||||||
"""Test access without password."""
|
|
||||||
req = requests.get(_url(const.URL_API))
|
|
||||||
|
|
||||||
self.assertEqual(401, req.status_code)
|
|
||||||
|
|
||||||
def test_access_denied_with_wrong_password(self):
|
|
||||||
"""Test ascces with wrong password."""
|
|
||||||
req = requests.get(
|
|
||||||
_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):
|
|
||||||
"""Test access with password in URL."""
|
|
||||||
req = requests.get(
|
|
||||||
"{}?api_password={}".format(_url(const.URL_API), API_PASSWORD))
|
|
||||||
|
|
||||||
self.assertEqual(200, req.status_code)
|
|
||||||
|
|
||||||
def test_api_list_state_entities(self):
|
def test_api_list_state_entities(self):
|
||||||
"""Test if the debug interface allows us to list state entities."""
|
"""Test if the debug interface allows us to list state entities."""
|
||||||
req = requests.get(_url(const.URL_API_STATES),
|
req = requests.get(_url(const.URL_API_STATES),
|
||||||
|
|
110
tests/components/test_http.py
Normal file
110
tests/components/test_http.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
"""The tests for the Home Assistant HTTP component."""
|
||||||
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from homeassistant import bootstrap, const
|
||||||
|
import homeassistant.components.http as http
|
||||||
|
|
||||||
|
from tests.common import get_test_instance_port, get_test_home_assistant
|
||||||
|
|
||||||
|
API_PASSWORD = "test1234"
|
||||||
|
SERVER_PORT = get_test_instance_port()
|
||||||
|
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
|
||||||
|
HA_HEADERS = {
|
||||||
|
const.HTTP_HEADER_HA_AUTH: API_PASSWORD,
|
||||||
|
const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
hass = None
|
||||||
|
|
||||||
|
|
||||||
|
def _url(path=""):
|
||||||
|
"""Helper method to generate URLs."""
|
||||||
|
return HTTP_BASE_URL + path
|
||||||
|
|
||||||
|
|
||||||
|
def setUpModule(): # pylint: disable=invalid-name
|
||||||
|
"""Initialize a Home Assistant server."""
|
||||||
|
global hass
|
||||||
|
|
||||||
|
hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
hass.bus.listen('test_event', lambda _: _)
|
||||||
|
hass.states.set('test.test', 'a_state')
|
||||||
|
|
||||||
|
bootstrap.setup_component(
|
||||||
|
hass, http.DOMAIN,
|
||||||
|
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
|
||||||
|
http.CONF_SERVER_PORT: SERVER_PORT}})
|
||||||
|
|
||||||
|
bootstrap.setup_component(hass, 'api')
|
||||||
|
|
||||||
|
hass.start()
|
||||||
|
|
||||||
|
eventlet.sleep(0.05)
|
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
|
"""Stop the Home Assistant server."""
|
||||||
|
hass.stop()
|
||||||
|
|
||||||
|
|
||||||
|
class TestHttp:
|
||||||
|
"""Test HTTP component."""
|
||||||
|
|
||||||
|
def test_access_denied_without_password(self):
|
||||||
|
"""Test access without password."""
|
||||||
|
req = requests.get(_url(const.URL_API))
|
||||||
|
|
||||||
|
assert req.status_code == 401
|
||||||
|
|
||||||
|
def test_access_denied_with_wrong_password_in_header(self):
|
||||||
|
"""Test ascces with wrong password."""
|
||||||
|
req = requests.get(
|
||||||
|
_url(const.URL_API),
|
||||||
|
headers={const.HTTP_HEADER_HA_AUTH: 'wrongpassword'})
|
||||||
|
|
||||||
|
assert req.status_code == 401
|
||||||
|
|
||||||
|
def test_access_with_password_in_header(self, caplog):
|
||||||
|
"""Test access with password in URL."""
|
||||||
|
# Hide logging from requests package that we use to test logging
|
||||||
|
caplog.setLevel(logging.WARNING,
|
||||||
|
logger='requests.packages.urllib3.connectionpool')
|
||||||
|
|
||||||
|
req = requests.get(
|
||||||
|
_url(const.URL_API),
|
||||||
|
headers={const.HTTP_HEADER_HA_AUTH: API_PASSWORD})
|
||||||
|
|
||||||
|
assert req.status_code == 200
|
||||||
|
|
||||||
|
logs = caplog.text()
|
||||||
|
|
||||||
|
assert const.URL_API in logs
|
||||||
|
assert API_PASSWORD not in logs
|
||||||
|
|
||||||
|
def test_access_denied_with_wrong_password_in_url(self):
|
||||||
|
"""Test ascces with wrong password."""
|
||||||
|
req = requests.get(_url(const.URL_API),
|
||||||
|
params={'api_password': 'wrongpassword'})
|
||||||
|
|
||||||
|
assert req.status_code == 401
|
||||||
|
|
||||||
|
def test_access_with_password_in_url(self, caplog):
|
||||||
|
"""Test access with password in URL."""
|
||||||
|
# Hide logging from requests package that we use to test logging
|
||||||
|
caplog.setLevel(logging.WARNING,
|
||||||
|
logger='requests.packages.urllib3.connectionpool')
|
||||||
|
|
||||||
|
req = requests.get(_url(const.URL_API),
|
||||||
|
params={'api_password': API_PASSWORD})
|
||||||
|
|
||||||
|
assert req.status_code == 200
|
||||||
|
|
||||||
|
logs = caplog.text()
|
||||||
|
|
||||||
|
assert const.URL_API in logs
|
||||||
|
assert API_PASSWORD not in logs
|
Loading…
Add table
Add a link
Reference in a new issue