Complete official repository support (#59)

This commit is contained in:
Evgenii Alekseev 2022-05-03 00:49:32 +03:00 committed by GitHub
parent 5030395025
commit 571f720ae2
68 changed files with 1206 additions and 407 deletions

View File

@ -22,7 +22,7 @@ This package contains application (aka executable) related classes and everythin
This package contains everything which is required for any time of application run and separated to several packages: This package contains everything which is required for any time of application run and separated to several packages:
* `ahriman.core.alpm` package controls pacman related functions. It provides wrappers for `pyalpm` library and safe calls for repository tools (`repo-add` and `repo-remove`). * `ahriman.core.alpm` package controls pacman related functions. It provides wrappers for `pyalpm` library and safe calls for repository tools (`repo-add` and `repo-remove`). Also this package contains `ahriman.core.alpm.remote` package which provides wrapper for remote sources (e.g. AUR RPC and official repositories RPC).
* `ahriman.core.auth` package provides classes for authorization methods used by web mostly. Base class is `ahriman.core.auth.auth.Auth` which must be called by `load` method. * `ahriman.core.auth` package provides classes for authorization methods used by web mostly. Base class is `ahriman.core.auth.auth.Auth` which must be called by `load` method.
* `ahriman.core.build_tools` is a package which provides wrapper for `devtools` commands. * `ahriman.core.build_tools` is a package which provides wrapper for `devtools` commands.
* `ahriman.core.database` is everything including data and schema migrations for database. * `ahriman.core.database` is everything including data and schema migrations for database.

View File

@ -23,7 +23,6 @@ Base configuration settings.
libalpm and AUR related configuration. libalpm and AUR related configuration.
* `aur_url` - base url for AUR, string, required.
* `database` - path to pacman local database cache, string, required. * `database` - path to pacman local database cache, string, required.
* `repositories` - list of pacman repositories, space separated list of strings, required. * `repositories` - list of pacman repositories, space separated list of strings, required.
* `root` - root for alpm library, string, required. * `root` - root for alpm library, string, required.

View File

@ -113,6 +113,14 @@ Well it is supported also.
The last command will calculate diff from current tree to the `HEAD` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management). The last command will calculate diff from current tree to the `HEAD` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
### Hey, I would like to rebuild the official repository package
So it is the same as adding any other package, but due to restrictions you must specify source explicitly, e.g.:
```shell
sudo -u ahriman ahriman package-add pacmann -s repository
```
### Package build fails because it cannot validate PGP signature of source files ### Package build fails because it cannot validate PGP signature of source files
TL;DR TL;DR

View File

@ -4,7 +4,6 @@ logging = ahriman.ini.d/logging.ini
database = /var/lib/ahriman/ahriman.db database = /var/lib/ahriman/ahriman.db
[alpm] [alpm]
aur_url = https://aur.archlinux.org
database = /var/lib/pacman database = /var/lib/pacman
repositories = core extra community multilib repositories = core extra community multilib
root = / root = /

View File

@ -74,7 +74,7 @@
{% for package in packages %} {% for package in packages %}
<tr data-package-base="{{ package.base }}"> <tr data-package-base="{{ package.base }}">
<td data-checkbox="true"></td> <td data-checkbox="true"></td>
<td><a href="{{ package.web_url }}" title="{{ package.base }}">{{ package.base }}</a></td> <td>{% if package.web_url is not none %}<a href="{{ package.web_url }}" title="{{ package.base }}">{{ package.base }}</a>{% else %}{{ package.base }}{% endif %}</td>
<td>{{ package.version }}</td> <td>{{ package.version }}</td>
<td>{{ package.packages|join("<br>"|safe) }}</td> <td>{{ package.packages|join("<br>"|safe) }}</td>
<td>{{ package.groups|join("<br>"|safe) }}</td> <td>{{ package.groups|join("<br>"|safe) }}</td>

View File

@ -80,12 +80,13 @@ class ApplicationPackages(ApplicationProperties):
known_packages(Set[str]): list of packages which are known by the service known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled without_dependencies(bool): if set, dependency check will be disabled
""" """
package = Package.load(source, PackageSource.AUR, self.repository.pacman, self.repository.aur_url) package = Package.from_aur(source, self.repository.pacman)
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
self.database.remote_update(package)
with tmpdir() as local_path: with tmpdir() as local_path:
Sources.load(local_path, package.git_url, self.database.patches_get(package.base)) Sources.load(local_path, package.remote, self.database.patches_get(package.base))
self._process_dependencies(local_path, known_packages, without_dependencies) self._process_dependencies(local_path, known_packages, without_dependencies)
def _add_directory(self, source: str, *_: Any) -> None: def _add_directory(self, source: str, *_: Any) -> None:
@ -108,9 +109,10 @@ class ApplicationPackages(ApplicationProperties):
known_packages(Set[str]): list of packages which are known by the service known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled without_dependencies(bool): if set, dependency check will be disabled
""" """
package = Package.load(source, PackageSource.Local, self.repository.pacman, self.repository.aur_url) source_dir = Path(source)
package = Package.from_build(source_dir)
cache_dir = self.repository.paths.cache_for(package.base) cache_dir = self.repository.paths.cache_for(package.base)
shutil.copytree(Path(source), cache_dir) # copy package to store in caches shutil.copytree(source_dir, cache_dir) # copy package to store in caches
Sources.init(cache_dir) # we need to run init command in directory where we do have permissions Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
self.database.build_queue_insert(package) self.database.build_queue_insert(package)
@ -132,6 +134,18 @@ class ApplicationPackages(ApplicationProperties):
for chunk in response.iter_content(chunk_size=1024): for chunk in response.iter_content(chunk_size=1024):
local_file.write(chunk) local_file.write(chunk)
def _add_repository(self, source: str, *_: Any) -> None:
"""
add package from official repository
Args:
source(str): package base name
"""
package = Package.from_official(source, self.repository.pacman)
self.database.build_queue_insert(package)
self.database.remote_update(package)
# repository packages must not depend on unknown packages, thus we are not going to process dependencies
def _process_dependencies(self, local_path: Path, known_packages: Set[str], without_dependencies: bool) -> None: def _process_dependencies(self, local_path: Path, known_packages: Set[str], without_dependencies: bool) -> None:
""" """
process package dependencies process package dependencies

View File

@ -128,14 +128,14 @@ class ApplicationRepository(ApplicationProperties):
packages: List[str] = [] packages: List[str] = []
for single in probe.packages: for single in probe.packages:
try: try:
_ = Package.from_aur(single, probe.aur_url) _ = Package.from_aur(single, self.repository.pacman)
except Exception: except Exception:
packages.append(single) packages.append(single)
return packages return packages
def unknown_local(probe: Package) -> List[str]: def unknown_local(probe: Package) -> List[str]:
cache_dir = self.repository.paths.cache_for(probe.base) cache_dir = self.repository.paths.cache_for(probe.base)
local = Package.from_build(cache_dir, probe.aur_url) local = Package.from_build(cache_dir)
packages = set(probe.packages.keys()).difference(local.packages.keys()) packages = set(probe.packages.keys()).difference(local.packages.keys())
return list(packages) return list(packages)

View File

@ -29,7 +29,6 @@ from ahriman.core.configuration import Configuration
from ahriman.core.formatters.string_printer import StringPrinter from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.action import Action from ahriman.models.action import Action
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
class Patch(Handler): class Patch(Handler):
@ -57,21 +56,20 @@ class Patch(Handler):
elif args.action == Action.Remove: elif args.action == Action.Remove:
Patch.patch_set_remove(application, args.package) Patch.patch_set_remove(application, args.package)
elif args.action == Action.Update: elif args.action == Action.Update:
Patch.patch_set_create(application, args.package, args.track) Patch.patch_set_create(application, Path(args.package), args.track)
@staticmethod @staticmethod
def patch_set_create(application: Application, sources_dir: str, track: List[str]) -> None: def patch_set_create(application: Application, sources_dir: Path, track: List[str]) -> None:
""" """
create patch set for the package base create patch set for the package base
Args: Args:
application(Application): application instance application(Application): application instance
sources_dir(str): path to directory with the package sources sources_dir(Path): path to directory with the package sources
track(List[str]): track files which match the glob before creating the patch track(List[str]): track files which match the glob before creating the patch
""" """
package = Package.load(sources_dir, PackageSource.Local, application.repository.pacman, package = Package.from_build(sources_dir)
application.repository.aur_url) patch = Sources.patch_create(sources_dir, *track)
patch = Sources.patch_create(Path(sources_dir), *track)
application.database.patches_insert(package.base, patch) application.database.patches_insert(package.base, patch)
@staticmethod @staticmethod

View File

@ -22,6 +22,7 @@ import argparse
from dataclasses import fields from dataclasses import fields
from typing import Callable, Iterable, List, Tuple, Type from typing import Callable, Iterable, List, Tuple, Type
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler from ahriman.application.handlers.handler import Handler
from ahriman.core.alpm.remote.aur import AUR from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official from ahriman.core.alpm.remote.official import Official
@ -55,8 +56,10 @@ class Search(Handler):
no_report(bool): force disable reporting no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation unsafe(bool): if set no user check will be performed before path creation
""" """
official_packages_list = Official.multisearch(*args.search) application = Application(architecture, configuration, no_report, unsafe)
aur_packages_list = AUR.multisearch(*args.search)
official_packages_list = Official.multisearch(*args.search, pacman=application.repository.pacman)
aur_packages_list = AUR.multisearch(*args.search, pacman=application.repository.pacman)
Search.check_if_empty(args.exit_code, not official_packages_list and not aur_packages_list) Search.check_if_empty(args.exit_code, not official_packages_list and not aur_packages_list)
for packages_list in (official_packages_list, aur_packages_list): for packages_list in (official_packages_list, aur_packages_list):

View File

@ -17,8 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from pyalpm import Handle # type: ignore from pyalpm import Handle, Package, SIG_PACKAGE # type: ignore
from typing import Set from typing import Generator, Set
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -42,7 +42,7 @@ class Pacman:
pacman_root = configuration.getpath("alpm", "database") pacman_root = configuration.getpath("alpm", "database")
self.handle = Handle(root, str(pacman_root)) self.handle = Handle(root, str(pacman_root))
for repository in configuration.getlist("alpm", "repositories"): for repository in configuration.getlist("alpm", "repositories"):
self.handle.register_syncdb(repository, 0) # 0 is pgp_level self.handle.register_syncdb(repository, SIG_PACKAGE)
def all_packages(self) -> Set[str]: def all_packages(self) -> Set[str]:
""" """
@ -58,3 +58,19 @@ class Pacman:
result.update(package.provides) # provides list for meta-packages result.update(package.provides) # provides list for meta-packages
return result return result
def get(self, package_name: str) -> Generator[Package, None, None]:
"""
retrieve list of the packages from the repository by name
Args:
package_name(str): package name to search
Yields:
Package: list of packages which were returned by the query
"""
for database in self.handle.get_syncdbs():
package = database.get_pkg(package_name)
if package is None:
continue
yield package

View File

@ -19,8 +19,9 @@
# #
import requests import requests
from typing import Any, Dict, List, Optional from typing import Any, Dict, List
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote from ahriman.core.alpm.remote.remote import Remote
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text from ahriman.core.util import exception_response_text
@ -32,27 +33,15 @@ class AUR(Remote):
AUR RPC wrapper AUR RPC wrapper
Attributes: Attributes:
DEFAULT_AUR_URL(str): (class attribute) default AUR url
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
rpc_url(str): AUR RPC url
rpc_version(str): AUR RPC version
""" """
DEFAULT_RPC_URL = "https://aur.archlinux.org/rpc" DEFAULT_AUR_URL = "https://aur.archlinux.org"
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc"
DEFAULT_RPC_VERSION = "5" DEFAULT_RPC_VERSION = "5"
def __init__(self, rpc_url: Optional[str] = None, rpc_version: Optional[str] = None) -> None:
"""
default constructor
Args:
rpc_url(Optional[str], optional): AUR RPC url (Default value = None)
rpc_version(Optional[str], optional): AUR RPC version (Default value = None)
"""
Remote.__init__(self)
self.rpc_url = rpc_url or self.DEFAULT_RPC_URL
self.rpc_version = rpc_version or self.DEFAULT_RPC_VERSION
@staticmethod @staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]: def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
""" """
@ -73,6 +62,33 @@ class AUR(Remote):
raise InvalidPackageInfo(error_details) raise InvalidPackageInfo(error_details)
return [AURPackage.from_json(package) for package in response["results"]] return [AURPackage.from_json(package) for package in response["results"]]
@staticmethod
def remote_git_url(package_base: str, repository: str) -> str:
"""
generate remote git url from the package base
Args
package_base(str): package base
repository(str): repository name
Returns:
str: git url for the specific base
"""
return f"{AUR.DEFAULT_AUR_URL}/{package_base}.git"
@staticmethod
def remote_web_url(package_base: str) -> str:
"""
generate remote web url from the package base
Args
package_base(str): package base
Returns:
str: web url for the specific base
"""
return f"{AUR.DEFAULT_AUR_URL}/packages/{package_base}"
def make_request(self, request_type: str, *args: str, **kwargs: str) -> List[AURPackage]: def make_request(self, request_type: str, *args: str, **kwargs: str) -> List[AURPackage]:
""" """
perform request to AUR RPC perform request to AUR RPC
@ -87,7 +103,7 @@ class AUR(Remote):
""" """
query: Dict[str, Any] = { query: Dict[str, Any] = {
"type": request_type, "type": request_type,
"v": self.rpc_version "v": self.DEFAULT_RPC_VERSION
} }
arg_query = "arg[]" if len(args) > 1 else "arg" arg_query = "arg[]" if len(args) > 1 else "arg"
@ -97,7 +113,7 @@ class AUR(Remote):
query[key] = value query[key] = value
try: try:
response = requests.get(self.rpc_url, params=query) response = requests.get(self.DEFAULT_RPC_URL, params=query)
response.raise_for_status() response.raise_for_status()
return self.parse_response(response.json()) return self.parse_response(response.json())
except requests.HTTPError as e: except requests.HTTPError as e:
@ -110,12 +126,13 @@ class AUR(Remote):
self.logger.exception("could not perform request by using type %s", request_type) self.logger.exception("could not perform request by using type %s", request_type)
raise raise
def package_info(self, package_name: str) -> AURPackage: def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
""" """
get package info by its name get package info by its name
Args: Args:
package_name(str): package name to search package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns: Returns:
AURPackage: package which match the package name AURPackage: package which match the package name
@ -123,12 +140,13 @@ class AUR(Remote):
packages = self.make_request("info", package_name) packages = self.make_request("info", package_name)
return next(package for package in packages if package.name == package_name) return next(package for package in packages if package.name == package_name)
def package_search(self, *keywords: str) -> List[AURPackage]: def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
Args: Args:
*keywords(str): keywords to search *keywords(str): keywords to search
pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria List[AURPackage]: list of packages which match the criteria

