Skip to content

Commit

Permalink
Extend generate category data tests (#3957)
Browse files Browse the repository at this point in the history
* Add tests to veriry paths when there are existing content

* Use 0 as default

* Add release exception testing

* simplify snapshots

* Add status test

* Limit gen error test to integration

* Done combine old tests
  • Loading branch information
ludeeus authored Aug 12, 2024
1 parent cc53265 commit 77d7e36
Show file tree
Hide file tree
Showing 24 changed files with 965 additions and 21 deletions.
63 changes: 42 additions & 21 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@


class CategoryTestData(TypedDict):
id: str
repository: str
category: str
files: list[str]
Expand All @@ -69,42 +70,47 @@ class CategoryTestData(TypedDict):

_CATEGORY_TEST_DATA: tuple[CategoryTestData] = (
CategoryTestData(
id="1296265",
category=HacsCategory.APPDAEMON,
repository="hacs-test-org/appdaemon-basic",
id="appdaemon",
files=["__init__.py", "example.py"],
version_base="1.0.0",
version_update="2.0.0",
),
CategoryTestData(
id="1296269",
category=HacsCategory.INTEGRATION,
repository="hacs-test-org/integration-basic",
files=["__init__.py", "manifest.json", "module/__init__.py"],
version_base="1.0.0",
version_update="2.0.0",
),
CategoryTestData(
id="1296267",
category=HacsCategory.PLUGIN,
repository="hacs-test-org/plugin-basic",
files=["example.js", "example.js.gz"],
version_base="1.0.0",
version_update="2.0.0",
),
CategoryTestData(
id="1296262",
category=HacsCategory.PYTHON_SCRIPT,
repository="hacs-test-org/python_script-basic",
files=["example.py"],
version_base="1.0.0",
version_update="2.0.0",
),
CategoryTestData(
id="1296268",
category=HacsCategory.TEMPLATE,
repository="hacs-test-org/template-basic",
files=["example.jinja"],
version_base="1.0.0",
version_update="2.0.0",
),
CategoryTestData(
id="1296266",
category=HacsCategory.THEME,
repository="hacs-test-org/theme-basic",
files=["example.yaml"],
Expand All @@ -117,6 +123,7 @@ class CategoryTestData(TypedDict):
def category_test_data_parametrized(
*,
xfail_categories: list[HacsCategory] | None = None,
categories: Iterable[HacsCategory] = [entry["category"] for entry in _CATEGORY_TEST_DATA],
**kwargs,
):
return (
Expand All @@ -128,6 +135,7 @@ def category_test_data_parametrized(
id=entry["repository"],
)
for entry in _CATEGORY_TEST_DATA
if entry["category"] in categories
)


Expand Down Expand Up @@ -176,7 +184,8 @@ def _sort_list(entry):
returndata[key] = value
elif isinstance(value, dict):
returndata[key] = recursive_remove_key(
{k: value[k] for k in sorted(value.keys())}, to_remove,
{k: value[k] for k in sorted(value.keys())},
to_remove,
)
elif isinstance(value, (list, set)):
returndata[key] = [recursive_remove_key(item, to_remove) for item in _sort_list(value)]
Expand All @@ -199,7 +208,8 @@ def fixture(filename, asjson=True):
return json_func.loads(fptr.read())
return fptr.read()
except OSError as err:
raise OSError(f"Missing fixture for {path.split('fixtures/')[1]}") from err
raise OSError(f"Missing fixture for {
path.split('fixtures/')[1]}") from err


def dummy_repository_base(hacs, repository=None):
Expand Down Expand Up @@ -590,18 +600,22 @@ async def mock_remove(store):
"""Remove data."""
data.pop(store.key, None)

with patch(
"homeassistant.helpers.storage.Store._async_load",
side_effect=mock_async_load,
autospec=True,
), patch(
"homeassistant.helpers.storage.Store._write_data",
side_effect=mock_write_data,
autospec=True,
), patch(
"homeassistant.helpers.storage.Store.async_remove",
side_effect=mock_remove,
autospec=True,
with (
patch(
"homeassistant.helpers.storage.Store._async_load",
side_effect=mock_async_load,
autospec=True,
),
patch(
"homeassistant.helpers.storage.Store._write_data",
side_effect=mock_write_data,
autospec=True,
),
patch(
"homeassistant.helpers.storage.Store.async_remove",
side_effect=mock_remove,
autospec=True,
),
):
yield data

Expand Down Expand Up @@ -665,7 +679,9 @@ async def _async_close_websession(event: ha.Event) -> None:
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_close_websession)

self.client = await clientsession.ws_connect(
"ws://localhost:8123/api/websocket", timeout=1, autoclose=True,
"ws://localhost:8123/api/websocket",
timeout=1,
autoclose=True,
)
auth_response = await self.client.receive_json()
assert auth_response["type"] == "auth_required"
Expand Down Expand Up @@ -735,7 +751,8 @@ def add(self, url: str, response: MockedResponse) -> None:
def get(self, url: str, *args, **kwargs) -> MockedResponse:
data = {"url": url, "args": list(args), "kwargs": kwargs}
if (request := REQUEST_CONTEXT.get()) is not None:
data["_test_caller"] = f"{request.node.location[0]}::{request.node.name}"
data["_test_caller"] = f"{
request.node.location[0]}::{request.node.name}"
data["_uses_setup_integration"] = request.node.name != "test_integration_setup" and (
"setup_integration" in request.fixturenames or "hacs" in request.fixturenames
)
Expand All @@ -760,7 +777,8 @@ async def _request(self, method: str, str_or_url: StrOrURL, *args, **kwargs):
return resp

url = URL(str_or_url)
fixture_file = f"fixtures/proxy/{url.host}{url.path}{'.json' if url.host in ('api.github.com', 'data-v2.hacs.xyz') and not url.path.endswith('.json') else ''}"
fixture_file = f"fixtures/proxy/{url.host}{url.path}{'.json' if url.host in (
'api.github.com', 'data-v2.hacs.xyz') and not url.path.endswith('.json') else ''}"
fp = os.path.join(
os.path.dirname(__file__),
fixture_file,
Expand Down Expand Up @@ -813,7 +831,8 @@ async def _request(method: str, str_or_url: StrOrURL, *args, **kwargs):
return resp

url = URL(str_or_url)
fixture_file = f"fixtures/proxy/{url.host}{url.path}{'.json' if url.host in ('api.github.com', 'data-v2.hacs.xyz') and not url.path.endswith('.json') else ''}"
fixture_file = f"fixtures/proxy/{url.host}{url.path}{'.json' if url.host in (
'api.github.com', 'data-v2.hacs.xyz') and not url.path.endswith('.json') else ''}"
fp = os.path.join(
os.path.dirname(__file__),
fixture_file,
Expand Down Expand Up @@ -851,7 +870,8 @@ async def json(**kwargs):


def create_config_entry(
data: dict[str, Any] = None, options: dict[str, Any] = None,
data: dict[str, Any] = None,
options: dict[str, Any] = None,
) -> MockConfigEntry:
return MockConfigEntry(
version=1,
Expand All @@ -868,7 +888,8 @@ def create_config_entry(
async def setup_integration(hass: ha.HomeAssistant, config_entry: MockConfigEntry) -> None:
mock_session = await client_session_proxy(hass)
with patch(
"homeassistant.helpers.aiohttp_client.async_get_clientsession", return_value=mock_session,
"homeassistant.helpers.aiohttp_client.async_get_clientsession",
return_value=mock_session,
):
hass.data.pop("custom_components", None)
config_entry.add_to_hass(hass)
Expand Down
143 changes: 143 additions & 0 deletions tests/scripts/data/test_generate_category_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"""Test generate category data."""

import asyncio
import json
from typing import Any

from homeassistant.core import HomeAssistant
import pytest
Expand All @@ -25,6 +28,24 @@
}


def get_generated_category_data(category: str) -> dict[str, Any]:
"""Get the generated data."""
compare = {}

with open(f"{OUTPUT_DIR}/{category}/data.json", encoding="utf-8") as file:
compare["data"] = recursive_remove_key(
json.loads(file.read()), ("last_fetched",))

with open(f"{OUTPUT_DIR}/{category}/repositories.json", encoding="utf-8") as file:
compare["repositories"] = recursive_remove_key(
json.loads(file.read()), ())

with open(f"{OUTPUT_DIR}/summary.json", encoding="utf-8") as file:
compare["summary"] = recursive_remove_key(json.loads(file.read()), ())

return compare


@pytest.mark.parametrize("category_test_data", category_test_data_parametrized())
async def test_generate_category_data_single_repository(
hass: HomeAssistant,
Expand Down Expand Up @@ -105,3 +126,125 @@ async def test_generate_category_data(
f"scripts/data/generate_category_data/{
category_test_data['category']}/summary.json",
)


@pytest.mark.parametrize("category_test_data", category_test_data_parametrized())
async def test_generate_category_data_with_prior_content(
hass: HomeAssistant,
response_mocker: ResponseMocker,
snapshots: SnapshotFixture,
category_test_data: CategoryTestData,
):
"""Test behaviour with prior content."""
category_data = {
"integration": {
"domain": "example",
"manifest": {"name": "Proxy manifest"},
"manifest_name": "Proxy manifest",
}
}
response_mocker.add(
f"https://data-v2.hacs.xyz/{category_test_data['category']}/data.json",
MockedResponse(
content={
category_test_data["id"]: {
"description": "This your first repo!",
"downloads": 0,
"etag_repository": "321",
"full_name": category_test_data["repository"],
"last_updated": "2011-01-26T19:06:43Z",
"last_version": category_test_data["version_base"],
"stargazers_count": 0,
"topics": ["api", "atom", "electron", "octocat"],
**category_data.get(category_test_data["category"], {}),
}
}
),
)

await generate_category_data(category_test_data["category"])

snapshots.assert_match(
safe_json_dumps(get_generated_category_data(
category_test_data["category"])),
f"scripts/data/test_generate_category_data_with_prior_content/{
category_test_data['category']}.json",
)


@pytest.mark.parametrize(
"category_test_data", category_test_data_parametrized(
categories=["integration"])
)
@pytest.mark.parametrize("error", (asyncio.CancelledError, asyncio.TimeoutError, Exception("base")))
async def test_generate_category_data_errors_release(
hass: HomeAssistant,
response_mocker: ResponseMocker,
snapshots: SnapshotFixture,
category_test_data: CategoryTestData,
error: Exception,
request: pytest.FixtureRequest,
):
"""Test behaviour if single repository."""
response_mocker.add(
f"https://api.github.com/repos/{
category_test_data['repository']}/releases/latest",
MockedResponse(exception=error),
)
await generate_category_data(category_test_data["category"])

