mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-10 18:56:30 +00:00
Move repository urls to store settings file (#3665)
* Move repository urls to store settings file * Remove default repos from supervisor config * Fix clone at initial store load * Mock git load in repository fixture
This commit is contained in:
parent
deeaf2133b
commit
ccd2c31390
@ -35,7 +35,7 @@ from ..coresys import CoreSysAttributes
|
|||||||
from ..exceptions import APIError, APIForbidden
|
from ..exceptions import APIError, APIForbidden
|
||||||
from ..store.addon import AddonStore
|
from ..store.addon import AddonStore
|
||||||
from ..store.repository import Repository
|
from ..store.repository import Repository
|
||||||
from ..validate import validate_repository
|
from ..store.validate import validate_repository
|
||||||
|
|
||||||
SCHEMA_UPDATE = vol.Schema(
|
SCHEMA_UPDATE = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -46,8 +46,9 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
|
from ..store.validate import repositories
|
||||||
from ..utils.validate import validate_timezone
|
from ..utils.validate import validate_timezone
|
||||||
from ..validate import repositories, version_tag, wait_boot
|
from ..validate import version_tag, wait_boot
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@ -115,7 +116,7 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
ATTR_DEBUG_BLOCK: self.sys_config.debug_block,
|
ATTR_DEBUG_BLOCK: self.sys_config.debug_block,
|
||||||
ATTR_DIAGNOSTICS: self.sys_config.diagnostics,
|
ATTR_DIAGNOSTICS: self.sys_config.diagnostics,
|
||||||
ATTR_ADDONS: list_addons,
|
ATTR_ADDONS: list_addons,
|
||||||
ATTR_ADDONS_REPOSITORIES: self.sys_config.addons_repositories,
|
ATTR_ADDONS_REPOSITORIES: self.sys_store.repository_urls,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -497,18 +497,16 @@ class Backup(CoreSysAttributes):
|
|||||||
|
|
||||||
def store_repositories(self):
|
def store_repositories(self):
|
||||||
"""Store repository list into backup."""
|
"""Store repository list into backup."""
|
||||||
self.repositories = self.sys_config.addons_repositories
|
self.repositories = self.sys_store.repository_urls
|
||||||
|
|
||||||
async def restore_repositories(self, replace: bool = False):
|
async def restore_repositories(self, replace: bool = False):
|
||||||
"""Restore repositories from backup.
|
"""Restore repositories from backup.
|
||||||
|
|
||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
"""
|
"""
|
||||||
new_list: set[str] = set(self.repositories)
|
await self.sys_store.update_repositories(
|
||||||
if not replace:
|
self.repositories, add_with_errors=True, replace=replace
|
||||||
new_list.update(self.sys_config.addons_repositories)
|
)
|
||||||
|
|
||||||
await self.sys_store.update_repositories(list(new_list), add_with_errors=True)
|
|
||||||
|
|
||||||
def store_dockerconfig(self):
|
def store_dockerconfig(self):
|
||||||
"""Store the configuration for Docker."""
|
"""Store the configuration for Docker."""
|
||||||
|
@ -28,7 +28,8 @@ from ..const import (
|
|||||||
FOLDER_SHARE,
|
FOLDER_SHARE,
|
||||||
FOLDER_SSL,
|
FOLDER_SSL,
|
||||||
)
|
)
|
||||||
from ..validate import SCHEMA_DOCKER_CONFIG, repositories, version_tag
|
from ..store.validate import repositories
|
||||||
|
from ..validate import SCHEMA_DOCKER_CONFIG, version_tag
|
||||||
|
|
||||||
ALL_FOLDERS = [
|
ALL_FOLDERS = [
|
||||||
FOLDER_SHARE,
|
FOLDER_SHARE,
|
||||||
|
@ -327,3 +327,7 @@ class CoreConfig(FileConfiguration):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self._data[ATTR_ADDONS_CUSTOM_LIST].remove(repo)
|
self._data[ATTR_ADDONS_CUSTOM_LIST].remove(repo)
|
||||||
|
|
||||||
|
def clear_addons_repositories(self) -> None:
|
||||||
|
"""Clear custom repositories list from core config."""
|
||||||
|
self._data[ATTR_ADDONS_CUSTOM_LIST] = []
|
||||||
|
@ -52,7 +52,6 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict:
|
|||||||
"supervisor": {
|
"supervisor": {
|
||||||
"channel": coresys.updater.channel,
|
"channel": coresys.updater.channel,
|
||||||
"installed_addons": installed_addons,
|
"installed_addons": installed_addons,
|
||||||
"repositories": coresys.config.addons_repositories,
|
|
||||||
},
|
},
|
||||||
"host": {
|
"host": {
|
||||||
"arch": coresys.arch.default,
|
"arch": coresys.arch.default,
|
||||||
@ -79,6 +78,9 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict:
|
|||||||
],
|
],
|
||||||
"unhealthy": coresys.resolution.unhealthy,
|
"unhealthy": coresys.resolution.unhealthy,
|
||||||
},
|
},
|
||||||
|
"store": {
|
||||||
|
"repositories": coresys.store.repository_urls,
|
||||||
|
},
|
||||||
"misc": {
|
"misc": {
|
||||||
"fallback_dns": coresys.plugins.dns.fallback,
|
"fallback_dns": coresys.plugins.dns.fallback,
|
||||||
},
|
},
|
||||||
|
@ -29,14 +29,9 @@ class FixupStoreExecuteRemove(FixupBase):
|
|||||||
|
|
||||||
# Remove repository
|
# Remove repository
|
||||||
try:
|
try:
|
||||||
await repository.remove()
|
await self.sys_store.remove_repository(repository)
|
||||||
except StoreError:
|
except StoreError:
|
||||||
raise ResolutionFixupError() from None
|
raise ResolutionFixupError() from None
|
||||||
else:
|
|
||||||
self.sys_store.repositories.pop(repository.slug, None)
|
|
||||||
|
|
||||||
self.sys_config.drop_addon_repository(repository.source)
|
|
||||||
self.sys_config.save_data()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def suggestion(self) -> SuggestionType:
|
def suggestion(self) -> SuggestionType:
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ..const import URL_HASSIO_ADDONS
|
from supervisor.store.validate import SCHEMA_STORE_FILE
|
||||||
|
from supervisor.utils.common import FileConfiguration
|
||||||
|
|
||||||
|
from ..const import ATTR_REPOSITORIES, URL_HASSIO_ADDONS
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
StoreError,
|
StoreError,
|
||||||
@ -15,29 +18,43 @@ from ..exceptions import (
|
|||||||
from ..jobs.decorator import Job, JobCondition
|
from ..jobs.decorator import Job, JobCondition
|
||||||
from ..resolution.const import ContextType, IssueType, SuggestionType
|
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||||
from .addon import AddonStore
|
from .addon import AddonStore
|
||||||
from .const import StoreType
|
from .const import FILE_HASSIO_STORE, StoreType
|
||||||
from .data import StoreData
|
from .data import StoreData
|
||||||
from .repository import Repository
|
from .repository import Repository
|
||||||
|
from .validate import BUILTIN_REPOSITORIES, ensure_builtin_repositories
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
BUILTIN_REPOSITORIES = {StoreType.CORE.value, StoreType.LOCAL.value}
|
|
||||||
|
|
||||||
|
class StoreManager(CoreSysAttributes, FileConfiguration):
|
||||||
class StoreManager(CoreSysAttributes):
|
|
||||||
"""Manage add-ons inside Supervisor."""
|
"""Manage add-ons inside Supervisor."""
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize Docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
|
super().__init__(FILE_HASSIO_STORE, SCHEMA_STORE_FILE)
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.data = StoreData(coresys)
|
self.data = StoreData(coresys)
|
||||||
self.repositories: dict[str, Repository] = {}
|
self._repositories: dict[str, Repository] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all(self) -> list[Repository]:
|
def all(self) -> list[Repository]:
|
||||||
"""Return list of add-on repositories."""
|
"""Return list of add-on repositories."""
|
||||||
return list(self.repositories.values())
|
return list(self.repositories.values())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def repositories(self) -> dict[str, Repository]:
|
||||||
|
"""Return repositories dictionary."""
|
||||||
|
return self._repositories
|
||||||
|
|
||||||
|
@property
|
||||||
|
def repository_urls(self) -> list[str]:
|
||||||
|
"""Return source URL for all git repositories."""
|
||||||
|
return [
|
||||||
|
repository.url
|
||||||
|
for repository in self.all
|
||||||
|
if repository.type == StoreType.GIT
|
||||||
|
]
|
||||||
|
|
||||||
def get(self, slug: str) -> Repository:
|
def get(self, slug: str) -> Repository:
|
||||||
"""Return Repository with slug."""
|
"""Return Repository with slug."""
|
||||||
if slug not in self.repositories:
|
if slug not in self.repositories:
|
||||||
@ -56,11 +73,17 @@ class StoreManager(CoreSysAttributes):
|
|||||||
"""Start up add-on management."""
|
"""Start up add-on management."""
|
||||||
await self.data.update()
|
await self.data.update()
|
||||||
|
|
||||||
# Init Supervisor built-in repositories
|
# Backwards compatibility - Remove after 2022.9
|
||||||
repositories = set(self.sys_config.addons_repositories) | BUILTIN_REPOSITORIES
|
if len(self.sys_config.addons_repositories) > 0:
|
||||||
|
self._data[ATTR_REPOSITORIES] = ensure_builtin_repositories(
|
||||||
|
self.sys_config.addons_repositories
|
||||||
|
)
|
||||||
|
self.sys_config.clear_addons_repositories()
|
||||||
|
|
||||||
# Init custom repositories and load add-ons
|
# Init custom repositories and load add-ons
|
||||||
await self.update_repositories(repositories, add_with_errors=True)
|
await self.update_repositories(
|
||||||
|
self._data[ATTR_REPOSITORIES], add_with_errors=True
|
||||||
|
)
|
||||||
|
|
||||||
async def reload(self) -> None:
|
async def reload(self) -> None:
|
||||||
"""Update add-ons from repository and reload list."""
|
"""Update add-ons from repository and reload list."""
|
||||||
@ -144,9 +167,9 @@ class StoreManager(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Add Repository to list
|
# Add Repository to list
|
||||||
if repository.type == StoreType.GIT:
|
self._data[ATTR_REPOSITORIES].append(url)
|
||||||
self.sys_config.add_addon_repository(repository.source)
|
|
||||||
self.repositories[repository.slug] = repository
|
self.repositories[repository.slug] = repository
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
# Persist changes
|
# Persist changes
|
||||||
if persist:
|
if persist:
|
||||||
@ -155,7 +178,7 @@ class StoreManager(CoreSysAttributes):
|
|||||||
|
|
||||||
async def remove_repository(self, repository: Repository, *, persist: bool = True):
|
async def remove_repository(self, repository: Repository, *, persist: bool = True):
|
||||||
"""Remove a repository."""
|
"""Remove a repository."""
|
||||||
if repository.type != StoreType.GIT:
|
if repository.url in BUILTIN_REPOSITORIES:
|
||||||
raise StoreInvalidAddonRepo(
|
raise StoreInvalidAddonRepo(
|
||||||
"Can't remove built-in repositories!", logger=_LOGGER.error
|
"Can't remove built-in repositories!", logger=_LOGGER.error
|
||||||
)
|
)
|
||||||
@ -166,7 +189,8 @@ class StoreManager(CoreSysAttributes):
|
|||||||
logger=_LOGGER.error,
|
logger=_LOGGER.error,
|
||||||
)
|
)
|
||||||
await self.repositories.pop(repository.slug).remove()
|
await self.repositories.pop(repository.slug).remove()
|
||||||
self.sys_config.drop_addon_repository(repository.url)
|
self._data[ATTR_REPOSITORIES].remove(repository.url)
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
if persist:
|
if persist:
|
||||||
await self.data.update()
|
await self.data.update()
|
||||||
@ -174,10 +198,18 @@ class StoreManager(CoreSysAttributes):
|
|||||||
|
|
||||||
@Job(conditions=[JobCondition.INTERNET_SYSTEM])
|
@Job(conditions=[JobCondition.INTERNET_SYSTEM])
|
||||||
async def update_repositories(
|
async def update_repositories(
|
||||||
self, list_repositories, *, add_with_errors: bool = False
|
self,
|
||||||
|
list_repositories: list[str],
|
||||||
|
*,
|
||||||
|
add_with_errors: bool = False,
|
||||||
|
replace: bool = True,
|
||||||
):
|
):
|
||||||
"""Add a new custom repository."""
|
"""Add a new custom repository."""
|
||||||
new_rep = set(list_repositories)
|
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}
|
old_rep = {repository.source for repository in self.all}
|
||||||
|
|
||||||
# Add new repositories
|
# Add new repositories
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
"""Constants for the add-on store."""
|
"""Constants for the add-on store."""
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from supervisor.const import SUPERVISOR_DATA
|
||||||
|
|
||||||
|
FILE_HASSIO_STORE = Path(SUPERVISOR_DATA, "store.json")
|
||||||
|
|
||||||
|
|
||||||
class StoreType(str, Enum):
|
class StoreType(str, Enum):
|
||||||
|
@ -13,8 +13,8 @@ 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
|
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||||
from ..utils import remove_folder
|
from ..utils import remove_folder
|
||||||
from ..validate import RE_REPOSITORY
|
|
||||||
from .utils import get_hash_from_repository
|
from .utils import get_hash_from_repository
|
||||||
|
from .validate import RE_REPOSITORY
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ class Repository(CoreSysAttributes):
|
|||||||
self._type = StoreType.CORE
|
self._type = StoreType.CORE
|
||||||
else:
|
else:
|
||||||
self.git = GitRepoCustom(coresys, repository)
|
self.git = GitRepoCustom(coresys, repository)
|
||||||
self.source = repository
|
|
||||||
self._slug = get_hash_from_repository(repository)
|
self._slug = get_hash_from_repository(repository)
|
||||||
self._type = StoreType.GIT
|
self._type = StoreType.GIT
|
||||||
|
|
||||||
|
@ -2,7 +2,19 @@
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import ATTR_MAINTAINER, ATTR_NAME, ATTR_URL
|
from supervisor.store.const import StoreType
|
||||||
|
|
||||||
|
from ..const import ATTR_MAINTAINER, ATTR_NAME, ATTR_REPOSITORIES, ATTR_URL
|
||||||
|
from ..validate import RE_REPOSITORY
|
||||||
|
|
||||||
|
URL_COMMUNITY_ADDONS = "https://github.com/hassio-addons/repository"
|
||||||
|
URL_ESPHOME = "https://github.com/esphome/home-assistant-addon"
|
||||||
|
BUILTIN_REPOSITORIES = {
|
||||||
|
StoreType.CORE.value,
|
||||||
|
StoreType.LOCAL.value,
|
||||||
|
URL_COMMUNITY_ADDONS,
|
||||||
|
URL_ESPHOME,
|
||||||
|
}
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_REPOSITORY_CONFIG = vol.Schema(
|
SCHEMA_REPOSITORY_CONFIG = vol.Schema(
|
||||||
@ -13,3 +25,37 @@ SCHEMA_REPOSITORY_CONFIG = vol.Schema(
|
|||||||
},
|
},
|
||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_repository(repository: str) -> str:
|
||||||
|
"""Validate a valid repository."""
|
||||||
|
if repository in [StoreType.CORE.value, StoreType.LOCAL.value]:
|
||||||
|
return repository
|
||||||
|
|
||||||
|
data = RE_REPOSITORY.match(repository)
|
||||||
|
if not data:
|
||||||
|
raise vol.Invalid("No valid repository format!") from None
|
||||||
|
|
||||||
|
# Validate URL
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
vol.Url()(data.group("url"))
|
||||||
|
|
||||||
|
return repository
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_builtin_repositories(addon_repositories: list[str]) -> list[str]:
|
||||||
|
"""Ensure builtin repositories are in list."""
|
||||||
|
return list(set(addon_repositories) | BUILTIN_REPOSITORIES)
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
|
repositories = vol.All([validate_repository], vol.Unique(), ensure_builtin_repositories)
|
||||||
|
|
||||||
|
SCHEMA_STORE_FILE = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
ATTR_REPOSITORIES, default=list(BUILTIN_REPOSITORIES)
|
||||||
|
): repositories,
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
||||||
|
@ -41,6 +41,7 @@ from .const import (
|
|||||||
)
|
)
|
||||||
from .utils.validate import validate_timezone
|
from .utils.validate import validate_timezone
|
||||||
|
|
||||||
|
# Move to store.validate when addons_repository config removed
|
||||||
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
||||||
RE_REGISTRY = re.compile(r"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$")
|
RE_REGISTRY = re.compile(r"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$")
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ def dns_url(url: str) -> str:
|
|||||||
dns_server_list = vol.All(vol.Length(max=8), [dns_url])
|
dns_server_list = vol.All(vol.Length(max=8), [dns_url])
|
||||||
|
|
||||||
|
|
||||||
|
# Remove with addons_repositories config
|
||||||
def validate_repository(repository: str) -> str:
|
def validate_repository(repository: str) -> str:
|
||||||
"""Validate a valid repository."""
|
"""Validate a valid repository."""
|
||||||
data = RE_REPOSITORY.match(repository)
|
data = RE_REPOSITORY.match(repository)
|
||||||
@ -146,13 +148,7 @@ SCHEMA_SUPERVISOR_CONFIG = vol.Schema(
|
|||||||
ATTR_VERSION, default=AwesomeVersion(SUPERVISOR_VERSION)
|
ATTR_VERSION, default=AwesomeVersion(SUPERVISOR_VERSION)
|
||||||
): version_tag,
|
): version_tag,
|
||||||
vol.Optional(ATTR_IMAGE): docker_image,
|
vol.Optional(ATTR_IMAGE): docker_image,
|
||||||
vol.Optional(
|
vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[]): repositories,
|
||||||
ATTR_ADDONS_CUSTOM_LIST,
|
|
||||||
default=[
|
|
||||||
"https://github.com/hassio-addons/repository",
|
|
||||||
"https://github.com/esphome/home-assistant-addon",
|
|
||||||
],
|
|
||||||
): repositories,
|
|
||||||
vol.Optional(ATTR_WAIT_BOOT, default=5): wait_boot,
|
vol.Optional(ATTR_WAIT_BOOT, default=5): wait_boot,
|
||||||
vol.Optional(ATTR_LOGGING, default=LogLevel.INFO): vol.Coerce(LogLevel),
|
vol.Optional(ATTR_LOGGING, default=LogLevel.INFO): vol.Coerce(LogLevel),
|
||||||
vol.Optional(ATTR_DEBUG, default=False): vol.Boolean(),
|
vol.Optional(ATTR_DEBUG, default=False): vol.Boolean(),
|
||||||
|
@ -82,7 +82,7 @@ async def test_api_store_add_repository(api_client: TestClient, coresys: CoreSys
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert REPO_URL in coresys.config.addons_repositories
|
assert REPO_URL in coresys.store.repository_urls
|
||||||
assert isinstance(coresys.store.get_from_url(REPO_URL), Repository)
|
assert isinstance(coresys.store.get_from_url(REPO_URL), Repository)
|
||||||
|
|
||||||
|
|
||||||
@ -93,5 +93,5 @@ async def test_api_store_remove_repository(
|
|||||||
response = await api_client.delete(f"/store/repositories/{repository.slug}")
|
response = await api_client.delete(f"/store/repositories/{repository.slug}")
|
||||||
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert repository.url not in coresys.config.addons_repositories
|
assert repository.url not in coresys.store.repository_urls
|
||||||
assert repository.slug not in coresys.store.repositories
|
assert repository.slug not in coresys.store.repositories
|
||||||
|
@ -26,7 +26,7 @@ async def test_api_supervisor_options_add_repository(
|
|||||||
api_client: TestClient, coresys: CoreSys
|
api_client: TestClient, coresys: CoreSys
|
||||||
):
|
):
|
||||||
"""Test add a repository via POST /supervisor/options REST API."""
|
"""Test add a repository via POST /supervisor/options REST API."""
|
||||||
assert REPO_URL not in coresys.config.addons_repositories
|
assert REPO_URL not in coresys.store.repository_urls
|
||||||
with pytest.raises(StoreNotFound):
|
with pytest.raises(StoreNotFound):
|
||||||
coresys.store.get_from_url(REPO_URL)
|
coresys.store.get_from_url(REPO_URL)
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ async def test_api_supervisor_options_add_repository(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert REPO_URL in coresys.config.addons_repositories
|
assert REPO_URL in coresys.store.repository_urls
|
||||||
assert isinstance(coresys.store.get_from_url(REPO_URL), Repository)
|
assert isinstance(coresys.store.get_from_url(REPO_URL), Repository)
|
||||||
|
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ async def test_api_supervisor_options_remove_repository(
|
|||||||
api_client: TestClient, coresys: CoreSys, repository: Repository
|
api_client: TestClient, coresys: CoreSys, repository: Repository
|
||||||
):
|
):
|
||||||
"""Test remove a repository via POST /supervisor/options REST API."""
|
"""Test remove a repository via POST /supervisor/options REST API."""
|
||||||
assert repository.url in coresys.config.addons_repositories
|
assert repository.url in coresys.store.repository_urls
|
||||||
assert repository.slug in coresys.store.repositories
|
assert repository.slug in coresys.store.repositories
|
||||||
|
|
||||||
response = await api_client.post(
|
response = await api_client.post(
|
||||||
@ -54,7 +54,7 @@ async def test_api_supervisor_options_remove_repository(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert repository.url not in coresys.config.addons_repositories
|
assert repository.url not in coresys.store.repository_urls
|
||||||
assert repository.slug not in coresys.store.repositories
|
assert repository.slug not in coresys.store.repositories
|
||||||
|
|
||||||
|
|
||||||
@ -65,14 +65,18 @@ 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 patch(
|
with patch(
|
||||||
"supervisor.store.repository.Repository.load", side_effect=git_error
|
"supervisor.store.repository.Repository.load", side_effect=git_error
|
||||||
), patch("supervisor.store.repository.Repository.validate", return_value=False):
|
), patch(
|
||||||
|
"supervisor.store.repository.Repository.validate", return_value=False
|
||||||
|
), patch(
|
||||||
|
"supervisor.store.repository.Repository.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]}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status == 400
|
assert response.status == 400
|
||||||
assert len(coresys.resolution.suggestions) == 0
|
assert len(coresys.resolution.suggestions) == 0
|
||||||
assert REPO_URL not in coresys.config.addons_repositories
|
assert REPO_URL not in coresys.store.repository_urls
|
||||||
with pytest.raises(StoreNotFound):
|
with pytest.raises(StoreNotFound):
|
||||||
coresys.store.get_from_url(REPO_URL)
|
coresys.store.get_from_url(REPO_URL)
|
||||||
|
|
||||||
@ -92,7 +96,7 @@ async def test_api_supervisor_options_repo_error_with_config_change(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status == 400
|
assert response.status == 400
|
||||||
assert REPO_URL not in coresys.config.addons_repositories
|
assert REPO_URL not in coresys.store.repository_urls
|
||||||
|
|
||||||
assert coresys.config.debug
|
assert coresys.config.debug
|
||||||
coresys.updater.save_data.assert_called_once()
|
coresys.updater.save_data.assert_called_once()
|
||||||
|
@ -16,7 +16,7 @@ from supervisor import config as su_config
|
|||||||
from supervisor.addons.addon import Addon
|
from supervisor.addons.addon import Addon
|
||||||
from supervisor.api import RestAPI
|
from supervisor.api import RestAPI
|
||||||
from supervisor.bootstrap import initialize_coresys
|
from supervisor.bootstrap import initialize_coresys
|
||||||
from supervisor.const import REQUEST_FROM
|
from supervisor.const import ATTR_REPOSITORIES, REQUEST_FROM
|
||||||
from supervisor.coresys import CoreSys
|
from supervisor.coresys import CoreSys
|
||||||
from supervisor.dbus.agent import OSAgent
|
from supervisor.dbus.agent import OSAgent
|
||||||
from supervisor.dbus.const import DBUS_SIGNAL_NM_CONNECTION_ACTIVE_CHANGED
|
from supervisor.dbus.const import DBUS_SIGNAL_NM_CONNECTION_ACTIVE_CHANGED
|
||||||
@ -39,7 +39,6 @@ from .const import TEST_ADDON_SLUG
|
|||||||
|
|
||||||
async def mock_async_return_true() -> bool:
|
async def mock_async_return_true() -> bool:
|
||||||
"""Mock methods to return True."""
|
"""Mock methods to return True."""
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -74,7 +73,6 @@ def docker() -> DockerAPI:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def dbus() -> DBus:
|
def dbus() -> DBus:
|
||||||
"""Mock DBUS."""
|
"""Mock DBUS."""
|
||||||
|
|
||||||
dbus_commands = []
|
dbus_commands = []
|
||||||
|
|
||||||
async def mock_get_properties(dbus_obj, interface):
|
async def mock_get_properties(dbus_obj, interface):
|
||||||
@ -208,6 +206,7 @@ async def coresys(loop, docker, network_manager, aiohttp_client, run_dir) -> Cor
|
|||||||
coresys_obj._jobs.save_data = MagicMock()
|
coresys_obj._jobs.save_data = MagicMock()
|
||||||
coresys_obj._resolution.save_data = MagicMock()
|
coresys_obj._resolution.save_data = MagicMock()
|
||||||
coresys_obj._addons.data.save_data = MagicMock()
|
coresys_obj._addons.data.save_data = MagicMock()
|
||||||
|
coresys_obj._store.save_data = MagicMock()
|
||||||
|
|
||||||
# Mock test client
|
# Mock test client
|
||||||
coresys_obj.arch._default_arch = "amd64"
|
coresys_obj.arch._default_arch = "amd64"
|
||||||
@ -246,7 +245,9 @@ async def coresys(loop, docker, network_manager, aiohttp_client, run_dir) -> Cor
|
|||||||
unwrap(coresys_obj.updater.fetch_data), coresys_obj.updater
|
unwrap(coresys_obj.updater.fetch_data), coresys_obj.updater
|
||||||
)
|
)
|
||||||
|
|
||||||
yield coresys_obj
|
# Don't remove files/folders related to addons and stores
|
||||||
|
with patch("supervisor.store.git.GitRepo._remove"):
|
||||||
|
yield coresys_obj
|
||||||
|
|
||||||
await coresys_obj.websession.close()
|
await coresys_obj.websession.close()
|
||||||
|
|
||||||
@ -315,21 +316,29 @@ def store_addon(coresys: CoreSys, tmp_path, repository):
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def repository(coresys: CoreSys):
|
async def repository(coresys: CoreSys):
|
||||||
"""Repository fixture."""
|
"""Repository fixture."""
|
||||||
coresys.config.drop_addon_repository("https://github.com/hassio-addons/repository")
|
coresys.store._data[ATTR_REPOSITORIES].remove(
|
||||||
coresys.config.drop_addon_repository(
|
"https://github.com/hassio-addons/repository"
|
||||||
|
)
|
||||||
|
coresys.store._data[ATTR_REPOSITORIES].remove(
|
||||||
"https://github.com/esphome/home-assistant-addon"
|
"https://github.com/esphome/home-assistant-addon"
|
||||||
)
|
)
|
||||||
await coresys.store.load()
|
coresys.config.clear_addons_repositories()
|
||||||
repository_obj = Repository(
|
|
||||||
coresys, "https://github.com/awesome-developer/awesome-repo"
|
|
||||||
)
|
|
||||||
|
|
||||||
coresys.store.repositories[repository_obj.slug] = repository_obj
|
with patch(
|
||||||
coresys.config.add_addon_repository(
|
"supervisor.store.validate.BUILTIN_REPOSITORIES", {"local", "core"}
|
||||||
"https://github.com/awesome-developer/awesome-repo"
|
), patch("supervisor.store.git.GitRepo.load", return_value=None):
|
||||||
)
|
await coresys.store.load()
|
||||||
|
|
||||||
yield repository_obj
|
repository_obj = Repository(
|
||||||
|
coresys, "https://github.com/awesome-developer/awesome-repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
coresys.store.repositories[repository_obj.slug] = repository_obj
|
||||||
|
coresys.store._data[ATTR_REPOSITORIES].append(
|
||||||
|
"https://github.com/awesome-developer/awesome-repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
yield repository_obj
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
3
tests/fixtures/addons/git/5c53de3b/repository.yaml
vendored
Normal file
3
tests/fixtures/addons/git/5c53de3b/repository.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
name: ESPHome add-ons
|
||||||
|
url: "https://home-assistant.io"
|
||||||
|
maintainer: dev@ha.com
|
3
tests/fixtures/addons/git/a0d7b954/repository.yaml
vendored
Normal file
3
tests/fixtures/addons/git/a0d7b954/repository.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
name: Community add-ons
|
||||||
|
url: "https://home-assistant.io"
|
||||||
|
maintainer: dev@ha.com
|
@ -1,36 +1,34 @@
|
|||||||
"""Test evaluation base."""
|
"""Test evaluation base."""
|
||||||
# pylint: disable=import-error,protected-access
|
# pylint: disable=import-error,protected-access
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import patch
|
||||||
|
|
||||||
from supervisor.coresys import CoreSys
|
from supervisor.coresys import CoreSys
|
||||||
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.resolution.fixups.store_execute_remove import FixupStoreExecuteRemove
|
from supervisor.resolution.fixups.store_execute_remove import FixupStoreExecuteRemove
|
||||||
|
from supervisor.store.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
async def test_fixup(coresys: CoreSys):
|
async def test_fixup(coresys: CoreSys, repository: Repository):
|
||||||
"""Test fixup."""
|
"""Test fixup."""
|
||||||
store_execute_remove = FixupStoreExecuteRemove(coresys)
|
store_execute_remove = FixupStoreExecuteRemove(coresys)
|
||||||
|
|
||||||
assert store_execute_remove.auto is False
|
assert store_execute_remove.auto is False
|
||||||
|
|
||||||
coresys.resolution.suggestions = Suggestion(
|
coresys.resolution.suggestions = Suggestion(
|
||||||
SuggestionType.EXECUTE_REMOVE, ContextType.STORE, reference="test"
|
SuggestionType.EXECUTE_REMOVE, ContextType.STORE, reference=repository.slug
|
||||||
)
|
)
|
||||||
coresys.resolution.issues = Issue(
|
coresys.resolution.issues = Issue(
|
||||||
IssueType.CORRUPT_REPOSITORY, ContextType.STORE, reference="test"
|
IssueType.CORRUPT_REPOSITORY, ContextType.STORE, reference=repository.slug
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_repositorie = AsyncMock()
|
with patch.object(type(repository), "remove") as remove_repo:
|
||||||
mock_repositorie.slug = "test"
|
await store_execute_remove()
|
||||||
|
|
||||||
coresys.store.repositories["test"] = mock_repositorie
|
assert remove_repo.called
|
||||||
|
|
||||||
await store_execute_remove()
|
assert coresys.store.save_data.called
|
||||||
|
|
||||||
assert mock_repositorie.remove.called
|
|
||||||
assert coresys.config.save_data.called
|
|
||||||
assert len(coresys.resolution.suggestions) == 0
|
assert len(coresys.resolution.suggestions) == 0
|
||||||
assert len(coresys.resolution.issues) == 0
|
assert len(coresys.resolution.issues) == 0
|
||||||
|
|
||||||
assert "test" not in coresys.store.repositories
|
assert repository.slug not in coresys.store.repositories
|
||||||
|
@ -23,7 +23,7 @@ async def test_add_valid_repository(
|
|||||||
coresys: CoreSys, store_manager: StoreManager, use_update: bool
|
coresys: CoreSys, store_manager: StoreManager, use_update: bool
|
||||||
):
|
):
|
||||||
"""Test add custom repository."""
|
"""Test add custom repository."""
|
||||||
current = coresys.config.addons_repositories
|
current = coresys.store.repository_urls
|
||||||
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
||||||
"supervisor.utils.common.read_yaml_file",
|
"supervisor.utils.common.read_yaml_file",
|
||||||
return_value={"name": "Awesome repository"},
|
return_value={"name": "Awesome repository"},
|
||||||
@ -35,7 +35,7 @@ async def test_add_valid_repository(
|
|||||||
|
|
||||||
assert store_manager.get_from_url("http://example.com").validate()
|
assert store_manager.get_from_url("http://example.com").validate()
|
||||||
|
|
||||||
assert "http://example.com" in coresys.config.addons_repositories
|
assert "http://example.com" in coresys.store.repository_urls
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("use_update", [True, False])
|
@pytest.mark.parametrize("use_update", [True, False])
|
||||||
@ -43,7 +43,7 @@ async def test_add_invalid_repository(
|
|||||||
coresys: CoreSys, store_manager: StoreManager, use_update: bool
|
coresys: CoreSys, store_manager: StoreManager, use_update: bool
|
||||||
):
|
):
|
||||||
"""Test add invalid custom repository."""
|
"""Test add invalid custom repository."""
|
||||||
current = coresys.config.addons_repositories
|
current = coresys.store.repository_urls
|
||||||
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
||||||
"pathlib.Path.read_text",
|
"pathlib.Path.read_text",
|
||||||
return_value="",
|
return_value="",
|
||||||
@ -59,7 +59,7 @@ async def test_add_invalid_repository(
|
|||||||
|
|
||||||
assert not store_manager.get_from_url("http://example.com").validate()
|
assert not store_manager.get_from_url("http://example.com").validate()
|
||||||
|
|
||||||
assert "http://example.com" in coresys.config.addons_repositories
|
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
|
||||||
|
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ async def test_error_on_invalid_repository(
|
|||||||
coresys: CoreSys, store_manager: StoreManager, use_update
|
coresys: CoreSys, store_manager: StoreManager, use_update
|
||||||
):
|
):
|
||||||
"""Test invalid repository not added."""
|
"""Test invalid repository not added."""
|
||||||
current = coresys.config.addons_repositories
|
current = coresys.store.repository_urls
|
||||||
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
||||||
"pathlib.Path.read_text",
|
"pathlib.Path.read_text",
|
||||||
return_value="",
|
return_value="",
|
||||||
@ -78,7 +78,7 @@ async def test_error_on_invalid_repository(
|
|||||||
else:
|
else:
|
||||||
await store_manager.add_repository("http://example.com")
|
await store_manager.add_repository("http://example.com")
|
||||||
|
|
||||||
assert "http://example.com" not in coresys.config.addons_repositories
|
assert "http://example.com" not in coresys.store.repository_urls
|
||||||
assert len(coresys.resolution.suggestions) == 0
|
assert len(coresys.resolution.suggestions) == 0
|
||||||
with pytest.raises(StoreNotFound):
|
with pytest.raises(StoreNotFound):
|
||||||
store_manager.get_from_url("http://example.com")
|
store_manager.get_from_url("http://example.com")
|
||||||
@ -89,7 +89,7 @@ async def test_add_invalid_repository_file(
|
|||||||
coresys: CoreSys, store_manager: StoreManager, use_update: bool
|
coresys: CoreSys, store_manager: StoreManager, use_update: bool
|
||||||
):
|
):
|
||||||
"""Test add invalid custom repository file."""
|
"""Test add invalid custom repository file."""
|
||||||
current = coresys.config.addons_repositories
|
current = coresys.store.repository_urls
|
||||||
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
||||||
"pathlib.Path.read_text",
|
"pathlib.Path.read_text",
|
||||||
return_value=json.dumps({"name": "Awesome repository"}),
|
return_value=json.dumps({"name": "Awesome repository"}),
|
||||||
@ -105,7 +105,7 @@ async def test_add_invalid_repository_file(
|
|||||||
|
|
||||||
assert not store_manager.get_from_url("http://example.com").validate()
|
assert not store_manager.get_from_url("http://example.com").validate()
|
||||||
|
|
||||||
assert "http://example.com" in coresys.config.addons_repositories
|
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
|
||||||
|
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ async def test_add_repository_with_git_error(
|
|||||||
suggestion_type: SuggestionType,
|
suggestion_type: SuggestionType,
|
||||||
):
|
):
|
||||||
"""Test repo added with issue on git error."""
|
"""Test repo added with issue on git error."""
|
||||||
current = coresys.config.addons_repositories
|
current = coresys.store.repository_urls
|
||||||
with patch("supervisor.store.repository.Repository.load", side_effect=git_error):
|
with patch("supervisor.store.repository.Repository.load", side_effect=git_error):
|
||||||
if use_update:
|
if use_update:
|
||||||
await store_manager.update_repositories(
|
await store_manager.update_repositories(
|
||||||
@ -137,7 +137,7 @@ async def test_add_repository_with_git_error(
|
|||||||
"http://example.com", add_with_errors=True
|
"http://example.com", add_with_errors=True
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "http://example.com" in coresys.config.addons_repositories
|
assert "http://example.com" in coresys.store.repository_urls
|
||||||
assert coresys.resolution.suggestions[-1].type == suggestion_type
|
assert coresys.resolution.suggestions[-1].type == suggestion_type
|
||||||
assert isinstance(store_manager.get_from_url("http://example.com"), Repository)
|
assert isinstance(store_manager.get_from_url("http://example.com"), Repository)
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ async def test_error_on_repository_with_git_error(
|
|||||||
git_error: StoreGitError,
|
git_error: StoreGitError,
|
||||||
):
|
):
|
||||||
"""Test repo not added on git error."""
|
"""Test repo not added on git error."""
|
||||||
current = coresys.config.addons_repositories
|
current = coresys.store.repository_urls
|
||||||
with patch(
|
with patch(
|
||||||
"supervisor.store.repository.Repository.load", side_effect=git_error
|
"supervisor.store.repository.Repository.load", side_effect=git_error
|
||||||
), pytest.raises(StoreError):
|
), pytest.raises(StoreError):
|
||||||
@ -167,7 +167,7 @@ async def test_error_on_repository_with_git_error(
|
|||||||
else:
|
else:
|
||||||
await store_manager.add_repository("http://example.com")
|
await store_manager.add_repository("http://example.com")
|
||||||
|
|
||||||
assert "http://example.com" not in coresys.config.addons_repositories
|
assert "http://example.com" not in coresys.store.repository_urls
|
||||||
assert len(coresys.resolution.suggestions) == 0
|
assert len(coresys.resolution.suggestions) == 0
|
||||||
with pytest.raises(StoreNotFound):
|
with pytest.raises(StoreNotFound):
|
||||||
store_manager.get_from_url("http://example.com")
|
store_manager.get_from_url("http://example.com")
|
||||||
@ -182,6 +182,8 @@ async def test_preinstall_valid_repository(
|
|||||||
await store_manager.update_repositories(BUILTIN_REPOSITORIES)
|
await store_manager.update_repositories(BUILTIN_REPOSITORIES)
|
||||||
assert store_manager.get("core").validate()
|
assert store_manager.get("core").validate()
|
||||||
assert store_manager.get("local").validate()
|
assert store_manager.get("local").validate()
|
||||||
|
assert store_manager.get("a0d7b954").validate()
|
||||||
|
assert store_manager.get("5c53de3b").validate()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("use_update", [True, False])
|
@pytest.mark.parametrize("use_update", [True, False])
|
||||||
@ -192,7 +194,7 @@ async def test_remove_repository(
|
|||||||
use_update: bool,
|
use_update: bool,
|
||||||
):
|
):
|
||||||
"""Test removing a custom repository."""
|
"""Test removing a custom repository."""
|
||||||
assert repository.url in coresys.config.addons_repositories
|
assert repository.url in coresys.store.repository_urls
|
||||||
assert repository.slug in coresys.store.repositories
|
assert repository.slug in coresys.store.repositories
|
||||||
|
|
||||||
if use_update:
|
if use_update:
|
||||||
@ -200,7 +202,7 @@ async def test_remove_repository(
|
|||||||
else:
|
else:
|
||||||
await store_manager.remove_repository(repository)
|
await store_manager.remove_repository(repository)
|
||||||
|
|
||||||
assert repository.url not in coresys.config.addons_repositories
|
assert repository.url not in coresys.store.repository_urls
|
||||||
assert repository.slug not in coresys.addons.store
|
assert repository.slug not in coresys.addons.store
|
||||||
assert repository.slug not in coresys.store.repositories
|
assert repository.slug not in coresys.store.repositories
|
||||||
|
|
||||||
@ -233,15 +235,16 @@ 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."""
|
||||||
current = coresys.config.addons_repositories
|
|
||||||
initial = len(current)
|
|
||||||
with patch("supervisor.store.repository.Repository.validate", return_value=True):
|
with patch("supervisor.store.repository.Repository.validate", return_value=True):
|
||||||
with patch("supervisor.store.repository.Repository.load", return_value=None):
|
with patch("supervisor.store.repository.Repository.load", return_value=None):
|
||||||
await store_manager.update_repositories(current)
|
await store_manager.update_repositories([])
|
||||||
|
|
||||||
store_manager.data.update.assert_called_once()
|
store_manager.data.update.assert_called_once()
|
||||||
store_manager.data.update.reset_mock()
|
store_manager.data.update.reset_mock()
|
||||||
|
|
||||||
|
current = coresys.store.repository_urls
|
||||||
|
initial = len(current)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"supervisor.store.repository.Repository.load",
|
"supervisor.store.repository.Repository.load",
|
||||||
side_effect=[None, StoreGitError()],
|
side_effect=[None, StoreGitError()],
|
||||||
@ -250,7 +253,7 @@ async def test_update_partial_error(coresys: CoreSys, store_manager: StoreManage
|
|||||||
current + ["http://example.com", "http://example2.com"]
|
current + ["http://example.com", "http://example2.com"]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(coresys.config.addons_repositories) == initial + 1
|
assert len(coresys.store.repository_urls) == initial + 1
|
||||||
store_manager.data.update.assert_called_once()
|
store_manager.data.update.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
@ -258,7 +261,7 @@ async def test_error_adding_duplicate(
|
|||||||
coresys: CoreSys, store_manager: StoreManager, repository: Repository
|
coresys: CoreSys, store_manager: StoreManager, repository: Repository
|
||||||
):
|
):
|
||||||
"""Test adding a duplicate repository causes an error."""
|
"""Test adding a duplicate repository causes an error."""
|
||||||
assert repository.url in coresys.config.addons_repositories
|
assert repository.url in coresys.store.repository_urls
|
||||||
with patch(
|
with patch(
|
||||||
"supervisor.store.repository.Repository.validate", return_value=True
|
"supervisor.store.repository.Repository.validate", return_value=True
|
||||||
), patch(
|
), patch(
|
||||||
@ -267,3 +270,20 @@ async def test_error_adding_duplicate(
|
|||||||
StoreError
|
StoreError
|
||||||
):
|
):
|
||||||
await store_manager.add_repository(repository.url)
|
await store_manager.add_repository(repository.url)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_add_with_update_repositories(
|
||||||
|
coresys: CoreSys, store_manager: StoreManager, repository: Repository
|
||||||
|
):
|
||||||
|
"""Test adding repositories to existing ones using update."""
|
||||||
|
assert repository.url in coresys.store.repository_urls
|
||||||
|
assert "http://example.com" not in coresys.store.repository_urls
|
||||||
|
|
||||||
|
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
||||||
|
"supervisor.utils.common.read_yaml_file",
|
||||||
|
return_value={"name": "Awesome repository"},
|
||||||
|
), patch("pathlib.Path.exists", return_value=True):
|
||||||
|
await store_manager.update_repositories(["http://example.com"], replace=False)
|
||||||
|
|
||||||
|
assert repository.url in coresys.store.repository_urls
|
||||||
|
assert "http://example.com" in coresys.store.repository_urls
|
||||||
|
98
tests/store/test_store_manager.py
Normal file
98
tests/store/test_store_manager.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
"""Test store manager."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from supervisor.const import ATTR_ADDONS_CUSTOM_LIST
|
||||||
|
from supervisor.coresys import CoreSys
|
||||||
|
from supervisor.store import StoreManager
|
||||||
|
from supervisor.store.repository import Repository
|
||||||
|
|
||||||
|
|
||||||
|
async def test_default_load(coresys: CoreSys):
|
||||||
|
"""Test default load from config."""
|
||||||
|
store_manager = StoreManager(coresys)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"supervisor.store.repository.Repository.load", return_value=None
|
||||||
|
), patch.object(
|
||||||
|
type(coresys.config), "addons_repositories", return_value=[]
|
||||||
|
), patch(
|
||||||
|
"pathlib.Path.exists", return_value=True
|
||||||
|
):
|
||||||
|
await store_manager.load()
|
||||||
|
|
||||||
|
assert len(store_manager.all) == 4
|
||||||
|
assert isinstance(store_manager.get("core"), Repository)
|
||||||
|
assert isinstance(store_manager.get("local"), Repository)
|
||||||
|
|
||||||
|
assert len(store_manager.repository_urls) == 2
|
||||||
|
assert (
|
||||||
|
"https://github.com/hassio-addons/repository" in store_manager.repository_urls
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"https://github.com/esphome/home-assistant-addon"
|
||||||
|
in store_manager.repository_urls
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_with_custom_repository(coresys: CoreSys):
|
||||||
|
"""Test load from config with custom repository."""
|
||||||
|
with patch(
|
||||||
|
"supervisor.utils.common.read_json_or_yaml_file",
|
||||||
|
return_value={"repositories": ["http://example.com"]},
|
||||||
|
), patch("pathlib.Path.is_file", return_value=True):
|
||||||
|
store_manager = StoreManager(coresys)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"supervisor.store.repository.Repository.load", return_value=None
|
||||||
|
), patch.object(
|
||||||
|
type(coresys.config), "addons_repositories", return_value=[]
|
||||||
|
), patch(
|
||||||
|
"supervisor.store.repository.Repository.validate", return_value=True
|
||||||
|
), patch(
|
||||||
|
"pathlib.Path.exists", return_value=True
|
||||||
|
):
|
||||||
|
await store_manager.load()
|
||||||
|
|
||||||
|
assert len(store_manager.all) == 5
|
||||||
|
assert isinstance(store_manager.get("core"), Repository)
|
||||||
|
assert isinstance(store_manager.get("local"), Repository)
|
||||||
|
|
||||||
|
assert len(store_manager.repository_urls) == 3
|
||||||
|
assert (
|
||||||
|
"https://github.com/hassio-addons/repository" in store_manager.repository_urls
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"https://github.com/esphome/home-assistant-addon"
|
||||||
|
in store_manager.repository_urls
|
||||||
|
)
|
||||||
|
assert "http://example.com" in store_manager.repository_urls
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_from_core_config(coresys: CoreSys):
|
||||||
|
"""Test custom repositories loaded from core config when present."""
|
||||||
|
store_manager = StoreManager(coresys)
|
||||||
|
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
coresys.config._data[ATTR_ADDONS_CUSTOM_LIST] = ["http://example.com"]
|
||||||
|
assert coresys.config.addons_repositories == ["http://example.com"]
|
||||||
|
|
||||||
|
with patch("supervisor.store.repository.Repository.load", return_value=None), patch(
|
||||||
|
"supervisor.store.repository.Repository.validate", return_value=True
|
||||||
|
), patch("pathlib.Path.exists", return_value=True):
|
||||||
|
await store_manager.load()
|
||||||
|
|
||||||
|
assert len(store_manager.all) == 5
|
||||||
|
assert isinstance(store_manager.get("core"), Repository)
|
||||||
|
assert isinstance(store_manager.get("local"), Repository)
|
||||||
|
|
||||||
|
assert len(store_manager.repository_urls) == 3
|
||||||
|
assert (
|
||||||
|
"https://github.com/hassio-addons/repository" in store_manager.repository_urls
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"https://github.com/esphome/home-assistant-addon"
|
||||||
|
in store_manager.repository_urls
|
||||||
|
)
|
||||||
|
assert "http://example.com" in store_manager.repository_urls
|
||||||
|
|
||||||
|
assert coresys.config.addons_repositories == []
|
58
tests/store/test_validate.py
Normal file
58
tests/store/test_validate.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""Test schema validation."""
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from voluptuous import Invalid
|
||||||
|
|
||||||
|
from supervisor.const import ATTR_REPOSITORIES
|
||||||
|
from supervisor.store.validate import SCHEMA_STORE_FILE, repositories
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config",
|
||||||
|
[
|
||||||
|
{},
|
||||||
|
{ATTR_REPOSITORIES: []},
|
||||||
|
{ATTR_REPOSITORIES: ["https://github.com/esphome/home-assistant-addon"]},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_default_config(config: dict[Any]):
|
||||||
|
"""Test built-ins included by default."""
|
||||||
|
conf = SCHEMA_STORE_FILE(config)
|
||||||
|
assert ATTR_REPOSITORIES in conf
|
||||||
|
assert "core" in conf[ATTR_REPOSITORIES]
|
||||||
|
assert "local" in conf[ATTR_REPOSITORIES]
|
||||||
|
assert "https://github.com/hassio-addons/repository" in conf[ATTR_REPOSITORIES]
|
||||||
|
assert 1 == len(
|
||||||
|
[
|
||||||
|
repo
|
||||||
|
for repo in conf[ATTR_REPOSITORIES]
|
||||||
|
if repo == "https://github.com/esphome/home-assistant-addon"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"repo_list,valid",
|
||||||
|
[
|
||||||
|
([], True),
|
||||||
|
(["core", "local"], True),
|
||||||
|
(["https://github.com/hassio-addons/repository"], True),
|
||||||
|
(["not_a_url"], False),
|
||||||
|
(["https://fail.com/duplicate", "https://fail.com/duplicate"], False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_repository_validate(repo_list: list[str], valid: bool):
|
||||||
|
"""Test repository list validate."""
|
||||||
|
if valid:
|
||||||
|
processed = repositories(repo_list)
|
||||||
|
assert len(processed) == 4
|
||||||
|
assert set(repositories(repo_list)) == {
|
||||||
|
"core",
|
||||||
|
"local",
|
||||||
|
"https://github.com/hassio-addons/repository",
|
||||||
|
"https://github.com/esphome/home-assistant-addon",
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
with pytest.raises(Invalid):
|
||||||
|
repositories(repo_list)
|
Loading…
x
Reference in New Issue
Block a user