View File

@ -19,8 +19,9 @@
# #
import requests import requests
from typing import Any, Dict, List, Optional from typing import Any, Dict, List
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote from ahriman.core.alpm.remote.remote import Remote
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text from ahriman.core.util import exception_response_text
@ -32,22 +33,15 @@ class Official(Remote):
official repository RPC wrapper official repository RPC wrapper
Attributes: Attributes:
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url
rpc_url(str): AUR RPC url DEFAULT_SEARCH_REPOSITORIES(List[str]): (class attribute) default list of repositories to search
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
""" """
DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib", "Community"]
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json" DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
def __init__(self, rpc_url: Optional[str] = None) -> None:
"""
default constructor
Args:
rpc_url(Optional[str], optional): AUR RPC url (Default value = None)
"""
Remote.__init__(self)
self.rpc_url = rpc_url or self.DEFAULT_RPC_URL
@staticmethod @staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]: def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
""" """
@ -66,6 +60,35 @@ class Official(Remote):
raise InvalidPackageInfo("API validation error") raise InvalidPackageInfo("API validation error")
return [AURPackage.from_repo(package) for package in response["results"]] return [AURPackage.from_repo(package) for package in response["results"]]
@staticmethod
def remote_git_url(package_base: str, repository: str) -> str:
"""
generate remote git url from the package base
Args
package_base(str): package base
repository(str): repository name
Returns:
str: git url for the specific base
"""
if repository.lower() in ("core", "extra", "testing", "kde-unstable"):
return "https://github.com/archlinux/svntogit-packages.git" # hardcoded, ok
return "https://github.com/archlinux/svntogit-community.git"
@staticmethod
def remote_web_url(package_base: str) -> str:
"""
generate remote web url from the package base
Args
package_base(str): package base
Returns:
str: web url for the specific base
"""
return f"{Official.DEFAULT_ARCHLINUX_URL}/packages/{package_base}"
def make_request(self, *args: str, by: str) -> List[AURPackage]: def make_request(self, *args: str, by: str) -> List[AURPackage]:
""" """
perform request to official repositories RPC perform request to official repositories RPC
@ -78,7 +101,7 @@ class Official(Remote):
List[AURPackage]: response parsed to package list List[AURPackage]: response parsed to package list
""" """
try: try:
response = requests.get(self.rpc_url, params={by: args}) response = requests.get(self.DEFAULT_RPC_URL, params={by: args, "repo": self.DEFAULT_SEARCH_REPOSITORIES})
response.raise_for_status() response.raise_for_status()
return self.parse_response(response.json()) return self.parse_response(response.json())
except requests.HTTPError as e: except requests.HTTPError as e:
@ -88,12 +111,13 @@ class Official(Remote):
self.logger.exception("could not perform request") self.logger.exception("could not perform request")
raise raise
def package_info(self, package_name: str) -> AURPackage: def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
""" """
get package info by its name get package info by its name
Args: Args:
package_name(str): package name to search package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns: Returns:
AURPackage: package which match the package name AURPackage: package which match the package name
@ -101,12 +125,13 @@ class Official(Remote):
packages = self.make_request(package_name, by="name") packages = self.make_request(package_name, by="name")
return next(package for package in packages if package.name == package_name) return next(package for package in packages if package.name == package_name)
def package_search(self, *keywords: str) -> List[AURPackage]: def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
Args: Args:
*keywords(str): keywords to search *keywords(str): keywords to search
pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria List[AURPackage]: list of packages which match the criteria

View File

@ -0,0 +1,51 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.official import Official
from ahriman.models.aur_package import AURPackage
class OfficialSyncdb(Official):
"""
official repository wrapper based on synchronized databases.
Despite the fact that official repository provides an API for the interaction according to the comment in issue
https://github.com/arcan1s/ahriman/pull/59#issuecomment-1106412297 we might face rate limits while requesting
updates.
This approach also has limitations, because we don't require superuser rights (neither going to download database
separately), the database file might be outdated and must be handled manually (or kind of). This behaviour might be
changed in the future.
Still we leave search function based on the official repositories RPC.
"""
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
"""
get package info by its name
Args:
package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
"""
return next(AURPackage.from_pacman(package) for package in pacman.get(package_name))

View File

@ -23,6 +23,7 @@ import logging
from typing import Dict, List, Type from typing import Dict, List, Type
from ahriman.core.alpm.pacman import Pacman
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
@ -41,26 +42,28 @@ class Remote:
self.logger = logging.getLogger("build_details") self.logger = logging.getLogger("build_details")
@classmethod @classmethod
def info(cls: Type[Remote], package_name: str) -> AURPackage: def info(cls: Type[Remote], package_name: str, *, pacman: Pacman) -> AURPackage:
""" """
get package info by its name get package info by its name
Args: Args:
package_name(str): package name to search package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns: Returns:
AURPackage: package which match the package name AURPackage: package which match the package name
""" """
return cls().package_info(package_name) return cls().package_info(package_name, pacman=pacman)
@classmethod @classmethod
def multisearch(cls: Type[Remote], *keywords: str) -> List[AURPackage]: def multisearch(cls: Type[Remote], *keywords: str, pacman: Pacman) -> List[AURPackage]:
""" """
search in remote repository by using API with multiple words. This method is required in order to handle search in remote repository by using API with multiple words. This method is required in order to handle
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
Args: Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool" *keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages each of them matches all search terms List[AURPackage]: list of packages each of them matches all search terms
@ -68,7 +71,7 @@ class Remote:
instance = cls() instance = cls()
packages: Dict[str, AURPackage] = {} packages: Dict[str, AURPackage] = {}
for term in filter(lambda word: len(word) > 3, keywords): for term in filter(lambda word: len(word) > 3, keywords):
portion = instance.search(term) portion = instance.search(term, pacman=pacman)
packages = { packages = {
package.name: package # not mistake to group them by name package.name: package # not mistake to group them by name
for package in portion for package in portion
@ -76,25 +79,60 @@ class Remote:
} }
return list(packages.values()) return list(packages.values())
@staticmethod
def remote_git_url(package_base: str, repository: str) -> str:
"""
generate remote git url from the package base
Args
package_base(str): package base
repository(str): repository name
Returns:
str: git url for the specific base
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
@staticmethod
def remote_web_url(package_base: str) -> str:
"""
generate remote web url from the package base
Args
package_base(str): package base
Returns:
str: web url for the specific base
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
@classmethod @classmethod
def search(cls: Type[Remote], *keywords: str) -> List[AURPackage]: def search(cls: Type[Remote], *keywords: str, pacman: Pacman) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
Args: Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool" *keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria List[AURPackage]: list of packages which match the criteria
""" """
return cls().package_search(*keywords) return cls().package_search(*keywords, pacman=pacman)
def package_info(self, package_name: str) -> AURPackage: def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
""" """
get package info by its name get package info by its name
Args: Args:
package_name(str): package name to search package_name(str): package name to search
pacman(Pacman): alpm wrapper instance
Returns: Returns:
AURPackage: package which match the package name AURPackage: package which match the package name
@ -104,12 +142,13 @@ class Remote:
""" """
raise NotImplementedError raise NotImplementedError
def package_search(self, *keywords: str) -> List[AURPackage]: def package_search(self, *keywords: str, pacman: Pacman) -> List[AURPackage]:
""" """
search package in AUR web search package in AUR web
Args: Args:
*keywords(str): keywords to search *keywords(str): keywords to search
pacman(Pacman): alpm wrapper instance
Returns: Returns:
List[AURPackage]: list of packages which match the criteria List[AURPackage]: list of packages which match the criteria

View File

@ -18,11 +18,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import logging import logging
import shutil
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
from ahriman.core.util import check_output from ahriman.core.util import check_output, walk
from ahriman.models.remote_source import RemoteSource
class Sources: class Sources:
@ -30,12 +32,14 @@ class Sources:
helper to download package sources (PKGBUILD etc) helper to download package sources (PKGBUILD etc)
Attributes: Attributes:
DEFAULT_BRANCH(str): (class attribute) default branch to process git repositories.
Must be used only for local stored repositories, use RemoteSource descriptor instead for real packages
logger(logging.Logger): (class attribute) class logger logger(logging.Logger): (class attribute) class logger
""" """
DEFAULT_BRANCH = "master" # default fallback branch
logger = logging.getLogger("build_details") logger = logging.getLogger("build_details")
_branch = "master" # in case if BLM would like to change it
_check_output = check_output _check_output = check_output
@staticmethod @staticmethod
@ -73,13 +77,13 @@ class Sources:
return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger) return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod @staticmethod
def fetch(sources_dir: Path, remote: Optional[str]) -> None: def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
""" """
either clone repository or update it to origin/`branch` either clone repository or update it to origin/`branch`
Args: Args:
sources_dir(Path): local path to fetch sources_dir(Path): local path to fetch
remote(Optional[str]): remote target (from where to fetch) remote(Optional[RemoteSource]): remote target (from where to fetch)
""" """
# local directory exists and there is .git directory # local directory exists and there is .git directory
is_initialized_git = (sources_dir / ".git").is_dir() is_initialized_git = (sources_dir / ".git").is_dir()
@ -88,22 +92,30 @@ class Sources:
Sources.logger.info("skip update at %s because there are no branches configured", sources_dir) Sources.logger.info("skip update at %s because there are no branches configured", sources_dir)
return return
branch = remote.branch if remote is not None else Sources.DEFAULT_BRANCH
if is_initialized_git: if is_initialized_git:
Sources.logger.info("update HEAD to remote at %s", sources_dir) Sources.logger.info("update HEAD to remote at %s using branch %s", sources_dir, branch)
Sources._check_output("git", "fetch", "origin", Sources._branch, Sources._check_output("git", "fetch", "origin", branch,
exception=None, cwd=sources_dir, logger=Sources.logger)
elif remote is not None:
Sources.logger.info("clone remote %s to %s using branch %s", remote.git_url, sources_dir, branch)
Sources._check_output("git", "clone", "--branch", branch, "--single-branch",
remote.git_url, str(sources_dir),
exception=None, cwd=sources_dir, logger=Sources.logger) exception=None, cwd=sources_dir, logger=Sources.logger)
elif remote is None:
Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
else: else:
Sources.logger.info("clone remote %s to %s", remote, sources_dir) Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
Sources._check_output("git", "clone", remote, str(sources_dir),
exception=None, cwd=sources_dir, logger=Sources.logger)
# and now force reset to our branch # and now force reset to our branch
Sources._check_output("git", "checkout", "--force", Sources._branch, Sources._check_output("git", "checkout", "--force", branch,
exception=None, cwd=sources_dir, logger=Sources.logger) exception=None, cwd=sources_dir, logger=Sources.logger)
Sources._check_output("git", "reset", "--hard", f"origin/{Sources._branch}", Sources._check_output("git", "reset", "--hard", f"origin/{branch}",
exception=None, cwd=sources_dir, logger=Sources.logger) exception=None, cwd=sources_dir, logger=Sources.logger)
# move content if required
# we are using full path to source directory in order to make append possible
pkgbuild_dir = remote.pkgbuild_dir if remote is not None else sources_dir.resolve()
Sources.move((sources_dir / pkgbuild_dir).resolve(), sources_dir)
@staticmethod @staticmethod
def has_remotes(sources_dir: Path) -> bool: def has_remotes(sources_dir: Path) -> bool:
""" """
@ -126,17 +138,17 @@ class Sources:
Args: Args:
sources_dir(Path): local path to sources sources_dir(Path): local path to sources
""" """
Sources._check_output("git", "init", "--initial-branch", Sources._branch, Sources._check_output("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH,
exception=None, cwd=sources_dir, logger=Sources.logger) exception=None, cwd=sources_dir, logger=Sources.logger)
@staticmethod @staticmethod
def load(sources_dir: Path, remote: str, patch: Optional[str]) -> None: def load(sources_dir: Path, remote: Optional[RemoteSource], patch: Optional[str]) -> None:
""" """
fetch sources from remote and apply patches fetch sources from remote and apply patches
Args: Args:
sources_dir(Path): local path to fetch sources_dir(Path): local path to fetch
remote(str): remote target (from where to fetch) remote(Optional[RemoteSource]): remote target (from where to fetch)
patch(Optional[str]): optional patch to be applied patch(Optional[str]): optional patch to be applied
""" """
Sources.fetch(sources_dir, remote) Sources.fetch(sources_dir, remote)
@ -145,6 +157,21 @@ class Sources:
return return
Sources.patch_apply(sources_dir, patch) Sources.patch_apply(sources_dir, patch)
@staticmethod
def move(pkgbuild_dir: Path, sources_dir: Path) -> None:
"""
move content from pkgbuild_dir to sources_dir
Args:
pkgbuild_dir(Path): path to directory with pkgbuild from which need to move
sources_dir(Path): path to target directory
"""
if pkgbuild_dir == sources_dir:
return # directories are the same, no need to move
for src in walk(pkgbuild_dir):
dst = sources_dir / src.relative_to(pkgbuild_dir)
shutil.move(src, dst)
@staticmethod @staticmethod
def patch_apply(sources_dir: Path, patch: str) -> None: def patch_apply(sources_dir: Path, patch: str) -> None:
""" """

View File

@ -107,4 +107,4 @@ class Task:
if self.paths.cache_for(self.package.base).is_dir(): if self.paths.cache_for(self.package.base).is_dir():
# no need to clone whole repository, just copy from cache first # no need to clone whole repository, just copy from cache first
shutil.copytree(self.paths.cache_for(self.package.base), path, dirs_exist_ok=True) shutil.copytree(self.paths.cache_for(self.package.base), path, dirs_exist_ok=True)
Sources.load(path, self.package.git_url, database.patches_get(self.package.base)) Sources.load(path, self.package.remote, database.patches_get(self.package.base))

View File