snapshots.assert_match(
safe_json_dumps(get_generated_category_data(
category_test_data["category"])),
f"scripts/data/test_generate_category_data_errors_release/{
category_test_data['category']}/{request.node.callspec.id.split("-")[0]}.json",
)


@pytest.mark.parametrize(
"category_test_data", category_test_data_parametrized(
categories=["integration"])
)
@pytest.mark.parametrize("status", (304, 404))
async def test_generate_category_data_error_status_release(
hass: HomeAssistant,
response_mocker: ResponseMocker,
snapshots: SnapshotFixture,
category_test_data: CategoryTestData,
status: int,
):
"""Test behaviour with error status and existing content."""
response_mocker.add(
f"https://data-v2.hacs.xyz/{category_test_data['category']}/data.json",
MockedResponse(
content={
category_test_data["id"]: {
"description": "This your first repo!",
"downloads": 0,
"etag_repository": "321",
"full_name": category_test_data["repository"],
"last_updated": "2011-01-26T19:06:43Z",
"last_version": category_test_data["version_base"],
"stargazers_count": 0,
"topics": ["api", "atom", "electron", "octocat"],
"domain": "example",
"manifest": {"name": "Proxy manifest"},
"manifest_name": "Proxy manifest",
}
}
),
)

response_mocker.add(
f"https://api.github.com/repos/{
category_test_data['repository']}/releases/latest",
MockedResponse(status=status, content={}),
)
await generate_category_data(category_test_data["category"])

