mirror of
https://github.com/home-assistant/core.git
synced 2026-10-11 00:41:55 -04:00
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
103 lines
3.3 KiB
Python
103 lines
3.3 KiB
Python
"""Tests for the KNX ETS project store.
|
|
|
|
`KNXProject` keeps the parsed project behind a lazy cache: `Store.async_load`
|
|
skips its own cache for keys containing "/", so every read would otherwise
|
|
re-parse the multi-megabyte file. Whether the full project is held in memory is
|
|
left to whoever asks for it, so setup does not populate the cache.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components.knx.const import KNX_MODULE_KEY
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from .conftest import KNXTestKit
|
|
|
|
from tests.typing import WebSocketGenerator
|
|
|
|
|
|
@pytest.mark.usefixtures("load_knxproj")
|
|
async def test_project_is_read_once(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|
"""A second read must not re-parse the store file."""
|
|
await knx.setup_integration()
|
|
project = hass.data[KNX_MODULE_KEY].project
|
|
|
|
assert await project.get_knxproject() is await project.get_knxproject()
|
|
|
|
|
|
@pytest.mark.usefixtures("load_knxproj")
|
|
async def test_setup_does_not_hold_the_full_project(
|
|
hass: HomeAssistant, knx: KNXTestKit
|
|
) -> None:
|
|
"""Setup takes what it needs and leaves the rest collectable.
|
|
|
|
Holding the whole project would cost memory on every install that has one,
|
|
including those that never use the LLM API or the project WebSocket command.
|
|
"""
|
|
await knx.setup_integration()
|
|
project = hass.data[KNX_MODULE_KEY].project
|
|
|
|
assert project.loaded is True
|
|
assert project._project is None
|
|
|
|
|
|
@pytest.mark.usefixtures("load_knxproj")
|
|
async def test_cache_not_resurrected_by_concurrent_load(
|
|
hass: HomeAssistant, knx: KNXTestKit
|
|
) -> None:
|
|
"""A load in flight must not restore a project that was removed meanwhile."""
|
|
await knx.setup_integration()
|
|
project = hass.data[KNX_MODULE_KEY].project
|
|
loading = asyncio.Event()
|
|
original_load = project._store.async_load
|
|
|
|
async def slow_load() -> Any:
|
|
loading.set()
|
|
await asyncio.sleep(0)
|
|
return await original_load()
|
|
|
|
with patch.object(project._store, "async_load", slow_load):
|
|
load = hass.async_create_task(project.get_knxproject())
|
|
await loading.wait()
|
|
await project.remove_project_file()
|
|
await load
|
|
|
|
assert project._project is None
|
|
|
|
|
|
@pytest.mark.usefixtures("load_knxproj")
|
|
async def test_cache_is_refreshed_on_upload(
|
|
hass: HomeAssistant,
|
|
knx: KNXTestKit,
|
|
hass_ws_client: WebSocketGenerator,
|
|
project_data: dict[str, Any],
|
|
) -> None:
|
|
"""An upload must not leave a warm cache serving the old project."""
|
|
await knx.setup_integration()
|
|
project = hass.data[KNX_MODULE_KEY].project
|
|
assert await project.get_knxproject() is not None
|
|
|
|
new_data = {**project_data, "info": {**project_data["info"], "name": "Reuploaded"}}
|
|
client = await hass_ws_client(hass)
|
|
await client.send_json_auto_id(
|
|
{"type": "knx/project_file_process", "file_id": "1234", "password": ""}
|
|
)
|
|
with (
|
|
patch(
|
|
"homeassistant.components.knx.project.process_uploaded_file"
|
|
) as file_upload_mock,
|
|
patch("xknxproject.XKNXProj.parse", return_value=new_data),
|
|
):
|
|
file_upload_mock.return_value.__enter__.return_value = ""
|
|
res = await client.receive_json()
|
|
|
|
assert res["success"], res
|
|
knxproject = await project.get_knxproject()
|
|
assert knxproject["info"]["name"] == "Reuploaded"
|