@ -20,6 +20,7 @@
from sqlite3 import Connection from sqlite3 import Connection
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database.data.package_remotes import migrate_package_remotes
from ahriman.core.database.data.package_statuses import migrate_package_statuses from ahriman.core.database.data.package_statuses import migrate_package_statuses
from ahriman.core.database.data.patches import migrate_patches from ahriman.core.database.data.patches import migrate_patches
from ahriman.core.database.data.users import migrate_users_data from ahriman.core.database.data.users import migrate_users_data
@ -43,3 +44,5 @@ def migrate_data(
migrate_package_statuses(connection, repository_paths) migrate_package_statuses(connection, repository_paths)
migrate_patches(connection, repository_paths) migrate_patches(connection, repository_paths)
migrate_users_data(connection, configuration) migrate_users_data(connection, configuration)
if result.old_version <= 1:
migrate_package_remotes(connection, repository_paths)

View File

@ -0,0 +1,61 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from sqlite3 import Connection
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths
# pylint: disable=protected-access
def migrate_package_remotes(connection: Connection, paths: RepositoryPaths) -> None:
"""
perform migration for package remote sources
Args:
connection(Connection): database connection
paths(RepositoryPaths): repository paths instance
"""
from ahriman.core.database.operations.package_operations import PackageOperations
def insert_remote(base: str, remote: RemoteSource) -> None:
connection.execute(
"""
update package_bases set
branch = :branch, git_url = :git_url, path = :path,
web_url = :web_url, source = :source
where package_base = :package_base
""",
dict(
package_base=base,
branch=remote.branch, git_url=remote.git_url, path=remote.path,
web_url=remote.web_url, source=remote.source
)
)
packages = PackageOperations._packages_get_select_package_bases(connection)
for package_base, package in packages.items():
local_cache = paths.cache_for(package_base)
if local_cache.exists() and not package.is_vcs:
continue # skip packages which are not VCS and with local cache
remote_source = RemoteSource.from_remote(PackageSource.AUR, package_base, "aur")
if remote_source is None:
continue # should never happen
insert_remote(package_base, remote_source)

View File

@ -42,7 +42,7 @@ def migrate_package_statuses(connection: Connection, paths: RepositoryPaths) ->
values values
(:package_base, :version, :aur_url) (:package_base, :version, :aur_url)
""", """,
dict(package_base=metadata.base, version=metadata.version, aur_url=metadata.aur_url)) dict(package_base=metadata.base, version=metadata.version, aur_url=""))
connection.execute( connection.execute(
""" """
insert into package_statuses insert into package_statuses

View File

@ -0,0 +1,40 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
steps = [
"""
alter table package_bases add column branch text
""",
"""
alter table package_bases add column git_url text
""",
"""
alter table package_bases add column path text
""",
"""
alter table package_bases add column web_url text
""",
"""
alter table package_bases add column source text
""",
"""
alter table package_bases drop column aur_url
""",
]

View File

@ -24,6 +24,7 @@ from ahriman.core.database.operations.operations import Operations
from ahriman.models.build_status import BuildStatus from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.remote_source import RemoteSource
class PackageOperations(Operations): class PackageOperations(Operations):
@ -75,13 +76,22 @@ class PackageOperations(Operations):
connection.execute( connection.execute(
""" """
insert into package_bases insert into package_bases
(package_base, version, aur_url) (package_base, version, source, branch, git_url, path, web_url)
values values
(:package_base, :version, :aur_url) (:package_base, :version, :source, :branch, :git_url, :path, :web_url)
on conflict (package_base) do update set on conflict (package_base) do update set
version = :version, aur_url = :aur_url version = :version, branch = :branch, git_url = :git_url, path = :path, web_url = :web_url, source = :source
""", """,
dict(package_base=package.base, version=package.version, aur_url=package.aur_url)) dict(
package_base=package.base,
version=package.version,
branch=package.remote.branch if package.remote is not None else None,
git_url=package.remote.git_url if package.remote is not None else None,
path=package.remote.path if package.remote is not None else None,
web_url=package.remote.web_url if package.remote is not None else None,
source=package.remote.source.value if package.remote is not None else None,
)
)
@staticmethod @staticmethod
def _package_update_insert_packages(connection: Connection, package: Package) -> None: def _package_update_insert_packages(connection: Connection, package: Package) -> None:
@ -144,7 +154,7 @@ class PackageOperations(Operations):
Dict[str, Package]: map of the package base to its descriptor (without packages themselves) Dict[str, Package]: map of the package base to its descriptor (without packages themselves)
""" """
return { return {
row["package_base"]: Package(row["package_base"], row["version"], row["aur_url"], {}) row["package_base"]: Package(row["package_base"], row["version"], RemoteSource.from_json(row), {})
for row in connection.execute("""select * from package_bases""") for row in connection.execute("""select * from package_bases""")
} }
@ -225,3 +235,28 @@ class PackageOperations(Operations):
yield package, statuses.get(package_base, BuildStatus()) yield package, statuses.get(package_base, BuildStatus())
return self.with_connection(lambda connection: list(run(connection))) return self.with_connection(lambda connection: list(run(connection)))
def remote_update(self, package: Package) -> None:
"""
update package remote source
Args:
package(Package): package properties
"""
return self.with_connection(
lambda connection: self._package_update_insert_base(connection, package),
commit=True)
def remotes_get(self) -> Dict[str, RemoteSource]:
"""
get packages remotes based on current settings
Returns:
Dict[str, RemoteSource]: map of package base to its remote sources
"""
packages = self.with_connection(self._packages_get_select_package_bases)
return {
package_base: package.remote
for package_base, package in packages.items()
if package.remote is not None
}

View File

@ -81,5 +81,5 @@ class SQLite(AuthOperations, BuildOperations, PackageOperations, PatchOperations
paths = configuration.repository_paths paths = configuration.repository_paths
self.with_connection(lambda conn: Migrations.migrate(conn, configuration)) self.with_connection(lambda connection: Migrations.migrate(connection, configuration))
paths.chown(self.path) paths.chown(self.path)

View File

@ -24,7 +24,6 @@ from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.core.util import package_like from ahriman.core.util import package_like
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
class Repository(Executor, UpdateHandler): class Repository(Executor, UpdateHandler):
@ -42,11 +41,15 @@ class Repository(Executor, UpdateHandler):
Returns: Returns:
List[Package]: list of read packages List[Package]: list of read packages
""" """
sources = self.database.remotes_get()
result: Dict[str, Package] = {} result: Dict[str, Package] = {}
# we are iterating over bases, not single packages # we are iterating over bases, not single packages
for full_path in packages: for full_path in packages:
try: try:
local = Package.load(str(full_path), PackageSource.Archive, self.pacman, self.aur_url) local = Package.from_archive(full_path, self.pacman, None)
local.remote = sources.get(local.base)
current = result.setdefault(local.base, local) current = result.setdefault(local.base, local)
if current.version != local.version: if current.version != local.version:
# force version to max of them # force version to max of them
@ -95,5 +98,5 @@ class Repository(Executor, UpdateHandler):
return [ return [
package package
for package in packages for package in packages
if depends_on is None or depends_on.intersection(package.full_depends(self.pacman, packages)) if depends_on.intersection(package.full_depends(self.pacman, packages))
] ]

View File

@ -35,7 +35,6 @@ class RepositoryProperties:
Attributes: Attributes:
architecture(str): repository architecture architecture(str): repository architecture
aur_url(str): base AUR url
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
database(SQLite): database instance database(SQLite): database instance
ignore_list(List[str]): package bases which will be ignored during auto updates ignore_list(List[str]): package bases which will be ignored during auto updates
@ -65,7 +64,6 @@ class RepositoryProperties:
self.configuration = configuration self.configuration = configuration
self.database = database self.database = database
self.aur_url = configuration.get("alpm", "aur_url")
self.name = configuration.get("repository", "name") self.name = configuration.get("repository", "name")
self.paths = configuration.repository_paths self.paths = configuration.repository_paths

View File

@ -62,12 +62,17 @@ class UpdateHandler(Cleaner):
continue continue
if filter_packages and local.base not in filter_packages: if filter_packages and local.base not in filter_packages:
continue continue
source = local.remote.source if local.remote is not None else None
try: try:
remote = Package.load(local.base, PackageSource.AUR, self.pacman, self.aur_url) if source == PackageSource.Repository:
remote = Package.from_official(local.base, self.pacman)
else:
remote = Package.from_aur(local.base, self.pacman)
if local.is_outdated(remote, self.paths): if local.is_outdated(remote, self.paths):
self.reporter.set_pending(local.base) self.reporter.set_pending(local.base)
result.append(remote) result.append(remote)
else:
self.reporter.set_success(local) self.reporter.set_success(local)
except Exception: except Exception:
self.reporter.set_failed(local.base) self.reporter.set_failed(local.base)
@ -89,7 +94,7 @@ class UpdateHandler(Cleaner):
for dirname in self.paths.cache.iterdir(): for dirname in self.paths.cache.iterdir():
try: try:
Sources.fetch(dirname, remote=None) Sources.fetch(dirname, remote=None)
remote = Package.load(str(dirname), PackageSource.Local, self.pacman, self.aur_url) remote = Package.from_build(dirname)
local = packages.get(remote.base) local = packages.get(remote.base)
if local is None: if local is None:

View File

@ -70,7 +70,7 @@ class Leaf:
Leaf: loaded class Leaf: loaded class
""" """
with tmpdir() as clone_dir: with tmpdir() as clone_dir:
Sources.load(clone_dir, package.git_url, database.patches_get(package.base)) Sources.load(clone_dir, package.remote, database.patches_get(package.base))
dependencies = Package.dependencies(clone_dir) dependencies = Package.dependencies(clone_dir)
return cls(package, dependencies) return cls(package, dependencies)

View File

@ -20,7 +20,7 @@
from enum import Enum from enum import Enum
class Action(Enum): class Action(str, Enum):
""" """
base action enumeration base action enumeration

View File

@ -23,6 +23,7 @@ import datetime
import inflection import inflection
from dataclasses import dataclass, field, fields from dataclasses import dataclass, field, fields
from pyalpm import Package # type: ignore
from typing import Any, Callable, Dict, List, Optional, Type from typing import Any, Callable, Dict, List, Optional, Type
from ahriman.core.util import filter_json, full_version from ahriman.core.util import filter_json, full_version
@ -47,6 +48,7 @@ class AURPackage:
first_submitted(datetime.datetime): timestamp of the first package submission first_submitted(datetime.datetime): timestamp of the first package submission
last_modified(datetime.datetime): timestamp of the last package submission last_modified(datetime.datetime): timestamp of the last package submission
url_path(str): AUR package path url_path(str): AUR package path
repository(str): repository name of the package
depends(List[str]): list of package dependencies depends(List[str]): list of package dependencies
make_depends(List[str]): list of package make dependencies make_depends(List[str]): list of package make dependencies
opt_depends(List[str]): list of package optional dependencies opt_depends(List[str]): list of package optional dependencies
@ -70,6 +72,7 @@ class AURPackage:
url: Optional[str] = None url: Optional[str] = None
out_of_date: Optional[datetime.datetime] = None out_of_date: Optional[datetime.datetime] = None
maintainer: Optional[str] = None maintainer: Optional[str] = None
repository: str = "aur"
depends: List[str] = field(default_factory=list) depends: List[str] = field(default_factory=list)
make_depends: List[str] = field(default_factory=list) make_depends: List[str] = field(default_factory=list)
opt_depends: List[str] = field(default_factory=list) opt_depends: List[str] = field(default_factory=list)
@ -94,6 +97,42 @@ class AURPackage:
properties = cls.convert(dump) properties = cls.convert(dump)
return cls(**filter_json(properties, known_fields)) return cls(**filter_json(properties, known_fields))
@classmethod
def from_pacman(cls: Type[AURPackage], package: Package) -> AURPackage:
"""
construct package descriptor from official repository wrapper
Args:
package(Package): pyalpm package descriptor
Returns:
AURPackage: AUR package descriptor
"""
return cls(
id=0,
name=package.name,
package_base_id=0,
package_base=package.base,
version=package.version,
description=package.desc,
num_votes=0,
popularity=0.0,
first_submitted=datetime.datetime.utcfromtimestamp(0),
last_modified=datetime.datetime.utcfromtimestamp(package.builddate),
url_path="",
url=package.url,
out_of_date=None,
maintainer=None,
repository=package.db.name,
depends=package.depends,
make_depends=package.makedepends,
opt_depends=package.optdepends,
conflicts=package.conflicts,
provides=package.provides,
license=package.licenses,
keywords=[],
)
@classmethod @classmethod
def from_repo(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage: def from_repo(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage:
""" """
@ -122,6 +161,7 @@ class AURPackage:
dump["flag_date"], dump["flag_date"],
"%Y-%m-%dT%H:%M:%S.%fZ") if dump["flag_date"] is not None else None, "%Y-%m-%dT%H:%M:%S.%fZ") if dump["flag_date"] is not None else None,
maintainer=next(iter(dump["maintainers"]), None), maintainer=next(iter(dump["maintainers"]), None),
repository=dump["repo"],
depends=dump["depends"], depends=dump["depends"],
make_depends=dump["makedepends"], make_depends=dump["makedepends"],
opt_depends=dump["optdepends"], opt_depends=dump["optdepends"],

View File

@ -23,7 +23,7 @@ from enum import Enum
from typing import Type from typing import Type
class AuthSettings(Enum): class AuthSettings(str, Enum):
""" """
web authorization type web authorization type

View File

@ -28,7 +28,7 @@ from typing import Any, Dict, Type
from ahriman.core.util import filter_json, pretty_datetime from ahriman.core.util import filter_json, pretty_datetime
class BuildStatusEnum(Enum): class BuildStatusEnum(str, Enum):
""" """
build status enumeration build status enumeration

View File

@ -26,15 +26,17 @@ from dataclasses import asdict, dataclass
from pathlib import Path from pathlib import Path
from pyalpm import vercmp # type: ignore from pyalpm import vercmp # type: ignore
from srcinfo.parse import parse_srcinfo # type: ignore from srcinfo.parse import parse_srcinfo # type: ignore
from typing import Any, Dict, Iterable, List, Set, Type from typing import Any, Dict, Iterable, List, Optional, Set, Type
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.aur import AUR from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official from ahriman.core.alpm.remote.official import Official
from ahriman.core.alpm.remote.official_syncdb import OfficialSyncdb
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.core.util import check_output, full_version from ahriman.core.util import check_output, full_version
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -44,15 +46,16 @@ class Package:
package properties representation package properties representation
Attributes: Attributes:
aur_url(str): AUR root url
base(str): package base name base(str): package base name
packages(Dict[str, PackageDescription): map of package names to their properties. Filled only on load from archive packages(Dict[str, PackageDescription): map of package names to their properties.
Filled only on load from archive
remote(Optional[RemoteSource]): package remote source if applicable
version(str): package full version version(str): package full version
""" """
base: str base: str
version: str version: str
aur_url: str remote: Optional[RemoteSource]
packages: Dict[str, PackageDescription] packages: Dict[str, PackageDescription]
_check_output = check_output _check_output = check_output
@ -67,16 +70,6 @@ class Package:
""" """
return sorted(set(sum([package.depends for package in self.packages.values()], start=[]))) return sorted(set(sum([package.depends for package in self.packages.values()], start=[])))
@property
def git_url(self) -> str:
"""
get git clone url
Returns:
str: package git url to clone
"""
return f"{self.aur_url}/{self.base}.git"
@property @property
def groups(self) -> List[str]: def groups(self) -> List[str]:
""" """
@ -122,56 +115,46 @@ class Package:
""" """
return sorted(set(sum([package.licenses for package in self.packages.values()], start=[]))) return sorted(set(sum([package.licenses for package in self.packages.values()], start=[])))
@property
def web_url(self) -> str:
"""
get package url which can be used to see package in web
Returns:
str: package AUR url
"""
return f"{self.aur_url}/packages/{self.base}"
@classmethod @classmethod
def from_archive(cls: Type[Package], path: Path, pacman: Pacman, aur_url: str) -> Package: def from_archive(cls: Type[Package], path: Path, pacman: Pacman, remote: Optional[RemoteSource]) -> Package:
""" """
construct package properties from package archive construct package properties from package archive
Args: Args:
path(Path): path to package archive path(Path): path to package archive
pacman(Pacman): alpm wrapper instance pacman(Pacman): alpm wrapper instance
aur_url(str): AUR root url remote(RemoteSource): package remote source if applicable
Returns: Returns:
Package: package properties Package: package properties
""" """
package = pacman.handle.load_pkg(str(path)) package = pacman.handle.load_pkg(str(path))
return cls(package.base, package.version, aur_url, description = PackageDescription.from_package(package, path)
{package.name: PackageDescription.from_package(package, path)}) return cls(package.base, package.version, remote, {package.name: description})
@classmethod @classmethod
def from_aur(cls: Type[Package], name: str, aur_url: str) -> Package: def from_aur(cls: Type[Package], name: str, pacman: Pacman) -> Package:
""" """
construct package properties from AUR page construct package properties from AUR page
Args: Args:
name(str): package name (either base or normal name) name(str): package name (either base or normal name)
aur_url(str): AUR root url pacman(Pacman): alpm wrapper instance
Returns: Returns:
Package: package properties Package: package properties
""" """
package = AUR.info(name) package = AUR.info(name, pacman=pacman)
return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()}) remote = RemoteSource.from_remote(PackageSource.AUR, package.package_base, package.repository)
return cls(package.package_base, package.version, remote, {package.name: PackageDescription()})
@classmethod @classmethod
def from_build(cls: Type[Package], path: Path, aur_url: str) -> Package: def from_build(cls: Type[Package], path: Path) -> Package:
""" """
construct package properties from sources directory construct package properties from sources directory
Args: Args:
path(Path): path to package sources directory path(Path): path to package sources directory
aur_url(str): AUR root url
Returns: Returns:
Package: package properties Package: package properties
@ -179,13 +162,14 @@ class Package:
Raises: Raises:
InvalidPackageInfo: if there are parsing errors InvalidPackageInfo: if there are parsing errors
""" """
srcinfo, errors = parse_srcinfo((path / ".SRCINFO").read_text()) srcinfo_source = Package._check_output("makepkg", "--printsrcinfo", exception=None, cwd=path)
srcinfo, errors = parse_srcinfo(srcinfo_source)
if errors: if errors:
raise InvalidPackageInfo(errors) raise InvalidPackageInfo(errors)
packages = {key: PackageDescription() for key in srcinfo["packages"]} packages = {key: PackageDescription() for key in srcinfo["packages"]}
version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"]) version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"])
return cls(srcinfo["pkgbase"], version, aur_url, packages) return cls(srcinfo["pkgbase"], version, None, packages)
@classmethod @classmethod
def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package: def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package:
@ -202,59 +186,29 @@ class Package:
key: PackageDescription.from_json(value) key: PackageDescription.from_json(value)
for key, value in dump.get("packages", {}).items() for key, value in dump.get("packages", {}).items()
} }
return Package( remote = dump.get("remote", {})
return cls(
base=dump["base"], base=dump["base"],
version=dump["version"], version=dump["version"],
aur_url=dump["aur_url"], remote=RemoteSource.from_json(remote),
packages=packages) packages=packages)
@classmethod @classmethod
def from_official(cls: Type[Package], name: str, aur_url: str) -> Package: def from_official(cls: Type[Package], name: str, pacman: Pacman, use_syncdb: bool = True) -> Package:
""" """
construct package properties from official repository page construct package properties from official repository page
Args: Args:
name(str): package name (either base or normal name) name(str): package name (either base or normal name)
aur_url(str): AUR root url pacman(Pacman): alpm wrapper instance
use_syncdb(bool): use pacman databases instead of official repositories RPC (Default value = True)
Returns: Returns:
Package: package properties Package: package properties
""" """
package = Official.info(name) package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman)
return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()}) remote = RemoteSource.from_remote(PackageSource.Repository, package.package_base, package.repository)
return cls(package.package_base, package.version, remote, {package.name: PackageDescription()})
@classmethod
def load(cls: Type[Package], package: str, source: PackageSource, pacman: Pacman, aur_url: str) -> Package:
"""
package constructor from available sources
Args:
package(str): one of path to sources directory, path to archive or package name/base
source(PackageSource): source of the package required to define the load method
pacman(Pacman): alpm wrapper instance (required to load from archive)
aur_url(str): AUR root url
Returns:
Package: package properties
Raises:
InvalidPackageInfo: if supplied package source is not valid
"""
try:
resolved_source = source.resolve(package)
if resolved_source == PackageSource.Archive:
return cls.from_archive(Path(package), pacman, aur_url)
if resolved_source == PackageSource.AUR:
return cls.from_aur(package, aur_url)
if resolved_source == PackageSource.Local:
return cls.from_build(Path(package), aur_url)
if resolved_source == PackageSource.Repository:
return cls.from_official(package, aur_url)
raise InvalidPackageInfo(f"Unsupported local package source {resolved_source}")
except InvalidPackageInfo:
raise
except Exception as e:
raise InvalidPackageInfo(str(e))
@staticmethod @staticmethod
def dependencies(path: Path) -> Set[str]: def dependencies(path: Path) -> Set[str]:
@ -279,7 +233,8 @@ class Package:
package_name = package_name.split(symbol)[0] package_name = package_name.split(symbol)[0]
return package_name return package_name
srcinfo, errors = parse_srcinfo((path / ".SRCINFO").read_text()) srcinfo_source = Package._check_output("makepkg", "--printsrcinfo", exception=None, cwd=path)
srcinfo, errors = parse_srcinfo(srcinfo_source)
if errors: if errors:
raise InvalidPackageInfo(errors) raise InvalidPackageInfo(errors)
makedepends = extract_packages(srcinfo.get("makedepends", [])) makedepends = extract_packages(srcinfo.get("makedepends", []))
@ -310,7 +265,7 @@ class Package:
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
logger = logging.getLogger("build_details") logger = logging.getLogger("build_details")
Sources.load(paths.cache_for(self.base), self.git_url, None) Sources.load(paths.cache_for(self.base), self.remote, None)
try: try:
# update pkgver first # update pkgver first

