mirror of https://github.com/home-assistant/core
440 lines
15 KiB
Python
440 lines
15 KiB
Python
"""The tests for the Tomato device tracker platform."""
|
|
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
import requests
|
|
import requests_mock
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.device_tracker import DOMAIN as DEVICE_TRACKER_DOMAIN
|
|
import homeassistant.components.tomato.device_tracker as tomato
|
|
from homeassistant.const import (
|
|
CONF_HOST,
|
|
CONF_PASSWORD,
|
|
CONF_PLATFORM,
|
|
CONF_PORT,
|
|
CONF_SSL,
|
|
CONF_USERNAME,
|
|
CONF_VERIFY_SSL,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
|
|
def mock_session_response(*args, **kwargs):
|
|
"""Mock data generation for session response."""
|
|
|
|
class MockSessionResponse:
|
|
def __init__(self, text, status_code) -> None:
|
|
self.text = text
|
|
self.status_code = status_code
|
|
|
|
# Username: foo
|
|
# Password: bar
|
|
if args[0].headers["Authorization"] != "Basic Zm9vOmJhcg==":
|
|
return MockSessionResponse(None, 401)
|
|
if "gimmie_bad_data" in args[0].body:
|
|
return MockSessionResponse("This shouldn't (wldev = be here.;", 200)
|
|
if "gimmie_good_data" in args[0].body:
|
|
return MockSessionResponse(
|
|
"wldev = [ ['eth1','F4:F5:D8:AA:AA:AA',"
|
|
"-42,5500,1000,7043,0],['eth1','58:EF:68:00:00:00',"
|
|
"-42,5500,1000,7043,0]];\n"
|
|
"dhcpd_lease = [ ['chromecast','172.10.10.5','F4:F5:D8:AA:AA:AA',"
|
|
"'0 days, 16:17:08'],['wemo','172.10.10.6','58:EF:68:00:00:00',"
|
|
"'0 days, 12:09:08']];",
|
|
200,
|
|
)
|
|
|
|
return MockSessionResponse(None, 200)
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_exception_logger():
|
|
"""Mock pyunifi."""
|
|
with mock.patch(
|
|
"homeassistant.components.tomato.device_tracker._LOGGER.exception"
|
|
) as mock_exception_logger:
|
|
yield mock_exception_logger
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_session_send():
|
|
"""Mock requests.Session().send."""
|
|
with mock.patch("requests.Session.send") as mock_session_send:
|
|
yield mock_session_send
|
|
|
|
|
|
def test_config_missing_optional_params(hass: HomeAssistant, mock_session_send) -> None:
|
|
"""Test the setup without optional parameters."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "password",
|
|
tomato.CONF_HTTP_ID: "1234567890",
|
|
}
|
|
)
|
|
}
|
|
result = tomato.get_scanner(hass, config)
|
|
assert result.req.url == "http://tomato-router:80/update.cgi"
|
|
assert result.req.headers == {
|
|
"Content-Length": "32",
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Authorization": "Basic Zm9vOnBhc3N3b3Jk",
|
|
}
|
|
assert "_http_id=1234567890" in result.req.body
|
|
assert "exec=devlist" in result.req.body
|
|
|
|
|
|
@mock.patch("os.access", return_value=True)
|
|
@mock.patch("os.path.isfile", mock.Mock(return_value=True))
|
|
def test_config_default_nonssl_port(hass: HomeAssistant, mock_session_send) -> None:
|
|
"""Test the setup without a default port set without ssl enabled."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "password",
|
|
tomato.CONF_HTTP_ID: "1234567890",
|
|
}
|
|
)
|
|
}
|
|
result = tomato.get_scanner(hass, config)
|
|
assert result.req.url == "http://tomato-router:80/update.cgi"
|
|
|
|
|
|
@mock.patch("os.access", return_value=True)
|
|
@mock.patch("os.path.isfile", mock.Mock(return_value=True))
|
|
def test_config_default_ssl_port(hass: HomeAssistant, mock_session_send) -> None:
|
|
"""Test the setup without a default port set with ssl enabled."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_SSL: True,
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "password",
|
|
tomato.CONF_HTTP_ID: "1234567890",
|
|
}
|
|
)
|
|
}
|
|
result = tomato.get_scanner(hass, config)
|
|
assert result.req.url == "https://tomato-router:443/update.cgi"
|
|
|
|
|
|
@mock.patch("os.access", return_value=True)
|
|
@mock.patch("os.path.isfile", mock.Mock(return_value=True))
|
|
def test_config_verify_ssl_but_no_ssl_enabled(
|
|
hass: HomeAssistant, mock_session_send
|
|
) -> None:
|
|
"""Test the setup with a string with ssl_verify but ssl not enabled."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_PORT: 1234,
|
|
CONF_SSL: False,
|
|
CONF_VERIFY_SSL: "/test/tomato.crt",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "password",
|
|
tomato.CONF_HTTP_ID: "1234567890",
|
|
}
|
|
)
|
|
}
|
|
result = tomato.get_scanner(hass, config)
|
|
assert result.req.url == "http://tomato-router:1234/update.cgi"
|
|
assert result.req.headers == {
|
|
"Content-Length": "32",
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Authorization": "Basic Zm9vOnBhc3N3b3Jk",
|
|
}
|
|
assert "_http_id=1234567890" in result.req.body
|
|
assert "exec=devlist" in result.req.body
|
|
assert mock_session_send.call_count == 1
|
|
assert mock_session_send.mock_calls[0] == mock.call(result.req, timeout=60)
|
|
|
|
|
|
@mock.patch("os.access", return_value=True)
|
|
@mock.patch("os.path.isfile", mock.Mock(return_value=True))
|
|
def test_config_valid_verify_ssl_path(hass: HomeAssistant, mock_session_send) -> None:
|
|
"""Test the setup with a string for ssl_verify.
|
|
|
|
Representing the absolute path to a CA certificate bundle.
|
|
"""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_PORT: 1234,
|
|
CONF_SSL: True,
|
|
CONF_VERIFY_SSL: "/test/tomato.crt",
|
|
CONF_USERNAME: "bar",
|
|
CONF_PASSWORD: "foo",
|
|
tomato.CONF_HTTP_ID: "0987654321",
|
|
}
|
|
)
|
|
}
|
|
result = tomato.get_scanner(hass, config)
|
|
assert result.req.url == "https://tomato-router:1234/update.cgi"
|
|
assert result.req.headers == {
|
|
"Content-Length": "32",
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Authorization": "Basic YmFyOmZvbw==",
|
|
}
|
|
assert "_http_id=0987654321" in result.req.body
|
|
assert "exec=devlist" in result.req.body
|
|
assert mock_session_send.call_count == 1
|
|
assert mock_session_send.mock_calls[0] == mock.call(
|
|
result.req, timeout=60, verify="/test/tomato.crt"
|
|
)
|
|
|
|
|
|
def test_config_valid_verify_ssl_bool(hass: HomeAssistant, mock_session_send) -> None:
|
|
"""Test the setup with a bool for ssl_verify."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_PORT: 1234,
|
|
CONF_SSL: True,
|
|
CONF_VERIFY_SSL: "False",
|
|
CONF_USERNAME: "bar",
|
|
CONF_PASSWORD: "foo",
|
|
tomato.CONF_HTTP_ID: "0987654321",
|
|
}
|
|
)
|
|
}
|
|
result = tomato.get_scanner(hass, config)
|
|
assert result.req.url == "https://tomato-router:1234/update.cgi"
|
|
assert result.req.headers == {
|
|
"Content-Length": "32",
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"Authorization": "Basic YmFyOmZvbw==",
|
|
}
|
|
assert "_http_id=0987654321" in result.req.body
|
|
assert "exec=devlist" in result.req.body
|
|
assert mock_session_send.call_count == 1
|
|
assert mock_session_send.mock_calls[0] == mock.call(
|
|
result.req, timeout=60, verify=False
|
|
)
|
|
|
|
|
|
def test_config_errors() -> None:
|
|
"""Test for configuration errors."""
|
|
with pytest.raises(vol.Invalid):
|
|
tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
# No Host,
|
|
CONF_PORT: 1234,
|
|
CONF_SSL: True,
|
|
CONF_VERIFY_SSL: "False",
|
|
CONF_USERNAME: "bar",
|
|
CONF_PASSWORD: "foo",
|
|
tomato.CONF_HTTP_ID: "0987654321",
|
|
}
|
|
)
|
|
with pytest.raises(vol.Invalid):
|
|
tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_PORT: -123456789, # Bad Port
|
|
CONF_SSL: True,
|
|
CONF_VERIFY_SSL: "False",
|
|
CONF_USERNAME: "bar",
|
|
CONF_PASSWORD: "foo",
|
|
tomato.CONF_HTTP_ID: "0987654321",
|
|
}
|
|
)
|
|
with pytest.raises(vol.Invalid):
|
|
tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_PORT: 1234,
|
|
CONF_SSL: True,
|
|
CONF_VERIFY_SSL: "False",
|
|
# No Username
|
|
CONF_PASSWORD: "foo",
|
|
tomato.CONF_HTTP_ID: "0987654321",
|
|
}
|
|
)
|
|
with pytest.raises(vol.Invalid):
|
|
tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_PORT: 1234,
|
|
CONF_SSL: True,
|
|
CONF_VERIFY_SSL: "False",
|
|
CONF_USERNAME: "bar",
|
|
# No Password
|
|
tomato.CONF_HTTP_ID: "0987654321",
|
|
}
|
|
)
|
|
with pytest.raises(vol.Invalid):
|
|
tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_PORT: 1234,
|
|
CONF_SSL: True,
|
|
CONF_VERIFY_SSL: "False",
|
|
CONF_USERNAME: "bar",
|
|
CONF_PASSWORD: "foo",
|
|
# No HTTP_ID
|
|
}
|
|
)
|
|
|
|
|
|
@mock.patch("requests.Session.send", side_effect=mock_session_response)
|
|
def test_config_bad_credentials(hass: HomeAssistant, mock_exception_logger) -> None:
|
|
"""Test the setup with bad credentials."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "i_am",
|
|
CONF_PASSWORD: "an_imposter",
|
|
tomato.CONF_HTTP_ID: "1234",
|
|
}
|
|
)
|
|
}
|
|
|
|
tomato.get_scanner(hass, config)
|
|
|
|
assert mock_exception_logger.call_count == 1
|
|
assert mock_exception_logger.mock_calls[0] == mock.call(
|
|
"Failed to authenticate, please check your username and password"
|
|
)
|
|
|
|
|
|
@mock.patch("requests.Session.send", side_effect=mock_session_response)
|
|
def test_bad_response(hass: HomeAssistant, mock_exception_logger) -> None:
|
|
"""Test the setup with bad response from router."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "bar",
|
|
tomato.CONF_HTTP_ID: "gimmie_bad_data",
|
|
}
|
|
)
|
|
}
|
|
|
|
tomato.get_scanner(hass, config)
|
|
|
|
assert mock_exception_logger.call_count == 1
|
|
assert mock_exception_logger.mock_calls[0] == mock.call(
|
|
"Failed to parse response from router"
|
|
)
|
|
|
|
|
|
@mock.patch("requests.Session.send", side_effect=mock_session_response)
|
|
def test_scan_devices(hass: HomeAssistant, mock_exception_logger) -> None:
|
|
"""Test scanning for new devices."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "bar",
|
|
tomato.CONF_HTTP_ID: "gimmie_good_data",
|
|
}
|
|
)
|
|
}
|
|
|
|
scanner = tomato.get_scanner(hass, config)
|
|
assert scanner.scan_devices() == ["F4:F5:D8:AA:AA:AA", "58:EF:68:00:00:00"]
|
|
|
|
|
|
@mock.patch("requests.Session.send", side_effect=mock_session_response)
|
|
def test_bad_connection(hass: HomeAssistant, mock_exception_logger) -> None:
|
|
"""Test the router with a connection error."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "bar",
|
|
tomato.CONF_HTTP_ID: "gimmie_good_data",
|
|
}
|
|
)
|
|
}
|
|
|
|
with requests_mock.Mocker() as adapter:
|
|
adapter.register_uri(
|
|
"POST",
|
|
"http://tomato-router:80/update.cgi",
|
|
exc=requests.exceptions.ConnectionError,
|
|
)
|
|
tomato.get_scanner(hass, config)
|
|
assert mock_exception_logger.call_count == 1
|
|
assert mock_exception_logger.mock_calls[0] == mock.call(
|
|
"Failed to connect to the router or invalid http_id supplied"
|
|
)
|
|
|
|
|
|
@mock.patch("requests.Session.send", side_effect=mock_session_response)
|
|
def test_router_timeout(hass: HomeAssistant, mock_exception_logger) -> None:
|
|
"""Test the router with a timeout error."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "bar",
|
|
tomato.CONF_HTTP_ID: "gimmie_good_data",
|
|
}
|
|
)
|
|
}
|
|
|
|
with requests_mock.Mocker() as adapter:
|
|
adapter.register_uri(
|
|
"POST",
|
|
"http://tomato-router:80/update.cgi",
|
|
exc=requests.exceptions.Timeout,
|
|
)
|
|
tomato.get_scanner(hass, config)
|
|
assert mock_exception_logger.call_count == 1
|
|
assert mock_exception_logger.mock_calls[0] == mock.call(
|
|
"Connection to the router timed out"
|
|
)
|
|
|
|
|
|
@mock.patch("requests.Session.send", side_effect=mock_session_response)
|
|
def test_get_device_name(hass: HomeAssistant, mock_exception_logger) -> None:
|
|
"""Test getting device names."""
|
|
config = {
|
|
DEVICE_TRACKER_DOMAIN: tomato.PLATFORM_SCHEMA(
|
|
{
|
|
CONF_PLATFORM: DEVICE_TRACKER_DOMAIN,
|
|
CONF_HOST: "tomato-router",
|
|
CONF_USERNAME: "foo",
|
|
CONF_PASSWORD: "bar",
|
|
tomato.CONF_HTTP_ID: "gimmie_good_data",
|
|
}
|
|
)
|
|
}
|
|
|
|
scanner = tomato.get_scanner(hass, config)
|
|
assert scanner.get_device_name("F4:F5:D8:AA:AA:AA") == "chromecast"
|
|
assert scanner.get_device_name("58:EF:68:00:00:00") == "wemo"
|
|
assert scanner.get_device_name("AA:BB:CC:00:00:00") is None
|