mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-17 22:26:30 +00:00
Refactoring around add-on store Repository classes (#5990)
* Rename repository fixture to test_repository Also don't remove the built-in repositories. The list was incomplete, and tests don't seem to require that anymore. * Get rid of StoreType The type doesn't have much value, we have constant strings anyways. * Introduce types.py * Use slug to determine which repository urls to return * Simplify BuiltinRepository enum * Mock GitRepo load * Improve URL handling and repository creation logic * Refactor update_repositories * Get rid of get_from_url It is no longer used in production code. * More refactoring * Address pylint * Introduce is_git_based property to Repository class Return all git based URLs, including the Core repository. * Revert "Introduce is_git_based property to Repository class" This reverts commit dfd5ad79bf23e0e127fc45d97d6f8de0e796faa0. * Fold type.py into const.py Align more with how Supervisor code is typically structured. * Update supervisor/store/__init__.py Co-authored-by: Mike Degatano <michael.degatano@gmail.com> * Apply repository remove suggestion * Fix tests --------- Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
This commit is contained in:
parent
7873c457d5
commit
baf9695cf7
@ -931,5 +931,5 @@ class Backup(JobGroup):
|
||||
Return a coroutine.
|
||||
"""
|
||||
return self.sys_store.update_repositories(
|
||||
self.repositories, add_with_errors=True, replace=replace
|
||||
self.repositories, issue_on_error=True, replace=replace
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ import asyncio
|
||||
from collections.abc import Awaitable
|
||||
import logging
|
||||
|
||||
from ..const import ATTR_REPOSITORIES, URL_HASSIO_ADDONS
|
||||
from ..const import ATTR_REPOSITORIES, REPOSITORY_CORE, URL_HASSIO_ADDONS
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import (
|
||||
StoreError,
|
||||
@ -18,14 +18,10 @@ from ..jobs.decorator import Job, JobCondition
|
||||
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||
from ..utils.common import FileConfiguration
|
||||
from .addon import AddonStore
|
||||
from .const import FILE_HASSIO_STORE, StoreType
|
||||
from .const import FILE_HASSIO_STORE, BuiltinRepository
|
||||
from .data import StoreData
|
||||
from .repository import Repository
|
||||
from .validate import (
|
||||
BUILTIN_REPOSITORIES,
|
||||
SCHEMA_STORE_FILE,
|
||||
ensure_builtin_repositories,
|
||||
)
|
||||
from .validate import SCHEMA_STORE_FILE, ensure_builtin_repositories
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -56,7 +52,8 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
return [
|
||||
repository.source
|
||||
for repository in self.all
|
||||
if repository.type == StoreType.GIT
|
||||
if repository.slug
|
||||
not in {BuiltinRepository.LOCAL.value, BuiltinRepository.CORE.value}
|
||||
]
|
||||
|
||||
def get(self, slug: str) -> Repository:
|
||||
@ -65,19 +62,11 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
raise StoreNotFound()
|
||||
return self.repositories[slug]
|
||||
|
||||
def get_from_url(self, url: str) -> Repository:
|
||||
"""Return Repository with slug."""
|
||||
for repository in self.all:
|
||||
if repository.source != url:
|
||||
continue
|
||||
return repository
|
||||
raise StoreNotFound()
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Start up add-on management."""
|
||||
# Init custom repositories and load add-ons
|
||||
await self.update_repositories(
|
||||
self._data[ATTR_REPOSITORIES], add_with_errors=True
|
||||
self._data[ATTR_REPOSITORIES], issue_on_error=True
|
||||
)
|
||||
|
||||
@Job(
|
||||
@ -126,14 +115,14 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
)
|
||||
async def add_repository(self, url: str, *, persist: bool = True) -> None:
|
||||
"""Add a repository."""
|
||||
await self._add_repository(url, persist=persist, add_with_errors=False)
|
||||
await self._add_repository(url, persist=persist, issue_on_error=False)
|
||||
|
||||
async def _add_repository(
|
||||
self, url: str, *, persist: bool = True, add_with_errors: bool = False
|
||||
self, url: str, *, persist: bool = True, issue_on_error: bool = False
|
||||
) -> None:
|
||||
"""Add a repository."""
|
||||
if url == URL_HASSIO_ADDONS:
|
||||
url = StoreType.CORE
|
||||
url = REPOSITORY_CORE
|
||||
|
||||
repository = Repository.create(self.coresys, url)
|
||||
|
||||
@ -145,7 +134,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
await repository.load()
|
||||
except StoreGitCloneError as err:
|
||||
_LOGGER.error("Can't retrieve data from %s due to %s", url, err)
|
||||
if add_with_errors:
|
||||
if issue_on_error:
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
@ -158,7 +147,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
|
||||
except StoreGitError as err:
|
||||
_LOGGER.error("Can't load data from repository %s due to %s", url, err)
|
||||
if add_with_errors:
|
||||
if issue_on_error:
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
@ -171,7 +160,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
|
||||
except StoreJobError as err:
|
||||
_LOGGER.error("Can't add repository %s due to %s", url, err)
|
||||
if add_with_errors:
|
||||
if issue_on_error:
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.FATAL_ERROR,
|
||||
ContextType.STORE,
|
||||
@ -184,7 +173,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
|
||||
else:
|
||||
if not await repository.validate():
|
||||
if add_with_errors:
|
||||
if issue_on_error:
|
||||
_LOGGER.error("%s is not a valid add-on repository", url)
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.CORRUPT_REPOSITORY,
|
||||
@ -213,7 +202,7 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
|
||||
async def remove_repository(self, repository: Repository, *, persist: bool = True):
|
||||
"""Remove a repository."""
|
||||
if repository.source in BUILTIN_REPOSITORIES:
|
||||
if repository.is_builtin:
|
||||
raise StoreInvalidAddonRepo(
|
||||
"Can't remove built-in repositories!", logger=_LOGGER.error
|
||||
)
|
||||
@ -236,38 +225,54 @@ class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||
self,
|
||||
list_repositories: list[str],
|
||||
*,
|
||||
add_with_errors: bool = False,
|
||||
issue_on_error: bool = False,
|
||||
replace: bool = True,
|
||||
):
|
||||
"""Add a new custom repository."""
|
||||
new_rep = set(
|
||||
ensure_builtin_repositories(list_repositories)
|
||||
if replace
|
||||
else list_repositories + self.repository_urls
|
||||
)
|
||||
old_rep = {repository.source for repository in self.all}
|
||||
"""Update repositories by adding new ones and removing stale ones."""
|
||||
current_repositories = {repository.source for repository in self.all}
|
||||
|
||||
# Determine changes needed
|
||||
if replace:
|
||||
target_repositories = set(ensure_builtin_repositories(list_repositories))
|
||||
repositories_to_add = target_repositories - current_repositories
|
||||
else:
|
||||
# When not replacing, just add the new repositories
|
||||
repositories_to_add = set(list_repositories) - current_repositories
|
||||
target_repositories = current_repositories | repositories_to_add
|
||||
|
||||
# Add new repositories
|
||||
add_errors = await asyncio.gather(
|
||||
*[
|
||||
self._add_repository(url, persist=False, add_with_errors=True)
|
||||
if add_with_errors
|
||||
# Use _add_repository to avoid JobCondition.SUPERVISOR_UPDATED
|
||||
# to prevent proper loading of repositories on startup.
|
||||
self._add_repository(url, persist=False, issue_on_error=True)
|
||||
if issue_on_error
|
||||
else self.add_repository(url, persist=False)
|
||||
for url in new_rep - old_rep
|
||||
for url in repositories_to_add
|
||||
],
|
||||
return_exceptions=True,
|
||||
)
|
||||
|
||||
# Delete stale repositories
|
||||
remove_errors = await asyncio.gather(
|
||||
*[
|
||||
self.remove_repository(self.get_from_url(url), persist=False)
|
||||
for url in old_rep - new_rep - BUILTIN_REPOSITORIES
|
||||
],
|
||||
return_exceptions=True,
|
||||
)
|
||||
remove_errors: list[BaseException | None] = []
|
||||
if replace:
|
||||
# Determine repositories to remove
|
||||
repositories_to_remove: list[Repository] = [
|
||||
repository
|
||||
for repository in self.all
|
||||
if repository.source not in target_repositories
|
||||
and not repository.is_builtin
|
||||
]
|
||||
|
||||
# Always update data, even there are errors, some changes may have succeeded
|
||||
# Remove repositories
|
||||
remove_errors = await asyncio.gather(
|
||||
*[
|
||||
self.remove_repository(repository, persist=False)
|
||||
for repository in repositories_to_remove
|
||||
],
|
||||
return_exceptions=True,
|
||||
)
|
||||
|
||||
# Always update data, even if there are errors, some changes may have succeeded
|
||||
await self.data.update()
|
||||
await self._read_addons()
|
||||
|
||||
|
@ -3,14 +3,39 @@
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
|
||||
from ..const import SUPERVISOR_DATA
|
||||
from ..const import (
|
||||
REPOSITORY_CORE,
|
||||
REPOSITORY_LOCAL,
|
||||
SUPERVISOR_DATA,
|
||||
URL_HASSIO_ADDONS,
|
||||
)
|
||||
|
||||
FILE_HASSIO_STORE = Path(SUPERVISOR_DATA, "store.json")
|
||||
"""Repository type definitions for the store."""
|
||||
|
||||
|
||||
class StoreType(StrEnum):
|
||||
"""Store Types."""
|
||||
class BuiltinRepository(StrEnum):
|
||||
"""All built-in repositories that come pre-configured."""
|
||||
|
||||
CORE = "core"
|
||||
LOCAL = "local"
|
||||
GIT = "git"
|
||||
# Local repository (non-git, special handling)
|
||||
LOCAL = REPOSITORY_LOCAL
|
||||
|
||||
# Git-based built-in repositories
|
||||
CORE = REPOSITORY_CORE
|
||||
COMMUNITY_ADDONS = "https://github.com/hassio-addons/repository"
|
||||
ESPHOME = "https://github.com/esphome/home-assistant-addon"
|
||||
MUSIC_ASSISTANT = "https://github.com/music-assistant/home-assistant-addon"
|
||||
|
||||
@property
|
||||
def git_url(self) -> str:
|
||||
"""Return the git URL for this repository."""
|
||||
if self == BuiltinRepository.LOCAL:
|
||||
raise RuntimeError("Local repository does not have a git URL")
|
||||
if self == BuiltinRepository.CORE:
|
||||
return URL_HASSIO_ADDONS
|
||||
else:
|
||||
return self.value # For URL-based repos, value is the URL
|
||||
|
||||
|
||||
# All repositories that are considered "built-in" and protected from removal
|
||||
ALL_BUILTIN_REPOSITORIES = {repo.value for repo in BuiltinRepository}
|
||||
|
@ -25,7 +25,6 @@ from ..exceptions import ConfigurationFileError
|
||||
from ..resolution.const import ContextType, IssueType, SuggestionType, UnhealthyReason
|
||||
from ..utils.common import find_one_filetype, read_json_or_yaml_file
|
||||
from ..utils.json import read_json_file
|
||||
from .const import StoreType
|
||||
from .utils import extract_hash_from_path
|
||||
from .validate import SCHEMA_REPOSITORY_CONFIG
|
||||
|
||||
@ -169,7 +168,7 @@ class StoreData(CoreSysAttributes):
|
||||
self.sys_resolution.add_unhealthy_reason(
|
||||
UnhealthyReason.OSERROR_BAD_MESSAGE
|
||||
)
|
||||
elif path.stem != StoreType.LOCAL:
|
||||
elif repository != REPOSITORY_LOCAL:
|
||||
suggestion = [SuggestionType.EXECUTE_RESET]
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.CORRUPT_REPOSITORY,
|
||||
|
@ -10,14 +10,21 @@ import voluptuous as vol
|
||||
|
||||
from supervisor.utils import get_latest_mtime
|
||||
|
||||
from ..const import ATTR_MAINTAINER, ATTR_NAME, ATTR_URL, FILE_SUFFIX_CONFIGURATION
|
||||
from ..const import (
|
||||
ATTR_MAINTAINER,
|
||||
ATTR_NAME,
|
||||
ATTR_URL,
|
||||
FILE_SUFFIX_CONFIGURATION,
|
||||
REPOSITORY_CORE,
|
||||
REPOSITORY_LOCAL,
|
||||
)
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import ConfigurationFileError, StoreError
|
||||
from ..utils.common import read_json_or_yaml_file
|
||||
from .const import StoreType
|
||||
from .const import BuiltinRepository
|
||||
from .git import GitRepo
|
||||
from .utils import get_hash_from_repository
|
||||
from .validate import SCHEMA_REPOSITORY_CONFIG, BuiltinRepository
|
||||
from .validate import SCHEMA_REPOSITORY_CONFIG
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
UNKNOWN = "unknown"
|
||||
@ -26,21 +33,45 @@ UNKNOWN = "unknown"
|
||||
class Repository(CoreSysAttributes, ABC):
|
||||
"""Add-on store repository in Supervisor."""
|
||||
|
||||
def __init__(self, coresys: CoreSys, repository: str):
|
||||
def __init__(self, coresys: CoreSys, repository: str, local_path: Path, slug: str):
|
||||
"""Initialize add-on store repository object."""
|
||||
self._slug: str
|
||||
self._type: StoreType
|
||||
self._slug: str = slug
|
||||
self._local_path: Path = local_path
|
||||
self.coresys: CoreSys = coresys
|
||||
self.source: str = repository
|
||||
|
||||
@staticmethod
|
||||
def create(coresys: CoreSys, repository: str) -> Repository:
|
||||
"""Create a repository instance."""
|
||||
if repository == StoreType.LOCAL:
|
||||
return RepositoryLocal(coresys)
|
||||
if repository in BuiltinRepository:
|
||||
return RepositoryGitBuiltin(coresys, BuiltinRepository(repository))
|
||||
return RepositoryCustom(coresys, repository)
|
||||
return Repository._create_builtin(coresys, BuiltinRepository(repository))
|
||||
else:
|
||||
return Repository._create_custom(coresys, repository)
|
||||
|
||||
@staticmethod
|
||||
def _create_builtin(coresys: CoreSys, builtin: BuiltinRepository) -> Repository:
|
||||
"""Create builtin repository."""
|
||||
if builtin == BuiltinRepository.LOCAL:
|
||||
slug = REPOSITORY_LOCAL
|
||||
local_path = coresys.config.path_addons_local
|
||||
return RepositoryLocal(coresys, local_path, slug)
|
||||
elif builtin == BuiltinRepository.CORE:
|
||||
slug = REPOSITORY_CORE
|
||||
local_path = coresys.config.path_addons_core
|
||||
else:
|
||||
# For other builtin repositories (URL-based)
|
||||
slug = get_hash_from_repository(builtin.value)
|
||||
local_path = coresys.config.path_addons_git / slug
|
||||
return RepositoryGitBuiltin(
|
||||
coresys, builtin.value, local_path, slug, builtin.git_url
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _create_custom(coresys: CoreSys, repository: str) -> RepositoryCustom:
|
||||
"""Create custom repository."""
|
||||
slug = get_hash_from_repository(repository)
|
||||
local_path = coresys.config.path_addons_git / slug
|
||||
return RepositoryCustom(coresys, repository, local_path, slug)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return internal representation."""
|
||||
@ -52,9 +83,9 @@ class Repository(CoreSysAttributes, ABC):
|
||||
return self._slug
|
||||
|
||||
@property
|
||||
def type(self) -> StoreType:
|
||||
"""Return type of the store."""
|
||||
return self._type
|
||||
def local_path(self) -> Path:
|
||||
"""Return local path to repository."""
|
||||
return self._local_path
|
||||
|
||||
@property
|
||||
def data(self) -> dict:
|
||||
@ -76,6 +107,11 @@ class Repository(CoreSysAttributes, ABC):
|
||||
"""Return url of repository."""
|
||||
return self.data.get(ATTR_MAINTAINER, UNKNOWN)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_builtin(self) -> bool:
|
||||
"""Return True if this is a built-in repository."""
|
||||
|
||||
@abstractmethod
|
||||
async def validate(self) -> bool:
|
||||
"""Check if store is valid."""
|
||||
@ -103,12 +139,10 @@ class Repository(CoreSysAttributes, ABC):
|
||||
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
|
||||
@property
|
||||
def is_builtin(self) -> bool:
|
||||
"""Return True if this is a built-in repository."""
|
||||
return True
|
||||
|
||||
async def validate(self) -> bool:
|
||||
"""Assume built-in repositories are always valid."""
|
||||
@ -171,15 +205,15 @@ class RepositoryGit(Repository, ABC):
|
||||
class RepositoryLocal(RepositoryBuiltin):
|
||||
"""A local add-on repository."""
|
||||
|
||||
def __init__(self, coresys: CoreSys) -> None:
|
||||
def __init__(self, coresys: CoreSys, local_path: Path, slug: str) -> None:
|
||||
"""Initialize object."""
|
||||
super().__init__(coresys, BuiltinRepository.LOCAL)
|
||||
super().__init__(coresys, BuiltinRepository.LOCAL.value, local_path, slug)
|
||||
self._latest_mtime: float | None = None
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Load addon repository."""
|
||||
self._latest_mtime, _ = await self.sys_run_in_executor(
|
||||
get_latest_mtime, self.sys_config.path_addons_local
|
||||
get_latest_mtime, self.local_path
|
||||
)
|
||||
|
||||
async def update(self) -> bool:
|
||||
@ -189,7 +223,7 @@ class RepositoryLocal(RepositoryBuiltin):
|
||||
"""
|
||||
# Check local modifications
|
||||
latest_mtime, modified_path = await self.sys_run_in_executor(
|
||||
get_latest_mtime, self.sys_config.path_addons_local
|
||||
get_latest_mtime, self.local_path
|
||||
)
|
||||
if self._latest_mtime != latest_mtime:
|
||||
_LOGGER.debug(
|
||||
@ -212,21 +246,26 @@ class RepositoryLocal(RepositoryBuiltin):
|
||||
class RepositoryGitBuiltin(RepositoryBuiltin, RepositoryGit):
|
||||
"""A built-in add-on repository based on git."""
|
||||
|
||||
def __init__(self, coresys: CoreSys, builtin: BuiltinRepository) -> None:
|
||||
def __init__(
|
||||
self, coresys: CoreSys, repository: str, local_path: Path, slug: str, url: str
|
||||
) -> None:
|
||||
"""Initialize object."""
|
||||
super().__init__(coresys, builtin)
|
||||
self._git = GitRepo(coresys, builtin.get_path(coresys), builtin.url)
|
||||
super().__init__(coresys, repository, local_path, slug)
|
||||
self._git = GitRepo(coresys, local_path, url)
|
||||
|
||||
|
||||
class RepositoryCustom(RepositoryGit):
|
||||
"""A custom add-on repository."""
|
||||
|
||||
def __init__(self, coresys: CoreSys, url: str) -> None:
|
||||
def __init__(self, coresys: CoreSys, url: str, local_path: Path, slug: 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)
|
||||
super().__init__(coresys, url, local_path, slug)
|
||||
self._git = GitRepo(coresys, local_path, url)
|
||||
|
||||
@property
|
||||
def is_builtin(self) -> bool:
|
||||
"""Return True if this is a built-in repository."""
|
||||
return False
|
||||
|
||||
async def remove(self) -> None:
|
||||
"""Remove add-on repository."""
|
||||
|
@ -1,62 +1,10 @@
|
||||
"""Validate add-ons options schema."""
|
||||
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_MAINTAINER,
|
||||
ATTR_NAME,
|
||||
ATTR_REPOSITORIES,
|
||||
ATTR_URL,
|
||||
URL_HASSIO_ADDONS,
|
||||
)
|
||||
from ..coresys import CoreSys
|
||||
from ..const import ATTR_MAINTAINER, ATTR_NAME, ATTR_REPOSITORIES, ATTR_URL
|
||||
from ..validate import RE_REPOSITORY
|
||||
from .const import StoreType
|
||||
from .utils import get_hash_from_repository
|
||||
|
||||
URL_COMMUNITY_ADDONS = "https://github.com/hassio-addons/repository"
|
||||
URL_ESPHOME = "https://github.com/esphome/home-assistant-addon"
|
||||
URL_MUSIC_ASSISTANT = "https://github.com/music-assistant/home-assistant-addon"
|
||||
|
||||
|
||||
class BuiltinRepository(StrEnum):
|
||||
"""Built-in add-on repository."""
|
||||
|
||||
CORE = StoreType.CORE.value
|
||||
LOCAL = StoreType.LOCAL.value
|
||||
COMMUNITY_ADDONS = URL_COMMUNITY_ADDONS
|
||||
ESPHOME = URL_ESPHOME
|
||||
MUSIC_ASSISTANT = URL_MUSIC_ASSISTANT
|
||||
|
||||
def __init__(self, value: str) -> None:
|
||||
"""Initialize repository item."""
|
||||
if value == StoreType.LOCAL:
|
||||
self.id = value
|
||||
self.url = ""
|
||||
self.type = StoreType.LOCAL
|
||||
elif value == StoreType.CORE:
|
||||
self.id = value
|
||||
self.url = URL_HASSIO_ADDONS
|
||||
self.type = StoreType.CORE
|
||||
else:
|
||||
self.id = get_hash_from_repository(value)
|
||||
self.url = value
|
||||
self.type = StoreType.GIT
|
||||
|
||||
def get_path(self, coresys: CoreSys) -> Path:
|
||||
"""Get path to git repo for repository."""
|
||||
if self.id == StoreType.LOCAL:
|
||||
return coresys.config.path_addons_local
|
||||
if self.id == StoreType.CORE:
|
||||
return coresys.config.path_addons_core
|
||||
return Path(coresys.config.path_addons_git, self.id)
|
||||
|
||||
|
||||
BUILTIN_REPOSITORIES = {r.value for r in BuiltinRepository}
|
||||
|
||||
from .const import ALL_BUILTIN_REPOSITORIES, BuiltinRepository
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_REPOSITORY_CONFIG = vol.Schema(
|
||||
@ -75,12 +23,12 @@ def ensure_builtin_repositories(addon_repositories: list[str]) -> list[str]:
|
||||
Note: This should not be used in validation as the resulting list is not
|
||||
stable. This can have side effects when comparing data later on.
|
||||
"""
|
||||
return list(set(addon_repositories) | BUILTIN_REPOSITORIES)
|
||||
return list(set(addon_repositories) | ALL_BUILTIN_REPOSITORIES)
|
||||
|
||||
|
||||
def validate_repository(repository: str) -> str:
|
||||
"""Validate a valid repository."""
|
||||
if repository in [StoreType.CORE, StoreType.LOCAL]:
|
||||
if repository in BuiltinRepository:
|
||||
return repository
|
||||
|
||||
data = RE_REPOSITORY.match(repository)
|
||||
@ -99,7 +47,7 @@ repositories = vol.All([validate_repository], vol.Unique())
|
||||
SCHEMA_STORE_FILE = vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
ATTR_REPOSITORIES, default=list(BUILTIN_REPOSITORIES)
|
||||
ATTR_REPOSITORIES, default=list(ALL_BUILTIN_REPOSITORIES)
|
||||
): repositories,
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
|
@ -209,7 +209,7 @@ async def test_watchdog_on_stop(coresys: CoreSys, install_addon_ssh: Addon) -> N
|
||||
|
||||
|
||||
async def test_listener_attached_on_install(
|
||||
coresys: CoreSys, mock_amd64_arch_supported: None, repository
|
||||
coresys: CoreSys, mock_amd64_arch_supported: None, test_repository
|
||||
):
|
||||
"""Test events listener attached on addon install."""
|
||||
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
|
||||
@ -242,7 +242,7 @@ async def test_listener_attached_on_install(
|
||||
)
|
||||
async def test_watchdog_during_attach(
|
||||
coresys: CoreSys,
|
||||
repository: Repository,
|
||||
test_repository: Repository,
|
||||
boot_timedelta: timedelta,
|
||||
restart_count: int,
|
||||
):
|
||||
@ -710,7 +710,7 @@ async def test_local_example_install(
|
||||
coresys: CoreSys,
|
||||
container: MagicMock,
|
||||
tmp_supervisor_data: Path,
|
||||
repository,
|
||||
test_repository,
|
||||
mock_aarch64_arch_supported: None,
|
||||
):
|
||||
"""Test install of an addon."""
|
||||
|
@ -67,7 +67,7 @@ async def fixture_remove_wait_boot(coresys: CoreSys) -> AsyncGenerator[None]:
|
||||
|
||||
@pytest.fixture(name="install_addon_example_image")
|
||||
async def fixture_install_addon_example_image(
|
||||
coresys: CoreSys, repository
|
||||
coresys: CoreSys, test_repository
|
||||
) -> Generator[Addon]:
|
||||
"""Install local_example add-on with image."""
|
||||
store = coresys.addons.store["local_example_image"]
|
||||
|
@ -54,7 +54,7 @@ async def test_addons_info(
|
||||
|
||||
# DEPRECATED - Remove with legacy routing logic on 1/2023
|
||||
async def test_addons_info_not_installed(
|
||||
api_client: TestClient, coresys: CoreSys, repository: Repository
|
||||
api_client: TestClient, coresys: CoreSys, test_repository: Repository
|
||||
):
|
||||
"""Test getting addon info for not installed addon."""
|
||||
resp = await api_client.get(f"/addons/{TEST_ADDON_SLUG}/info")
|
||||
@ -533,7 +533,7 @@ async def test_addon_not_found(
|
||||
("get", "/addons/local_ssh/logs/boots/1/follow", False),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("repository")
|
||||
@pytest.mark.usefixtures("test_repository")
|
||||
async def test_addon_not_installed(
|
||||
api_client: TestClient, method: str, url: str, json_expected: bool
|
||||
):
|
||||
|
@ -30,7 +30,7 @@ REPO_URL = "https://github.com/awesome-developer/awesome-repo"
|
||||
async def test_api_store(
|
||||
api_client: TestClient,
|
||||
store_addon: AddonStore,
|
||||
repository: Repository,
|
||||
test_repository: Repository,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
):
|
||||
"""Test /store REST API."""
|
||||
@ -38,7 +38,7 @@ async def test_api_store(
|
||||
result = await resp.json()
|
||||
|
||||
assert result["data"]["addons"][-1]["slug"] == store_addon.slug
|
||||
assert result["data"]["repositories"][-1]["slug"] == repository.slug
|
||||
assert result["data"]["repositories"][-1]["slug"] == test_repository.slug
|
||||
|
||||
assert (
|
||||
f"Add-on {store_addon.slug} not supported on this platform" not in caplog.text
|
||||
@ -73,23 +73,25 @@ async def test_api_store_addons_addon_version(
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_store_repositories(api_client: TestClient, repository: Repository):
|
||||
async def test_api_store_repositories(
|
||||
api_client: TestClient, test_repository: Repository
|
||||
):
|
||||
"""Test /store/repositories REST API."""
|
||||
resp = await api_client.get("/store/repositories")
|
||||
result = await resp.json()
|
||||
|
||||
assert result["data"][-1]["slug"] == repository.slug
|
||||
assert result["data"][-1]["slug"] == test_repository.slug
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_store_repositories_repository(
|
||||
api_client: TestClient, repository: Repository
|
||||
api_client: TestClient, test_repository: Repository
|
||||
):
|
||||
"""Test /store/repositories/{repository} REST API."""
|
||||
resp = await api_client.get(f"/store/repositories/{repository.slug}")
|
||||
resp = await api_client.get(f"/store/repositories/{test_repository.slug}")
|
||||
result = await resp.json()
|
||||
|
||||
assert result["data"]["slug"] == repository.slug
|
||||
assert result["data"]["slug"] == test_repository.slug
|
||||
|
||||
|
||||
async def test_api_store_add_repository(
|
||||
@ -106,18 +108,17 @@ async def test_api_store_add_repository(
|
||||
|
||||
assert response.status == 200
|
||||
assert REPO_URL in coresys.store.repository_urls
|
||||
assert isinstance(coresys.store.get_from_url(REPO_URL), Repository)
|
||||
|
||||
|
||||
async def test_api_store_remove_repository(
|
||||
api_client: TestClient, coresys: CoreSys, repository: Repository
|
||||
api_client: TestClient, coresys: CoreSys, test_repository: Repository
|
||||
):
|
||||
"""Test DELETE /store/repositories/{repository} REST API."""
|
||||
response = await api_client.delete(f"/store/repositories/{repository.slug}")
|
||||
response = await api_client.delete(f"/store/repositories/{test_repository.slug}")
|
||||
|
||||
assert response.status == 200
|
||||
assert repository.source not in coresys.store.repository_urls
|
||||
assert repository.slug not in coresys.store.repositories
|
||||
assert test_repository.source not in coresys.store.repository_urls
|
||||
assert test_repository.slug not in coresys.store.repositories
|
||||
|
||||
|
||||
async def test_api_store_update_healthcheck(
|
||||
@ -329,7 +330,7 @@ async def test_store_addon_not_found(
|
||||
("post", "/addons/local_ssh/update"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("repository")
|
||||
@pytest.mark.usefixtures("test_repository")
|
||||
async def test_store_addon_not_installed(api_client: TestClient, method: str, url: str):
|
||||
"""Test store addon not installed error."""
|
||||
resp = await api_client.request(method, url)
|
||||
|
@ -9,12 +9,7 @@ from blockbuster import BlockingError
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.exceptions import (
|
||||
HassioError,
|
||||
HostNotSupportedError,
|
||||
StoreGitError,
|
||||
StoreNotFound,
|
||||
)
|
||||
from supervisor.exceptions import HassioError, HostNotSupportedError, StoreGitError
|
||||
from supervisor.store.repository import Repository
|
||||
|
||||
from tests.api import common_test_api_advanced_logs
|
||||
@ -38,8 +33,6 @@ async def test_api_supervisor_options_add_repository(
|
||||
):
|
||||
"""Test add a repository via POST /supervisor/options REST API."""
|
||||
assert REPO_URL not in coresys.store.repository_urls
|
||||
with pytest.raises(StoreNotFound):
|
||||
coresys.store.get_from_url(REPO_URL)
|
||||
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
@ -51,23 +44,22 @@ async def test_api_supervisor_options_add_repository(
|
||||
|
||||
assert response.status == 200
|
||||
assert REPO_URL in coresys.store.repository_urls
|
||||
assert isinstance(coresys.store.get_from_url(REPO_URL), Repository)
|
||||
|
||||
|
||||
async def test_api_supervisor_options_remove_repository(
|
||||
api_client: TestClient, coresys: CoreSys, repository: Repository
|
||||
api_client: TestClient, coresys: CoreSys, test_repository: Repository
|
||||
):
|
||||
"""Test remove a repository via POST /supervisor/options REST API."""
|
||||
assert repository.source in coresys.store.repository_urls
|
||||
assert repository.slug in coresys.store.repositories
|
||||
assert test_repository.source in coresys.store.repository_urls
|
||||
assert test_repository.slug in coresys.store.repositories
|
||||
|
||||
response = await api_client.post(
|
||||
"/supervisor/options", json={"addons_repositories": []}
|
||||
)
|
||||
|
||||
assert response.status == 200
|
||||
assert repository.source not in coresys.store.repository_urls
|
||||
assert repository.slug not in coresys.store.repositories
|
||||
assert test_repository.source not in coresys.store.repository_urls
|
||||
assert test_repository.slug not in coresys.store.repositories
|
||||
|
||||
|
||||
@pytest.mark.parametrize("git_error", [None, StoreGitError()])
|
||||
@ -87,8 +79,6 @@ async def test_api_supervisor_options_repositories_skipped_on_error(
|
||||
assert response.status == 400
|
||||
assert len(coresys.resolution.suggestions) == 0
|
||||
assert REPO_URL not in coresys.store.repository_urls
|
||||
with pytest.raises(StoreNotFound):
|
||||
coresys.store.get_from_url(REPO_URL)
|
||||
|
||||
|
||||
async def test_api_supervisor_options_repo_error_with_config_change(
|
||||
|
@ -591,7 +591,7 @@ def run_supervisor_state(request: pytest.FixtureRequest) -> Generator[MagicMock]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store_addon(coresys: CoreSys, tmp_path, repository):
|
||||
def store_addon(coresys: CoreSys, tmp_path, test_repository):
|
||||
"""Store add-on fixture."""
|
||||
addon_obj = AddonStore(coresys, "test_store_addon")
|
||||
|
||||
@ -604,18 +604,11 @@ def store_addon(coresys: CoreSys, tmp_path, repository):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def repository(coresys: CoreSys):
|
||||
"""Repository fixture."""
|
||||
coresys.store._data[ATTR_REPOSITORIES].remove(
|
||||
"https://github.com/hassio-addons/repository"
|
||||
)
|
||||
coresys.store._data[ATTR_REPOSITORIES].remove(
|
||||
"https://github.com/esphome/home-assistant-addon"
|
||||
)
|
||||
async def test_repository(coresys: CoreSys):
|
||||
"""Test add-on store repository fixture."""
|
||||
coresys.config._data[ATTR_ADDONS_CUSTOM_LIST] = []
|
||||
|
||||
with (
|
||||
patch("supervisor.store.validate.BUILTIN_REPOSITORIES", {"local", "core"}),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
):
|
||||
await coresys.store.load()
|
||||
@ -633,7 +626,7 @@ async def repository(coresys: CoreSys):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def install_addon_ssh(coresys: CoreSys, repository):
|
||||
async def install_addon_ssh(coresys: CoreSys, test_repository):
|
||||
"""Install local_ssh add-on."""
|
||||
store = coresys.addons.store[TEST_ADDON_SLUG]
|
||||
await coresys.addons.data.install(store)
|
||||
@ -645,7 +638,7 @@ async def install_addon_ssh(coresys: CoreSys, repository):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def install_addon_example(coresys: CoreSys, repository):
|
||||
async def install_addon_example(coresys: CoreSys, test_repository):
|
||||
"""Install local_example add-on."""
|
||||
store = coresys.addons.store["local_example"]
|
||||
await coresys.addons.data.install(store)
|
||||
|
@ -10,7 +10,7 @@ from supervisor.resolution.fixups.store_execute_remove import FixupStoreExecuteR
|
||||
from supervisor.store.repository import Repository
|
||||
|
||||
|
||||
async def test_fixup(coresys: CoreSys, repository: Repository):
|
||||
async def test_fixup(coresys: CoreSys, test_repository: Repository):
|
||||
"""Test fixup."""
|
||||
store_execute_remove = FixupStoreExecuteRemove(coresys)
|
||||
|
||||
@ -18,16 +18,20 @@ async def test_fixup(coresys: CoreSys, repository: Repository):
|
||||
|
||||
coresys.resolution.add_suggestion(
|
||||
Suggestion(
|
||||
SuggestionType.EXECUTE_REMOVE, ContextType.STORE, reference=repository.slug
|
||||
SuggestionType.EXECUTE_REMOVE,
|
||||
ContextType.STORE,
|
||||
reference=test_repository.slug,
|
||||
)
|
||||
)
|
||||
coresys.resolution.add_issue(
|
||||
Issue(
|
||||
IssueType.CORRUPT_REPOSITORY, ContextType.STORE, reference=repository.slug
|
||||
IssueType.CORRUPT_REPOSITORY,
|
||||
ContextType.STORE,
|
||||
reference=test_repository.slug,
|
||||
)
|
||||
)
|
||||
|
||||
with patch.object(type(repository), "remove") as remove_repo:
|
||||
with patch.object(type(test_repository), "remove") as remove_repo:
|
||||
await store_execute_remove()
|
||||
|
||||
assert remove_repo.called
|
||||
@ -36,4 +40,4 @@ async def test_fixup(coresys: CoreSys, repository: Repository):
|
||||
assert len(coresys.resolution.suggestions) == 0
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
assert repository.slug not in coresys.store.repositories
|
||||
assert test_repository.slug not in coresys.store.repositories
|
||||
|
@ -3,14 +3,14 @@
|
||||
from supervisor.coresys import CoreSys
|
||||
|
||||
|
||||
def test_local_store(coresys: CoreSys, repository) -> None:
|
||||
def test_local_store(coresys: CoreSys, test_repository) -> None:
|
||||
"""Test loading from local store."""
|
||||
assert coresys.store.get("local")
|
||||
|
||||
assert "local_ssh" in coresys.addons.store
|
||||
|
||||
|
||||
def test_core_store(coresys: CoreSys, repository) -> None:
|
||||
def test_core_store(coresys: CoreSys, test_repository) -> None:
|
||||
"""Test loading from core store."""
|
||||
assert coresys.store.get("core")
|
||||
|
||||
|
@ -15,11 +15,20 @@ from supervisor.exceptions import (
|
||||
StoreNotFound,
|
||||
)
|
||||
from supervisor.resolution.const import SuggestionType
|
||||
from supervisor.store import BUILTIN_REPOSITORIES, StoreManager
|
||||
from supervisor.store import StoreManager
|
||||
from supervisor.store.addon import AddonStore
|
||||
from supervisor.store.const import ALL_BUILTIN_REPOSITORIES
|
||||
from supervisor.store.repository import Repository
|
||||
|
||||
|
||||
def get_repository_by_url(store_manager: StoreManager, url: str) -> Repository:
|
||||
"""Test helper to get repository by URL."""
|
||||
for repository in store_manager.all:
|
||||
if repository.source == url:
|
||||
return repository
|
||||
raise StoreNotFound()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _auto_supervisor_internet(supervisor_internet):
|
||||
# Use the supervisor_internet fixture to ensure that all tests has internet access
|
||||
@ -33,7 +42,7 @@ async def test_add_valid_repository(
|
||||
"""Test add custom repository."""
|
||||
current = coresys.store.repository_urls
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
patch(
|
||||
"supervisor.utils.common.read_yaml_file",
|
||||
return_value={"name": "Awesome repository"},
|
||||
@ -45,7 +54,7 @@ async def test_add_valid_repository(
|
||||
else:
|
||||
await store_manager.add_repository("http://example.com")
|
||||
|
||||
assert store_manager.get_from_url("http://example.com").validate()
|
||||
assert get_repository_by_url(store_manager, "http://example.com").validate()
|
||||
|
||||
assert "http://example.com" in coresys.store.repository_urls
|
||||
|
||||
@ -54,17 +63,19 @@ async def test_add_invalid_repository(coresys: CoreSys, store_manager: StoreMana
|
||||
"""Test add invalid custom repository."""
|
||||
current = coresys.store.repository_urls
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
patch(
|
||||
"pathlib.Path.read_text",
|
||||
return_value="",
|
||||
),
|
||||
):
|
||||
await store_manager.update_repositories(
|
||||
current + ["http://example.com"], add_with_errors=True
|
||||
current + ["http://example.com"], issue_on_error=True
|
||||
)
|
||||
|
||||
assert not await store_manager.get_from_url("http://example.com").validate()
|
||||
assert not await get_repository_by_url(
|
||||
store_manager, "http://example.com"
|
||||
).validate()
|
||||
|
||||
assert "http://example.com" in coresys.store.repository_urls
|
||||
assert coresys.resolution.suggestions[-1].type == SuggestionType.EXECUTE_REMOVE
|
||||
@ -77,7 +88,7 @@ async def test_error_on_invalid_repository(
|
||||
"""Test invalid repository not added."""
|
||||
current = coresys.store.repository_urls
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
patch(
|
||||
"pathlib.Path.read_text",
|
||||
return_value="",
|
||||
@ -91,8 +102,6 @@ async def test_error_on_invalid_repository(
|
||||
|
||||
assert "http://example.com" not in coresys.store.repository_urls
|
||||
assert len(coresys.resolution.suggestions) == 0
|
||||
with pytest.raises(StoreNotFound):
|
||||
store_manager.get_from_url("http://example.com")
|
||||
|
||||
|
||||
async def test_add_invalid_repository_file(
|
||||
@ -101,7 +110,7 @@ async def test_add_invalid_repository_file(
|
||||
"""Test add invalid custom repository file."""
|
||||
current = coresys.store.repository_urls
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
patch(
|
||||
"pathlib.Path.read_text",
|
||||
return_value=json.dumps({"name": "Awesome repository"}),
|
||||
@ -109,10 +118,12 @@ async def test_add_invalid_repository_file(
|
||||
patch("pathlib.Path.exists", return_value=False),
|
||||
):
|
||||
await store_manager.update_repositories(
|
||||
current + ["http://example.com"], add_with_errors=True
|
||||
current + ["http://example.com"], issue_on_error=True
|
||||
)
|
||||
|
||||
assert not await store_manager.get_from_url("http://example.com").validate()
|
||||
assert not await get_repository_by_url(
|
||||
store_manager, "http://example.com"
|
||||
).validate()
|
||||
|
||||
assert "http://example.com" in coresys.store.repository_urls
|
||||
assert coresys.resolution.suggestions[-1].type == SuggestionType.EXECUTE_REMOVE
|
||||
@ -133,14 +144,13 @@ async def test_add_repository_with_git_error(
|
||||
):
|
||||
"""Test repo added with issue on git error."""
|
||||
current = coresys.store.repository_urls
|
||||
with patch("supervisor.store.repository.RepositoryGit.load", side_effect=git_error):
|
||||
with patch("supervisor.store.git.GitRepo.load", side_effect=git_error):
|
||||
await store_manager.update_repositories(
|
||||
current + ["http://example.com"], add_with_errors=True
|
||||
current + ["http://example.com"], issue_on_error=True
|
||||
)
|
||||
|
||||
assert "http://example.com" in coresys.store.repository_urls
|
||||
assert coresys.resolution.suggestions[-1].type == suggestion_type
|
||||
assert isinstance(store_manager.get_from_url("http://example.com"), Repository)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@ -161,7 +171,7 @@ async def test_error_on_repository_with_git_error(
|
||||
"""Test repo not added on git error."""
|
||||
current = coresys.store.repository_urls
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", side_effect=git_error),
|
||||
patch("supervisor.store.git.GitRepo.load", side_effect=git_error),
|
||||
pytest.raises(StoreError),
|
||||
):
|
||||
if use_update:
|
||||
@ -171,8 +181,6 @@ async def test_error_on_repository_with_git_error(
|
||||
|
||||
assert "http://example.com" not in coresys.store.repository_urls
|
||||
assert len(coresys.resolution.suggestions) == 0
|
||||
with pytest.raises(StoreNotFound):
|
||||
store_manager.get_from_url("http://example.com")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -180,8 +188,8 @@ async def test_preinstall_valid_repository(
|
||||
coresys: CoreSys, store_manager: StoreManager
|
||||
):
|
||||
"""Test add core repository valid."""
|
||||
with patch("supervisor.store.repository.RepositoryGit.load", return_value=None):
|
||||
await store_manager.update_repositories(BUILTIN_REPOSITORIES)
|
||||
with patch("supervisor.store.git.GitRepo.load", return_value=None):
|
||||
await store_manager.update_repositories(list(ALL_BUILTIN_REPOSITORIES))
|
||||
|
||||
def validate():
|
||||
assert store_manager.get("core").validate()
|
||||
@ -197,21 +205,21 @@ async def test_preinstall_valid_repository(
|
||||
async def test_remove_repository(
|
||||
coresys: CoreSys,
|
||||
store_manager: StoreManager,
|
||||
repository: Repository,
|
||||
test_repository: Repository,
|
||||
use_update: bool,
|
||||
):
|
||||
"""Test removing a custom repository."""
|
||||
assert repository.source in coresys.store.repository_urls
|
||||
assert repository.slug in coresys.store.repositories
|
||||
assert test_repository.source in coresys.store.repository_urls
|
||||
assert test_repository.slug in coresys.store.repositories
|
||||
|
||||
if use_update:
|
||||
await store_manager.update_repositories([])
|
||||
else:
|
||||
await store_manager.remove_repository(repository)
|
||||
await store_manager.remove_repository(test_repository)
|
||||
|
||||
assert repository.source not in coresys.store.repository_urls
|
||||
assert repository.slug not in coresys.addons.store
|
||||
assert repository.slug not in coresys.store.repositories
|
||||
assert test_repository.source not in coresys.store.repository_urls
|
||||
assert test_repository.slug not in coresys.addons.store
|
||||
assert test_repository.slug not in coresys.store.repositories
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_update", [True, False])
|
||||
@ -243,7 +251,7 @@ async def test_remove_used_repository(
|
||||
async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManager):
|
||||
"""Test partial error on update does partial save and errors."""
|
||||
with patch("supervisor.store.repository.RepositoryGit.validate", return_value=True):
|
||||
with patch("supervisor.store.repository.RepositoryGit.load", return_value=None):
|
||||
with patch("supervisor.store.git.GitRepo.load", return_value=None):
|
||||
await store_manager.update_repositories([])
|
||||
|
||||
store_manager.data.update.assert_called_once()
|
||||
@ -254,7 +262,7 @@ async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManage
|
||||
|
||||
with (
|
||||
patch(
|
||||
"supervisor.store.repository.RepositoryGit.load",
|
||||
"supervisor.store.git.GitRepo.load",
|
||||
side_effect=[None, StoreGitError()],
|
||||
),
|
||||
pytest.raises(StoreError),
|
||||
@ -268,27 +276,27 @@ async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManage
|
||||
|
||||
|
||||
async def test_error_adding_duplicate(
|
||||
coresys: CoreSys, store_manager: StoreManager, repository: Repository
|
||||
coresys: CoreSys, store_manager: StoreManager, test_repository: Repository
|
||||
):
|
||||
"""Test adding a duplicate repository causes an error."""
|
||||
assert repository.source in coresys.store.repository_urls
|
||||
assert test_repository.source in coresys.store.repository_urls
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.validate", return_value=True),
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
pytest.raises(StoreError),
|
||||
):
|
||||
await store_manager.add_repository(repository.source)
|
||||
await store_manager.add_repository(test_repository.source)
|
||||
|
||||
|
||||
async def test_add_with_update_repositories(
|
||||
coresys: CoreSys, store_manager: StoreManager, repository: Repository
|
||||
coresys: CoreSys, store_manager: StoreManager, test_repository: Repository
|
||||
):
|
||||
"""Test adding repositories to existing ones using update."""
|
||||
assert repository.source in coresys.store.repository_urls
|
||||
assert test_repository.source in coresys.store.repository_urls
|
||||
assert "http://example.com" not in coresys.store.repository_urls
|
||||
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
patch(
|
||||
"supervisor.utils.common.read_yaml_file",
|
||||
return_value={"name": "Awesome repository"},
|
||||
@ -297,7 +305,7 @@ async def test_add_with_update_repositories(
|
||||
):
|
||||
await store_manager.update_repositories(["http://example.com"], replace=False)
|
||||
|
||||
assert repository.source in coresys.store.repository_urls
|
||||
assert test_repository.source in coresys.store.repository_urls
|
||||
assert "http://example.com" in coresys.store.repository_urls
|
||||
|
||||
|
||||
@ -326,7 +334,7 @@ async def test_repositories_loaded_ignore_updates(
|
||||
):
|
||||
"""Test repositories loaded whether or not supervisor needs an update."""
|
||||
with (
|
||||
patch("supervisor.store.repository.RepositoryGit.load", return_value=None),
|
||||
patch("supervisor.store.git.GitRepo.load", return_value=None),
|
||||
patch.object(
|
||||
type(coresys.supervisor),
|
||||
"need_update",
|
||||
|
@ -203,7 +203,7 @@ async def test_update_unavailable_addon(
|
||||
)
|
||||
async def test_install_unavailable_addon(
|
||||
coresys: CoreSys,
|
||||
repository: Repository,
|
||||
test_repository: Repository,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
config: dict[str, Any],
|
||||
log: str,
|
||||
|
Loading…
x
Reference in New Issue
Block a user