View File

@ -26,7 +26,7 @@ from urllib.parse import urlparse
from ahriman.core.util import package_like from ahriman.core.util import package_like
class PackageSource(Enum): class PackageSource(str, Enum):
""" """
package source for addition enumeration package source for addition enumeration

View File

@ -0,0 +1,124 @@
#
# Copyright (c) 2021-2022 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import annotations
from dataclasses import asdict, dataclass, fields
from pathlib import Path
from typing import Any, Dict, Optional, Type
from ahriman.core.util import filter_json
from ahriman.models.package_source import PackageSource
@dataclass
class RemoteSource:
"""
remote package source properties
Attributes:
branch(str): branch of the git repository
git_url(str): url of the git repository
path(str): path to directory with PKGBUILD inside the git repository
source(PackageSource): package source pointer used by some parsers
web_url(str): url of the package in the web interface
"""
git_url: str
web_url: str
path: str
branch: str
source: PackageSource
def __post_init__(self) -> None:
"""
convert source to enum type
"""
self.source = PackageSource(self.source)
@property
def pkgbuild_dir(self) -> Path:
"""
get path to directory with package sources (PKGBUILD etc)
Returns:
Path: path to directory with package sources based on settings
"""
return Path(self.path)
@classmethod
def from_json(cls: Type[RemoteSource], dump: Dict[str, Any]) -> Optional[RemoteSource]:
"""
construct remote source from the json dump (or database row)
Args:
dump(Dict[str, Any]): json dump body
Returns:
Optional[RemoteSource]: remote source
"""
# filter to only known fields
known_fields = [pair.name for pair in fields(cls)]
dump = filter_json(dump, known_fields)
if dump:
return cls(**dump)
return None
@classmethod
def from_remote(cls: Type[RemoteSource], source: PackageSource, package_base: str,
repository: str) -> Optional[RemoteSource]:
"""
generate remote source from the package base
Args:
source(PackageSource): source of the package
package_base(str): package base
repository(str): repository name
Returns:
Optional[RemoteSource]: generated remote source if any, None otherwise
"""
if source == PackageSource.AUR:
from ahriman.core.alpm.remote.aur import AUR
return RemoteSource(
git_url=AUR.remote_git_url(package_base, repository),
web_url=AUR.remote_web_url(package_base),
path=".",
branch="master",
source=source,
)
if source == PackageSource.Repository:
from ahriman.core.alpm.remote.official import Official
return RemoteSource(
git_url=Official.remote_git_url(package_base, repository),
web_url=Official.remote_web_url(package_base),
path="trunk",
branch=f"packages/{package_base}",
source=source,
)
return None
def view(self) -> Dict[str, Any]:
"""
generate json package remote view
Returns:
Dict[str, Any]: json-friendly dictionary
"""
return asdict(self)

View File

@ -23,7 +23,7 @@ from enum import Enum
from typing import Type from typing import Type
class ReportSettings(Enum): class ReportSettings(str, Enum):
""" """
report targets enumeration report targets enumeration

View File

@ -23,7 +23,7 @@ from enum import Enum
from typing import Type from typing import Type
class SignSettings(Enum): class SignSettings(str, Enum):
""" """
sign targets enumeration sign targets enumeration

View File

@ -23,7 +23,7 @@ from enum import Enum
from typing import Type from typing import Type
class SmtpSSLSettings(Enum): class SmtpSSLSettings(str, Enum):
""" """
SMTP SSL mode enumeration SMTP SSL mode enumeration

View File

@ -23,7 +23,7 @@ from enum import Enum
from typing import Type from typing import Type
class UploadSettings(Enum): class UploadSettings(str, Enum):
""" """
remote synchronization targets enumeration remote synchronization targets enumeration

View File

@ -20,7 +20,7 @@
from enum import Enum from enum import Enum
class UserAccess(Enum): class UserAccess(str, Enum):
""" """
web user access enumeration web user access enumeration

View File