snapshots.assert_match(
safe_json_dumps(get_generated_category_data(
category_test_data["category"])),
f"scripts/data/test_generate_category_data_error_status_release/{
category_test_data['category']}/{status}.json",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"tests/scripts/data/test_generate_category_data.py::test_generate_category_data_error_status_release[304-hacs-test-org/integration-basic]": {
"https://api.github.com/rate_limit": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/contents/custom_components/example/manifest.json": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/contents/hacs.json": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/git/trees/1.0.0": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/releases": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/git/trees/1.0.0": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/releases/latest": 1,
"https://api.github.com/repos/hacs/default/contents/integration": 1,
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/branches/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1,
"https://data-v2.hacs.xyz/integration/data.json": 1,
"https://data-v2.hacs.xyz/removed/repositories.json": 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"tests/scripts/data/test_generate_category_data.py::test_generate_category_data_error_status_release[404-hacs-test-org/integration-basic]": {
"https://api.github.com/rate_limit": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/contents/custom_components/example/manifest.json": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/contents/hacs.json": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/git/trees/1.0.0": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic-custom/releases": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/git/trees/1.0.0": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/releases": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/releases/latest": 1,
"https://api.github.com/repos/hacs/default/contents/integration": 1,
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/branches/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1,
"https://data-v2.hacs.xyz/integration/data.json": 1,
"https://data-v2.hacs.xyz/removed/repositories.json": 1
}
}
Loading

0 comments on commit 77d7e36

Please sign in to comment.