Refactor addon git repo (#5987)

* Refactor Repository into setup with inheritance

* Remove subclasses of GitRepo
This commit is contained in:
Mike Degatano 2025-07-03 07:53:52 -04:00 committed by GitHub
parent 3e20a0937d
commit abc44946bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 183 additions and 139 deletions

View File

@ -42,13 +42,8 @@ class FixupStoreExecuteReset(FixupBase):
_LOGGER.warning("Can't find store %s for fixup", reference) _LOGGER.warning("Can't find store %s for fixup", reference)
return return
# Local add-ons are not a git repo, can't remove and re-pull
try: try:
if repository.git: await repository.reset()
await repository.git.reset()
# Load data again
await repository.load()
except StoreError: except StoreError:
raise ResolutionFixupError() from None raise ResolutionFixupError() from None

View File

@ -135,7 +135,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
if url == URL_HASSIO_ADDONS: if url == URL_HASSIO_ADDONS:
url = StoreType.CORE url = StoreType.CORE
repository = Repository(self.coresys, url) repository = Repository.create(self.coresys, url)
if repository.slug in self.repositories: if repository.slug in self.repositories:
raise StoreError(f"Can't add {url}, already in the store", _LOGGER.error) raise StoreError(f"Can't add {url}, already in the store", _LOGGER.error)
@ -183,7 +183,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
raise err raise err
else: else:
if not await self.sys_run_in_executor(repository.validate): if not await repository.validate():
if add_with_errors: if add_with_errors:
_LOGGER.error("%s is not a valid add-on repository", url) _LOGGER.error("%s is not a valid add-on repository", url)
self.sys_resolution.create_issue( self.sys_resolution.create_issue(

View File

@ -1,6 +1,5 @@
"""Init file for Supervisor add-on Git.""" """Init file for Supervisor add-on Git."""
from abc import ABC, abstractmethod
import asyncio import asyncio
import errno import errno
import functools as ft import functools as ft
@ -16,17 +15,14 @@ from ..exceptions import StoreGitCloneError, StoreGitError, StoreJobError
from ..jobs.decorator import Job, JobCondition from ..jobs.decorator import Job, JobCondition
from ..resolution.const import ContextType, IssueType, SuggestionType, UnhealthyReason from ..resolution.const import ContextType, IssueType, SuggestionType, UnhealthyReason
from ..utils import remove_folder from ..utils import remove_folder
from .utils import get_hash_from_repository from .validate import RE_REPOSITORY
from .validate import RE_REPOSITORY, BuiltinRepository
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class GitRepo(CoreSysAttributes, ABC): class GitRepo(CoreSysAttributes):
"""Manage Add-on Git repository.""" """Manage Add-on Git repository."""
builtin: bool
def __init__(self, coresys: CoreSys, path: Path, url: str): def __init__(self, coresys: CoreSys, path: Path, url: str):
"""Initialize Git base wrapper.""" """Initialize Git base wrapper."""
self.coresys: CoreSys = coresys self.coresys: CoreSys = coresys
@ -239,38 +235,8 @@ class GitRepo(CoreSysAttributes, ABC):
) )
raise StoreGitError() from err raise StoreGitError() from err
@abstractmethod
async def remove(self) -> None: async def remove(self) -> None:
"""Remove a repository.""" """Remove a repository."""
class GitRepoBuiltin(GitRepo):
"""Built-in add-ons repository."""
builtin: bool = True
def __init__(self, coresys: CoreSys, repository: BuiltinRepository):
"""Initialize Git Supervisor add-on repository."""
super().__init__(coresys, repository.get_path(coresys), repository.url)
async def remove(self) -> None:
"""Raise. Cannot remove built-in repositories."""
raise RuntimeError("Cannot remove built-in repositories!")
class GitRepoCustom(GitRepo):
"""Custom add-ons repository."""
builtin: bool = False
def __init__(self, coresys, url):
"""Initialize custom Git Supervisor addo-n repository."""
path = Path(coresys.config.path_addons_git, get_hash_from_repository(url))
super().__init__(coresys, path, url)
async def remove(self) -> None:
"""Remove a custom repository."""
if self.lock.locked(): if self.lock.locked():
_LOGGER.warning( _LOGGER.warning(
"Cannot remove add-on repository %s, there is already a task in progress", "Cannot remove add-on repository %s, there is already a task in progress",

View File

@ -1,5 +1,8 @@
"""Represent a Supervisor repository.""" """Represent a Supervisor repository."""
from __future__ import annotations
from abc import ABC, abstractmethod
import logging import logging
from pathlib import Path from pathlib import Path
@ -12,7 +15,7 @@ from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import ConfigurationFileError, StoreError from ..exceptions import ConfigurationFileError, StoreError
from ..utils.common import read_json_or_yaml_file from ..utils.common import read_json_or_yaml_file
from .const import StoreType from .const import StoreType
from .git import GitRepo, GitRepoBuiltin, GitRepoCustom from .git import GitRepo
from .utils import get_hash_from_repository from .utils import get_hash_from_repository
from .validate import SCHEMA_REPOSITORY_CONFIG, BuiltinRepository from .validate import SCHEMA_REPOSITORY_CONFIG, BuiltinRepository
@ -20,28 +23,24 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
UNKNOWN = "unknown" UNKNOWN = "unknown"
class Repository(CoreSysAttributes): class Repository(CoreSysAttributes, ABC):
"""Add-on store repository in Supervisor.""" """Add-on store repository in Supervisor."""
def __init__(self, coresys: CoreSys, repository: str): def __init__(self, coresys: CoreSys, repository: str):
"""Initialize add-on store repository object.""" """Initialize add-on store repository object."""
self._slug: str
self._type: StoreType
self.coresys: CoreSys = coresys self.coresys: CoreSys = coresys
self.git: GitRepo | None = None
self.source: str = repository self.source: str = repository
@staticmethod
def create(coresys: CoreSys, repository: str) -> Repository:
"""Create a repository instance."""
if repository == StoreType.LOCAL: if repository == StoreType.LOCAL:
self._slug = repository return RepositoryLocal(coresys)
self._type = StoreType.LOCAL if repository in BuiltinRepository:
self._latest_mtime: float | None = None return RepositoryGitBuiltin(coresys, BuiltinRepository(repository))
elif repository in BuiltinRepository: return RepositoryCustom(coresys, repository)
builtin = BuiltinRepository(repository)
self.git = GitRepoBuiltin(coresys, builtin)
self._slug = builtin.id
self._type = builtin.type
else:
self.git = GitRepoCustom(coresys, repository)
self._slug = get_hash_from_repository(repository)
self._type = StoreType.GIT
def __repr__(self) -> str: def __repr__(self) -> str:
"""Return internal representation.""" """Return internal representation."""
@ -77,17 +76,75 @@ class Repository(CoreSysAttributes):
"""Return url of repository.""" """Return url of repository."""
return self.data.get(ATTR_MAINTAINER, UNKNOWN) return self.data.get(ATTR_MAINTAINER, UNKNOWN)
def validate(self) -> bool: @abstractmethod
"""Check if store is valid. async def validate(self) -> bool:
"""Check if store is valid."""
Must be run in executor. @abstractmethod
async def load(self) -> None:
"""Load addon repository."""
@abstractmethod
async def update(self) -> bool:
"""Update add-on repository.
Returns True if the repository was updated.
""" """
if not self.git or self.type == StoreType.CORE:
@abstractmethod
async def remove(self) -> None:
"""Remove add-on repository."""
@abstractmethod
async def reset(self) -> None:
"""Reset add-on repository to fix corruption issue with files."""
class RepositoryBuiltin(Repository, ABC):
"""A built-in add-on repository."""
def __init__(self, coresys: CoreSys, builtin: BuiltinRepository) -> None:
"""Initialize object."""
super().__init__(coresys, builtin.value)
self._builtin = builtin
self._slug = builtin.id
self._type = builtin.type
async def validate(self) -> bool:
"""Assume built-in repositories are always valid."""
return True return True
async def remove(self) -> None:
"""Raise. Not supported for built-in repositories."""
raise StoreError("Can't remove built-in repositories!", _LOGGER.error)
class RepositoryGit(Repository, ABC):
"""A git based add-on repository."""
_git: GitRepo
async def load(self) -> None:
"""Load addon repository."""
await self._git.load()
async def update(self) -> bool:
"""Update add-on repository.
Returns True if the repository was updated.
"""
if not await self.validate():
return False
return await self._git.pull()
async def validate(self) -> bool:
"""Check if store is valid."""
def validate_file() -> bool:
# If exists? # If exists?
for filetype in FILE_SUFFIX_CONFIGURATION: for filetype in FILE_SUFFIX_CONFIGURATION:
repository_file = Path(self.git.path / f"repository{filetype}") repository_file = Path(self._git.path / f"repository{filetype}")
if repository_file.exists(): if repository_file.exists():
break break
@ -103,26 +160,33 @@ class Repository(CoreSysAttributes):
return True return True
return await self.sys_run_in_executor(validate_file)
async def reset(self) -> None:
"""Reset add-on repository to fix corruption issue with files."""
await self._git.reset()
await self.load()
class RepositoryLocal(RepositoryBuiltin):
"""A local add-on repository."""
def __init__(self, coresys: CoreSys) -> None:
"""Initialize object."""
super().__init__(coresys, BuiltinRepository.LOCAL)
self._latest_mtime: float | None = None
async def load(self) -> None: async def load(self) -> None:
"""Load addon repository.""" """Load addon repository."""
if not self.git:
self._latest_mtime, _ = await self.sys_run_in_executor( self._latest_mtime, _ = await self.sys_run_in_executor(
get_latest_mtime, self.sys_config.path_addons_local get_latest_mtime, self.sys_config.path_addons_local
) )
return
await self.git.load()
async def update(self) -> bool: async def update(self) -> bool:
"""Update add-on repository. """Update add-on repository.
Returns True if the repository was updated. Returns True if the repository was updated.
""" """
if not await self.sys_run_in_executor(self.validate):
return False
if self.git:
return await self.git.pull()
# Check local modifications # Check local modifications
latest_mtime, modified_path = await self.sys_run_in_executor( latest_mtime, modified_path = await self.sys_run_in_executor(
get_latest_mtime, self.sys_config.path_addons_local get_latest_mtime, self.sys_config.path_addons_local
@ -138,9 +202,32 @@ class Repository(CoreSysAttributes):
return False return False
async def reset(self) -> None:
"""Raise. Not supported for local repository."""
raise StoreError(
"Can't reset local repository as it is not git based!", _LOGGER.error
)
class RepositoryGitBuiltin(RepositoryBuiltin, RepositoryGit):
"""A built-in add-on repository based on git."""
def __init__(self, coresys: CoreSys, builtin: BuiltinRepository) -> None:
"""Initialize object."""
super().__init__(coresys, builtin)
self._git = GitRepo(coresys, builtin.get_path(coresys), builtin.url)
class RepositoryCustom(RepositoryGit):
"""A custom add-on repository."""
def __init__(self, coresys: CoreSys, url: str) -> None:
"""Initialize object."""
super().__init__(coresys, url)
self._slug = get_hash_from_repository(url)
self._type = StoreType.GIT
self._git = GitRepo(coresys, coresys.config.path_addons_git / self._slug, url)
async def remove(self) -> None: async def remove(self) -> None:
"""Remove add-on repository.""" """Remove add-on repository."""
if not self.git or self.git.builtin: await self._git.remove()
raise StoreError("Can't remove built-in repositories!", _LOGGER.error)
await self.git.remove()

View File

@ -820,7 +820,7 @@ async def test_paths_cache(coresys: CoreSys, install_addon_ssh: Addon):
with ( with (
patch("supervisor.addons.addon.Path.exists", return_value=True), patch("supervisor.addons.addon.Path.exists", return_value=True),
patch("supervisor.store.repository.Repository.update", return_value=True), patch("supervisor.store.repository.RepositoryLocal.update", return_value=True),
): ):
await coresys.store.reload(coresys.store.get("local")) await coresys.store.reload(coresys.store.get("local"))

View File

@ -29,7 +29,7 @@ from supervisor.plugins.dns import PluginDns
from supervisor.resolution.const import ContextType, IssueType, SuggestionType from supervisor.resolution.const import ContextType, IssueType, SuggestionType
from supervisor.resolution.data import Issue, Suggestion from supervisor.resolution.data import Issue, Suggestion
from supervisor.store.addon import AddonStore from supervisor.store.addon import AddonStore
from supervisor.store.repository import Repository from supervisor.store.repository import RepositoryLocal
from supervisor.utils import check_exception_chain from supervisor.utils import check_exception_chain
from supervisor.utils.common import write_json_file from supervisor.utils.common import write_json_file
@ -442,7 +442,7 @@ async def test_store_data_changes_during_update(
update_task = coresys.create_task(simulate_update()) update_task = coresys.create_task(simulate_update())
await asyncio.sleep(0) await asyncio.sleep(0)
with patch.object(Repository, "update", return_value=True): with patch.object(RepositoryLocal, "update", return_value=True):
await coresys.store.reload() await coresys.store.reload()
assert "image" not in coresys.store.data.addons["local_ssh"] assert "image" not in coresys.store.data.addons["local_ssh"]

View File

@ -97,8 +97,8 @@ async def test_api_store_add_repository(
) -> None: ) -> None:
"""Test POST /store/repositories REST API.""" """Test POST /store/repositories REST API."""
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch("supervisor.store.repository.Repository.validate", return_value=True), patch("supervisor.store.repository.RepositoryGit.validate", return_value=True),
): ):
response = await api_client.post( response = await api_client.post(
"/store/repositories", json={"repository": REPO_URL} "/store/repositories", json={"repository": REPO_URL}

View File

@ -42,8 +42,8 @@ async def test_api_supervisor_options_add_repository(
coresys.store.get_from_url(REPO_URL) coresys.store.get_from_url(REPO_URL)
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch("supervisor.store.repository.Repository.validate", return_value=True), patch("supervisor.store.repository.RepositoryGit.validate", return_value=True),
): ):
response = await api_client.post( response = await api_client.post(
"/supervisor/options", json={"addons_repositories": [REPO_URL]} "/supervisor/options", json={"addons_repositories": [REPO_URL]}
@ -76,9 +76,9 @@ async def test_api_supervisor_options_repositories_skipped_on_error(
): ):
"""Test repositories skipped on error via POST /supervisor/options REST API.""" """Test repositories skipped on error via POST /supervisor/options REST API."""
with ( with (
patch("supervisor.store.repository.Repository.load", side_effect=git_error), patch("supervisor.store.repository.RepositoryGit.load", side_effect=git_error),
patch("supervisor.store.repository.Repository.validate", return_value=False), patch("supervisor.store.repository.RepositoryGit.validate", return_value=False),
patch("supervisor.store.repository.Repository.remove"), patch("supervisor.store.repository.RepositoryCustom.remove"),
): ):
response = await api_client.post( response = await api_client.post(
"/supervisor/options", json={"addons_repositories": [REPO_URL]} "/supervisor/options", json={"addons_repositories": [REPO_URL]}
@ -98,7 +98,7 @@ async def test_api_supervisor_options_repo_error_with_config_change(
assert not coresys.config.debug assert not coresys.config.debug
with patch( with patch(
"supervisor.store.repository.Repository.load", side_effect=StoreGitError() "supervisor.store.repository.RepositoryGit.load", side_effect=StoreGitError()
): ):
response = await api_client.post( response = await api_client.post(
"/supervisor/options", "/supervisor/options",

View File

@ -409,7 +409,7 @@ async def coresys(
coresys_obj.init_websession = AsyncMock() coresys_obj.init_websession = AsyncMock()
# Don't remove files/folders related to addons and stores # Don't remove files/folders related to addons and stores
with patch("supervisor.store.git.GitRepoCustom.remove"): with patch("supervisor.store.git.GitRepo.remove"):
yield coresys_obj yield coresys_obj
await coresys_obj.dbus.unload() await coresys_obj.dbus.unload()
@ -611,7 +611,7 @@ async def repository(coresys: CoreSys):
): ):
await coresys.store.load() await coresys.store.load()
repository_obj = Repository( repository_obj = Repository.create(
coresys, "https://github.com/awesome-developer/awesome-repo" coresys, "https://github.com/awesome-developer/awesome-repo"
) )

View File

@ -67,7 +67,7 @@ async def test_fixup(coresys: CoreSys):
path = path or obj.path path = path or obj.path
await coresys.run_in_executor((path / ".git").mkdir) await coresys.run_in_executor((path / ".git").mkdir)
coresys.store.repositories["94cfad5a"] = Repository( coresys.store.repositories["94cfad5a"] = Repository.create(
coresys, "https://github.com/home-assistant/addons-example" coresys, "https://github.com/home-assistant/addons-example"
) )
with ( with (
@ -97,7 +97,7 @@ async def test_fixup_clone_fail(coresys: CoreSys):
assert test_repo.exists() assert test_repo.exists()
assert corrupt_marker.exists() assert corrupt_marker.exists()
coresys.store.repositories["94cfad5a"] = Repository( coresys.store.repositories["94cfad5a"] = Repository.create(
coresys, "https://github.com/home-assistant/addons-example" coresys, "https://github.com/home-assistant/addons-example"
) )
with ( with (
@ -129,7 +129,7 @@ async def test_fixup_move_fail(coresys: CoreSys, error_num: int, unhealthy: bool
add_store_reset_suggestion(coresys) add_store_reset_suggestion(coresys)
test_repo.mkdir(parents=True) test_repo.mkdir(parents=True)
coresys.store.repositories["94cfad5a"] = Repository( coresys.store.repositories["94cfad5a"] = Repository.create(
coresys, "https://github.com/home-assistant/addons-example" coresys, "https://github.com/home-assistant/addons-example"
) )
with ( with (

View File

@ -33,7 +33,7 @@ async def test_add_valid_repository(
"""Test add custom repository.""" """Test add custom repository."""
current = coresys.store.repository_urls current = coresys.store.repository_urls
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch( patch(
"supervisor.utils.common.read_yaml_file", "supervisor.utils.common.read_yaml_file",
return_value={"name": "Awesome repository"}, return_value={"name": "Awesome repository"},
@ -54,7 +54,7 @@ async def test_add_invalid_repository(coresys: CoreSys, store_manager: StoreMana
"""Test add invalid custom repository.""" """Test add invalid custom repository."""
current = coresys.store.repository_urls current = coresys.store.repository_urls
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch( patch(
"pathlib.Path.read_text", "pathlib.Path.read_text",
return_value="", return_value="",
@ -64,9 +64,7 @@ async def test_add_invalid_repository(coresys: CoreSys, store_manager: StoreMana
current + ["http://example.com"], add_with_errors=True current + ["http://example.com"], add_with_errors=True
) )
assert not await coresys.run_in_executor( assert not await store_manager.get_from_url("http://example.com").validate()
store_manager.get_from_url("http://example.com").validate
)
assert "http://example.com" in coresys.store.repository_urls assert "http://example.com" in coresys.store.repository_urls
assert coresys.resolution.suggestions[-1].type == SuggestionType.EXECUTE_REMOVE assert coresys.resolution.suggestions[-1].type == SuggestionType.EXECUTE_REMOVE
@ -79,7 +77,7 @@ async def test_error_on_invalid_repository(
"""Test invalid repository not added.""" """Test invalid repository not added."""
current = coresys.store.repository_urls current = coresys.store.repository_urls
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch( patch(
"pathlib.Path.read_text", "pathlib.Path.read_text",
return_value="", return_value="",
@ -103,7 +101,7 @@ async def test_add_invalid_repository_file(
"""Test add invalid custom repository file.""" """Test add invalid custom repository file."""
current = coresys.store.repository_urls current = coresys.store.repository_urls
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch( patch(
"pathlib.Path.read_text", "pathlib.Path.read_text",
return_value=json.dumps({"name": "Awesome repository"}), return_value=json.dumps({"name": "Awesome repository"}),
@ -114,7 +112,7 @@ async def test_add_invalid_repository_file(
current + ["http://example.com"], add_with_errors=True current + ["http://example.com"], add_with_errors=True
) )
assert not store_manager.get_from_url("http://example.com").validate() assert not await store_manager.get_from_url("http://example.com").validate()
assert "http://example.com" in coresys.store.repository_urls assert "http://example.com" in coresys.store.repository_urls
assert coresys.resolution.suggestions[-1].type == SuggestionType.EXECUTE_REMOVE assert coresys.resolution.suggestions[-1].type == SuggestionType.EXECUTE_REMOVE
@ -135,7 +133,7 @@ async def test_add_repository_with_git_error(
): ):
"""Test repo added with issue on git error.""" """Test repo added with issue on git error."""
current = coresys.store.repository_urls current = coresys.store.repository_urls
with patch("supervisor.store.repository.Repository.load", side_effect=git_error): with patch("supervisor.store.repository.RepositoryGit.load", side_effect=git_error):
await store_manager.update_repositories( await store_manager.update_repositories(
current + ["http://example.com"], add_with_errors=True current + ["http://example.com"], add_with_errors=True
) )
@ -163,7 +161,7 @@ async def test_error_on_repository_with_git_error(
"""Test repo not added on git error.""" """Test repo not added on git error."""
current = coresys.store.repository_urls current = coresys.store.repository_urls
with ( with (
patch("supervisor.store.repository.Repository.load", side_effect=git_error), patch("supervisor.store.repository.RepositoryGit.load", side_effect=git_error),
pytest.raises(StoreError), pytest.raises(StoreError),
): ):
if use_update: if use_update:
@ -182,7 +180,7 @@ async def test_preinstall_valid_repository(
coresys: CoreSys, store_manager: StoreManager coresys: CoreSys, store_manager: StoreManager
): ):
"""Test add core repository valid.""" """Test add core repository valid."""
with patch("supervisor.store.repository.Repository.load", return_value=None): with patch("supervisor.store.repository.RepositoryGit.load", return_value=None):
await store_manager.update_repositories(BUILTIN_REPOSITORIES) await store_manager.update_repositories(BUILTIN_REPOSITORIES)
def validate(): def validate():
@ -244,8 +242,8 @@ async def test_remove_used_repository(
async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManager): async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManager):
"""Test partial error on update does partial save and errors.""" """Test partial error on update does partial save and errors."""
with patch("supervisor.store.repository.Repository.validate", return_value=True): with patch("supervisor.store.repository.RepositoryGit.validate", return_value=True):
with patch("supervisor.store.repository.Repository.load", return_value=None): with patch("supervisor.store.repository.RepositoryGit.load", return_value=None):
await store_manager.update_repositories([]) await store_manager.update_repositories([])
store_manager.data.update.assert_called_once() store_manager.data.update.assert_called_once()
@ -256,7 +254,7 @@ async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManage
with ( with (
patch( patch(
"supervisor.store.repository.Repository.load", "supervisor.store.repository.RepositoryGit.load",
side_effect=[None, StoreGitError()], side_effect=[None, StoreGitError()],
), ),
pytest.raises(StoreError), pytest.raises(StoreError),
@ -275,8 +273,8 @@ async def test_error_adding_duplicate(
"""Test adding a duplicate repository causes an error.""" """Test adding a duplicate repository causes an error."""
assert repository.source in coresys.store.repository_urls assert repository.source in coresys.store.repository_urls
with ( with (
patch("supervisor.store.repository.Repository.validate", return_value=True), patch("supervisor.store.repository.RepositoryGit.validate", return_value=True),
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
pytest.raises(StoreError), pytest.raises(StoreError),
): ):
await store_manager.add_repository(repository.source) await store_manager.add_repository(repository.source)
@ -290,7 +288,7 @@ async def test_add_with_update_repositories(
assert "http://example.com" not in coresys.store.repository_urls assert "http://example.com" not in coresys.store.repository_urls
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch( patch(
"supervisor.utils.common.read_yaml_file", "supervisor.utils.common.read_yaml_file",
return_value={"name": "Awesome repository"}, return_value={"name": "Awesome repository"},
@ -328,7 +326,7 @@ async def test_repositories_loaded_ignore_updates(
): ):
"""Test repositories loaded whether or not supervisor needs an update.""" """Test repositories loaded whether or not supervisor needs an update."""
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch.object( patch.object(
type(coresys.supervisor), type(coresys.supervisor),
"need_update", "need_update",

View File

@ -15,13 +15,6 @@ from supervisor.store.git import GitRepo
REPO_URL = "https://github.com/awesome-developer/awesome-repo" REPO_URL = "https://github.com/awesome-developer/awesome-repo"
class GitRepoTest(GitRepo):
"""Implementation of GitRepo for tests that allows direct setting of path."""
async def remove(self) -> None:
"""Not implemented."""
@pytest.fixture(name="clone_from") @pytest.fixture(name="clone_from")
async def fixture_clone_from(): async def fixture_clone_from():
"""Mock git clone_from.""" """Mock git clone_from."""
@ -35,7 +28,7 @@ async def test_git_clone(
): ):
"""Test git clone.""" """Test git clone."""
fragment = f"#{branch}" if branch else "" fragment = f"#{branch}" if branch else ""
repo = GitRepoTest(coresys, tmp_path, f"{REPO_URL}{fragment}") repo = GitRepo(coresys, tmp_path, f"{REPO_URL}{fragment}")
await repo.clone.__wrapped__(repo) await repo.clone.__wrapped__(repo)
@ -63,7 +56,7 @@ async def test_git_clone_error(
coresys: CoreSys, tmp_path: Path, clone_from: AsyncMock, git_error: Exception coresys: CoreSys, tmp_path: Path, clone_from: AsyncMock, git_error: Exception
): ):
"""Test git clone error.""" """Test git clone error."""
repo = GitRepoTest(coresys, tmp_path, REPO_URL) repo = GitRepo(coresys, tmp_path, REPO_URL)
clone_from.side_effect = git_error clone_from.side_effect = git_error
with pytest.raises(StoreGitCloneError): with pytest.raises(StoreGitCloneError):
@ -75,7 +68,7 @@ async def test_git_clone_error(
async def test_git_load(coresys: CoreSys, tmp_path: Path): async def test_git_load(coresys: CoreSys, tmp_path: Path):
"""Test git load.""" """Test git load."""
repo_dir = tmp_path / "repo" repo_dir = tmp_path / "repo"
repo = GitRepoTest(coresys, repo_dir, REPO_URL) repo = GitRepo(coresys, repo_dir, REPO_URL)
repo.clone = AsyncMock() repo.clone = AsyncMock()
# Test with non-existing git repo root directory # Test with non-existing git repo root directory
@ -113,7 +106,7 @@ async def test_git_load(coresys: CoreSys, tmp_path: Path):
async def test_git_load_error(coresys: CoreSys, tmp_path: Path, git_errors: Exception): async def test_git_load_error(coresys: CoreSys, tmp_path: Path, git_errors: Exception):
"""Test git load error.""" """Test git load error."""
coresys.hardware.disk.get_disk_free_space = lambda x: 5000 coresys.hardware.disk.get_disk_free_space = lambda x: 5000
repo = GitRepoTest(coresys, tmp_path, REPO_URL) repo = GitRepo(coresys, tmp_path, REPO_URL)
# Pretend we have a repo # Pretend we have a repo
(tmp_path / ".git").mkdir() (tmp_path / ".git").mkdir()

View File

@ -32,7 +32,8 @@ async def test_default_load(coresys: CoreSys):
refresh_cache_calls.add(obj.slug) refresh_cache_calls.add(obj.slug)
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch("supervisor.store.repository.RepositoryLocal.load", return_value=None),
patch.object(type(coresys.config), "addons_repositories", return_value=[]), patch.object(type(coresys.config), "addons_repositories", return_value=[]),
patch("pathlib.Path.exists", return_value=True), patch("pathlib.Path.exists", return_value=True),
patch.object(AddonStore, "refresh_path_cache", new=mock_refresh_cache), patch.object(AddonStore, "refresh_path_cache", new=mock_refresh_cache),
@ -80,9 +81,13 @@ async def test_load_with_custom_repository(coresys: CoreSys):
store_manager = await StoreManager(coresys).load_config() store_manager = await StoreManager(coresys).load_config()
with ( with (
patch("supervisor.store.repository.Repository.load", return_value=None), patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
patch("supervisor.store.repository.RepositoryLocal.load", return_value=None),
patch.object(type(coresys.config), "addons_repositories", return_value=[]), patch.object(type(coresys.config), "addons_repositories", return_value=[]),
patch("supervisor.store.repository.Repository.validate", return_value=True), patch("supervisor.store.repository.RepositoryGit.validate", return_value=True),
patch(
"supervisor.store.repository.RepositoryLocal.validate", return_value=True
),
patch("pathlib.Path.exists", return_value=True), patch("pathlib.Path.exists", return_value=True),
patch.object(AddonStore, "refresh_path_cache", new=mock_refresh_cache), patch.object(AddonStore, "refresh_path_cache", new=mock_refresh_cache),
): ):