@ -86,7 +86,7 @@ class IndexView(BaseView):
"status_color": status.status.bootstrap_color(), "status_color": status.status.bootstrap_color(),
"timestamp": pretty_datetime(status.timestamp), "timestamp": pretty_datetime(status.timestamp),
"version": package.version, "version": package.version,
"web_url": package.web_url, "web_url": package.remote.web_url if package.remote is not None else None,
} for package, status in sorted(self.service.packages, key=lambda item: item[0].base) } for package, status in sorted(self.service.packages, key=lambda item: item[0].base)
] ]
service = { service = {

View File

@ -50,7 +50,7 @@ class SearchView(BaseView):
HTTPNotFound: if no packages found HTTPNotFound: if no packages found
""" """
search: List[str] = self.request.query.getall("for", default=[]) search: List[str] = self.request.query.getall("for", default=[])
packages = AUR.multisearch(*search) packages = AUR.multisearch(*search, pacman=self.service.repository.pacman)
if not packages: if not packages:
raise HTTPNotFound(reason=f"No packages found for terms: {search}") raise HTTPNotFound(reason=f"No packages found for terms: {search}")

View File

@ -44,19 +44,21 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac
""" """
must add package from AUR must add package from AUR
""" """
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load") load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load")
dependencies_mock = mocker.patch( dependencies_mock = mocker.patch(
"ahriman.application.application.application_packages.ApplicationPackages._process_dependencies") "ahriman.application.application.application_packages.ApplicationPackages._process_dependencies")
build_queue_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.build_queue_insert") build_queue_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.build_queue_insert")
update_remote_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.remote_update")
application_packages._add_aur(package_ahriman.base, set(), False) application_packages._add_aur(package_ahriman.base, set(), False)
load_mock.assert_called_once_with( load_mock.assert_called_once_with(
pytest.helpers.anyvar(int), pytest.helpers.anyvar(int),
package_ahriman.git_url, package_ahriman.remote,
pytest.helpers.anyvar(int)) pytest.helpers.anyvar(int))
dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int), set(), False) dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int), set(), False)
build_queue_mock.assert_called_once_with(package_ahriman) build_queue_mock.assert_called_once_with(package_ahriman)
update_remote_mock.assert_called_once_with(package_ahriman)
def test_add_directory( def test_add_directory(
@ -80,7 +82,7 @@ def test_add_local(application_packages: ApplicationPackages, package_ahriman: P
""" """
must add package from local sources must add package from local sources
""" """
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init") init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
copytree_mock = mocker.patch("shutil.copytree") copytree_mock = mocker.patch("shutil.copytree")
dependencies_mock = mocker.patch( dependencies_mock = mocker.patch(
@ -112,6 +114,20 @@ def test_add_remote(application_packages: ApplicationPackages, package_descripti
response_mock.raise_for_status.assert_called_once_with() response_mock.raise_for_status.assert_called_once_with()
def test_add_repository(application_packages: ApplicationPackages, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must add package from official repository
"""
mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman)
build_queue_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.build_queue_insert")
update_remote_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.remote_update")
application_packages._add_repository(package_ahriman.base)
build_queue_mock.assert_called_once_with(package_ahriman)
update_remote_mock.assert_called_once_with(package_ahriman)
def test_process_dependencies(application_packages: ApplicationPackages, mocker: MockerFixture) -> None: def test_process_dependencies(application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
""" """
must process dependencies addition must process dependencies addition

View File

@ -14,7 +14,7 @@ def test_finalize(application_repository: ApplicationRepository) -> None:
must raise NotImplemented for missing finalize method must raise NotImplemented for missing finalize method
""" """
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
application_repository._finalize([]) application_repository._finalize(Result())
def test_clean_cache(application_repository: ApplicationRepository, mocker: MockerFixture) -> None: def test_clean_cache(application_repository: ApplicationRepository, mocker: MockerFixture) -> None:
@ -58,8 +58,8 @@ def test_report(application_repository: ApplicationRepository, mocker: MockerFix
must generate report must generate report
""" """
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_report") executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_report")
application_repository.report(["a"], []) application_repository.report(["a"], Result())
executor_mock.assert_called_once_with(["a"], []) executor_mock.assert_called_once_with(["a"], Result())
def test_sign(application_repository: ApplicationRepository, package_ahriman: Package, package_python_schedule: Package, def test_sign(application_repository: ApplicationRepository, package_ahriman: Package, package_python_schedule: Package,
@ -179,7 +179,6 @@ def test_update(application_repository: ApplicationRepository, package_ahriman:
mocker.patch("ahriman.core.tree.Tree.load", return_value=tree) mocker.patch("ahriman.core.tree.Tree.load", return_value=tree)
mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=paths) mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=paths)
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=result) build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=result)
update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update", return_value=result) update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update", return_value=result)
finalize_mock = mocker.patch( finalize_mock = mocker.patch(
@ -201,7 +200,6 @@ def test_update_empty(application_repository: ApplicationRepository, package_ahr
mocker.patch("ahriman.core.tree.Tree.load", return_value=tree) mocker.patch("ahriman.core.tree.Tree.load", return_value=tree)
mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=[]) mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=[])
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
mocker.patch("ahriman.core.repository.executor.Executor.process_build") mocker.patch("ahriman.core.repository.executor.Executor.process_build")
update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update") update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update")

View File

@ -1,6 +1,7 @@
import argparse import argparse
import pytest import pytest
from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman.application.application import Application from ahriman.application.application import Application
@ -37,7 +38,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_create") application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_create")
Patch.run(args, "x86_64", configuration, True, False) Patch.run(args, "x86_64", configuration, True, False)
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, args.track) application_mock.assert_called_once_with(pytest.helpers.anyvar(int), Path(args.package), args.track)
def test_run_list(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: def test_run_list(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
@ -96,11 +97,11 @@ def test_patch_set_create(application: Application, package_ahriman: Package, mo
must create patch set for the package must create patch set for the package
""" """
mocker.patch("pathlib.Path.mkdir") mocker.patch("pathlib.Path.mkdir")
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
mocker.patch("ahriman.core.build_tools.sources.Sources.patch_create", return_value="patch") mocker.patch("ahriman.core.build_tools.sources.Sources.patch_create", return_value="patch")
create_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.patches_insert") create_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.patches_insert")
Patch.patch_set_create(application, "path", ["*.patch"]) Patch.patch_set_create(application, Path("path"), ["*.patch"])
create_mock.assert_called_once_with(package_ahriman.base, "patch") create_mock.assert_called_once_with(package_ahriman.base, "patch")

View File

@ -34,6 +34,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
must run command must run command
""" """
args = _default_args(args) args = _default_args(args)
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
aur_search_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[aur_package_ahriman]) aur_search_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[aur_package_ahriman])
official_search_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch", official_search_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch",
return_value=[aur_package_ahriman]) return_value=[aur_package_ahriman])
@ -41,8 +42,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
Search.run(args, "x86_64", configuration, True, False) Search.run(args, "x86_64", configuration, True, False)
aur_search_mock.assert_called_once_with("ahriman") aur_search_mock.assert_called_once_with("ahriman", pacman=pytest.helpers.anyvar(int))
official_search_mock.assert_called_once_with("ahriman") official_search_mock.assert_called_once_with("ahriman", pacman=pytest.helpers.anyvar(int))
check_mock.assert_called_once_with(False, False) check_mock.assert_called_once_with(False, False)
print_mock.assert_has_calls([mock.call(False), mock.call(False)]) print_mock.assert_has_calls([mock.call(False), mock.call(False)])
@ -56,6 +57,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat
mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[]) mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[])
mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch", return_value=[]) mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch", return_value=[])
mocker.patch("ahriman.core.formatters.printer.Printer.print") mocker.patch("ahriman.core.formatters.printer.Printer.print")
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Search.run(args, "x86_64", configuration, True, False) Search.run(args, "x86_64", configuration, True, False)
@ -70,6 +72,7 @@ def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_pa
args = _default_args(args) args = _default_args(args)
mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[aur_package_ahriman]) mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[aur_package_ahriman])
mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch", return_value=[]) mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch", return_value=[])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort") sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
Search.run(args, "x86_64", configuration, True, False) Search.run(args, "x86_64", configuration, True, False)
@ -88,6 +91,7 @@ def test_run_sort_by(args: argparse.Namespace, configuration: Configuration, aur
args.sort_by = "field" args.sort_by = "field"
mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[aur_package_ahriman]) mocker.patch("ahriman.core.alpm.remote.aur.AUR.multisearch", return_value=[aur_package_ahriman])
mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch", return_value=[]) mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch", return_value=[])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort") sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
Search.run(args, "x86_64", configuration, True, False) Search.run(args, "x86_64", configuration, True, False)

View File

@ -6,6 +6,7 @@ from pytest_mock import MockerFixture
from typing import Any, Dict, Type, TypeVar from typing import Any, Dict, Type, TypeVar
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.auth.auth import Auth from ahriman.core.auth.auth import Auth
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite from ahriman.core.database.sqlite import SQLite
@ -15,6 +16,8 @@ from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
from ahriman.models.result import Result from ahriman.models.result import Result
from ahriman.models.user import User from ahriman.models.user import User
@ -101,8 +104,8 @@ def aur_package_ahriman() -> AURPackage:
description="ArcH Linux ReposItory MANager", description="ArcH Linux ReposItory MANager",
num_votes=0, num_votes=0,
popularity=0, popularity=0,
first_submitted=datetime.datetime(2021, 4, 9, 22, 44, 45), first_submitted=datetime.datetime.utcfromtimestamp(1618008285),
last_modified=datetime.datetime(2021, 12, 25, 23, 11, 11), last_modified=datetime.datetime.utcfromtimestamp(1640473871),
url_path="/cgit/aur.git/snapshot/ahriman.tar.gz", url_path="/cgit/aur.git/snapshot/ahriman.tar.gz",
url="https://github.com/arcan1s/ahriman", url="https://github.com/arcan1s/ahriman",
out_of_date=None, out_of_date=None,
@ -155,13 +158,14 @@ def aur_package_akonadi() -> AURPackage:
version="21.12.3-2", version="21.12.3-2",
description="PIM layer, which provides an asynchronous API to access all kind of PIM data", description="PIM layer, which provides an asynchronous API to access all kind of PIM data",
num_votes=0, num_votes=0,
popularity=0, popularity=0.0,
first_submitted=datetime.datetime(1970, 1, 1, 0, 0, 0), first_submitted=datetime.datetime.utcfromtimestamp(0),
last_modified=datetime.datetime(2022, 3, 6, 8, 39, 50, 610000), last_modified=datetime.datetime.utcfromtimestamp(1646555990.610),
url_path="", url_path="",
url="https://kontact.kde.org", url="https://kontact.kde.org",
out_of_date=None, out_of_date=None,
maintainer="felixonmars", maintainer="felixonmars",
repository="extra",
depends=[ depends=[
"libakonadi", "libakonadi",
"mariadb", "mariadb",
@ -245,7 +249,7 @@ def package_ahriman(package_description_ahriman: PackageDescription) -> Package:
return Package( return Package(
base="ahriman", base="ahriman",
version="1.7.0-1", version="1.7.0-1",
aur_url="https://aur.archlinux.org", remote=RemoteSource.from_remote(PackageSource.AUR, "ahriman", "aur"),
packages=packages) packages=packages)
@ -270,7 +274,7 @@ def package_python_schedule(
return Package( return Package(
base="python-schedule", base="python-schedule",
version="1.0.0-2", version="1.0.0-2",
aur_url="https://aur.archlinux.org", remote=RemoteSource.from_remote(PackageSource.AUR, "python-schedule", "aur"),
packages=packages) packages=packages)
@ -344,6 +348,34 @@ def package_description_python2_schedule() -> PackageDescription:
url="https://github.com/dbader/schedule") url="https://github.com/dbader/schedule")
@pytest.fixture
def pacman(configuration: Configuration) -> Pacman:
"""
fixture for pacman wrapper
Args:
configuration(Configuration): configuration fixture
Returns:
Pacman: pacman wrapper test instance
"""
return Pacman(configuration)
@pytest.fixture
def remote_source(package_ahriman: Package) -> RemoteSource:
"""
remote source fixture
Args:
package_ahriman(Package): package fixture
Returns:
RemoteSource: remote source test instance
"""
return RemoteSource.from_remote(PackageSource.AUR, "ahriman", "aur")
@pytest.fixture @pytest.fixture
def repository_paths(configuration: Configuration) -> RepositoryPaths: def repository_paths(configuration: Configuration) -> RepositoryPaths:
""" """

View File

@ -2,6 +2,7 @@ import pytest
from ahriman.core.alpm.remote.aur import AUR from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official from ahriman.core.alpm.remote.official import Official
from ahriman.core.alpm.remote.official_syncdb import OfficialSyncdb
from ahriman.core.alpm.remote.remote import Remote from ahriman.core.alpm.remote.remote import Remote
@ -27,6 +28,17 @@ def official() -> Official:
return Official() return Official()
@pytest.fixture
def official_syncdb() -> OfficialSyncdb:
"""
official repository fixture with database processing
Returns:
OfficialSyncdb: official repository with database processing helper instance
"""
return OfficialSyncdb()
@pytest.fixture @pytest.fixture
def remote() -> Remote: def remote() -> Remote:
""" """

View File

@ -6,6 +6,7 @@ from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.aur import AUR from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
@ -49,6 +50,23 @@ def test_parse_response_unknown_error() -> None:
AUR.parse_response({"type": "error"}) AUR.parse_response({"type": "error"})
def test_remote_git_url(aur_package_ahriman: AURPackage) -> None:
"""
must generate package git url
"""
git_url = AUR.remote_git_url(aur_package_ahriman.package_base, aur_package_ahriman.repository)
assert git_url.endswith(".git")
assert git_url.startswith(AUR.DEFAULT_AUR_URL)
def test_remote_web_url(aur_package_ahriman: AURPackage) -> None:
"""
must generate package git url
"""
web_url = AUR.remote_web_url(aur_package_ahriman.package_base)
assert web_url.startswith(AUR.DEFAULT_AUR_URL)
def test_make_request(aur: AUR, aur_package_ahriman: AURPackage, def test_make_request(aur: AUR, aur_package_ahriman: AURPackage,
mocker: MockerFixture, resource_path_root: Path) -> None: mocker: MockerFixture, resource_path_root: Path) -> None:
""" """
@ -109,19 +127,19 @@ def test_make_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None
aur.make_request("info", "ahriman") aur.make_request("info", "ahriman")
def test_package_info(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None: def test_package_info(aur: AUR, aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
""" """
must make request for info must make request for info
""" """
request_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.make_request", return_value=[aur_package_ahriman]) request_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.make_request", return_value=[aur_package_ahriman])
assert aur.package_info(aur_package_ahriman.name) == aur_package_ahriman assert aur.package_info(aur_package_ahriman.name, pacman=pacman) == aur_package_ahriman
request_mock.assert_called_once_with("info", aur_package_ahriman.name) request_mock.assert_called_once_with("info", aur_package_ahriman.name)
def test_package_search(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None: def test_package_search(aur: AUR, aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
""" """
must make request for search must make request for search
""" """
request_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.make_request", return_value=[aur_package_ahriman]) request_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.make_request", return_value=[aur_package_ahriman])
assert aur.package_search(aur_package_ahriman.name) == [aur_package_ahriman] assert aur.package_search(aur_package_ahriman.name, pacman=pacman) == [aur_package_ahriman]
request_mock.assert_called_once_with("search", aur_package_ahriman.name, by="name-desc") request_mock.assert_called_once_with("search", aur_package_ahriman.name, by="name-desc")

View File

@ -6,6 +6,7 @@ from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.official import Official from ahriman.core.alpm.remote.official import Official
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
@ -41,6 +42,38 @@ def test_parse_response_unknown_error(resource_path_root: Path) -> None:
Official.parse_response(json.loads(response)) Official.parse_response(json.loads(response))
def test_remote_git_url(aur_package_akonadi: AURPackage) -> None:
"""
must generate package git url for core packages
"""
git_urls = [
Official.remote_git_url(aur_package_akonadi.package_base, repository)
for repository in ("core", "extra", "Core", "Extra")
]
assert all(git_url.endswith("svntogit-packages.git") for git_url in git_urls)
assert len(set(git_urls)) == 1
def test_remote_git_url_community(aur_package_akonadi: AURPackage) -> None:
"""
must generate package git url for core packages
"""
git_urls = [
Official.remote_git_url(aur_package_akonadi.package_base, repository)
for repository in ("community", "multilib", "Community", "Multilib")
]
assert all(git_url.endswith("svntogit-community.git") for git_url in git_urls)
assert len(set(git_urls)) == 1
def test_remote_web_url(aur_package_akonadi: AURPackage) -> None:
"""
must generate package git url
"""
web_url = Official.remote_web_url(aur_package_akonadi.package_base)
assert web_url.startswith(Official.DEFAULT_ARCHLINUX_URL)
def test_make_request(official: Official, aur_package_akonadi: AURPackage, def test_make_request(official: Official, aur_package_akonadi: AURPackage,
mocker: MockerFixture, resource_path_root: Path) -> None: mocker: MockerFixture, resource_path_root: Path) -> None:
""" """
@ -51,7 +84,8 @@ def test_make_request(official: Official, aur_package_akonadi: AURPackage,
request_mock = mocker.patch("requests.get", return_value=response_mock) request_mock = mocker.patch("requests.get", return_value=response_mock)
assert official.make_request("akonadi", by="q") == [aur_package_akonadi] assert official.make_request("akonadi", by="q") == [aur_package_akonadi]
request_mock.assert_called_once_with("https://archlinux.org/packages/search/json", params={"q": ("akonadi",)}) request_mock.assert_called_once_with("https://archlinux.org/packages/search/json",
params={"q": ("akonadi",), "repo": Official.DEFAULT_SEARCH_REPOSITORIES})
def test_make_request_failed(official: Official, mocker: MockerFixture) -> None: def test_make_request_failed(official: Official, mocker: MockerFixture) -> None:
@ -72,21 +106,23 @@ def test_make_request_failed_http_error(official: Official, mocker: MockerFixtur
official.make_request("akonadi", by="q") official.make_request("akonadi", by="q")
def test_package_info(official: Official, aur_package_akonadi: AURPackage, mocker: MockerFixture) -> None: def test_package_info(official: Official, aur_package_akonadi: AURPackage, pacman: Pacman,
mocker: MockerFixture) -> None:
""" """
must make request for info must make request for info
""" """
request_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.make_request", request_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.make_request",
return_value=[aur_package_akonadi]) return_value=[aur_package_akonadi])
assert official.package_info(aur_package_akonadi.name) == aur_package_akonadi assert official.package_info(aur_package_akonadi.name, pacman=pacman) == aur_package_akonadi
request_mock.assert_called_once_with(aur_package_akonadi.name, by="name") request_mock.assert_called_once_with(aur_package_akonadi.name, by="name")
def test_package_search(official: Official, aur_package_akonadi: AURPackage, mocker: MockerFixture) -> None: def test_package_search(official: Official, aur_package_akonadi: AURPackage, pacman: Pacman,
mocker: MockerFixture) -> None:
""" """
must make request for search must make request for search
""" """
request_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.make_request", request_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.make_request",
return_value=[aur_package_akonadi]) return_value=[aur_package_akonadi])
assert official.package_search(aur_package_akonadi.name) == [aur_package_akonadi] assert official.package_search(aur_package_akonadi.name, pacman=pacman) == [aur_package_akonadi]
request_mock.assert_called_once_with(aur_package_akonadi.name, by="q") request_mock.assert_called_once_with(aur_package_akonadi.name, by="q")

View File

@ -0,0 +1,18 @@
from pytest_mock import MockerFixture
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.official_syncdb import OfficialSyncdb
from ahriman.models.aur_package import AURPackage
def test_package_info(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURPackage,
pacman: Pacman, mocker: MockerFixture) -> None:
"""
must return package info from the database
"""
mocker.patch("ahriman.models.aur_package.AURPackage.from_pacman", return_value=aur_package_akonadi)
get_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.get", return_value=[aur_package_akonadi])
package = official_syncdb.package_info(aur_package_akonadi.name, pacman=pacman)
get_mock.assert_called_once_with(aur_package_akonadi.name)
assert package == aur_package_akonadi

View File

@ -3,70 +3,87 @@ import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest import mock from unittest import mock
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote from ahriman.core.alpm.remote.remote import Remote
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
def test_info(mocker: MockerFixture) -> None: def test_info(pacman: Pacman, mocker: MockerFixture) -> None:
""" """
must call info method must call info method
""" """
info_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.package_info") info_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.package_info")
Remote.info("ahriman") Remote.info("ahriman", pacman=pacman)
info_mock.assert_called_once_with("ahriman") info_mock.assert_called_once_with("ahriman", pacman=pacman)
def test_multisearch(aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None: def test_multisearch(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
""" """
must search in AUR with multiple words must search in AUR with multiple words
""" """
terms = ["ahriman", "is", "cool"] terms = ["ahriman", "is", "cool"]
search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search", return_value=[aur_package_ahriman]) search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search", return_value=[aur_package_ahriman])
assert Remote.multisearch(*terms) == [aur_package_ahriman] assert Remote.multisearch(*terms, pacman=pacman) == [aur_package_ahriman]
search_mock.assert_has_calls([mock.call("ahriman"), mock.call("cool")]) search_mock.assert_has_calls([mock.call("ahriman", pacman=pacman), mock.call("cool", pacman=pacman)])
def test_multisearch_empty(mocker: MockerFixture) -> None: def test_multisearch_empty(pacman: Pacman, mocker: MockerFixture) -> None:
""" """
must return empty list if no long terms supplied must return empty list if no long terms supplied
""" """
terms = ["it", "is"] terms = ["it", "is"]
search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search") search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search")
assert Remote.multisearch(*terms) == [] assert Remote.multisearch(*terms, pacman=pacman) == []
search_mock.assert_not_called() search_mock.assert_not_called()
def test_multisearch_single(aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None: def test_multisearch_single(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
""" """
must search in AUR with one word must search in AUR with one word
""" """
search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search", return_value=[aur_package_ahriman]) search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search", return_value=[aur_package_ahriman])
assert Remote.multisearch("ahriman") == [aur_package_ahriman] assert Remote.multisearch("ahriman", pacman=pacman) == [aur_package_ahriman]
search_mock.assert_called_once_with("ahriman") search_mock.assert_called_once_with("ahriman", pacman=pacman)
def test_search(mocker: MockerFixture) -> None: def test_remote_git_url(remote: Remote, pacman: Pacman) -> None:
"""
must raise NotImplemented for missing remote git url
"""
with pytest.raises(NotImplementedError):
remote.remote_git_url("package", "repositorys")
def test_remote_web_url(remote: Remote, pacman: Pacman) -> None:
"""
must raise NotImplemented for missing remote web url
"""
with pytest.raises(NotImplementedError):
remote.remote_web_url("package")
def test_search(pacman: Pacman, mocker: MockerFixture) -> None:
""" """
must call search method must call search method
""" """
search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.package_search") search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.package_search")
Remote.search("ahriman") Remote.search("ahriman", pacman=pacman)
search_mock.assert_called_once_with("ahriman") search_mock.assert_called_once_with("ahriman", pacman=pacman)
def test_package_info(remote: Remote) -> None: def test_package_info(remote: Remote, pacman: Pacman) -> None:
""" """
must raise NotImplemented for missing package info method must raise NotImplemented for missing package info method
""" """
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
remote.package_info("package") remote.package_info("package", pacman=pacman)
def test_package_search(remote: Remote) -> None: def test_package_search(remote: Remote, pacman: Pacman) -> None:
""" """
must raise NotImplemented for missing package search method must raise NotImplemented for missing package search method
""" """
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
remote.package_search("package") remote.package_search("package", pacman=pacman)

View File

@ -15,3 +15,17 @@ def test_all_packages_with_provides(pacman: Pacman) -> None:
package list must contain provides packages package list must contain provides packages
""" """
assert "sh" in pacman.all_packages() assert "sh" in pacman.all_packages()
def test_get(pacman: Pacman) -> None:
"""
must retrieve package
"""
assert list(pacman.get("pacman"))
def test_get_empty(pacman: Pacman) -> None:
"""
must return empty packages list without exception
"""
assert not list(pacman.get("some-random-name"))

View File

@ -5,6 +5,7 @@ from pytest_mock import MockerFixture
from unittest import mock from unittest import mock
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.models.remote_source import RemoteSource
def test_add(mocker: MockerFixture) -> None: def test_add(mocker: MockerFixture) -> None:
@ -45,7 +46,7 @@ def test_diff(mocker: MockerFixture) -> None:
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
def test_fetch_empty(mocker: MockerFixture) -> None: def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None:
""" """
must do nothing in case if no branches available must do nothing in case if no branches available
""" """
@ -53,46 +54,51 @@ def test_fetch_empty(mocker: MockerFixture) -> None:
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False) mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
Sources.fetch(Path("local"), "remote") Sources.fetch(Path("local"), remote_source)
check_output_mock.assert_not_called() check_output_mock.assert_not_called()
def test_fetch_existing(mocker: MockerFixture) -> None: def test_fetch_existing(remote_source: RemoteSource, mocker: MockerFixture) -> None:
""" """
must fetch new package via fetch command must fetch new package via fetch command
""" """
mocker.patch("pathlib.Path.is_dir", return_value=True) mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=True) mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local") local = Path("local")
Sources.fetch(local, "remote") Sources.fetch(local, remote_source)
check_output_mock.assert_has_calls([ check_output_mock.assert_has_calls([
mock.call("git", "fetch", "origin", Sources._branch, mock.call("git", "fetch", "origin", remote_source.branch,
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "checkout", "--force", Sources._branch, mock.call("git", "checkout", "--force", remote_source.branch,
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "reset", "--hard", f"origin/{Sources._branch}", mock.call("git", "reset", "--hard", f"origin/{remote_source.branch}",
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
]) ])
move_mock.assert_called_once_with(local.resolve(), local)
def test_fetch_new(mocker: MockerFixture) -> None: def test_fetch_new(remote_source: RemoteSource, mocker: MockerFixture) -> None:
""" """
must fetch new package via clone command must fetch new package via clone command
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local") local = Path("local")
Sources.fetch(local, "remote") Sources.fetch(local, remote_source)
check_output_mock.assert_has_calls([ check_output_mock.assert_has_calls([
mock.call("git", "clone", "remote", str(local), exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), mock.call("git", "clone", "--branch", remote_source.branch, "--single-branch",
mock.call("git", "checkout", "--force", Sources._branch, remote_source.git_url, str(local), exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "checkout", "--force", remote_source.branch,
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "reset", "--hard", f"origin/{Sources._branch}", mock.call("git", "reset", "--hard", f"origin/{remote_source.branch}",
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
]) ])
move_mock.assert_called_once_with(local.resolve(), local)
def test_fetch_new_without_remote(mocker: MockerFixture) -> None: def test_fetch_new_without_remote(mocker: MockerFixture) -> None:
@ -101,15 +107,28 @@ def test_fetch_new_without_remote(mocker: MockerFixture) -> None:
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local") local = Path("local")
Sources.fetch(local, None) Sources.fetch(local, None)
check_output_mock.assert_has_calls([ check_output_mock.assert_has_calls([
mock.call("git", "checkout", "--force", Sources._branch, mock.call("git", "checkout", "--force", Sources.DEFAULT_BRANCH,
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "reset", "--hard", f"origin/{Sources._branch}", mock.call("git", "reset", "--hard", f"origin/{Sources.DEFAULT_BRANCH}",
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
]) ])
move_mock.assert_called_once_with(local.resolve(), local)
def test_fetch_relative(remote_source: RemoteSource, mocker: MockerFixture) -> None:
"""
must process move correctly on relative directory
"""
mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
Sources.fetch(Path("path"), remote_source)
move_mock.assert_called_once_with(Path("path").resolve(), Path("path"))
def test_has_remotes(mocker: MockerFixture) -> None: def test_has_remotes(mocker: MockerFixture) -> None:
@ -140,33 +159,53 @@ def test_init(mocker: MockerFixture) -> None:
local = Path("local") local = Path("local")
Sources.init(local) Sources.init(local)
check_output_mock.assert_called_once_with("git", "init", "--initial-branch", Sources._branch, check_output_mock.assert_called_once_with("git", "init", "--initial-branch", Sources.DEFAULT_BRANCH,
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
def test_load(mocker: MockerFixture) -> None: def test_load(remote_source: RemoteSource, mocker: MockerFixture) -> None:
""" """
must load packages sources correctly must load packages sources correctly
""" """
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply") patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
Sources.load(Path("local"), "remote", "patch") Sources.load(Path("local"), remote_source, "patch")
fetch_mock.assert_called_once_with(Path("local"), "remote") fetch_mock.assert_called_once_with(Path("local"), remote_source)
patch_mock.assert_called_once_with(Path("local"), "patch") patch_mock.assert_called_once_with(Path("local"), "patch")
def test_load_no_patch(mocker: MockerFixture) -> None: def test_load_no_patch(remote_source: RemoteSource, mocker: MockerFixture) -> None:
""" """
must load packages sources correctly without patches must load packages sources correctly without patches
""" """
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply") patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
Sources.load(Path("local"), "remote", None) Sources.load(Path("local"), remote_source, None)
patch_mock.assert_not_called() patch_mock.assert_not_called()
def test_move(mocker: MockerFixture) -> None:
"""
must move content between directories
"""
mocker.patch("ahriman.core.build_tools.sources.walk", return_value=[Path("/source/path")])
move_mock = mocker.patch("shutil.move")
Sources.move(Path("/source"), Path("/destination"))
move_mock.assert_called_once_with(Path("/source/path"), Path("/destination/path"))
def test_move_same(mocker: MockerFixture) -> None:
"""
must not do anything in case if directories are the same
"""
walk_mock = mocker.patch("ahriman.core.build_tools.sources.walk")
Sources.move(Path("/same"), Path("/same"))
walk_mock.assert_not_called()
def test_patch_apply(mocker: MockerFixture) -> None: def test_patch_apply(mocker: MockerFixture) -> None:
""" """
must apply patches if any must apply patches if any

View File

@ -1,6 +1,5 @@
import pytest import pytest
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.repo import Repo from ahriman.core.alpm.repo import Repo
from ahriman.core.build_tools.task import Task from ahriman.core.build_tools.task import Task
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@ -37,20 +36,6 @@ def leaf_python_schedule(package_python_schedule: Package) -> Leaf:
return Leaf(package_python_schedule, set()) return Leaf(package_python_schedule, set())
@pytest.fixture
def pacman(configuration: Configuration) -> Pacman:
"""
fixture for pacman wrapper
Args:
configuration(Configuration): configuration fixture
Returns:
Pacman: pacman wrapper test instance
"""
return Pacman(configuration)
@pytest.fixture @pytest.fixture
def repo(configuration: Configuration, repository_paths: RepositoryPaths) -> Repo: def repo(configuration: Configuration, repository_paths: RepositoryPaths) -> Repo:
""" """

View File

@ -15,11 +15,24 @@ def test_migrate_data_initial(connection: Connection, configuration: Configurati
packages = mocker.patch("ahriman.core.database.data.migrate_package_statuses") packages = mocker.patch("ahriman.core.database.data.migrate_package_statuses")
patches = mocker.patch("ahriman.core.database.data.migrate_patches") patches = mocker.patch("ahriman.core.database.data.migrate_patches")
users = mocker.patch("ahriman.core.database.data.migrate_users_data") users = mocker.patch("ahriman.core.database.data.migrate_users_data")
remotes = mocker.patch("ahriman.core.database.data.migrate_package_remotes")
migrate_data(MigrationResult(old_version=0, new_version=900), connection, configuration) migrate_data(MigrationResult(old_version=0, new_version=900), connection, configuration)
packages.assert_called_once_with(connection, repository_paths) packages.assert_called_once_with(connection, repository_paths)
patches.assert_called_once_with(connection, repository_paths) patches.assert_called_once_with(connection, repository_paths)
users.assert_called_once_with(connection, configuration) users.assert_called_once_with(connection, configuration)
remotes.assert_called_once_with(connection, repository_paths)
def test_migrate_data_remotes(connection: Connection, configuration: Configuration,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must perform initial migration
"""
remotes = mocker.patch("ahriman.core.database.data.migrate_package_remotes")
migrate_data(MigrationResult(old_version=1, new_version=900), connection, configuration)
remotes.assert_called_once_with(connection, repository_paths)
def test_migrate_data_skip(connection: Connection, configuration: Configuration, mocker: MockerFixture) -> None: def test_migrate_data_skip(connection: Connection, configuration: Configuration, mocker: MockerFixture) -> None:

View File

@ -0,0 +1,66 @@
import pytest
from pytest_mock import MockerFixture
from sqlite3 import Connection
from ahriman.core.database.data import migrate_package_remotes
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
def test_migrate_package_remotes(package_ahriman: Package, connection: Connection, repository_paths: RepositoryPaths,
mocker: MockerFixture) -> None:
"""
must put package remotes to database
"""
mocker.patch(
"ahriman.core.database.operations.package_operations.PackageOperations._packages_get_select_package_bases",
return_value={package_ahriman.base: package_ahriman})
mocker.patch("pathlib.Path.exists", return_value=False)
migrate_package_remotes(connection, repository_paths)
connection.execute.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
def test_migrate_package_remotes_has_local(package_ahriman: Package, connection: Connection,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must skip processing for packages which have local cache
"""
mocker.patch(
"ahriman.core.database.operations.package_operations.PackageOperations._packages_get_select_package_bases",
return_value={package_ahriman.base: package_ahriman})
mocker.patch("pathlib.Path.exists", return_value=True)
migrate_package_remotes(connection, repository_paths)
connection.execute.assert_not_called()
def test_migrate_package_remotes_vcs(package_ahriman: Package, connection: Connection,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must process VCS packages with local cache
"""
mocker.patch(
"ahriman.core.database.operations.package_operations.PackageOperations._packages_get_select_package_bases",
return_value={package_ahriman.base: package_ahriman})
mocker.patch("pathlib.Path.exists", return_value=True)
mocker.patch.object(Package, "is_vcs", True)
migrate_package_remotes(connection, repository_paths)
connection.execute.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
def test_migrate_package_remotes_no_remotes(package_ahriman: Package, connection: Connection,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must skip processing in case if no remotes generated (should never happen)
"""
mocker.patch(
"ahriman.core.database.operations.package_operations.PackageOperations._packages_get_select_package_bases",
return_value={package_ahriman.base: package_ahriman})
mocker.patch("pathlib.Path.exists", return_value=False)
mocker.patch("ahriman.models.remote_source.RemoteSource.from_remote", return_value=None)
migrate_package_remotes(connection, repository_paths)
connection.execute.assert_not_called()

View File

@ -9,7 +9,7 @@ from ahriman.core.database.data import migrate_users_data
def test_migrate_users_data(connection: Connection, configuration: Configuration) -> None: def test_migrate_users_data(connection: Connection, configuration: Configuration) -> None:
""" """
must users to database must put users to database
""" """
configuration.set_option("auth:read", "user1", "password1") configuration.set_option("auth:read", "user1", "password1")
configuration.set_option("auth:write", "user2", "password2") configuration.set_option("auth:write", "user2", "password2")

View File

@ -0,0 +1,8 @@
from ahriman.core.database.migrations.m001_package_source import steps
def test_migration_package_source() -> None:
"""
migration must not be empty
"""
assert steps

View File

@ -7,6 +7,8 @@ from unittest import mock
from ahriman.core.database.sqlite import SQLite from ahriman.core.database.sqlite import SQLite
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
def test_package_remove_package_base(database: SQLite, connection: Connection) -> None: def test_package_remove_package_base(database: SQLite, connection: Connection) -> None:
@ -166,3 +168,23 @@ def test_package_update_update(database: SQLite, package_ahriman: Package) -> No
assert next(db_status.status assert next(db_status.status
for db_package, db_status in database.packages_get() for db_package, db_status in database.packages_get()
if db_package.base == package_ahriman.base) == BuildStatusEnum.Failed if db_package.base == package_ahriman.base) == BuildStatusEnum.Failed
def test_remote_update_get(database: SQLite, package_ahriman: Package) -> None:
"""
must insert and retrieve package remote
"""
database.remote_update(package_ahriman)
assert database.remotes_get()[package_ahriman.base] == package_ahriman.remote
def test_remote_update_update(database: SQLite, package_ahriman: Package) -> None:
"""
must perform package remote update for existing package
"""
database.remote_update(package_ahriman)
remote_source = RemoteSource.from_remote(PackageSource.Repository, package_ahriman.base, "community")
package_ahriman.remote = remote_source
database.remote_update(package_ahriman)
assert database.remotes_get()[package_ahriman.base] == remote_source

View File

@ -15,11 +15,11 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag
single_packages = [ single_packages = [
Package(base=package_python_schedule.base, Package(base=package_python_schedule.base,
version=package_python_schedule.version, version=package_python_schedule.version,
aur_url=package_python_schedule.aur_url, remote=package_python_schedule.remote,
packages={package: props}) packages={package: props})
for package, props in package_python_schedule.packages.items() for package, props in package_python_schedule.packages.items()
] + [package_ahriman] ] + [package_ahriman]
mocker.patch("ahriman.models.package.Package.load", side_effect=single_packages) mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")]) packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
assert len(packages) == 2 assert len(packages) == 2
@ -36,7 +36,7 @@ def test_load_archives_failed(repository: Repository, mocker: MockerFixture) ->
""" """
must skip packages which cannot be loaded must skip packages which cannot be loaded
""" """
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception()) mocker.patch("ahriman.models.package.Package.from_archive", side_effect=Exception())
assert not repository.load_archives([Path("a.pkg.tar.xz")]) assert not repository.load_archives([Path("a.pkg.tar.xz")])
@ -55,12 +55,12 @@ def test_load_archives_different_version(repository: Repository, package_python_
single_packages = [ single_packages = [
Package(base=package_python_schedule.base, Package(base=package_python_schedule.base,
version=package_python_schedule.version, version=package_python_schedule.version,
aur_url=package_python_schedule.aur_url, remote=package_python_schedule.remote,
packages={package: props}) packages={package: props})
for package, props in package_python_schedule.packages.items() for package, props in package_python_schedule.packages.items()
] ]
single_packages[0].version = "0.0.1-1" single_packages[0].version = "0.0.1-1"
mocker.patch("ahriman.models.package.Package.load", side_effect=single_packages) mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")]) packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")])
assert len(packages) == 1 assert len(packages) == 1

View File

@ -5,6 +5,7 @@ from pytest_mock import MockerFixture
from ahriman.core.repository.update_handler import UpdateHandler from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
def test_packages(update_handler: UpdateHandler) -> None: def test_packages(update_handler: UpdateHandler) -> None:
@ -22,7 +23,22 @@ def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package,
""" """
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending")
assert update_handler.updates_aur([], False) == [package_ahriman]
status_client_mock.assert_called_once_with(package_ahriman.base)
def test_updates_aur_official(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must provide updates based on repository data
"""
package_ahriman.remote = RemoteSource.from_remote(PackageSource.Repository, package_ahriman.base, "community")
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending")
assert update_handler.updates_aur([], False) == [package_ahriman] assert update_handler.updates_aur([], False) == [package_ahriman]
@ -36,7 +52,7 @@ def test_updates_aur_success(update_handler: UpdateHandler, package_ahriman: Pac
""" """
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=False) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=False)
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
assert not update_handler.updates_aur([], False) assert not update_handler.updates_aur([], False)
@ -49,7 +65,7 @@ def test_updates_aur_failed(update_handler: UpdateHandler, package_ahriman: Pack
must update status via client for failed load must update status via client for failed load
""" """
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception()) mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed")
update_handler.updates_aur([], False) update_handler.updates_aur([], False)
@ -64,11 +80,10 @@ def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Pack
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages",
return_value=[package_ahriman, package_python_schedule]) return_value=[package_ahriman, package_python_schedule])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
package_load_mock = mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) package_load_mock = mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
assert update_handler.updates_aur([package_ahriman.base], False) == [package_ahriman] assert update_handler.updates_aur([package_ahriman.base], False) == [package_ahriman]
package_load_mock.assert_called_once_with(package_ahriman.base, PackageSource.AUR, package_load_mock.assert_called_once_with(package_ahriman.base, update_handler.pacman)
update_handler.pacman, update_handler.aur_url)
def test_updates_aur_ignore(update_handler: UpdateHandler, package_ahriman: Package, def test_updates_aur_ignore(update_handler: UpdateHandler, package_ahriman: Package,
@ -78,7 +93,7 @@ def test_updates_aur_ignore(update_handler: UpdateHandler, package_ahriman: Pack
""" """
update_handler.ignore_list = [package_ahriman.base] update_handler.ignore_list = [package_ahriman.base]
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
package_load_mock = mocker.patch("ahriman.models.package.Package.load") package_load_mock = mocker.patch("ahriman.models.package.Package.from_aur")
update_handler.updates_aur([], False) update_handler.updates_aur([], False)
package_load_mock.assert_not_called() package_load_mock.assert_not_called()
@ -105,13 +120,12 @@ def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package,
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base]) mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
package_load_mock = mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) package_load_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending")
assert update_handler.updates_local() == [package_ahriman] assert update_handler.updates_local() == [package_ahriman]
fetch_mock.assert_called_once_with(package_ahriman.base, remote=None) fetch_mock.assert_called_once_with(package_ahriman.base, remote=None)
package_load_mock.assert_called_once_with( package_load_mock.assert_called_once_with(package_ahriman.base)
package_ahriman.base, PackageSource.Local, update_handler.pacman, update_handler.aur_url)
status_client_mock.assert_called_once_with(package_ahriman.base) status_client_mock.assert_called_once_with(package_ahriman.base)
@ -123,7 +137,7 @@ def test_updates_local_unknown(update_handler: UpdateHandler, package_ahriman: P
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base]) mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown")
assert update_handler.updates_local() == [package_ahriman] assert update_handler.updates_local() == [package_ahriman]
@ -151,7 +165,7 @@ def test_updates_local_success(update_handler: UpdateHandler, package_ahriman: P
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base]) mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=False) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=False)
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
assert not update_handler.updates_local() assert not update_handler.updates_local()

View File

@ -51,7 +51,7 @@ def test_leaf_load(package_ahriman: Package, database: SQLite, mocker: MockerFix
assert leaf.dependencies == {"ahriman-dependency"} assert leaf.dependencies == {"ahriman-dependency"}
tempdir_mock.assert_called_once_with() tempdir_mock.assert_called_once_with()
load_mock.assert_called_once_with( load_mock.assert_called_once_with(
pytest.helpers.anyvar(int), package_ahriman.git_url, database.patches_get(package_ahriman.base)) pytest.helpers.anyvar(int), package_ahriman.remote, database.patches_get(package_ahriman.base))
dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int)) dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int))
rmtree_mock.assert_called_once_with(pytest.helpers.anyvar(int), ignore_errors=True) rmtree_mock.assert_called_once_with(pytest.helpers.anyvar(int), ignore_errors=True)

