Ability to mock long poll requests + refactor qwikswitch unit… (#33804)
* added the ability to mock a "long poll" get request by setting up the waiting request and feeding responses to it with this, refactored the qwikswitch test so it doesn't use global variables and is more understandable and maintainable * added import asyncio from merge * added assert that first call with long_poll has empty content * passing json instead of the binary string * use json instead of text in mock requests * refactored to use a proxy * return the proxy also for the http methods other than get * refactored so any side_effect can be used and created the long_poll side effect * simplified by using kwargs needed to move the json->text->content logic from mocker to mockrequest * no need to explicitly define method and urlpull/33994/head
parent
8e6e8dfbe0
commit
302e631984
|
@ -1,92 +1,81 @@
|
|||
"""Test qwikswitch sensors."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
import pytest
|
||||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.test_util.aiohttp import mock_aiohttp_client
|
||||
from tests.test_util.aiohttp import MockLongPollSideEffect
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AiohttpClientMockResponseList(list):
|
||||
"""Return multiple values for aiohttp Mocker.
|
||||
|
||||
aoihttp mocker uses decode to fetch the next value.
|
||||
"""
|
||||
|
||||
def decode(self, _):
|
||||
"""Return next item from list."""
|
||||
try:
|
||||
res = list.pop(self, 0)
|
||||
_LOGGER.debug("MockResponseList popped %s: %s", res, self)
|
||||
if isinstance(res, Exception):
|
||||
raise res
|
||||
return res
|
||||
except IndexError:
|
||||
raise AssertionError("MockResponseList empty")
|
||||
|
||||
async def wait_till_empty(self, hass):
|
||||
"""Wait until empty."""
|
||||
while self:
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
DEVICES = [
|
||||
{
|
||||
"id": "@000001",
|
||||
"name": "Switch 1",
|
||||
"type": "rel",
|
||||
"val": "OFF",
|
||||
"time": "1522777506",
|
||||
"rssi": "51%",
|
||||
},
|
||||
{
|
||||
"id": "@000002",
|
||||
"name": "Light 2",
|
||||
"type": "rel",
|
||||
"val": "ON",
|
||||
"time": "1522777507",
|
||||
"rssi": "45%",
|
||||
},
|
||||
{
|
||||
"id": "@000003",
|
||||
"name": "Dim 3",
|
||||
"type": "dim",
|
||||
"val": "280c00",
|
||||
"time": "1522777544",
|
||||
"rssi": "62%",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
LISTEN = AiohttpClientMockResponseList()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def aioclient_mock():
|
||||
"""HTTP client listen and devices."""
|
||||
devices = """[
|
||||
{"id":"@000001","name":"Switch 1","type":"rel","val":"OFF",
|
||||
"time":"1522777506","rssi":"51%"},
|
||||
{"id":"@000002","name":"Light 2","type":"rel","val":"ON",
|
||||
"time":"1522777507","rssi":"45%"},
|
||||
{"id":"@000003","name":"Dim 3","type":"dim","val":"280c00",
|
||||
"time":"1522777544","rssi":"62%"}]"""
|
||||
|
||||
with mock_aiohttp_client() as mock_session:
|
||||
mock_session.get("http://127.0.0.1:2020/&listen", content=LISTEN)
|
||||
mock_session.get("http://127.0.0.1:2020/&device", text=devices)
|
||||
yield mock_session
|
||||
|
||||
|
||||
async def test_binary_sensor_device(hass, aioclient_mock): # noqa: F811
|
||||
async def test_binary_sensor_device(hass, aioclient_mock):
|
||||
"""Test a binary sensor device."""
|
||||
config = {
|
||||
"qwikswitch": {
|
||||
"sensors": {"name": "s1", "id": "@a00001", "channel": 1, "type": "imod"}
|
||||
}
|
||||
}
|
||||
aioclient_mock.get("http://127.0.0.1:2020/&device", json=DEVICES)
|
||||
listen_mock = MockLongPollSideEffect()
|
||||
aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock)
|
||||
await async_setup_component(hass, QWIKSWITCH, config)
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state_obj = hass.states.get("binary_sensor.s1")
|
||||
assert state_obj.state == "off"
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
|
||||
LISTEN.append('{"id":"@a00001","cmd":"","data":"4e0e1601","rssi":"61%"}')
|
||||
LISTEN.append(ClientError()) # Will cause a sleep
|
||||
|
||||
listen_mock.queue_response(
|
||||
json={"id": "@a00001", "cmd": "", "data": "4e0e1601", "rssi": "61%"}
|
||||
)
|
||||
await asyncio.sleep(0.01)
|
||||
await hass.async_block_till_done()
|
||||
state_obj = hass.states.get("binary_sensor.s1")
|
||||
assert state_obj.state == "on"
|
||||
|
||||
LISTEN.append('{"id":"@a00001","cmd":"","data":"4e0e1701","rssi":"61%"}')
|
||||
hass.data[QWIKSWITCH]._sleep_task.cancel()
|
||||
await LISTEN.wait_till_empty(hass)
|
||||
listen_mock.queue_response(
|
||||
json={"id": "@a00001", "cmd": "", "data": "4e0e1701", "rssi": "61%"},
|
||||
)
|
||||
await asyncio.sleep(0.01)
|
||||
await hass.async_block_till_done()
|
||||
state_obj = hass.states.get("binary_sensor.s1")
|
||||
assert state_obj.state == "off"
|
||||
|
||||
listen_mock.stop()
|
||||
|
||||
async def test_sensor_device(hass, aioclient_mock): # noqa: F811
|
||||
|
||||
async def test_sensor_device(hass, aioclient_mock):
|
||||
"""Test a sensor device."""
|
||||
config = {
|
||||
"qwikswitch": {
|
||||
|
@ -98,18 +87,22 @@ async def test_sensor_device(hass, aioclient_mock): # noqa: F811
|
|||
}
|
||||
}
|
||||
}
|
||||
aioclient_mock.get("http://127.0.0.1:2020/&device", json=DEVICES)
|
||||
listen_mock = MockLongPollSideEffect()
|
||||
aioclient_mock.get("http://127.0.0.1:2020/&listen", side_effect=listen_mock)
|
||||
await async_setup_component(hass, QWIKSWITCH, config)
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state_obj = hass.states.get("sensor.ss1")
|
||||
assert state_obj.state == "None"
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
|
||||
LISTEN.append(
|
||||
'{"id":"@a00001","name":"ss1","type":"rel",' '"val":"4733800001a00000"}'
|
||||
listen_mock.queue_response(
|
||||
json={"id": "@a00001", "name": "ss1", "type": "rel", "val": "4733800001a00000"},
|
||||
)
|
||||
|
||||
await asyncio.sleep(0.01)
|
||||
await hass.async_block_till_done()
|
||||
state_obj = hass.states.get("sensor.ss1")
|
||||
assert state_obj.state == "416"
|
||||
|
||||
listen_mock.stop()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Aiohttp test utils."""
|
||||
import asyncio
|
||||
from contextlib import contextmanager
|
||||
import json as _json
|
||||
import re
|
||||
|
@ -6,7 +7,7 @@ from unittest import mock
|
|||
from urllib.parse import parse_qs
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp.client_exceptions import ClientResponseError
|
||||
from aiohttp.client_exceptions import ClientError, ClientResponseError
|
||||
from aiohttp.streams import StreamReader
|
||||
from yarl import URL
|
||||
|
||||
|
@ -48,15 +49,9 @@ class AiohttpClientMocker:
|
|||
headers={},
|
||||
exc=None,
|
||||
cookies=None,
|
||||
side_effect=None,
|
||||
):
|
||||
"""Mock a request."""
|
||||
if json is not None:
|
||||
text = _json.dumps(json)
|
||||
if text is not None:
|
||||
content = text.encode("utf-8")
|
||||
if content is None:
|
||||
content = b""
|
||||
|
||||
if not isinstance(url, RETYPE):
|
||||
url = URL(url)
|
||||
if params:
|
||||
|
@ -64,7 +59,16 @@ class AiohttpClientMocker:
|
|||
|
||||
self._mocks.append(
|
||||
AiohttpClientMockResponse(
|
||||
method, url, status, content, cookies, exc, headers
|
||||
method=method,
|
||||
url=url,
|
||||
status=status,
|
||||
response=content,
|
||||
json=json,
|
||||
text=text,
|
||||
cookies=cookies,
|
||||
exc=exc,
|
||||
headers=headers,
|
||||
side_effect=side_effect,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -134,7 +138,8 @@ class AiohttpClientMocker:
|
|||
for response in self._mocks:
|
||||
if response.match_request(method, url, params):
|
||||
self.mock_calls.append((method, url, data, headers))
|
||||
|
||||
if response.side_effect:
|
||||
response = await response.side_effect(method, url, data)
|
||||
if response.exc:
|
||||
raise response.exc
|
||||
return response
|
||||
|
@ -148,15 +153,32 @@ class AiohttpClientMockResponse:
|
|||
"""Mock Aiohttp client response."""
|
||||
|
||||
def __init__(
|
||||
self, method, url, status, response, cookies=None, exc=None, headers=None
|
||||
self,
|
||||
method,
|
||||
url,
|
||||
status=200,
|
||||
response=None,
|
||||
json=None,
|
||||
text=None,
|
||||
cookies=None,
|
||||
exc=None,
|
||||
headers=None,
|
||||
side_effect=None,
|
||||
):
|
||||
"""Initialize a fake response."""
|
||||
if json is not None:
|
||||
text = _json.dumps(json)
|
||||
if text is not None:
|
||||
response = text.encode("utf-8")
|
||||
if response is None:
|
||||
response = b""
|
||||
|
||||
self.method = method
|
||||
self._url = url
|
||||
self.status = status
|
||||
self.response = response
|
||||
self.exc = exc
|
||||
|
||||
self.side_effect = side_effect
|
||||
self._headers = headers or {}
|
||||
self._cookies = {}
|
||||
|
||||
|
@ -270,3 +292,31 @@ def mock_aiohttp_client():
|
|||
side_effect=create_session,
|
||||
):
|
||||
yield mocker
|
||||
|
||||
|
||||
class MockLongPollSideEffect:
|
||||
"""Imitate a long_poll request. Once created, actual responses are queued and if queue is empty, will await until done."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the queue."""
|
||||
self.semaphore = asyncio.Semaphore(0)
|
||||
self.response_list = []
|
||||
self.stopping = False
|
||||
|
||||
async def __call__(self, method, url, data):
|
||||
"""Fetch the next response from the queue or wait until the queue has items."""
|
||||
if self.stopping:
|
||||
raise ClientError()
|
||||
await self.semaphore.acquire()
|
||||
kwargs = self.response_list.pop(0)
|
||||
return AiohttpClientMockResponse(method=method, url=url, **kwargs)
|
||||
|
||||
def queue_response(self, **kwargs):
|
||||
"""Add a response to the long_poll queue."""
|
||||
self.response_list.append(kwargs)
|
||||
self.semaphore.release()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the current request and future ones. Avoids exception if there is someone waiting when exiting test."""
|
||||
self.stopping = True
|
||||
self.queue_response(exc=ClientError())
|
||||
|
|
Loading…
Reference in New Issue