116 lines
3.2 KiB
Python
116 lines
3.2 KiB
Python
"""Tests for the asynchronous neutral Bridge API client."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from aiohttp import web
|
|
from aiohttp.test_utils import TestClient
|
|
|
|
from custom_components.mb_secure.api import (
|
|
BridgeAuthenticationError,
|
|
BridgeClient,
|
|
)
|
|
|
|
TOKEN = "x" * 32
|
|
|
|
|
|
async def test_client_authenticates_and_parses_additive_info(
|
|
aiohttp_client,
|
|
socket_enabled: None,
|
|
) -> None:
|
|
"""The client authenticates locally and ignores additive response fields."""
|
|
|
|
async def info(request: web.Request) -> web.Response:
|
|
assert request.headers["Authorization"] == f"Bearer {TOKEN}"
|
|
return web.json_response(
|
|
{
|
|
"bridge_id": "bridge-installation-1",
|
|
"bridge_version": "0.1.0",
|
|
"api_version": 1,
|
|
"connected": True,
|
|
"capabilities": ["snapshot", "events"],
|
|
"future_field": True,
|
|
}
|
|
)
|
|
|
|
app = web.Application()
|
|
app.router.add_get("/v1/info", info)
|
|
http_client: TestClient = await aiohttp_client(app)
|
|
client = BridgeClient(
|
|
http_client.session,
|
|
host=str(http_client.make_url("/")),
|
|
port=80,
|
|
token=TOKEN,
|
|
)
|
|
|
|
result = await client.async_get_info()
|
|
|
|
assert result.bridge_id == "bridge-installation-1"
|
|
assert result.connected
|
|
|
|
|
|
async def test_client_maps_authentication_failures(
|
|
aiohttp_client,
|
|
socket_enabled: None,
|
|
) -> None:
|
|
"""HTTP authentication failures use the dedicated client exception."""
|
|
|
|
async def unauthorized(request: web.Request) -> web.Response:
|
|
return web.json_response({}, status=401)
|
|
|
|
app = web.Application()
|
|
app.router.add_get("/v1/info", unauthorized)
|
|
http_client: TestClient = await aiohttp_client(app)
|
|
client = BridgeClient(
|
|
http_client.session,
|
|
host=str(http_client.make_url("/")),
|
|
port=80,
|
|
token=TOKEN,
|
|
)
|
|
|
|
with pytest.raises(BridgeAuthenticationError):
|
|
await client.async_get_info()
|
|
|
|
|
|
async def test_client_parses_server_sent_domain_events(
|
|
aiohttp_client,
|
|
socket_enabled: None,
|
|
) -> None:
|
|
"""The event client accepts the mock's server-sent event framing."""
|
|
event_payload = {
|
|
"revision": 2,
|
|
"event_type": "state_changed",
|
|
"object_type": "area",
|
|
"object_id": "area-1",
|
|
"state": "armed_away",
|
|
}
|
|
|
|
async def events(request: web.Request) -> web.StreamResponse:
|
|
response = web.StreamResponse(
|
|
status=200,
|
|
headers={"Content-Type": "text/event-stream"},
|
|
)
|
|
await response.prepare(request)
|
|
await response.write(
|
|
f"event: domain_event\ndata: {json.dumps(event_payload)}\n\n".encode()
|
|
)
|
|
await response.write_eof()
|
|
return response
|
|
|
|
app = web.Application()
|
|
app.router.add_get("/v1/events", events)
|
|
http_client: TestClient = await aiohttp_client(app)
|
|
client = BridgeClient(
|
|
http_client.session,
|
|
host=str(http_client.make_url("/")),
|
|
port=80,
|
|
token=TOKEN,
|
|
)
|
|
|
|
stream = client.async_events()
|
|
event = await anext(stream)
|
|
await stream.aclose()
|
|
|
|
assert event.object_id == "area-1"
|
|
assert event.revision == 2
|