View File

@ -1,14 +1,18 @@
import datetime
import pytest import pytest
import time import time
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock, PropertyMock
from ahriman import version from ahriman import version
from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.counters import Counters from ahriman.models.counters import Counters
from ahriman.models.internal_status import InternalStatus from ahriman.models.internal_status import InternalStatus
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
from ahriman.models.user_identity import UserIdentity from ahriman.models.user_identity import UserIdentity
@ -67,7 +71,7 @@ def package_tpacpi_bat_git() -> Package:
return Package( return Package(
base="tpacpi-bat-git", base="tpacpi-bat-git",
version="3.1.r12.g4959b52-1", version="3.1.r12.g4959b52-1",
aur_url="https://aur.archlinux.org", remote=RemoteSource.from_remote(PackageSource.AUR, "tpacpi-bat-git", "aur"),
packages={"tpacpi-bat-git": PackageDescription()}) packages={"tpacpi-bat-git": PackageDescription()})
@ -88,22 +92,34 @@ def pyalpm_handle(pyalpm_package_ahriman: MagicMock) -> MagicMock:
@pytest.fixture @pytest.fixture
def pyalpm_package_ahriman(package_ahriman: Package) -> MagicMock: def pyalpm_package_ahriman(aur_package_ahriman: AURPackage) -> MagicMock:
""" """
mock object for pyalpm package mock object for pyalpm package
Args: Args:
package_ahriman(Package): package fixture aur_package_ahriman(AURPackage): package fixture
Returns: Returns:
MagicMock: pyalpm package mock MagicMock: pyalpm package mock
""" """
mock = MagicMock() mock = MagicMock()
type(mock).base = PropertyMock(return_value=package_ahriman.base) db = type(mock).db = MagicMock()
type(mock).depends = PropertyMock(return_value=["python-aur"])
type(mock).name = PropertyMock(return_value=package_ahriman.base) type(mock).base = PropertyMock(return_value=aur_package_ahriman.package_base)
type(mock).provides = PropertyMock(return_value=["python-ahriman"]) type(mock).builddate = PropertyMock(
type(mock).version = PropertyMock(return_value=package_ahriman.version) return_value=aur_package_ahriman.last_modified.replace(tzinfo=datetime.timezone.utc).timestamp())
type(mock).conflicts = PropertyMock(return_value=aur_package_ahriman.conflicts)
type(db).name = PropertyMock(return_value="aur")
type(mock).depends = PropertyMock(return_value=aur_package_ahriman.depends)
type(mock).desc = PropertyMock(return_value=aur_package_ahriman.description)
type(mock).licenses = PropertyMock(return_value=aur_package_ahriman.license)
type(mock).makedepends = PropertyMock(return_value=aur_package_ahriman.make_depends)
type(mock).name = PropertyMock(return_value=aur_package_ahriman.name)
type(mock).optdepends = PropertyMock(return_value=aur_package_ahriman.opt_depends)
type(mock).provides = PropertyMock(return_value=aur_package_ahriman.provides)
type(mock).version = PropertyMock(return_value=aur_package_ahriman.version)
type(mock).url = PropertyMock(return_value=aur_package_ahriman.url)
return mock return mock

