2016-12-04 18:57:48 +00:00
|
|
|
"""The tests for the emulated Hue component."""
|
2021-10-22 14:28:56 +00:00
|
|
|
from http import HTTPStatus
|
2016-12-04 18:57:48 +00:00
|
|
|
import json
|
|
|
|
import unittest
|
2022-05-29 16:27:32 +00:00
|
|
|
from unittest.mock import patch
|
2019-12-09 11:12:41 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
from aiohttp import web
|
2020-01-14 21:03:02 +00:00
|
|
|
import defusedxml.ElementTree as ET
|
2021-09-06 11:24:00 +00:00
|
|
|
import pytest
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
from homeassistant import setup
|
2020-01-14 21:03:02 +00:00
|
|
|
from homeassistant.components import emulated_hue
|
2020-08-23 07:50:59 +00:00
|
|
|
from homeassistant.components.emulated_hue import upnp
|
2021-10-22 14:28:56 +00:00
|
|
|
from homeassistant.const import CONTENT_TYPE_JSON
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
from tests.common import get_test_instance_port
|
2016-12-04 18:57:48 +00:00
|
|
|
|
|
|
|
BRIDGE_SERVER_PORT = get_test_instance_port()
|
|
|
|
|
|
|
|
|
2020-08-23 07:50:59 +00:00
|
|
|
class MockTransport:
|
|
|
|
"""Mock asyncio transport."""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
"""Create a place to store the sends."""
|
|
|
|
self.sends = []
|
|
|
|
|
|
|
|
def sendto(self, response, addr):
|
|
|
|
"""Mock sendto."""
|
|
|
|
self.sends.append((response, addr))
|
|
|
|
|
|
|
|
|
2021-10-06 00:46:09 +00:00
|
|
|
@pytest.fixture
|
2022-11-29 21:36:36 +00:00
|
|
|
def aiohttp_client(event_loop, aiohttp_client, socket_enabled):
|
2021-10-06 00:46:09 +00:00
|
|
|
"""Return aiohttp_client and allow opening sockets."""
|
|
|
|
return aiohttp_client
|
|
|
|
|
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
@pytest.fixture
|
|
|
|
def hue_client(aiohttp_client):
|
|
|
|
"""Return a hue API client."""
|
|
|
|
app = web.Application()
|
|
|
|
with unittest.mock.patch(
|
|
|
|
"homeassistant.components.emulated_hue.web.Application", return_value=app
|
|
|
|
):
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
async def client():
|
|
|
|
"""Return an authenticated client."""
|
|
|
|
return await aiohttp_client(app)
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
yield client
|
2016-12-04 18:57:48 +00:00
|
|
|
|
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
async def setup_hue(hass):
|
|
|
|
"""Set up the emulated_hue integration."""
|
2022-05-30 06:49:37 +00:00
|
|
|
with patch(
|
|
|
|
"homeassistant.components.emulated_hue.async_create_upnp_datagram_endpoint"
|
|
|
|
):
|
2022-05-29 16:27:32 +00:00
|
|
|
assert await setup.async_setup_component(
|
|
|
|
hass,
|
|
|
|
emulated_hue.DOMAIN,
|
|
|
|
{emulated_hue.DOMAIN: {emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT}},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
2016-12-04 18:57:48 +00:00
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_upnp_discovery_basic() -> None:
|
2021-09-06 11:24:00 +00:00
|
|
|
"""Tests the UPnP basic discovery response."""
|
|
|
|
upnp_responder_protocol = upnp.UPNPResponderProtocol(None, None, "192.0.2.42", 8080)
|
|
|
|
mock_transport = MockTransport()
|
|
|
|
upnp_responder_protocol.transport = mock_transport
|
2020-08-23 07:50:59 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
"""Original request emitted by the Hue Bridge v1 app."""
|
|
|
|
request = """M-SEARCH * HTTP/1.1
|
2020-05-25 19:55:23 +00:00
|
|
|
HOST:239.255.255.250:1900
|
|
|
|
ST:ssdp:all
|
|
|
|
Man:"ssdp:discover"
|
|
|
|
MX:3
|
|
|
|
|
|
|
|
"""
|
2021-09-06 11:24:00 +00:00
|
|
|
encoded_request = request.replace("\n", "\r\n").encode("utf-8")
|
2020-05-25 19:55:23 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
upnp_responder_protocol.datagram_received(encoded_request, 1234)
|
|
|
|
expected_response = """HTTP/1.1 200 OK
|
2020-05-25 19:55:23 +00:00
|
|
|
CACHE-CONTROL: max-age=60
|
|
|
|
EXT:
|
|
|
|
LOCATION: http://192.0.2.42:8080/description.xml
|
|
|
|
SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0
|
|
|
|
hue-bridgeid: 001788FFFE23BFC2
|
|
|
|
ST: urn:schemas-upnp-org:device:basic:1
|
|
|
|
USN: uuid:2f402f80-da50-11e1-9b23-001788255acc
|
|
|
|
|
|
|
|
"""
|
2021-09-06 11:24:00 +00:00
|
|
|
expected_send = expected_response.replace("\n", "\r\n").encode("utf-8")
|
|
|
|
|
|
|
|
assert mock_transport.sends == [(expected_send, 1234)]
|
2020-08-23 07:50:59 +00:00
|
|
|
|
2020-05-25 19:55:23 +00:00
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_upnp_discovery_rootdevice() -> None:
|
2021-09-06 11:24:00 +00:00
|
|
|
"""Tests the UPnP rootdevice discovery response."""
|
|
|
|
upnp_responder_protocol = upnp.UPNPResponderProtocol(None, None, "192.0.2.42", 8080)
|
|
|
|
mock_transport = MockTransport()
|
|
|
|
upnp_responder_protocol.transport = mock_transport
|
2020-08-23 07:50:59 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
"""Original request emitted by Busch-Jaeger free@home SysAP."""
|
|
|
|
request = """M-SEARCH * HTTP/1.1
|
2020-05-25 19:55:23 +00:00
|
|
|
HOST: 239.255.255.250:1900
|
|
|
|
MAN: "ssdp:discover"
|
|
|
|
MX: 40
|
|
|
|
ST: upnp:rootdevice
|
|
|
|
|
|
|
|
"""
|
2021-09-06 11:24:00 +00:00
|
|
|
encoded_request = request.replace("\n", "\r\n").encode("utf-8")
|
2020-05-25 19:55:23 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
upnp_responder_protocol.datagram_received(encoded_request, 1234)
|
|
|
|
expected_response = """HTTP/1.1 200 OK
|
2020-05-25 19:55:23 +00:00
|
|
|
CACHE-CONTROL: max-age=60
|
|
|
|
EXT:
|
|
|
|
LOCATION: http://192.0.2.42:8080/description.xml
|
|
|
|
SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.16.0
|
|
|
|
hue-bridgeid: 001788FFFE23BFC2
|
|
|
|
ST: upnp:rootdevice
|
|
|
|
USN: uuid:2f402f80-da50-11e1-9b23-001788255acc::upnp:rootdevice
|
|
|
|
|
|
|
|
"""
|
2021-09-06 11:24:00 +00:00
|
|
|
expected_send = expected_response.replace("\n", "\r\n").encode("utf-8")
|
2020-08-23 07:50:59 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
assert mock_transport.sends == [(expected_send, 1234)]
|
2020-08-23 07:50:59 +00:00
|
|
|
|
|
|
|
|
2023-02-07 13:20:06 +00:00
|
|
|
def test_upnp_no_response() -> None:
|
2021-09-06 11:24:00 +00:00
|
|
|
"""Tests the UPnP does not response on an invalid request."""
|
|
|
|
upnp_responder_protocol = upnp.UPNPResponderProtocol(None, None, "192.0.2.42", 8080)
|
|
|
|
mock_transport = MockTransport()
|
|
|
|
upnp_responder_protocol.transport = mock_transport
|
|
|
|
|
|
|
|
"""Original request emitted by the Hue Bridge v1 app."""
|
|
|
|
request = """INVALID * HTTP/1.1
|
2020-08-23 07:50:59 +00:00
|
|
|
HOST:239.255.255.250:1900
|
|
|
|
ST:ssdp:all
|
|
|
|
Man:"ssdp:discover"
|
|
|
|
MX:3
|
|
|
|
|
|
|
|
"""
|
2021-09-06 11:24:00 +00:00
|
|
|
encoded_request = request.replace("\n", "\r\n").encode("utf-8")
|
|
|
|
|
|
|
|
upnp_responder_protocol.datagram_received(encoded_request, 1234)
|
|
|
|
|
|
|
|
assert mock_transport.sends == []
|
|
|
|
|
|
|
|
|
|
|
|
async def test_description_xml(hass, hue_client):
|
|
|
|
"""Test the description."""
|
|
|
|
await setup_hue(hass)
|
|
|
|
client = await hue_client()
|
|
|
|
result = await client.get("/description.xml", timeout=5)
|
2020-08-23 07:50:59 +00:00
|
|
|
|
2021-10-22 14:28:56 +00:00
|
|
|
assert result.status == HTTPStatus.OK
|
2021-09-06 11:24:00 +00:00
|
|
|
assert "text/xml" in result.headers["content-type"]
|
2020-08-23 07:50:59 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
try:
|
|
|
|
root = ET.fromstring(await result.text())
|
|
|
|
ns = {"s": "urn:schemas-upnp-org:device-1-0"}
|
|
|
|
assert root.find("./s:device/s:serialNumber", ns).text == "001788FFFE23BFC2"
|
2022-06-02 11:58:04 +00:00
|
|
|
except Exception: # pylint: disable=broad-except
|
2021-09-06 11:24:00 +00:00
|
|
|
pytest.fail("description.xml is not valid XML!")
|
2020-05-25 19:55:23 +00:00
|
|
|
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
async def test_create_username(hass, hue_client):
|
|
|
|
"""Test the creation of an username."""
|
|
|
|
await setup_hue(hass)
|
|
|
|
client = await hue_client()
|
|
|
|
request_json = {"devicetype": "my_device"}
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
result = await client.post("/api", data=json.dumps(request_json), timeout=5)
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-10-22 14:28:56 +00:00
|
|
|
assert result.status == HTTPStatus.OK
|
2021-09-06 11:24:00 +00:00
|
|
|
assert CONTENT_TYPE_JSON in result.headers["content-type"]
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
resp_json = await result.json()
|
|
|
|
success_json = resp_json[0]
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
assert "success" in success_json
|
|
|
|
assert "username" in success_json["success"]
|
2016-12-04 18:57:48 +00:00
|
|
|
|
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
async def test_unauthorized_view(hass, hue_client):
|
|
|
|
"""Test unauthorized view."""
|
|
|
|
await setup_hue(hass)
|
|
|
|
client = await hue_client()
|
|
|
|
request_json = {"devicetype": "my_device"}
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
result = await client.get(
|
|
|
|
"/api/unauthorized", data=json.dumps(request_json), timeout=5
|
|
|
|
)
|
2019-12-05 23:23:54 +00:00
|
|
|
|
2021-10-22 14:28:56 +00:00
|
|
|
assert result.status == HTTPStatus.OK
|
2021-09-06 11:24:00 +00:00
|
|
|
assert CONTENT_TYPE_JSON in result.headers["content-type"]
|
2019-12-05 23:23:54 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
resp_json = await result.json()
|
|
|
|
assert len(resp_json) == 1
|
|
|
|
success_json = resp_json[0]
|
|
|
|
assert len(success_json) == 1
|
2019-12-05 23:23:54 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
assert "error" in success_json
|
|
|
|
error_json = success_json["error"]
|
|
|
|
assert len(error_json) == 3
|
|
|
|
assert "/" in error_json["address"]
|
|
|
|
assert "unauthorized user" in error_json["description"]
|
|
|
|
assert "1" in error_json["type"]
|
2019-12-05 23:23:54 +00:00
|
|
|
|
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
async def test_valid_username_request(hass, hue_client):
|
|
|
|
"""Test request with a valid username."""
|
|
|
|
await setup_hue(hass)
|
|
|
|
client = await hue_client()
|
|
|
|
request_json = {"invalid_key": "my_device"}
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-09-06 11:24:00 +00:00
|
|
|
result = await client.post("/api", data=json.dumps(request_json), timeout=5)
|
2016-12-04 18:57:48 +00:00
|
|
|
|
2021-10-22 14:28:56 +00:00
|
|
|
assert result.status == HTTPStatus.BAD_REQUEST
|