diff --git a/custom_components/hacs/base.py b/custom_components/hacs/base.py index 874fe5b0024..26b90b695cd 100644 --- a/custom_components/hacs/base.py +++ b/custom_components/hacs/base.py @@ -294,6 +294,59 @@ def set_repository_id(self, repository: HacsRepository, repo_id: str): repository.data.id = repo_id self.register(repository) + def reconcile_repository_id( + self, + repository: HacsRepository, + repo_id: str, + matching_repositories: list[HacsRepository], + ) -> HacsRepository: + """Reconcile a repository with its current ID.""" + registered_repository = self._repositories_by_id.get(repo_id) + if ( + registered_repository + and registered_repository.data.full_name_lower != repository.data.full_name_lower + ): + raise ValueError( + f"The repo id {repo_id} is already set to " + f"{registered_repository.data.full_name_lower}" + ) + + was_default = any( + self.is_default(str(candidate.data.id)) for candidate in matching_repositories + ) + + installed_repositories = [ + candidate for candidate in matching_repositories if candidate.data.installed + ] + if registered_repository and registered_repository.data.installed: + repository = registered_repository + elif installed_repositories: + repository = min( + installed_repositories, + key=lambda candidate: int(candidate.data.id), + ) + elif registered_repository: + repository = registered_repository + else: + repository = min( + matching_repositories, + key=lambda candidate: int(candidate.data.id), + ) + + for candidate in matching_repositories: + if candidate is not repository: + self.unregister(candidate) + + if str(repository.data.id) != repo_id: + self.unregister(repository) + repository.data.id = repo_id + self.register(repository) + + self._repositories_by_full_name[repository.data.full_name_lower] = repository + if was_default: + self.mark_default(repository) + return repository + def is_default(self, repository_id: str | None = None) -> bool: """Check if a repository is default.""" if not repository_id: @@ -857,6 +910,12 @@ async def async_get_category_repositories_experimental(self, category: str) -> N self.log.error("Could not update %s - %s", category, exception) return + repositories_by_full_name: dict[str, list[HacsRepository]] = {} + for repository in self.repositories.list_all: + repositories_by_full_name.setdefault(repository.data.full_name_lower, []).append( + repository + ) + await self.data.register_unknown_repositories(category_data, category) for repo_id, repo_data in category_data.items(): @@ -868,7 +927,16 @@ async def async_get_category_repositories_experimental(self, category: str) -> N if repo_name in self.common.archived_repositories: continue if repository := self.repositories.get_by_full_name(repo_name): - self.repositories.set_repository_id(repository, repo_id) + matching_repositories = repositories_by_full_name.get( + repository.data.full_name_lower, + [repository], + ) + if str(repository.data.id) != repo_id or len(matching_repositories) > 1: + repository = self.repositories.reconcile_repository_id( + repository, + repo_id, + matching_repositories, + ) self.repositories.mark_default(repository) if repository.data.last_fetched is None or ( repository.data.last_fetched.timestamp() < repo_data["last_fetched"] diff --git a/custom_components/hacs/utils/data.py b/custom_components/hacs/utils/data.py index f540272e62d..8414a5d3e5b 100644 --- a/custom_components/hacs/utils/data.py +++ b/custom_components/hacs/utils/data.py @@ -237,32 +237,37 @@ async def register_unknown_repositories( ): """Registry any unknown repositories.""" for repo_idx, (entry, repo_data) in enumerate(repositories.items()): - # async_register_repository is awaited in a loop - # since its unlikely to ever suspend at startup + if repo_idx % 100 == 0: + # yield to avoid blocking the event loop + await asyncio.sleep(0) + + repository_category = repo_data.get("category", category) if ( entry == "0" - or repo_data.get("category", category) is None + or repository_category is None or self.hacs.repositories.is_registered(repository_id=entry) ): continue + + if category is not None and (repository_full_name := repo_data.get("full_name")): + if renamed := self.hacs.common.renamed_repositories.get(repository_full_name): + repository_full_name = renamed + if self.hacs.repositories.get_by_full_name(repository_full_name): + continue + await self.hacs.async_register_repository( repository_full_name=repo_data["full_name"], - category=repo_data.get("category", category), + category=repository_category, check=False, repository_id=entry, ) - if repo_idx % 100 == 0: - # yield to avoid blocking the event loop - await asyncio.sleep(0) @callback def async_restore_repository(self, entry: str, repository_data: dict[str, Any]): """Restore repository.""" - repository: HacsRepository | None = None - if full_name := repository_data.get("full_name"): + repository: HacsRepository | None = self.hacs.repositories.get_by_id(entry) + if not repository and (full_name := repository_data.get("full_name")): repository = self.hacs.repositories.get_by_full_name(full_name) - if not repository: - repository = self.hacs.repositories.get_by_id(entry) if not repository: return diff --git a/tests/hacsbase/test_hacsbase_data.py b/tests/hacsbase/test_hacsbase_data.py index efe4a7815b3..e19e39bddcc 100644 --- a/tests/hacsbase/test_hacsbase_data.py +++ b/tests/hacsbase/test_hacsbase_data.py @@ -1,10 +1,20 @@ """Data Test Suite.""" -from unittest.mock import patch +from datetime import UTC, datetime +from unittest.mock import AsyncMock, patch + +import pytest from custom_components.hacs.base import HacsRepositories -from custom_components.hacs.enums import HacsGitHubRepo +from custom_components.hacs.enums import HacsCategory, HacsGitHubRepo +from custom_components.hacs.repositories import HacsIntegrationRepository from custom_components.hacs.utils.data import HacsData +KNOWN_REPOSITORY_COUNT = 1000 +REPOSITORY_FULL_NAME_CONFLICT = "other/repository" +REPOSITORY_ID_CURRENT = "10" +REPOSITORY_ID_NEXT = "20" +REPOSITORY_ID_STALE = "9" + async def test_hacs_data_async_write1(hacs, repository): data = HacsData(hacs) @@ -67,3 +77,353 @@ async def _mocked_loads(hass, key): await data.async_write() assert mock_async_save_to_store.called assert "Loading base repository information" not in caplog.text + + +@pytest.mark.parametrize( + "stored_ids", + [ + (REPOSITORY_ID_STALE,), + (REPOSITORY_ID_CURRENT, REPOSITORY_ID_STALE), + (REPOSITORY_ID_STALE, REPOSITORY_ID_CURRENT), + ], + ids=["stale-only", "current-then-stale", "stale-then-current"], +) +async def test_reconcile_recreated_repository_id( + hacs, repository_integration, caplog, stored_ids +): + data = HacsData(hacs) + hacs.repositories = HacsRepositories() + repository_full_name = repository_integration.data.full_name + + async def _mocked_loads(hass, key): + if key == "repositories": + repositories = {} + for repository_id in stored_ids: + repositories[repository_id] = { + "category": "integration", + "full_name": repository_full_name, + } + if repository_id == REPOSITORY_ID_STALE: + repositories[repository_id]["installed"] = True + return repositories + return {} + + with patch( + "custom_components.hacs.utils.data.async_load_from_store", + side_effect=_mocked_loads, + ): + assert await data.restore() + + stored_repository = hacs.repositories.get_by_id(REPOSITORY_ID_STALE) + assert stored_repository is not None + hacs.repositories.mark_default(stored_repository) + assert "duplicate IDs" not in caplog.text + + category_data = { + REPOSITORY_ID_CURRENT: { + "full_name": repository_full_name, + "last_fetched": 0.0, + } + } + with patch.object(hacs.data_client, "get_data", return_value=category_data): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + repository = hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is None + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is stored_repository + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is repository + assert hacs.repositories.get_by_full_name(repository_full_name) is repository + assert hacs.repositories.list_all == [stored_repository] + assert hacs.repositories.is_default(REPOSITORY_ID_CURRENT) + assert stored_repository.data.installed is True + + +async def test_recreated_repository_id_persists_across_restart( + hacs, repository_integration, caplog +): + data = HacsData(hacs) + hacs.repositories = HacsRepositories() + repository_full_name = repository_integration.data.full_name + stored_repositories = { + REPOSITORY_ID_STALE: { + "category": "integration", + "full_name": repository_full_name, + "installed": True, + "show_beta": True, + "version_installed": "1.0.0", + } + } + + async def _load_stale_repository(hass, key): + if key == "repositories": + return stored_repositories + return {} + + with patch( + "custom_components.hacs.utils.data.async_load_from_store", + side_effect=_load_stale_repository, + ): + assert await data.restore() + + stale_repository = hacs.repositories.get_by_id(REPOSITORY_ID_STALE) + assert stale_repository is not None + assert hacs.repositories.list_all == [stale_repository] + + category_data = { + REPOSITORY_ID_CURRENT: { + "full_name": repository_full_name, + "last_fetched": 0.0, + } + } + with patch.object(hacs.data_client, "get_data", return_value=category_data): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is None + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is stale_repository + assert hacs.repositories.list_all == [stale_repository] + assert hacs.repositories.is_default(REPOSITORY_ID_CURRENT) + assert stale_repository.data.installed is True + assert "duplicate IDs" not in caplog.text + + with patch( + "custom_components.hacs.utils.data.async_save_to_store", + new_callable=AsyncMock, + ) as async_save_to_store: + await data.async_write() + + persisted_repositories = next( + call.args[2] + for call in async_save_to_store.await_args_list + if call.args[1] == "repositories" + ) + assert set(persisted_repositories) == {REPOSITORY_ID_CURRENT} + assert REPOSITORY_ID_STALE not in persisted_repositories + assert persisted_repositories[REPOSITORY_ID_CURRENT]["installed"] is True + assert persisted_repositories[REPOSITORY_ID_CURRENT]["show_beta"] is True + assert ( + persisted_repositories[REPOSITORY_ID_CURRENT]["version_installed"] == "1.0.0" + ) + + hacs.repositories = HacsRepositories() + restarted_data = HacsData(hacs) + + async def _load_persisted_repository(hass, key): + if key == "repositories": + return persisted_repositories + return {} + + caplog.clear() + with patch( + "custom_components.hacs.utils.data.async_load_from_store", + side_effect=_load_persisted_repository, + ): + assert await restarted_data.restore() + + restarted_repository = hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) + assert restarted_repository is not None + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is None + assert hacs.repositories.list_all == [restarted_repository] + assert restarted_repository.data.installed is True + assert restarted_repository.data.show_beta is True + assert restarted_repository.data.installed_version == "1.0.0" + + with patch.object(hacs.data_client, "get_data", return_value=category_data): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is restarted_repository + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is None + assert hacs.repositories.list_all == [restarted_repository] + assert hacs.repositories.is_default(REPOSITORY_ID_CURRENT) + assert "duplicate IDs" not in caplog.text + + +async def test_reconcile_repository_id_rejects_different_slug( + hacs, repository_integration +): + hacs.repositories = HacsRepositories() + repository_integration.data.id = REPOSITORY_ID_STALE + repository_integration.data.installed = True + hacs.repositories.register(repository_integration) + hacs.repositories.mark_default(repository_integration) + await hacs.async_register_repository( + repository_full_name=REPOSITORY_FULL_NAME_CONFLICT, + category=HacsCategory.INTEGRATION, + check=False, + repository_id=REPOSITORY_ID_CURRENT, + ) + conflicting_repository = hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) + assert conflicting_repository is not None + + category_data = { + REPOSITORY_ID_CURRENT: { + "full_name": repository_integration.data.full_name, + } + } + with ( + patch.object(hacs.data_client, "get_data", return_value=category_data), + pytest.raises(ValueError, match=f"already set to {REPOSITORY_FULL_NAME_CONFLICT}"), + ): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is repository_integration + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is conflicting_repository + assert ( + hacs.repositories.get_by_full_name(repository_integration.data.full_name) + is repository_integration + ) + assert ( + hacs.repositories.get_by_full_name(REPOSITORY_FULL_NAME_CONFLICT) + is conflicting_repository + ) + assert repository_integration.data.installed is True + assert hacs.repositories.is_default(REPOSITORY_ID_STALE) + + +@pytest.mark.parametrize("excluded", ["removed", "archived"]) +async def test_excluded_repository_is_not_reconciled( + hacs, repository_integration, excluded +): + hacs.repositories = HacsRepositories() + repository_integration.data.id = REPOSITORY_ID_STALE + repository_integration.data.installed = True + hacs.repositories.register(repository_integration) + hacs.repositories.mark_default(repository_integration) + if excluded == "removed": + hacs.repositories.removed_repository(repository_integration.data.full_name) + else: + hacs.common.archived_repositories.add(repository_integration.data.full_name) + + category_data = { + REPOSITORY_ID_CURRENT: { + "full_name": repository_integration.data.full_name, + } + } + with patch.object(hacs.data_client, "get_data", return_value=category_data): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is repository_integration + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is None + assert hacs.repositories.is_default(REPOSITORY_ID_STALE) + + +async def test_known_catalog_merge_does_not_reconcile(hacs): + hacs.repositories = HacsRepositories() + category_data = {} + last_fetched = datetime.now(UTC) + for index in range(KNOWN_REPOSITORY_COUNT): + repository_id = str(index + KNOWN_REPOSITORY_COUNT) + repository = HacsIntegrationRepository(hacs, f"test/repository-{index}") + repository.data.id = repository_id + repository.data.last_fetched = last_fetched + hacs.repositories.register(repository) + category_data[repository_id] = { + "full_name": repository.data.full_name, + "last_fetched": 0.0, + } + + with ( + patch.object(hacs.data_client, "get_data", return_value=category_data), + patch.object( + hacs.repositories, + "reconcile_repository_id", + wraps=hacs.repositories.reconcile_repository_id, + ) as reconcile_repository_id, + patch( + "custom_components.hacs.utils.data.asyncio.sleep", + new_callable=AsyncMock, + ) as sleep, + ): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert reconcile_repository_id.call_count == 0 + assert sleep.await_count == KNOWN_REPOSITORY_COUNT // 100 + assert len(hacs.repositories.list_all) == KNOWN_REPOSITORY_COUNT + + +async def test_known_stored_repository_without_full_name(hacs, repository_integration): + data = HacsData(hacs) + hacs.repositories = HacsRepositories() + repository_integration.data.id = REPOSITORY_ID_CURRENT + hacs.repositories.register(repository_integration) + + await data.register_unknown_repositories( + { + REPOSITORY_ID_CURRENT: { + "category": HacsCategory.INTEGRATION, + } + } + ) + + +@pytest.mark.parametrize("current_registered_last", [False, True]) +async def test_multiple_installed_repositories_prefer_current_id( + hacs, repository_integration, current_registered_last +): + hacs.repositories = HacsRepositories() + repository_integration.data.id = REPOSITORY_ID_CURRENT + repository_integration.data.installed = True + stale_repository = HacsIntegrationRepository( + hacs, repository_integration.data.full_name + ) + stale_repository.data.id = REPOSITORY_ID_STALE + stale_repository.data.installed = True + repositories = ( + (stale_repository, repository_integration) + if current_registered_last + else (repository_integration, stale_repository) + ) + for repository in repositories: + hacs.repositories.register(repository) + hacs.repositories.mark_default(repository) + + category_data = { + REPOSITORY_ID_CURRENT: { + "full_name": repository_integration.data.full_name, + "last_fetched": 0.0, + } + } + with patch.object(hacs.data_client, "get_data", return_value=category_data): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is repository_integration + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is None + assert hacs.repositories.list_all == [repository_integration] + assert hacs.repositories.is_default(REPOSITORY_ID_CURRENT) + + +@pytest.mark.parametrize("stale_registered_last", [False, True]) +async def test_multiple_installed_repositories_use_numeric_id_order( + hacs, repository_integration, stale_registered_last +): + hacs.repositories = HacsRepositories() + repository_integration.data.id = REPOSITORY_ID_CURRENT + repository_integration.data.installed = True + stale_repository = HacsIntegrationRepository( + hacs, repository_integration.data.full_name + ) + stale_repository.data.id = REPOSITORY_ID_STALE + stale_repository.data.installed = True + repositories = ( + (repository_integration, stale_repository) + if stale_registered_last + else (stale_repository, repository_integration) + ) + for repository in repositories: + hacs.repositories.register(repository) + hacs.repositories.mark_default(repository) + + category_data = { + REPOSITORY_ID_NEXT: { + "full_name": repository_integration.data.full_name, + "last_fetched": 0.0, + } + } + with patch.object(hacs.data_client, "get_data", return_value=category_data): + await hacs.async_get_category_repositories_experimental(HacsCategory.INTEGRATION) + + assert hacs.repositories.get_by_id(REPOSITORY_ID_NEXT) is stale_repository + assert hacs.repositories.get_by_id(REPOSITORY_ID_STALE) is None + assert hacs.repositories.get_by_id(REPOSITORY_ID_CURRENT) is None + assert hacs.repositories.list_all == [stale_repository] + assert hacs.repositories.is_default(REPOSITORY_ID_NEXT) diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-excluded-repository-is-not-reconciled-archived.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-excluded-repository-is-not-reconciled-archived.json new file mode 100644 index 00000000000..a98127ed04d --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-excluded-repository-is-not-reconciled-archived.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_excluded_repository_is_not_reconciled[archived]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-excluded-repository-is-not-reconciled-removed.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-excluded-repository-is-not-reconciled-removed.json new file mode 100644 index 00000000000..fd12b0b176e --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-excluded-repository-is-not-reconciled-removed.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_excluded_repository_is_not_reconciled[removed]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-known-catalog-merge-does-not-reconcile.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-known-catalog-merge-does-not-reconcile.json new file mode 100644 index 00000000000..d3ce3e9a047 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-known-catalog-merge-does-not-reconcile.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_known_catalog_merge_does_not_reconcile": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-known-stored-repository-without-full-name.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-known-stored-repository-without-full-name.json new file mode 100644 index 00000000000..a3bf8fd955b --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-known-stored-repository-without-full-name.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_known_stored_repository_without_full_name": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-prefer-current-id-false.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-prefer-current-id-false.json new file mode 100644 index 00000000000..c6522acdc63 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-prefer-current-id-false.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_multiple_installed_repositories_prefer_current_id[False]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-prefer-current-id-true.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-prefer-current-id-true.json new file mode 100644 index 00000000000..0994886d879 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-prefer-current-id-true.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_multiple_installed_repositories_prefer_current_id[True]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-use-numeric-id-order-false.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-use-numeric-id-order-false.json new file mode 100644 index 00000000000..e31c1830fb5 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-use-numeric-id-order-false.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_multiple_installed_repositories_use_numeric_id_order[False]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-use-numeric-id-order-true.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-use-numeric-id-order-true.json new file mode 100644 index 00000000000..1377060e806 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-multiple-installed-repositories-use-numeric-id-order-true.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_multiple_installed_repositories_use_numeric_id_order[True]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-current-then-stale.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-current-then-stale.json new file mode 100644 index 00000000000..a136aa4cc05 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-current-then-stale.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_reconcile_recreated_repository_id[current-then-stale]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-stale-only.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-stale-only.json new file mode 100644 index 00000000000..12f200223b6 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-stale-only.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_reconcile_recreated_repository_id[stale-only]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-stale-then-current.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-stale-then-current.json new file mode 100644 index 00000000000..ff682b88310 --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-recreated-repository-id-stale-then-current.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_reconcile_recreated_repository_id[stale-then-current]": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-repository-id-rejects-different-slug.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-repository-id-rejects-different-slug.json new file mode 100644 index 00000000000..64a0f3f329b --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-reconcile-repository-id-rejects-different-slug.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_reconcile_repository_id_rejects_different_slug": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file diff --git a/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-recreated-repository-id-persists-across-restart.json b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-recreated-repository-id-persists-across-restart.json new file mode 100644 index 00000000000..641500f96eb --- /dev/null +++ b/tests/snapshots/api-usage/tests/hacsbase/test_hacsbase_datatest-recreated-repository-id-persists-across-restart.json @@ -0,0 +1,9 @@ +{ + "tests/hacsbase/test_hacsbase_data.py::test_recreated_repository_id_persists_across_restart": { + "https://api.github.com/repos/hacs/integration": 1, + "https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1, + "https://api.github.com/repos/hacs/integration/contents/hacs.json": 1, + "https://api.github.com/repos/hacs/integration/git/trees/main": 1, + "https://api.github.com/repos/hacs/integration/releases": 1 + } +} \ No newline at end of file