View File

@ -1,5 +1,6 @@
import datetime import datetime
import json import json
import pyalpm # typing: ignore
from dataclasses import asdict, fields from dataclasses import asdict, fields
from pathlib import Path from pathlib import Path
@ -53,6 +54,22 @@ def test_from_json_2(aur_package_ahriman: AURPackage, mocker: MockerFixture) ->
assert AURPackage.from_json(asdict(aur_package_ahriman)) == aur_package_ahriman assert AURPackage.from_json(asdict(aur_package_ahriman)) == aur_package_ahriman
def test_from_pacman(pyalpm_package_ahriman: pyalpm.Package, aur_package_ahriman: AURPackage,
resource_path_root: Path) -> None:
"""
must load package from repository database
"""
model = AURPackage.from_pacman(pyalpm_package_ahriman)
# some fields are missing so we are changing them
model.id = aur_package_ahriman.id
model.package_base_id = aur_package_ahriman.package_base_id
model.first_submitted = aur_package_ahriman.first_submitted
model.url_path = aur_package_ahriman.url_path
model.maintainer = aur_package_ahriman.maintainer
assert model == aur_package_ahriman
def test_from_repo(aur_package_akonadi: AURPackage, resource_path_root: Path) -> None: def test_from_repo(aur_package_akonadi: AURPackage, resource_path_root: Path) -> None:
""" """
must load package from repository api json must load package from repository api json

View File

