mirror of https://github.com/home-assistant/core
875 lines
26 KiB
Python
875 lines
26 KiB
Python
"""The tests for the Home Assistant API component."""
|
|
|
|
import asyncio
|
|
from http import HTTPStatus
|
|
import json
|
|
from typing import Any
|
|
from unittest.mock import patch
|
|
|
|
from aiohttp import ServerDisconnectedError, web
|
|
from aiohttp.test_utils import TestClient
|
|
import pytest
|
|
import voluptuous as vol
|
|
|
|
from homeassistant import const
|
|
from homeassistant.auth.models import Credentials
|
|
from homeassistant.bootstrap import DATA_LOGGING
|
|
import homeassistant.core as ha
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from tests.common import CLIENT_ID, MockUser, async_mock_service
|
|
from tests.typing import ClientSessionGenerator
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_api_client(
|
|
hass: HomeAssistant, hass_client: ClientSessionGenerator
|
|
) -> TestClient:
|
|
"""Start the Home Assistant HTTP component and return admin API client."""
|
|
hass.loop.run_until_complete(async_setup_component(hass, "api", {}))
|
|
return hass.loop.run_until_complete(hass_client())
|
|
|
|
|
|
async def test_api_list_state_entities(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the debug interface allows us to list state entities."""
|
|
hass.states.async_set("test.entity", "hello")
|
|
resp = await mock_api_client.get(const.URL_API_STATES)
|
|
assert resp.status == HTTPStatus.OK
|
|
json = await resp.json()
|
|
|
|
remote_data = [ha.State.from_dict(item).as_dict() for item in json]
|
|
local_data = [state.as_dict() for state in hass.states.async_all()]
|
|
assert remote_data == local_data
|
|
|
|
|
|
async def test_api_get_state(hass: HomeAssistant, mock_api_client: TestClient) -> None:
|
|
"""Test if the debug interface allows us to get a state."""
|
|
hass.states.async_set("hello.world", "nice", {"attr": 1})
|
|
resp = await mock_api_client.get("/api/states/hello.world")
|
|
assert resp.status == HTTPStatus.OK
|
|
json = await resp.json()
|
|
|
|
data = ha.State.from_dict(json)
|
|
|
|
state = hass.states.get("hello.world")
|
|
|
|
assert data.state == state.state
|
|
assert data.last_changed == state.last_changed
|
|
assert data.attributes == state.attributes
|
|
|
|
|
|
async def test_api_get_non_existing_state(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the debug interface allows us to get a state."""
|
|
resp = await mock_api_client.get("/api/states/does_not_exist")
|
|
assert resp.status == HTTPStatus.NOT_FOUND
|
|
|
|
|
|
async def test_api_state_change(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if we can change the state of an entity that exists."""
|
|
hass.states.async_set("test.test", "not_to_be_set")
|
|
|
|
await mock_api_client.post(
|
|
"/api/states/test.test", json={"state": "debug_state_change2"}
|
|
)
|
|
|
|
assert hass.states.get("test.test").state == "debug_state_change2"
|
|
|
|
|
|
async def test_api_state_change_of_non_existing_entity(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if changing a state of a non existing entity is possible."""
|
|
new_state = "debug_state_change"
|
|
|
|
resp = await mock_api_client.post(
|
|
"/api/states/test_entity.that_does_not_exist", json={"state": new_state}
|
|
)
|
|
|
|
assert resp.status == HTTPStatus.CREATED
|
|
|
|
assert hass.states.get("test_entity.that_does_not_exist").state == new_state
|
|
|
|
|
|
async def test_api_state_change_with_bad_entity_id(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if API sends appropriate error if we omit state."""
|
|
resp = await mock_api_client.post(
|
|
"/api/states/bad.entity.id", json={"state": "new_state"}
|
|
)
|
|
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
async def test_api_state_change_with_bad_state(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if API sends appropriate error if we omit state."""
|
|
resp = await mock_api_client.post(
|
|
"/api/states/test.test", json={"state": "x" * 256}
|
|
)
|
|
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
async def test_api_state_change_with_bad_data(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if API sends appropriate error if we omit state."""
|
|
resp = await mock_api_client.post(
|
|
"/api/states/test_entity.that_does_not_exist", json={}
|
|
)
|
|
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
async def test_api_state_change_to_zero_value(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if changing a state to a zero value is possible."""
|
|
resp = await mock_api_client.post(
|
|
"/api/states/test_entity.with_zero_state", json={"state": 0}
|
|
)
|
|
|
|
assert resp.status == HTTPStatus.CREATED
|
|
|
|
resp = await mock_api_client.post(
|
|
"/api/states/test_entity.with_zero_state", json={"state": 0.0}
|
|
)
|
|
|
|
assert resp.status == HTTPStatus.OK
|
|
|
|
|
|
async def test_api_state_change_push(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if we can push a change the state of an entity."""
|
|
hass.states.async_set("test.test", "not_to_be_set")
|
|
|
|
events = []
|
|
|
|
@ha.callback
|
|
def event_listener(event):
|
|
"""Track events."""
|
|
events.append(event)
|
|
|
|
hass.bus.async_listen(const.EVENT_STATE_CHANGED, event_listener)
|
|
|
|
await mock_api_client.post("/api/states/test.test", json={"state": "not_to_be_set"})
|
|
await hass.async_block_till_done()
|
|
assert len(events) == 0
|
|
|
|
await mock_api_client.post(
|
|
"/api/states/test.test", json={"state": "not_to_be_set", "force_update": True}
|
|
)
|
|
await hass.async_block_till_done()
|
|
assert len(events) == 1
|
|
|
|
|
|
async def test_api_fire_event_with_no_data(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the API allows us to fire an event."""
|
|
test_value = []
|
|
|
|
@ha.callback
|
|
def listener(event):
|
|
"""Record that our event got called."""
|
|
test_value.append(1)
|
|
|
|
hass.bus.async_listen_once("test.event_no_data", listener)
|
|
|
|
await mock_api_client.post("/api/events/test.event_no_data")
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(test_value) == 1
|
|
|
|
|
|
async def test_api_fire_event_with_data(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the API allows us to fire an event."""
|
|
test_value = []
|
|
|
|
@ha.callback
|
|
def listener(event):
|
|
"""Record that our event got called.
|
|
|
|
Also test if our data came through.
|
|
"""
|
|
if "test" in event.data:
|
|
test_value.append(1)
|
|
|
|
hass.bus.async_listen_once("test_event_with_data", listener)
|
|
|
|
await mock_api_client.post("/api/events/test_event_with_data", json={"test": 1})
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(test_value) == 1
|
|
|
|
|
|
async def test_api_fire_event_with_invalid_json(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the API allows us to fire an event."""
|
|
test_value = []
|
|
|
|
@ha.callback
|
|
def listener(event):
|
|
"""Record that our event got called."""
|
|
test_value.append(1)
|
|
|
|
hass.bus.async_listen_once("test_event_bad_data", listener)
|
|
|
|
resp = await mock_api_client.post(
|
|
"/api/events/test_event_bad_data", data=json.dumps("not an object")
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
assert len(test_value) == 0
|
|
|
|
# Try now with valid but unusable JSON
|
|
resp = await mock_api_client.post(
|
|
"/api/events/test_event_bad_data", data=json.dumps([1, 2, 3])
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
assert len(test_value) == 0
|
|
|
|
|
|
async def test_api_get_config(hass: HomeAssistant, mock_api_client: TestClient) -> None:
|
|
"""Test the return of the configuration."""
|
|
resp = await mock_api_client.get(const.URL_API_CONFIG)
|
|
result = await resp.json()
|
|
ignore_order_keys = (
|
|
"components",
|
|
"allowlist_external_dirs",
|
|
"whitelist_external_dirs",
|
|
"allowlist_external_urls",
|
|
)
|
|
config = hass.config.as_dict()
|
|
|
|
for key in ignore_order_keys:
|
|
if key in result:
|
|
result[key] = set(result[key])
|
|
config[key] = set(config[key])
|
|
|
|
assert result == config
|
|
|
|
|
|
async def test_api_get_components(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test the return of the components."""
|
|
resp = await mock_api_client.get(const.URL_API_COMPONENTS)
|
|
result = await resp.json()
|
|
assert set(result) == hass.config.components
|
|
|
|
|
|
async def test_api_get_event_listeners(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if we can get the list of events being listened for."""
|
|
resp = await mock_api_client.get(const.URL_API_EVENTS)
|
|
data = await resp.json()
|
|
|
|
local = hass.bus.async_listeners()
|
|
|
|
for event in data:
|
|
assert local.pop(event["event"]) == event["listener_count"]
|
|
|
|
assert len(local) == 0
|
|
|
|
|
|
async def test_api_get_services(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if we can get a dict describing current services."""
|
|
resp = await mock_api_client.get(const.URL_API_SERVICES)
|
|
data = await resp.json()
|
|
local_services = hass.services.async_services()
|
|
|
|
for serv_domain in data:
|
|
local = local_services.pop(serv_domain["domain"])
|
|
|
|
assert serv_domain["services"].keys() == local.keys()
|
|
|
|
|
|
async def test_api_call_service_no_data(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the API allows us to call a service."""
|
|
test_value = []
|
|
|
|
@ha.callback
|
|
def listener(service_call):
|
|
"""Record that our service got called."""
|
|
test_value.append(1)
|
|
|
|
hass.services.async_register("test_domain", "test_service", listener)
|
|
|
|
await mock_api_client.post("/api/services/test_domain/test_service")
|
|
await hass.async_block_till_done()
|
|
assert len(test_value) == 1
|
|
|
|
|
|
async def test_api_call_service_with_data(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the API allows us to call a service."""
|
|
|
|
@ha.callback
|
|
def listener(service_call):
|
|
"""Record that our service got called.
|
|
|
|
Also test if our data came through.
|
|
"""
|
|
hass.states.async_set(
|
|
"test.data",
|
|
"on",
|
|
{"data": service_call.data["test"]},
|
|
context=service_call.context,
|
|
)
|
|
|
|
hass.services.async_register("test_domain", "test_service", listener)
|
|
|
|
resp = await mock_api_client.post(
|
|
"/api/services/test_domain/test_service", json={"test": 1}
|
|
)
|
|
data = await resp.json()
|
|
assert len(data) == 1
|
|
state = data[0]
|
|
assert state["entity_id"] == "test.data"
|
|
assert state["state"] == "on"
|
|
assert state["attributes"] == {"data": 1}
|
|
|
|
|
|
SERVICE_DICT = {"changed_states": [], "service_response": {"foo": "bar"}}
|
|
RESP_REQUIRED = {
|
|
"message": (
|
|
"Service call requires responses but caller did not ask for "
|
|
"responses. Add ?return_response to query parameters."
|
|
)
|
|
}
|
|
RESP_UNSUPPORTED = {
|
|
"message": "Service does not support responses. Remove return_response from request."
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
(
|
|
"supports_response",
|
|
"requested_response",
|
|
"expected_number_of_service_calls",
|
|
"expected_status",
|
|
"expected_response",
|
|
),
|
|
[
|
|
(ha.SupportsResponse.ONLY, True, 1, HTTPStatus.OK, SERVICE_DICT),
|
|
(ha.SupportsResponse.ONLY, False, 0, HTTPStatus.BAD_REQUEST, RESP_REQUIRED),
|
|
(ha.SupportsResponse.OPTIONAL, True, 1, HTTPStatus.OK, SERVICE_DICT),
|
|
(ha.SupportsResponse.OPTIONAL, False, 1, HTTPStatus.OK, []),
|
|
(ha.SupportsResponse.NONE, True, 0, HTTPStatus.BAD_REQUEST, RESP_UNSUPPORTED),
|
|
(ha.SupportsResponse.NONE, False, 1, HTTPStatus.OK, []),
|
|
],
|
|
)
|
|
async def test_api_call_service_returns_response_requested_response(
|
|
hass: HomeAssistant,
|
|
mock_api_client: TestClient,
|
|
supports_response: ha.SupportsResponse,
|
|
requested_response: bool,
|
|
expected_number_of_service_calls: int,
|
|
expected_status: int,
|
|
expected_response: Any,
|
|
) -> None:
|
|
"""Test if the API allows us to call a service."""
|
|
test_value = []
|
|
|
|
@ha.callback
|
|
def listener(service_call):
|
|
"""Record that our service got called."""
|
|
test_value.append(1)
|
|
return {"foo": "bar"}
|
|
|
|
hass.services.async_register(
|
|
"test_domain", "test_service", listener, supports_response=supports_response
|
|
)
|
|
|
|
resp = await mock_api_client.post(
|
|
"/api/services/test_domain/test_service"
|
|
+ ("?return_response" if requested_response else "")
|
|
)
|
|
assert resp.status == expected_status
|
|
await hass.async_block_till_done()
|
|
assert len(test_value) == expected_number_of_service_calls
|
|
assert await resp.json() == expected_response
|
|
|
|
|
|
async def test_api_call_service_client_closed(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test that services keep running if client is closed."""
|
|
test_value = []
|
|
|
|
fut = hass.loop.create_future()
|
|
service_call_started = asyncio.Event()
|
|
|
|
async def listener(service_call):
|
|
"""Wait and return after mock_api_client.post finishes."""
|
|
service_call_started.set()
|
|
value = await fut
|
|
test_value.append(value)
|
|
|
|
hass.services.async_register("test_domain", "test_service", listener)
|
|
|
|
api_task = hass.async_create_task(
|
|
mock_api_client.post("/api/services/test_domain/test_service")
|
|
)
|
|
|
|
await service_call_started.wait()
|
|
|
|
assert len(test_value) == 0
|
|
|
|
await mock_api_client.close()
|
|
|
|
assert len(test_value) == 0
|
|
assert api_task.done()
|
|
|
|
with pytest.raises(ServerDisconnectedError):
|
|
await api_task
|
|
|
|
fut.set_result(1)
|
|
await hass.async_block_till_done()
|
|
|
|
assert len(test_value) == 1
|
|
assert test_value[0] == 1
|
|
|
|
|
|
async def test_api_template(hass: HomeAssistant, mock_api_client: TestClient) -> None:
|
|
"""Test the template API."""
|
|
hass.states.async_set("sensor.temperature", 10)
|
|
|
|
resp = await mock_api_client.post(
|
|
const.URL_API_TEMPLATE,
|
|
json={"template": "{{ states.sensor.temperature.state }}"},
|
|
)
|
|
|
|
body = await resp.text()
|
|
|
|
assert body == "10"
|
|
|
|
hass.states.async_set("sensor.temperature", 20)
|
|
resp = await mock_api_client.post(
|
|
const.URL_API_TEMPLATE,
|
|
json={"template": "{{ states.sensor.temperature.state }}"},
|
|
)
|
|
|
|
body = await resp.text()
|
|
|
|
assert body == "20"
|
|
|
|
hass.states.async_remove("sensor.temperature")
|
|
resp = await mock_api_client.post(
|
|
const.URL_API_TEMPLATE,
|
|
json={"template": "{{ states.sensor.temperature.state }}"},
|
|
)
|
|
|
|
body = await resp.text()
|
|
|
|
assert body == ""
|
|
|
|
|
|
async def test_api_template_cached(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test the template API uses the cache."""
|
|
hass.states.async_set("sensor.temperature", 30)
|
|
|
|
resp = await mock_api_client.post(
|
|
const.URL_API_TEMPLATE,
|
|
json={"template": "{{ states.sensor.temperature.state }}"},
|
|
)
|
|
|
|
body = await resp.text()
|
|
|
|
assert body == "30"
|
|
|
|
hass.states.async_set("sensor.temperature", 40)
|
|
resp = await mock_api_client.post(
|
|
const.URL_API_TEMPLATE,
|
|
json={"template": "{{ states.sensor.temperature.state }}"},
|
|
)
|
|
|
|
body = await resp.text()
|
|
|
|
assert body == "40"
|
|
|
|
|
|
async def test_api_template_error(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test the template API."""
|
|
hass.states.async_set("sensor.temperature", 10)
|
|
|
|
resp = await mock_api_client.post(
|
|
const.URL_API_TEMPLATE, json={"template": "{{ states.sensor.temperature.state"}
|
|
)
|
|
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
async def test_stream(hass: HomeAssistant, mock_api_client: TestClient) -> None:
|
|
"""Test the stream."""
|
|
listen_count = _listen_count(hass)
|
|
|
|
async with mock_api_client.get(const.URL_API_STREAM) as resp:
|
|
assert resp.status == HTTPStatus.OK
|
|
assert listen_count + 1 == _listen_count(hass)
|
|
|
|
hass.bus.async_fire("test_event")
|
|
|
|
data = await _stream_next_event(resp.content)
|
|
|
|
assert data["event_type"] == "test_event"
|
|
|
|
|
|
async def test_stream_with_restricted(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test the stream with restrictions."""
|
|
listen_count = _listen_count(hass)
|
|
|
|
async with mock_api_client.get(
|
|
f"{const.URL_API_STREAM}?restrict=test_event1,test_event3"
|
|
) as resp:
|
|
assert resp.status == HTTPStatus.OK
|
|
assert listen_count + 1 == _listen_count(hass)
|
|
|
|
hass.bus.async_fire("test_event1")
|
|
data = await _stream_next_event(resp.content)
|
|
assert data["event_type"] == "test_event1"
|
|
|
|
hass.bus.async_fire("test_event2")
|
|
hass.bus.async_fire("test_event3")
|
|
data = await _stream_next_event(resp.content)
|
|
assert data["event_type"] == "test_event3"
|
|
|
|
|
|
async def _stream_next_event(stream):
|
|
"""Read the stream for next event while ignoring ping."""
|
|
while True:
|
|
last_new_line = False
|
|
data = b""
|
|
|
|
while True:
|
|
dat = await stream.read(1)
|
|
if dat == b"\n" and last_new_line:
|
|
break
|
|
data += dat
|
|
last_new_line = dat == b"\n"
|
|
|
|
conv = data.decode("utf-8").strip()[6:]
|
|
|
|
if conv != "ping":
|
|
break
|
|
return json.loads(conv)
|
|
|
|
|
|
def _listen_count(hass: HomeAssistant) -> int:
|
|
"""Return number of event listeners."""
|
|
return sum(hass.bus.async_listeners().values())
|
|
|
|
|
|
async def test_api_error_log(
|
|
hass: HomeAssistant,
|
|
hass_client_no_auth: ClientSessionGenerator,
|
|
hass_access_token: str,
|
|
hass_admin_user: MockUser,
|
|
) -> None:
|
|
"""Test if we can fetch the error log."""
|
|
hass.data[DATA_LOGGING] = "/some/path"
|
|
await async_setup_component(hass, "api", {})
|
|
client = await hass_client_no_auth()
|
|
|
|
resp = await client.get(const.URL_API_ERROR_LOG)
|
|
# Verify auth required
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
with patch(
|
|
"aiohttp.web.FileResponse", return_value=web.Response(text="Hello")
|
|
) as mock_file:
|
|
resp = await client.get(
|
|
const.URL_API_ERROR_LOG,
|
|
headers={"Authorization": f"Bearer {hass_access_token}"},
|
|
)
|
|
|
|
assert len(mock_file.mock_calls) == 1
|
|
assert mock_file.mock_calls[0][1][0] == hass.data[DATA_LOGGING]
|
|
assert resp.status == HTTPStatus.OK
|
|
assert await resp.text() == "Hello"
|
|
|
|
# Verify we require admin user
|
|
hass_admin_user.groups = []
|
|
resp = await client.get(
|
|
const.URL_API_ERROR_LOG,
|
|
headers={"Authorization": f"Bearer {hass_access_token}"},
|
|
)
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
async def test_api_fire_event_context(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_access_token: str
|
|
) -> None:
|
|
"""Test if the API sets right context if we fire an event."""
|
|
test_value = []
|
|
|
|
@ha.callback
|
|
def listener(event):
|
|
"""Record that our event got called."""
|
|
test_value.append(event)
|
|
|
|
hass.bus.async_listen("test.event", listener)
|
|
|
|
await mock_api_client.post(
|
|
"/api/events/test.event",
|
|
headers={"authorization": f"Bearer {hass_access_token}"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
refresh_token = hass.auth.async_validate_access_token(hass_access_token)
|
|
|
|
assert len(test_value) == 1
|
|
assert test_value[0].context.user_id == refresh_token.user.id
|
|
|
|
|
|
async def test_api_call_service_context(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_access_token: str
|
|
) -> None:
|
|
"""Test if the API sets right context if we call a service."""
|
|
calls = async_mock_service(hass, "test_domain", "test_service")
|
|
|
|
await mock_api_client.post(
|
|
"/api/services/test_domain/test_service",
|
|
headers={"authorization": f"Bearer {hass_access_token}"},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
refresh_token = hass.auth.async_validate_access_token(hass_access_token)
|
|
|
|
assert len(calls) == 1
|
|
assert calls[0].context.user_id == refresh_token.user.id
|
|
|
|
|
|
async def test_api_set_state_context(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_access_token: str
|
|
) -> None:
|
|
"""Test if the API sets right context if we set state."""
|
|
await mock_api_client.post(
|
|
"/api/states/light.kitchen",
|
|
json={"state": "on"},
|
|
headers={"authorization": f"Bearer {hass_access_token}"},
|
|
)
|
|
|
|
refresh_token = hass.auth.async_validate_access_token(hass_access_token)
|
|
|
|
state = hass.states.get("light.kitchen")
|
|
assert state.context.user_id == refresh_token.user.id
|
|
|
|
|
|
async def test_event_stream_requires_admin(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_admin_user: MockUser
|
|
) -> None:
|
|
"""Test user needs to be admin to access event stream."""
|
|
hass_admin_user.groups = []
|
|
resp = await mock_api_client.get("/api/stream")
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
async def test_states(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_admin_user: MockUser
|
|
) -> None:
|
|
"""Test fetching all states as admin."""
|
|
hass.states.async_set("test.entity", "hello")
|
|
hass.states.async_set("test.entity2", "hello")
|
|
resp = await mock_api_client.get(const.URL_API_STATES)
|
|
assert resp.status == HTTPStatus.OK
|
|
json = await resp.json()
|
|
assert len(json) == 2
|
|
assert json[0]["entity_id"] == "test.entity"
|
|
assert json[1]["entity_id"] == "test.entity2"
|
|
|
|
|
|
async def test_states_view_filters(
|
|
hass: HomeAssistant,
|
|
hass_read_only_user: MockUser,
|
|
hass_client: ClientSessionGenerator,
|
|
) -> None:
|
|
"""Test filtering only visible states."""
|
|
assert not hass_read_only_user.is_admin
|
|
hass_read_only_user.mock_policy({"entities": {"entity_ids": {"test.entity": True}}})
|
|
await async_setup_component(hass, "api", {})
|
|
read_only_user_credential = Credentials(
|
|
id="mock-read-only-credential-id",
|
|
auth_provider_type="homeassistant",
|
|
auth_provider_id=None,
|
|
data={"username": "readonly"},
|
|
is_new=False,
|
|
)
|
|
await hass.auth.async_link_user(hass_read_only_user, read_only_user_credential)
|
|
|
|
refresh_token = await hass.auth.async_create_refresh_token(
|
|
hass_read_only_user, CLIENT_ID, credential=read_only_user_credential
|
|
)
|
|
token = hass.auth.async_create_access_token(refresh_token)
|
|
mock_api_client = await hass_client(token)
|
|
hass.states.async_set("test.entity", "hello")
|
|
hass.states.async_set("test.not_visible_entity", "invisible")
|
|
resp = await mock_api_client.get(const.URL_API_STATES)
|
|
assert resp.status == HTTPStatus.OK
|
|
json = await resp.json()
|
|
assert len(json) == 1
|
|
assert json[0]["entity_id"] == "test.entity"
|
|
|
|
|
|
async def test_get_entity_state_read_perm(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_admin_user: MockUser
|
|
) -> None:
|
|
"""Test getting a state requires read permission."""
|
|
hass_admin_user.mock_policy({})
|
|
hass_admin_user.groups = []
|
|
assert hass_admin_user.is_admin is False
|
|
resp = await mock_api_client.get("/api/states/light.test")
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
async def test_post_entity_state_admin(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_admin_user: MockUser
|
|
) -> None:
|
|
"""Test updating state requires admin."""
|
|
hass_admin_user.groups = []
|
|
resp = await mock_api_client.post("/api/states/light.test")
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
async def test_delete_entity_state_admin(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_admin_user: MockUser
|
|
) -> None:
|
|
"""Test deleting entity requires admin."""
|
|
hass_admin_user.groups = []
|
|
resp = await mock_api_client.delete("/api/states/light.test")
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
async def test_post_event_admin(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_admin_user: MockUser
|
|
) -> None:
|
|
"""Test sending event requires admin."""
|
|
hass_admin_user.groups = []
|
|
resp = await mock_api_client.post("/api/events/state_changed")
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
async def test_rendering_template_admin(
|
|
hass: HomeAssistant, mock_api_client: TestClient, hass_admin_user: MockUser
|
|
) -> None:
|
|
"""Test rendering a template requires admin."""
|
|
hass_admin_user.groups = []
|
|
resp = await mock_api_client.post(const.URL_API_TEMPLATE)
|
|
assert resp.status == HTTPStatus.UNAUTHORIZED
|
|
|
|
|
|
async def test_api_call_service_not_found(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the API fails 400 if unknown service."""
|
|
resp = await mock_api_client.post("/api/services/test_domain/test_service")
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
async def test_api_call_service_bad_data(
|
|
hass: HomeAssistant, mock_api_client: TestClient
|
|
) -> None:
|
|
"""Test if the API fails 400 if unknown service."""
|
|
test_value = []
|
|
|
|
@ha.callback
|
|
def listener(service_call):
|
|
"""Record that our service got called."""
|
|
test_value.append(1)
|
|
|
|
hass.services.async_register(
|
|
"test_domain", "test_service", listener, schema=vol.Schema({"hello": str})
|
|
)
|
|
|
|
resp = await mock_api_client.post(
|
|
"/api/services/test_domain/test_service", json={"hello": 5}
|
|
)
|
|
assert resp.status == HTTPStatus.BAD_REQUEST
|
|
|
|
|
|
async def test_api_status(hass: HomeAssistant, mock_api_client: TestClient) -> None:
|
|
"""Test getting the api status."""
|
|
resp = await mock_api_client.get("/api/")
|
|
assert resp.status == HTTPStatus.OK
|
|
json = await resp.json()
|
|
assert json["message"] == "API running."
|
|
|
|
|
|
async def test_api_core_state(hass: HomeAssistant, mock_api_client: TestClient) -> None:
|
|
"""Test getting core status."""
|
|
resp = await mock_api_client.get("/api/core/state")
|
|
assert resp.status == HTTPStatus.OK
|
|
json = await resp.json()
|
|
assert json == {
|
|
"state": "RUNNING",
|
|
"recorder_state": {"migration_in_progress": False, "migration_is_live": False},
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("migration_in_progress", "migration_is_live"),
|
|
[
|
|
(False, False),
|
|
(False, True),
|
|
(True, False),
|
|
(True, True),
|
|
],
|
|
)
|
|
async def test_api_core_state_recorder_migrating(
|
|
hass: HomeAssistant,
|
|
mock_api_client: TestClient,
|
|
migration_in_progress: bool,
|
|
migration_is_live: bool,
|
|
) -> None:
|
|
"""Test getting core status."""
|
|
with (
|
|
patch(
|
|
"homeassistant.helpers.recorder.async_migration_in_progress",
|
|
return_value=migration_in_progress,
|
|
),
|
|
patch(
|
|
"homeassistant.helpers.recorder.async_migration_is_live",
|
|
return_value=migration_is_live,
|
|
),
|
|
):
|
|
resp = await mock_api_client.get("/api/core/state")
|
|
assert resp.status == HTTPStatus.OK
|
|
json = await resp.json()
|
|
expected_recorder_state = {
|
|
"migration_in_progress": migration_in_progress,
|
|
"migration_is_live": migration_is_live,
|
|
}
|
|
assert json == {"state": "RUNNING", "recorder_state": expected_recorder_state}
|