@ -4,10 +4,10 @@ from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import InvalidPackageInfo from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -21,15 +21,6 @@ def test_depends(package_python_schedule: Package) -> None:
) )
def test_git_url(package_ahriman: Package) -> None:
"""
must generate valid git url
"""
assert package_ahriman.git_url.endswith(".git")
assert package_ahriman.git_url.startswith(package_ahriman.aur_url)
assert package_ahriman.base in package_ahriman.git_url
def test_groups(package_ahriman: Package) -> None: def test_groups(package_ahriman: Package) -> None:
""" """
must return list of groups for each package must return list of groups for each package
@ -80,30 +71,23 @@ def test_licenses(package_ahriman: Package) -> None:
assert sorted(package_ahriman.licenses) == package_ahriman.licenses assert sorted(package_ahriman.licenses) == package_ahriman.licenses
def test_web_url(package_ahriman: Package) -> None:
"""
must generate valid web url
"""
assert package_ahriman.web_url.startswith(package_ahriman.aur_url)
assert package_ahriman.base in package_ahriman.web_url
def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None: def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
""" """
must construct package from alpm library must construct package from alpm library
""" """
mocker.patch("ahriman.models.package_description.PackageDescription.from_package", mocker.patch("ahriman.models.package_description.PackageDescription.from_package",
return_value=package_ahriman.packages[package_ahriman.base]) return_value=package_ahriman.packages[package_ahriman.base])
assert Package.from_archive(Path("path"), pyalpm_handle, package_ahriman.aur_url) == package_ahriman assert Package.from_archive(Path("path"), pyalpm_handle, package_ahriman.remote) == package_ahriman
def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None: def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, pacman: Pacman,
mocker: MockerFixture) -> None:
""" """
must construct package from aur must construct package from aur
""" """
mocker.patch("ahriman.core.alpm.remote.aur.AUR.info", return_value=aur_package_ahriman) mocker.patch("ahriman.core.alpm.remote.aur.AUR.info", return_value=aur_package_ahriman)
package = Package.from_aur(package_ahriman.base, package_ahriman.aur_url) package = Package.from_aur(package_ahriman.base, pacman)
assert package_ahriman.base == package.base assert package_ahriman.base == package.base
assert package_ahriman.version == package.version assert package_ahriman.version == package.version
assert package_ahriman.packages.keys() == package.packages.keys() assert package_ahriman.packages.keys() == package.packages.keys()
@ -114,11 +98,12 @@ def test_from_build(package_ahriman: Package, mocker: MockerFixture, resource_pa
must construct package from srcinfo must construct package from srcinfo
""" """
srcinfo = (resource_path_root / "models" / "package_ahriman_srcinfo").read_text() srcinfo = (resource_path_root / "models" / "package_ahriman_srcinfo").read_text()
mocker.patch("pathlib.Path.read_text", return_value=srcinfo) mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
package = Package.from_build(Path("path"), package_ahriman.aur_url) package = Package.from_build(Path("path"))
assert package_ahriman.packages.keys() == package.packages.keys() assert package_ahriman.packages.keys() == package.packages.keys()
package_ahriman.packages = package.packages # we are not going to test PackageDescription here package_ahriman.packages = package.packages # we are not going to test PackageDescription here
package_ahriman.remote = None
assert package_ahriman == package assert package_ahriman == package
@ -126,11 +111,11 @@ def test_from_build_failed(package_ahriman: Package, mocker: MockerFixture) -> N
""" """
must raise exception if there are errors during srcinfo load must raise exception if there are errors during srcinfo load
""" """
mocker.patch("pathlib.Path.read_text", return_value="") mocker.patch("ahriman.models.package.Package._check_output", return_value="")
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"])) mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
with pytest.raises(InvalidPackageInfo): with pytest.raises(InvalidPackageInfo):
Package.from_build(Path("path"), package_ahriman.aur_url) Package.from_build(Path("path"))
def test_from_json_view_1(package_ahriman: Package) -> None: def test_from_json_view_1(package_ahriman: Package) -> None:
@ -154,97 +139,24 @@ def test_from_json_view_3(package_tpacpi_bat_git: Package) -> None:
assert Package.from_json(package_tpacpi_bat_git.view()) == package_tpacpi_bat_git assert Package.from_json(package_tpacpi_bat_git.view()) == package_tpacpi_bat_git
def test_from_official(package_ahriman: Package, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None: def test_from_official(package_ahriman: Package, aur_package_ahriman: AURPackage, pacman: Pacman,
mocker: MockerFixture) -> None:
""" """
must construct package from official repository must construct package from official repository
""" """
mocker.patch("ahriman.core.alpm.remote.official.Official.info", return_value=aur_package_ahriman) mocker.patch("ahriman.core.alpm.remote.official.Official.info", return_value=aur_package_ahriman)
package = Package.from_official(package_ahriman.base, package_ahriman.aur_url) package = Package.from_official(package_ahriman.base, pacman)
assert package_ahriman.base == package.base assert package_ahriman.base == package.base
assert package_ahriman.version == package.version assert package_ahriman.version == package.version
assert package_ahriman.packages.keys() == package.packages.keys() assert package_ahriman.packages.keys() == package.packages.keys()
def test_load_resolve(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must resolve source before package loading
"""
resolve_mock = mocker.patch("ahriman.models.package_source.PackageSource.resolve",
return_value=PackageSource.Archive)
mocker.patch("ahriman.models.package.Package.from_archive")
Package.load("path", PackageSource.Archive, pyalpm_handle, package_ahriman.aur_url)
resolve_mock.assert_called_once_with("path")
def test_load_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must load package from package archive
"""
load_mock = mocker.patch("ahriman.models.package.Package.from_archive")
Package.load("path", PackageSource.Archive, pyalpm_handle, package_ahriman.aur_url)
load_mock.assert_called_once_with(Path("path"), pyalpm_handle, package_ahriman.aur_url)
def test_load_from_aur(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must load package from AUR
"""
load_mock = mocker.patch("ahriman.models.package.Package.from_aur")
Package.load("path", PackageSource.AUR, pyalpm_handle, package_ahriman.aur_url)
load_mock.assert_called_once_with("path", package_ahriman.aur_url)
def test_load_from_build(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must load package from build directory
"""
load_mock = mocker.patch("ahriman.models.package.Package.from_build")
Package.load("path", PackageSource.Local, pyalpm_handle, package_ahriman.aur_url)
load_mock.assert_called_once_with(Path("path"), package_ahriman.aur_url)
def test_load_from_official(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must load package from AUR
"""
load_mock = mocker.patch("ahriman.models.package.Package.from_official")
Package.load("path", PackageSource.Repository, pyalpm_handle, package_ahriman.aur_url)
load_mock.assert_called_once_with("path", package_ahriman.aur_url)
def test_load_failure(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must raise InvalidPackageInfo on exception
"""
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=InvalidPackageInfo("exception!"))
with pytest.raises(InvalidPackageInfo):
Package.load("path", PackageSource.AUR, pyalpm_handle, package_ahriman.aur_url)
def test_load_failure_exception(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must raise InvalidPackageInfo on random eexception
"""
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
with pytest.raises(InvalidPackageInfo):
Package.load("path", PackageSource.AUR, pyalpm_handle, package_ahriman.aur_url)
def test_load_invalid_source(package_ahriman: Package, pyalpm_handle: MagicMock) -> None:
"""
must raise InvalidPackageInfo on unsupported source
"""
with pytest.raises(InvalidPackageInfo):
Package.load("path", PackageSource.Remote, pyalpm_handle, package_ahriman.aur_url)
def test_dependencies_failed(mocker: MockerFixture) -> None: def test_dependencies_failed(mocker: MockerFixture) -> None:
""" """
must raise exception if there are errors during srcinfo load must raise exception if there are errors during srcinfo load
""" """
mocker.patch("pathlib.Path.read_text", return_value="") mocker.patch("ahriman.models.package.Package._check_output", return_value="")
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"])) mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
with pytest.raises(InvalidPackageInfo): with pytest.raises(InvalidPackageInfo):
@ -256,7 +168,7 @@ def test_dependencies_with_version(mocker: MockerFixture, resource_path_root: Pa
must load correct list of dependencies with version must load correct list of dependencies with version
""" """
srcinfo = (resource_path_root / "models" / "package_yay_srcinfo").read_text() srcinfo = (resource_path_root / "models" / "package_yay_srcinfo").read_text()
mocker.patch("pathlib.Path.read_text", return_value=srcinfo) mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
assert Package.dependencies(Path("path")) == {"git", "go", "pacman"} assert Package.dependencies(Path("path")) == {"git", "go", "pacman"}
@ -266,7 +178,7 @@ def test_dependencies_with_version_and_overlap(mocker: MockerFixture, resource_p
must load correct list of dependencies with version must load correct list of dependencies with version
""" """
srcinfo = (resource_path_root / "models" / "package_gcc10_srcinfo").read_text() srcinfo = (resource_path_root / "models" / "package_gcc10_srcinfo").read_text()
mocker.patch("pathlib.Path.read_text", return_value=srcinfo) mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
assert Package.dependencies(Path("path")) == {"glibc", "doxygen", "binutils", "git", "libmpc", "python", "zstd"} assert Package.dependencies(Path("path")) == {"glibc", "doxygen", "binutils", "git", "libmpc", "python", "zstd"}
@ -328,7 +240,7 @@ def test_full_depends(package_ahriman: Package, package_python_schedule: Package
assert package_ahriman.full_depends(pyalpm_handle, [package_python_schedule]) == package_ahriman.depends assert package_ahriman.full_depends(pyalpm_handle, [package_python_schedule]) == package_ahriman.depends
package_python_schedule.packages[package_python_schedule.base].depends = [package_ahriman.base] package_python_schedule.packages[package_python_schedule.base].depends = [package_ahriman.base]
expected = sorted(set(package_python_schedule.depends + ["python-aur"])) expected = sorted(set(package_python_schedule.depends + package_ahriman.depends))
assert package_python_schedule.full_depends(pyalpm_handle, [package_python_schedule]) == expected assert package_python_schedule.full_depends(pyalpm_handle, [package_python_schedule]) == expected

View File

@ -1,4 +1,3 @@
from dataclasses import asdict
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.models.package_description import PackageDescription from ahriman.models.package_description import PackageDescription
@ -24,14 +23,14 @@ def test_from_json(package_description_ahriman: PackageDescription) -> None:
""" """
must construct description from json object must construct description from json object
""" """
assert PackageDescription.from_json(asdict(package_description_ahriman)) == package_description_ahriman assert PackageDescription.from_json(package_description_ahriman.view()) == package_description_ahriman
def test_from_json_with_unknown_fields(package_description_ahriman: PackageDescription) -> None: def test_from_json_with_unknown_fields(package_description_ahriman: PackageDescription) -> None:
""" """
must construct description from json object containing unknown fields must construct description from json object containing unknown fields
""" """
dump = asdict(package_description_ahriman) dump = package_description_ahriman.view()
dump.update(unknown_field="value") dump.update(unknown_field="value")
assert PackageDescription.from_json(dump) == package_description_ahriman assert PackageDescription.from_json(dump) == package_description_ahriman

View File

@ -0,0 +1,82 @@
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
def test_post_init(remote_source: RemoteSource) -> None:
"""
must convert string source to enum
"""
remote = RemoteSource(
git_url=remote_source.git_url,
web_url=remote_source.web_url,
path=remote_source.path,
branch=remote_source.branch,
source=remote_source.source.value,
)
assert remote == remote_source
def test_pkgbuild_dir(remote_source: RemoteSource) -> None:
"""
must return path as is in `path` property
"""
assert isinstance(remote_source.pkgbuild_dir, Path)
assert remote_source.pkgbuild_dir.name == ""
def test_from_json(remote_source: RemoteSource) -> None:
"""
must construct remote source from json
"""
assert RemoteSource.from_json(remote_source.view()) == remote_source
def test_from_json_empty() -> None:
"""
must return None in case of empty dictionary, which is required by the database wrapper
"""
assert RemoteSource.from_json({}) is None
def test_from_remote_aur(package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must construct remote from AUR source
"""
remote_git_url_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.remote_git_url")
remote_web_url_mock = mocker.patch("ahriman.core.alpm.remote.aur.AUR.remote_web_url")
remote = RemoteSource.from_remote(PackageSource.AUR, package_ahriman.base, "aur")
remote_git_url_mock.assert_called_once_with(package_ahriman.base, "aur")
remote_web_url_mock.assert_called_once_with(package_ahriman.base)
assert remote.pkgbuild_dir == Path(".")
assert remote.branch == "master"
assert remote.source == PackageSource.AUR
def test_from_remote_official(package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must construct remote from official repository source
"""
remote_git_url_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.remote_git_url")
remote_web_url_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.remote_web_url")
remote = RemoteSource.from_remote(PackageSource.Repository, package_ahriman.base, "community")
remote_git_url_mock.assert_called_once_with(package_ahriman.base, "community")
remote_web_url_mock.assert_called_once_with(package_ahriman.base)
assert remote.pkgbuild_dir == Path("trunk")
assert remote.branch.endswith(package_ahriman.base)
assert remote.source == PackageSource.Repository
def test_from_remote_other() -> None:
"""
must return None in case if source is not one of AUR or Repository
"""
assert RemoteSource.from_remote(PackageSource.Archive, "package", "repository") is None
assert RemoteSource.from_remote(PackageSource.Directory, "package", "repository") is None
assert RemoteSource.from_remote(PackageSource.Local, "package", "repository") is None
assert RemoteSource.from_remote(PackageSource.Remote, "package", "repository") is None

View File

@ -37,7 +37,7 @@ async def test_get_exception(client: TestClient, mocker: MockerFixture) -> None:
response = await client.get("/service-api/v1/search") response = await client.get("/service-api/v1/search")
assert response.status == 404 assert response.status == 404
search_mock.assert_called_once_with() search_mock.assert_called_once_with(pacman=pytest.helpers.anyvar(int))
async def test_get_join(client: TestClient, mocker: MockerFixture) -> None: async def test_get_join(client: TestClient, mocker: MockerFixture) -> None:
@ -48,4 +48,4 @@ async def test_get_join(client: TestClient, mocker: MockerFixture) -> None:
response = await client.get("/service-api/v1/search", params=[("for", "ahriman"), ("for", "maybe")]) response = await client.get("/service-api/v1/search", params=[("for", "ahriman"), ("for", "maybe")])
assert response.ok assert response.ok
search_mock.assert_called_once_with("ahriman", "maybe") search_mock.assert_called_once_with("ahriman", "maybe", pacman=pytest.helpers.anyvar(int))

View File

@ -4,7 +4,6 @@ logging = logging.ini
database = ../../../ahriman-test.db database = ../../../ahriman-test.db
[alpm] [alpm]
aur_url = https://aur.archlinux.org
database = /var/lib/pacman database = /var/lib/pacman
repositories = core extra community multilib repositories = core extra community multilib
root = / root = /