diff --git a/docs/architecture.md b/docs/architecture.md
index 490671e1..759c4219 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -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:
-* `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.build_tools` is a package which provides wrapper for `devtools` commands.
* `ahriman.core.database` is everything including data and schema migrations for database.
diff --git a/docs/configuration.md b/docs/configuration.md
index d3af5ed0..d6308a6a 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -23,7 +23,6 @@ Base configuration settings.
libalpm and AUR related configuration.
-* `aur_url` - base url for AUR, string, required.
* `database` - path to pacman local database cache, string, required.
* `repositories` - list of pacman repositories, space separated list of strings, required.
* `root` - root for alpm library, string, required.
diff --git a/docs/faq.md b/docs/faq.md
index 22ec6852..e4f34e54 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -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).
+### 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
TL;DR
diff --git a/package/share/ahriman/settings/ahriman.ini b/package/share/ahriman/settings/ahriman.ini
index 4805f947..a96c2749 100644
--- a/package/share/ahriman/settings/ahriman.ini
+++ b/package/share/ahriman/settings/ahriman.ini
@@ -4,7 +4,6 @@ logging = ahriman.ini.d/logging.ini
database = /var/lib/ahriman/ahriman.db
[alpm]
-aur_url = https://aur.archlinux.org
database = /var/lib/pacman
repositories = core extra community multilib
root = /
diff --git a/package/share/ahriman/templates/build-status.jinja2 b/package/share/ahriman/templates/build-status.jinja2
index 6aa15815..bfbc582f 100644
--- a/package/share/ahriman/templates/build-status.jinja2
+++ b/package/share/ahriman/templates/build-status.jinja2
@@ -74,7 +74,7 @@
{% for package in packages %}
|
- {{ package.base }} |
+ {% if package.web_url is not none %}{{ package.base }}{% else %}{{ package.base }}{% endif %} |
{{ package.version }} |
{{ package.packages|join(" "|safe) }} |
{{ package.groups|join(" "|safe) }} |
diff --git a/src/ahriman/application/application/application_packages.py b/src/ahriman/application/application/application_packages.py
index 17f9ef0e..c4184d7c 100644
--- a/src/ahriman/application/application/application_packages.py
+++ b/src/ahriman/application/application/application_packages.py
@@ -80,12 +80,13 @@ class ApplicationPackages(ApplicationProperties):
known_packages(Set[str]): list of packages which are known by the service
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.remote_update(package)
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)
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
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)
- 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
self.database.build_queue_insert(package)
@@ -132,6 +134,18 @@ class ApplicationPackages(ApplicationProperties):
for chunk in response.iter_content(chunk_size=1024):
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:
"""
process package dependencies
diff --git a/src/ahriman/application/application/application_repository.py b/src/ahriman/application/application/application_repository.py
index d8bc8f23..32187a55 100644
--- a/src/ahriman/application/application/application_repository.py
+++ b/src/ahriman/application/application/application_repository.py
@@ -128,14 +128,14 @@ class ApplicationRepository(ApplicationProperties):
packages: List[str] = []
for single in probe.packages:
try:
- _ = Package.from_aur(single, probe.aur_url)
+ _ = Package.from_aur(single, self.repository.pacman)
except Exception:
packages.append(single)
return packages
def unknown_local(probe: Package) -> List[str]:
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())
return list(packages)
diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py
index 919891d2..73d5f1e0 100644
--- a/src/ahriman/application/handlers/patch.py
+++ b/src/ahriman/application/handlers/patch.py
@@ -29,7 +29,6 @@ from ahriman.core.configuration import Configuration
from ahriman.core.formatters.string_printer import StringPrinter
from ahriman.models.action import Action
from ahriman.models.package import Package
-from ahriman.models.package_source import PackageSource
class Patch(Handler):
@@ -57,21 +56,20 @@ class Patch(Handler):
elif args.action == Action.Remove:
Patch.patch_set_remove(application, args.package)
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
- 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
Args:
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
"""
- package = Package.load(sources_dir, PackageSource.Local, application.repository.pacman,
- application.repository.aur_url)
- patch = Sources.patch_create(Path(sources_dir), *track)
+ package = Package.from_build(sources_dir)
+ patch = Sources.patch_create(sources_dir, *track)
application.database.patches_insert(package.base, patch)
@staticmethod
diff --git a/src/ahriman/application/handlers/search.py b/src/ahriman/application/handlers/search.py
index 0d5e7c61..8c42cf6b 100644
--- a/src/ahriman/application/handlers/search.py
+++ b/src/ahriman/application/handlers/search.py
@@ -22,6 +22,7 @@ import argparse
from dataclasses import fields
from typing import Callable, Iterable, List, Tuple, Type
+from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.alpm.remote.official import Official
@@ -55,8 +56,10 @@ class Search(Handler):
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
- official_packages_list = Official.multisearch(*args.search)
- aur_packages_list = AUR.multisearch(*args.search)
+ application = Application(architecture, configuration, no_report, unsafe)
+
+ 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)
for packages_list in (official_packages_list, aur_packages_list):
diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py
index cca3ce37..4dffa666 100644
--- a/src/ahriman/core/alpm/pacman.py
+++ b/src/ahriman/core/alpm/pacman.py
@@ -17,8 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-from pyalpm import Handle # type: ignore
-from typing import Set
+from pyalpm import Handle, Package, SIG_PACKAGE # type: ignore
+from typing import Generator, Set
from ahriman.core.configuration import Configuration
@@ -42,7 +42,7 @@ class Pacman:
pacman_root = configuration.getpath("alpm", "database")
self.handle = Handle(root, str(pacman_root))
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]:
"""
@@ -58,3 +58,19 @@ class Pacman:
result.update(package.provides) # provides list for meta-packages
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
diff --git a/src/ahriman/core/alpm/remote/aur.py b/src/ahriman/core/alpm/remote/aur.py
index 34a6c225..6f4e1a36 100644
--- a/src/ahriman/core/alpm/remote/aur.py
+++ b/src/ahriman/core/alpm/remote/aur.py
@@ -19,8 +19,9 @@
#
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.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text
@@ -32,27 +33,15 @@ class AUR(Remote):
AUR RPC wrapper
Attributes:
+ DEFAULT_AUR_URL(str): (class attribute) default AUR url
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
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"
- 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
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
"""
@@ -73,6 +62,33 @@ class AUR(Remote):
raise InvalidPackageInfo(error_details)
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]:
"""
perform request to AUR RPC
@@ -87,7 +103,7 @@ class AUR(Remote):
"""
query: Dict[str, Any] = {
"type": request_type,
- "v": self.rpc_version
+ "v": self.DEFAULT_RPC_VERSION
}
arg_query = "arg[]" if len(args) > 1 else "arg"
@@ -97,7 +113,7 @@ class AUR(Remote):
query[key] = value
try:
- response = requests.get(self.rpc_url, params=query)
+ response = requests.get(self.DEFAULT_RPC_URL, params=query)
response.raise_for_status()
return self.parse_response(response.json())
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)
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
Args:
package_name(str): package name to search
+ pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
@@ -123,12 +140,13 @@ class AUR(Remote):
packages = self.make_request("info", 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
Args:
*keywords(str): keywords to search
+ pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages which match the criteria
diff --git a/src/ahriman/core/alpm/remote/official.py b/src/ahriman/core/alpm/remote/official.py
index 7430154c..afd544a4 100644
--- a/src/ahriman/core/alpm/remote/official.py
+++ b/src/ahriman/core/alpm/remote/official.py
@@ -19,8 +19,9 @@
#
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.exceptions import InvalidPackageInfo
from ahriman.core.util import exception_response_text
@@ -32,22 +33,15 @@ class Official(Remote):
official repository RPC wrapper
Attributes:
- DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
- rpc_url(str): AUR RPC url
+ DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux 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"
- 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
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
"""
@@ -66,6 +60,35 @@ class Official(Remote):
raise InvalidPackageInfo("API validation error")
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]:
"""
perform request to official repositories RPC
@@ -78,7 +101,7 @@ class Official(Remote):
List[AURPackage]: response parsed to package list
"""
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()
return self.parse_response(response.json())
except requests.HTTPError as e:
@@ -88,12 +111,13 @@ class Official(Remote):
self.logger.exception("could not perform request")
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
Args:
package_name(str): package name to search
+ pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
@@ -101,12 +125,13 @@ class Official(Remote):
packages = self.make_request(package_name, by="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
Args:
*keywords(str): keywords to search
+ pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages which match the criteria
diff --git a/src/ahriman/core/alpm/remote/official_syncdb.py b/src/ahriman/core/alpm/remote/official_syncdb.py
new file mode 100644
index 00000000..99f8b471
--- /dev/null
+++ b/src/ahriman/core/alpm/remote/official_syncdb.py
@@ -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 .
+#
+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))
diff --git a/src/ahriman/core/alpm/remote/remote.py b/src/ahriman/core/alpm/remote/remote.py
index 57665f7e..360d3d83 100644
--- a/src/ahriman/core/alpm/remote/remote.py
+++ b/src/ahriman/core/alpm/remote/remote.py
@@ -23,6 +23,7 @@ import logging
from typing import Dict, List, Type
+from ahriman.core.alpm.pacman import Pacman
from ahriman.models.aur_package import AURPackage
@@ -41,26 +42,28 @@ class Remote:
self.logger = logging.getLogger("build_details")
@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
Args:
package_name(str): package name to search
+ pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
"""
- return cls().package_info(package_name)
+ return cls().package_info(package_name, pacman=pacman)
@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
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
+ pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages each of them matches all search terms
@@ -68,7 +71,7 @@ class Remote:
instance = cls()
packages: Dict[str, AURPackage] = {}
for term in filter(lambda word: len(word) > 3, keywords):
- portion = instance.search(term)
+ portion = instance.search(term, pacman=pacman)
packages = {
package.name: package # not mistake to group them by name
for package in portion
@@ -76,25 +79,60 @@ class Remote:
}
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
- 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
Args:
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
+ pacman(Pacman): alpm wrapper instance
Returns:
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
Args:
package_name(str): package name to search
+ pacman(Pacman): alpm wrapper instance
Returns:
AURPackage: package which match the package name
@@ -104,12 +142,13 @@ class Remote:
"""
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
Args:
*keywords(str): keywords to search
+ pacman(Pacman): alpm wrapper instance
Returns:
List[AURPackage]: list of packages which match the criteria
diff --git a/src/ahriman/core/build_tools/sources.py b/src/ahriman/core/build_tools/sources.py
index 8261e176..6e419958 100644
--- a/src/ahriman/core/build_tools/sources.py
+++ b/src/ahriman/core/build_tools/sources.py
@@ -18,11 +18,13 @@
# along with this program. If not, see .
#
import logging
+import shutil
from pathlib import Path
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:
@@ -30,12 +32,14 @@ class Sources:
helper to download package sources (PKGBUILD etc)
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
"""
+ DEFAULT_BRANCH = "master" # default fallback branch
logger = logging.getLogger("build_details")
- _branch = "master" # in case if BLM would like to change it
_check_output = check_output
@staticmethod
@@ -73,13 +77,13 @@ class Sources:
return Sources._check_output("git", "diff", exception=None, cwd=sources_dir, logger=Sources.logger)
@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`
Args:
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
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)
return
+ branch = remote.branch if remote is not None else Sources.DEFAULT_BRANCH
if is_initialized_git:
- Sources.logger.info("update HEAD to remote at %s", sources_dir)
- Sources._check_output("git", "fetch", "origin", Sources._branch,
+ Sources.logger.info("update HEAD to remote at %s using branch %s", sources_dir, 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)
- elif remote is None:
- Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
else:
- Sources.logger.info("clone remote %s to %s", remote, sources_dir)
- Sources._check_output("git", "clone", remote, str(sources_dir),
- exception=None, cwd=sources_dir, logger=Sources.logger)
+ Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
+
# 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)
- 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)
+ # 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
def has_remotes(sources_dir: Path) -> bool:
"""
@@ -126,17 +138,17 @@ class Sources:
Args:
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)
@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
Args:
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
"""
Sources.fetch(sources_dir, remote)
@@ -145,6 +157,21 @@ class Sources:
return
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
def patch_apply(sources_dir: Path, patch: str) -> None:
"""
diff --git a/src/ahriman/core/build_tools/task.py b/src/ahriman/core/build_tools/task.py
index 45625d29..1ce93f20 100644
--- a/src/ahriman/core/build_tools/task.py
+++ b/src/ahriman/core/build_tools/task.py
@@ -107,4 +107,4 @@ class Task:
if self.paths.cache_for(self.package.base).is_dir():
# 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)
- 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))
diff --git a/src/ahriman/core/database/data/__init__.py b/src/ahriman/core/database/data/__init__.py
index 8624672a..fc7597be 100644
--- a/src/ahriman/core/database/data/__init__.py
+++ b/src/ahriman/core/database/data/__init__.py
@@ -20,6 +20,7 @@
from sqlite3 import Connection
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.patches import migrate_patches
from ahriman.core.database.data.users import migrate_users_data
@@ -43,3 +44,5 @@ def migrate_data(
migrate_package_statuses(connection, repository_paths)
migrate_patches(connection, repository_paths)
migrate_users_data(connection, configuration)
+ if result.old_version <= 1:
+ migrate_package_remotes(connection, repository_paths)
diff --git a/src/ahriman/core/database/data/package_remotes.py b/src/ahriman/core/database/data/package_remotes.py
new file mode 100644
index 00000000..5624159c
--- /dev/null
+++ b/src/ahriman/core/database/data/package_remotes.py
@@ -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 .
+#
+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)
diff --git a/src/ahriman/core/database/data/package_statuses.py b/src/ahriman/core/database/data/package_statuses.py
index 98f3bd44..27777f31 100644
--- a/src/ahriman/core/database/data/package_statuses.py
+++ b/src/ahriman/core/database/data/package_statuses.py
@@ -42,7 +42,7 @@ def migrate_package_statuses(connection: Connection, paths: RepositoryPaths) ->
values
(: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(
"""
insert into package_statuses
diff --git a/src/ahriman/core/database/migrations/m001_package_source.py b/src/ahriman/core/database/migrations/m001_package_source.py
new file mode 100644
index 00000000..6c37f7a5
--- /dev/null
+++ b/src/ahriman/core/database/migrations/m001_package_source.py
@@ -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 .
+#
+
+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
+ """,
+]
diff --git a/src/ahriman/core/database/operations/package_operations.py b/src/ahriman/core/database/operations/package_operations.py
index 421bba3e..5d52f254 100644
--- a/src/ahriman/core/database/operations/package_operations.py
+++ b/src/ahriman/core/database/operations/package_operations.py
@@ -24,6 +24,7 @@ from ahriman.core.database.operations.operations import Operations
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
from ahriman.models.package_description import PackageDescription
+from ahriman.models.remote_source import RemoteSource
class PackageOperations(Operations):
@@ -75,13 +76,22 @@ class PackageOperations(Operations):
connection.execute(
"""
insert into package_bases
- (package_base, version, aur_url)
+ (package_base, version, source, branch, git_url, path, web_url)
values
- (:package_base, :version, :aur_url)
+ (:package_base, :version, :source, :branch, :git_url, :path, :web_url)
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
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)
"""
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""")
}
@@ -225,3 +235,28 @@ class PackageOperations(Operations):
yield package, statuses.get(package_base, BuildStatus())
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
+ }
diff --git a/src/ahriman/core/database/sqlite.py b/src/ahriman/core/database/sqlite.py
index ebe61e1b..d08a60a4 100644
--- a/src/ahriman/core/database/sqlite.py
+++ b/src/ahriman/core/database/sqlite.py
@@ -81,5 +81,5 @@ class SQLite(AuthOperations, BuildOperations, PackageOperations, PatchOperations
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)
diff --git a/src/ahriman/core/repository/repository.py b/src/ahriman/core/repository/repository.py
index 8b64dcde..b77a349f 100644
--- a/src/ahriman/core/repository/repository.py
+++ b/src/ahriman/core/repository/repository.py
@@ -24,7 +24,6 @@ from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.core.util import package_like
from ahriman.models.package import Package
-from ahriman.models.package_source import PackageSource
class Repository(Executor, UpdateHandler):
@@ -42,11 +41,15 @@ class Repository(Executor, UpdateHandler):
Returns:
List[Package]: list of read packages
"""
+ sources = self.database.remotes_get()
+
result: Dict[str, Package] = {}
# we are iterating over bases, not single packages
for full_path in packages:
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)
if current.version != local.version:
# force version to max of them
@@ -95,5 +98,5 @@ class Repository(Executor, UpdateHandler):
return [
package
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))
]
diff --git a/src/ahriman/core/repository/repository_properties.py b/src/ahriman/core/repository/repository_properties.py
index 119091c6..b712729d 100644
--- a/src/ahriman/core/repository/repository_properties.py
+++ b/src/ahriman/core/repository/repository_properties.py
@@ -35,7 +35,6 @@ class RepositoryProperties:
Attributes:
architecture(str): repository architecture
- aur_url(str): base AUR url
configuration(Configuration): configuration instance
database(SQLite): database instance
ignore_list(List[str]): package bases which will be ignored during auto updates
@@ -65,7 +64,6 @@ class RepositoryProperties:
self.configuration = configuration
self.database = database
- self.aur_url = configuration.get("alpm", "aur_url")
self.name = configuration.get("repository", "name")
self.paths = configuration.repository_paths
diff --git a/src/ahriman/core/repository/update_handler.py b/src/ahriman/core/repository/update_handler.py
index c9280d50..3f635ff1 100644
--- a/src/ahriman/core/repository/update_handler.py
+++ b/src/ahriman/core/repository/update_handler.py
@@ -62,13 +62,18 @@ class UpdateHandler(Cleaner):
continue
if filter_packages and local.base not in filter_packages:
continue
+ source = local.remote.source if local.remote is not None else None
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):
self.reporter.set_pending(local.base)
result.append(remote)
- self.reporter.set_success(local)
+ else:
+ self.reporter.set_success(local)
except Exception:
self.reporter.set_failed(local.base)
self.logger.exception("could not load remote package %s", local.base)
@@ -89,7 +94,7 @@ class UpdateHandler(Cleaner):
for dirname in self.paths.cache.iterdir():
try:
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)
if local is None:
diff --git a/src/ahriman/core/tree.py b/src/ahriman/core/tree.py
index eb2c5fcb..75eaad4f 100644
--- a/src/ahriman/core/tree.py
+++ b/src/ahriman/core/tree.py
@@ -70,7 +70,7 @@ class Leaf:
Leaf: loaded class
"""
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)
return cls(package, dependencies)
diff --git a/src/ahriman/models/action.py b/src/ahriman/models/action.py
index cc4cff28..1b66e60a 100644
--- a/src/ahriman/models/action.py
+++ b/src/ahriman/models/action.py
@@ -20,7 +20,7 @@
from enum import Enum
-class Action(Enum):
+class Action(str, Enum):
"""
base action enumeration
diff --git a/src/ahriman/models/aur_package.py b/src/ahriman/models/aur_package.py
index 5cd01d3b..aabab4ab 100644
--- a/src/ahriman/models/aur_package.py
+++ b/src/ahriman/models/aur_package.py
@@ -23,6 +23,7 @@ import datetime
import inflection
from dataclasses import dataclass, field, fields
+from pyalpm import Package # type: ignore
from typing import Any, Callable, Dict, List, Optional, Type
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
last_modified(datetime.datetime): timestamp of the last package submission
url_path(str): AUR package path
+ repository(str): repository name of the package
depends(List[str]): list of package dependencies
make_depends(List[str]): list of package make dependencies
opt_depends(List[str]): list of package optional dependencies
@@ -70,6 +72,7 @@ class AURPackage:
url: Optional[str] = None
out_of_date: Optional[datetime.datetime] = None
maintainer: Optional[str] = None
+ repository: str = "aur"
depends: List[str] = field(default_factory=list)
make_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)
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
def from_repo(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage:
"""
@@ -122,6 +161,7 @@ class AURPackage:
dump["flag_date"],
"%Y-%m-%dT%H:%M:%S.%fZ") if dump["flag_date"] is not None else None,
maintainer=next(iter(dump["maintainers"]), None),
+ repository=dump["repo"],
depends=dump["depends"],
make_depends=dump["makedepends"],
opt_depends=dump["optdepends"],
diff --git a/src/ahriman/models/auth_settings.py b/src/ahriman/models/auth_settings.py
index d698b91e..a0892aea 100644
--- a/src/ahriman/models/auth_settings.py
+++ b/src/ahriman/models/auth_settings.py
@@ -23,7 +23,7 @@ from enum import Enum
from typing import Type
-class AuthSettings(Enum):
+class AuthSettings(str, Enum):
"""
web authorization type
diff --git a/src/ahriman/models/build_status.py b/src/ahriman/models/build_status.py
index dad122a9..b04a694f 100644
--- a/src/ahriman/models/build_status.py
+++ b/src/ahriman/models/build_status.py
@@ -28,7 +28,7 @@ from typing import Any, Dict, Type
from ahriman.core.util import filter_json, pretty_datetime
-class BuildStatusEnum(Enum):
+class BuildStatusEnum(str, Enum):
"""
build status enumeration
diff --git a/src/ahriman/models/package.py b/src/ahriman/models/package.py
index 0e80ed50..8a0b28fe 100644
--- a/src/ahriman/models/package.py
+++ b/src/ahriman/models/package.py
@@ -26,15 +26,17 @@ from dataclasses import asdict, dataclass
from pathlib import Path
from pyalpm import vercmp # 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.remote.aur import AUR
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.util import check_output, full_version
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
@@ -44,15 +46,16 @@ class Package:
package properties representation
Attributes:
- aur_url(str): AUR root url
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
"""
base: str
version: str
- aur_url: str
+ remote: Optional[RemoteSource]
packages: Dict[str, PackageDescription]
_check_output = check_output
@@ -67,16 +70,6 @@ class Package:
"""
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
def groups(self) -> List[str]:
"""
@@ -122,56 +115,46 @@ class Package:
"""
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
- 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
Args:
path(Path): path to package archive
pacman(Pacman): alpm wrapper instance
- aur_url(str): AUR root url
+ remote(RemoteSource): package remote source if applicable
Returns:
Package: package properties
"""
package = pacman.handle.load_pkg(str(path))
- return cls(package.base, package.version, aur_url,
- {package.name: PackageDescription.from_package(package, path)})
+ description = PackageDescription.from_package(package, path)
+ return cls(package.base, package.version, remote, {package.name: description})
@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
Args:
name(str): package name (either base or normal name)
- aur_url(str): AUR root url
+ pacman(Pacman): alpm wrapper instance
Returns:
Package: package properties
"""
- package = AUR.info(name)
- return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()})
+ package = AUR.info(name, pacman=pacman)
+ remote = RemoteSource.from_remote(PackageSource.AUR, package.package_base, package.repository)
+ return cls(package.package_base, package.version, remote, {package.name: PackageDescription()})
@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
Args:
path(Path): path to package sources directory
- aur_url(str): AUR root url
Returns:
Package: package properties
@@ -179,13 +162,14 @@ class Package:
Raises:
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:
raise InvalidPackageInfo(errors)
packages = {key: PackageDescription() for key in srcinfo["packages"]}
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
def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package:
@@ -202,59 +186,29 @@ class Package:
key: PackageDescription.from_json(value)
for key, value in dump.get("packages", {}).items()
}
- return Package(
+ remote = dump.get("remote", {})
+ return cls(
base=dump["base"],
version=dump["version"],
- aur_url=dump["aur_url"],
+ remote=RemoteSource.from_json(remote),
packages=packages)
@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
Args:
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:
Package: package properties
"""
- package = Official.info(name)
- return cls(package.package_base, package.version, aur_url, {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))
+ package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman)
+ remote = RemoteSource.from_remote(PackageSource.Repository, package.package_base, package.repository)
+ return cls(package.package_base, package.version, remote, {package.name: PackageDescription()})
@staticmethod
def dependencies(path: Path) -> Set[str]:
@@ -279,7 +233,8 @@ class Package:
package_name = package_name.split(symbol)[0]
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:
raise InvalidPackageInfo(errors)
makedepends = extract_packages(srcinfo.get("makedepends", []))
@@ -310,7 +265,7 @@ class Package:
from ahriman.core.build_tools.sources import Sources
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:
# update pkgver first
diff --git a/src/ahriman/models/package_source.py b/src/ahriman/models/package_source.py
index 4b27adb0..71a025f0 100644
--- a/src/ahriman/models/package_source.py
+++ b/src/ahriman/models/package_source.py
@@ -26,7 +26,7 @@ from urllib.parse import urlparse
from ahriman.core.util import package_like
-class PackageSource(Enum):
+class PackageSource(str, Enum):
"""
package source for addition enumeration
diff --git a/src/ahriman/models/remote_source.py b/src/ahriman/models/remote_source.py
new file mode 100644
index 00000000..a72f4374
--- /dev/null
+++ b/src/ahriman/models/remote_source.py
@@ -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 .
+#
+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)
diff --git a/src/ahriman/models/report_settings.py b/src/ahriman/models/report_settings.py
index e06e11f0..181e8764 100644
--- a/src/ahriman/models/report_settings.py
+++ b/src/ahriman/models/report_settings.py
@@ -23,7 +23,7 @@ from enum import Enum
from typing import Type
-class ReportSettings(Enum):
+class ReportSettings(str, Enum):
"""
report targets enumeration
diff --git a/src/ahriman/models/sign_settings.py b/src/ahriman/models/sign_settings.py
index 93a55537..a09791d4 100644
--- a/src/ahriman/models/sign_settings.py
+++ b/src/ahriman/models/sign_settings.py
@@ -23,7 +23,7 @@ from enum import Enum
from typing import Type
-class SignSettings(Enum):
+class SignSettings(str, Enum):
"""
sign targets enumeration
diff --git a/src/ahriman/models/smtp_ssl_settings.py b/src/ahriman/models/smtp_ssl_settings.py
index fb700495..eeba8f46 100644
--- a/src/ahriman/models/smtp_ssl_settings.py
+++ b/src/ahriman/models/smtp_ssl_settings.py
@@ -23,7 +23,7 @@ from enum import Enum
from typing import Type
-class SmtpSSLSettings(Enum):
+class SmtpSSLSettings(str, Enum):
"""
SMTP SSL mode enumeration
diff --git a/src/ahriman/models/upload_settings.py b/src/ahriman/models/upload_settings.py
index 408e391d..3913f274 100644
--- a/src/ahriman/models/upload_settings.py
+++ b/src/ahriman/models/upload_settings.py
@@ -23,7 +23,7 @@ from enum import Enum
from typing import Type
-class UploadSettings(Enum):
+class UploadSettings(str, Enum):
"""
remote synchronization targets enumeration
diff --git a/src/ahriman/models/user_access.py b/src/ahriman/models/user_access.py
index 7e013f27..cd58970f 100644
--- a/src/ahriman/models/user_access.py
+++ b/src/ahriman/models/user_access.py
@@ -20,7 +20,7 @@
from enum import Enum
-class UserAccess(Enum):
+class UserAccess(str, Enum):
"""
web user access enumeration
diff --git a/src/ahriman/web/views/index.py b/src/ahriman/web/views/index.py
index ddb6ab4b..7792bf53 100644
--- a/src/ahriman/web/views/index.py
+++ b/src/ahriman/web/views/index.py
@@ -86,7 +86,7 @@ class IndexView(BaseView):
"status_color": status.status.bootstrap_color(),
"timestamp": pretty_datetime(status.timestamp),
"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)
]
service = {
diff --git a/src/ahriman/web/views/service/search.py b/src/ahriman/web/views/service/search.py
index dbb5fe62..ad6fb82c 100644
--- a/src/ahriman/web/views/service/search.py
+++ b/src/ahriman/web/views/service/search.py
@@ -50,7 +50,7 @@ class SearchView(BaseView):
HTTPNotFound: if no packages found
"""
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:
raise HTTPNotFound(reason=f"No packages found for terms: {search}")
diff --git a/tests/ahriman/application/application/test_application_packages.py b/tests/ahriman/application/application/test_application_packages.py
index 8454de10..d41769d4 100644
--- a/tests/ahriman/application/application/test_application_packages.py
+++ b/tests/ahriman/application/application/test_application_packages.py
@@ -44,19 +44,21 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac
"""
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")
dependencies_mock = mocker.patch(
"ahriman.application.application.application_packages.ApplicationPackages._process_dependencies")
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)
load_mock.assert_called_once_with(
pytest.helpers.anyvar(int),
- package_ahriman.git_url,
+ package_ahriman.remote,
pytest.helpers.anyvar(int))
dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int), set(), False)
build_queue_mock.assert_called_once_with(package_ahriman)
+ update_remote_mock.assert_called_once_with(package_ahriman)
def test_add_directory(
@@ -80,7 +82,7 @@ def test_add_local(application_packages: ApplicationPackages, package_ahriman: P
"""
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")
copytree_mock = mocker.patch("shutil.copytree")
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()
+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:
"""
must process dependencies addition
diff --git a/tests/ahriman/application/application/test_application_repository.py b/tests/ahriman/application/application/test_application_repository.py
index 0231ccff..0132ae4a 100644
--- a/tests/ahriman/application/application/test_application_repository.py
+++ b/tests/ahriman/application/application/test_application_repository.py
@@ -14,7 +14,7 @@ def test_finalize(application_repository: ApplicationRepository) -> None:
must raise NotImplemented for missing finalize method
"""
with pytest.raises(NotImplementedError):
- application_repository._finalize([])
+ application_repository._finalize(Result())
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
"""
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_report")
- application_repository.report(["a"], [])
- executor_mock.assert_called_once_with(["a"], [])
+ application_repository.report(["a"], Result())
+ executor_mock.assert_called_once_with(["a"], Result())
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.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)
update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update", return_value=result)
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.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")
update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update")
diff --git a/tests/ahriman/application/handlers/test_handler_patch.py b/tests/ahriman/application/handlers/test_handler_patch.py
index 5a100817..80b62f4b 100644
--- a/tests/ahriman/application/handlers/test_handler_patch.py
+++ b/tests/ahriman/application/handlers/test_handler_patch.py
@@ -1,6 +1,7 @@
import argparse
import pytest
+from pathlib import Path
from pytest_mock import MockerFixture
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")
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:
@@ -96,11 +97,11 @@ def test_patch_set_create(application: Application, package_ahriman: Package, mo
must create patch set for the package
"""
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")
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")
diff --git a/tests/ahriman/application/handlers/test_handler_search.py b/tests/ahriman/application/handlers/test_handler_search.py
index ed70a5c9..43976b70 100644
--- a/tests/ahriman/application/handlers/test_handler_search.py
+++ b/tests/ahriman/application/handlers/test_handler_search.py
@@ -34,6 +34,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
must run command
"""
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])
official_search_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.multisearch",
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")
Search.run(args, "x86_64", configuration, True, False)
- aur_search_mock.assert_called_once_with("ahriman")
- official_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", pacman=pytest.helpers.anyvar(int))
check_mock.assert_called_once_with(False, 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.official.Official.multisearch", return_value=[])
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")
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)
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.models.repository_paths.RepositoryPaths.tree_create")
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
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"
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.models.repository_paths.RepositoryPaths.tree_create")
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
Search.run(args, "x86_64", configuration, True, False)
diff --git a/tests/ahriman/conftest.py b/tests/ahriman/conftest.py
index 8e0891a2..213f2c88 100644
--- a/tests/ahriman/conftest.py
+++ b/tests/ahriman/conftest.py
@@ -6,6 +6,7 @@ from pytest_mock import MockerFixture
from typing import Any, Dict, Type, TypeVar
from unittest.mock import MagicMock
+from ahriman.core.alpm.pacman import Pacman
from ahriman.core.auth.auth import Auth
from ahriman.core.configuration import Configuration
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.package import Package
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.result import Result
from ahriman.models.user import User
@@ -101,8 +104,8 @@ def aur_package_ahriman() -> AURPackage:
description="ArcH Linux ReposItory MANager",
num_votes=0,
popularity=0,
- first_submitted=datetime.datetime(2021, 4, 9, 22, 44, 45),
- last_modified=datetime.datetime(2021, 12, 25, 23, 11, 11),
+ first_submitted=datetime.datetime.utcfromtimestamp(1618008285),
+ last_modified=datetime.datetime.utcfromtimestamp(1640473871),
url_path="/cgit/aur.git/snapshot/ahriman.tar.gz",
url="https://github.com/arcan1s/ahriman",
out_of_date=None,
@@ -155,13 +158,14 @@ def aur_package_akonadi() -> AURPackage:
version="21.12.3-2",
description="PIM layer, which provides an asynchronous API to access all kind of PIM data",
num_votes=0,
- popularity=0,
- first_submitted=datetime.datetime(1970, 1, 1, 0, 0, 0),
- last_modified=datetime.datetime(2022, 3, 6, 8, 39, 50, 610000),
+ popularity=0.0,
+ first_submitted=datetime.datetime.utcfromtimestamp(0),
+ last_modified=datetime.datetime.utcfromtimestamp(1646555990.610),
url_path="",
url="https://kontact.kde.org",
out_of_date=None,
maintainer="felixonmars",
+ repository="extra",
depends=[
"libakonadi",
"mariadb",
@@ -245,7 +249,7 @@ def package_ahriman(package_description_ahriman: PackageDescription) -> Package:
return Package(
base="ahriman",
version="1.7.0-1",
- aur_url="https://aur.archlinux.org",
+ remote=RemoteSource.from_remote(PackageSource.AUR, "ahriman", "aur"),
packages=packages)
@@ -270,7 +274,7 @@ def package_python_schedule(
return Package(
base="python-schedule",
version="1.0.0-2",
- aur_url="https://aur.archlinux.org",
+ remote=RemoteSource.from_remote(PackageSource.AUR, "python-schedule", "aur"),
packages=packages)
@@ -344,6 +348,34 @@ def package_description_python2_schedule() -> PackageDescription:
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
def repository_paths(configuration: Configuration) -> RepositoryPaths:
"""
diff --git a/tests/ahriman/core/alpm/remote/conftest.py b/tests/ahriman/core/alpm/remote/conftest.py
index 866bcaa5..072a6307 100644
--- a/tests/ahriman/core/alpm/remote/conftest.py
+++ b/tests/ahriman/core/alpm/remote/conftest.py
@@ -2,6 +2,7 @@ import pytest
from ahriman.core.alpm.remote.aur import AUR
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
@@ -27,6 +28,17 @@ def official() -> 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
def remote() -> Remote:
"""
diff --git a/tests/ahriman/core/alpm/remote/test_aur.py b/tests/ahriman/core/alpm/remote/test_aur.py
index 4eab4873..fa87a027 100644
--- a/tests/ahriman/core/alpm/remote/test_aur.py
+++ b/tests/ahriman/core/alpm/remote/test_aur.py
@@ -6,6 +6,7 @@ from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock
+from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.aur import AUR
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.models.aur_package import AURPackage
@@ -49,6 +50,23 @@ def test_parse_response_unknown_error() -> None:
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,
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")
-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
"""
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)
-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
"""
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")
diff --git a/tests/ahriman/core/alpm/remote/test_official.py b/tests/ahriman/core/alpm/remote/test_official.py
index c3a6fcb4..b453d91d 100644
--- a/tests/ahriman/core/alpm/remote/test_official.py
+++ b/tests/ahriman/core/alpm/remote/test_official.py
@@ -6,6 +6,7 @@ from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock
+from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.official import Official
from ahriman.core.exceptions import InvalidPackageInfo
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))
+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,
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)
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:
@@ -72,21 +106,23 @@ def test_make_request_failed_http_error(official: Official, mocker: MockerFixtur
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
"""
request_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.make_request",
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")
-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
"""
request_mock = mocker.patch("ahriman.core.alpm.remote.official.Official.make_request",
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")
diff --git a/tests/ahriman/core/alpm/remote/test_official_syncdb.py b/tests/ahriman/core/alpm/remote/test_official_syncdb.py
new file mode 100644
index 00000000..0c00b66f
--- /dev/null
+++ b/tests/ahriman/core/alpm/remote/test_official_syncdb.py
@@ -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
diff --git a/tests/ahriman/core/alpm/remote/test_remote.py b/tests/ahriman/core/alpm/remote/test_remote.py
index a23e84d7..e2a26fa0 100644
--- a/tests/ahriman/core/alpm/remote/test_remote.py
+++ b/tests/ahriman/core/alpm/remote/test_remote.py
@@ -3,70 +3,87 @@ import pytest
from pytest_mock import MockerFixture
from unittest import mock
+from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote
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
"""
info_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.package_info")
- Remote.info("ahriman")
- info_mock.assert_called_once_with("ahriman")
+ Remote.info("ahriman", pacman=pacman)
+ 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
"""
terms = ["ahriman", "is", "cool"]
search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search", return_value=[aur_package_ahriman])
- assert Remote.multisearch(*terms) == [aur_package_ahriman]
- search_mock.assert_has_calls([mock.call("ahriman"), mock.call("cool")])
+ assert Remote.multisearch(*terms, pacman=pacman) == [aur_package_ahriman]
+ 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
"""
terms = ["it", "is"]
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()
-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
"""
search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.search", return_value=[aur_package_ahriman])
- assert Remote.multisearch("ahriman") == [aur_package_ahriman]
- search_mock.assert_called_once_with("ahriman")
+ assert Remote.multisearch("ahriman", pacman=pacman) == [aur_package_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
"""
search_mock = mocker.patch("ahriman.core.alpm.remote.remote.Remote.package_search")
- Remote.search("ahriman")
- search_mock.assert_called_once_with("ahriman")
+ Remote.search("ahriman", pacman=pacman)
+ 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
"""
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
"""
with pytest.raises(NotImplementedError):
- remote.package_search("package")
+ remote.package_search("package", pacman=pacman)
diff --git a/tests/ahriman/core/alpm/test_pacman.py b/tests/ahriman/core/alpm/test_pacman.py
index 3823e3af..e2df983c 100644
--- a/tests/ahriman/core/alpm/test_pacman.py
+++ b/tests/ahriman/core/alpm/test_pacman.py
@@ -15,3 +15,17 @@ def test_all_packages_with_provides(pacman: Pacman) -> None:
package list must contain provides 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"))
diff --git a/tests/ahriman/core/build_tools/test_sources.py b/tests/ahriman/core/build_tools/test_sources.py
index b7f559f2..ee14e57f 100644
--- a/tests/ahriman/core/build_tools/test_sources.py
+++ b/tests/ahriman/core/build_tools/test_sources.py
@@ -5,6 +5,7 @@ from pytest_mock import MockerFixture
from unittest import mock
from ahriman.core.build_tools.sources import Sources
+from ahriman.models.remote_source import RemoteSource
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))
-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
"""
@@ -53,46 +54,51 @@ def test_fetch_empty(mocker: MockerFixture) -> None:
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")
- Sources.fetch(Path("local"), "remote")
+ Sources.fetch(Path("local"), remote_source)
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
"""
mocker.patch("pathlib.Path.is_dir", 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")
+ move_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.move")
local = Path("local")
- Sources.fetch(local, "remote")
+ Sources.fetch(local, remote_source)
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)),
- mock.call("git", "checkout", "--force", Sources._branch,
+ mock.call("git", "checkout", "--force", remote_source.branch,
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))
])
+ 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
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
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")
- Sources.fetch(local, "remote")
+ Sources.fetch(local, remote_source)
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", "checkout", "--force", Sources._branch,
+ mock.call("git", "clone", "--branch", remote_source.branch, "--single-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)),
- 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))
])
+ move_mock.assert_called_once_with(local.resolve(), local)
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)
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")
Sources.fetch(local, None)
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)),
- 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))
])
+ 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:
@@ -140,33 +159,53 @@ def test_init(mocker: MockerFixture) -> None:
local = Path("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))
-def test_load(mocker: MockerFixture) -> None:
+def test_load(remote_source: RemoteSource, mocker: MockerFixture) -> None:
"""
must load packages sources correctly
"""
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
- Sources.load(Path("local"), "remote", "patch")
- fetch_mock.assert_called_once_with(Path("local"), "remote")
+ Sources.load(Path("local"), remote_source, "patch")
+ fetch_mock.assert_called_once_with(Path("local"), remote_source)
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
"""
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
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()
+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:
"""
must apply patches if any
diff --git a/tests/ahriman/core/conftest.py b/tests/ahriman/core/conftest.py
index abaac578..22c9b4f0 100644
--- a/tests/ahriman/core/conftest.py
+++ b/tests/ahriman/core/conftest.py
@@ -1,6 +1,5 @@
import pytest
-from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.repo import Repo
from ahriman.core.build_tools.task import Task
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())
-@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 repo(configuration: Configuration, repository_paths: RepositoryPaths) -> Repo:
"""
diff --git a/tests/ahriman/core/database/data/test_data_init.py b/tests/ahriman/core/database/data/test_data_init.py
index a44e57fd..44016e8c 100644
--- a/tests/ahriman/core/database/data/test_data_init.py
+++ b/tests/ahriman/core/database/data/test_data_init.py
@@ -15,11 +15,24 @@ def test_migrate_data_initial(connection: Connection, configuration: Configurati
packages = mocker.patch("ahriman.core.database.data.migrate_package_statuses")
patches = mocker.patch("ahriman.core.database.data.migrate_patches")
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)
packages.assert_called_once_with(connection, repository_paths)
patches.assert_called_once_with(connection, repository_paths)
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:
diff --git a/tests/ahriman/core/database/data/test_package_remotes.py b/tests/ahriman/core/database/data/test_package_remotes.py
new file mode 100644
index 00000000..fc4e473a
--- /dev/null
+++ b/tests/ahriman/core/database/data/test_package_remotes.py
@@ -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()
diff --git a/tests/ahriman/core/database/data/test_users.py b/tests/ahriman/core/database/data/test_users.py
index 4bd69849..795144fc 100644
--- a/tests/ahriman/core/database/data/test_users.py
+++ b/tests/ahriman/core/database/data/test_users.py
@@ -9,7 +9,7 @@ from ahriman.core.database.data import migrate_users_data
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:write", "user2", "password2")
diff --git a/tests/ahriman/core/database/migrations/test_m001_package_source.py b/tests/ahriman/core/database/migrations/test_m001_package_source.py
new file mode 100644
index 00000000..a227db36
--- /dev/null
+++ b/tests/ahriman/core/database/migrations/test_m001_package_source.py
@@ -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
diff --git a/tests/ahriman/core/database/operations/test_package_operations.py b/tests/ahriman/core/database/operations/test_package_operations.py
index c916f844..2eea8fe5 100644
--- a/tests/ahriman/core/database/operations/test_package_operations.py
+++ b/tests/ahriman/core/database/operations/test_package_operations.py
@@ -7,6 +7,8 @@ from unittest import mock
from ahriman.core.database.sqlite import SQLite
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
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:
@@ -166,3 +168,23 @@ def test_package_update_update(database: SQLite, package_ahriman: Package) -> No
assert next(db_status.status
for db_package, db_status in database.packages_get()
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
diff --git a/tests/ahriman/core/repository/test_repository.py b/tests/ahriman/core/repository/test_repository.py
index 1a40f9e0..45c44b78 100644
--- a/tests/ahriman/core/repository/test_repository.py
+++ b/tests/ahriman/core/repository/test_repository.py
@@ -15,11 +15,11 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag
single_packages = [
Package(base=package_python_schedule.base,
version=package_python_schedule.version,
- aur_url=package_python_schedule.aur_url,
+ remote=package_python_schedule.remote,
packages={package: props})
for package, props in package_python_schedule.packages.items()
] + [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")])
assert len(packages) == 2
@@ -36,7 +36,7 @@ def test_load_archives_failed(repository: Repository, mocker: MockerFixture) ->
"""
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")])
@@ -55,12 +55,12 @@ def test_load_archives_different_version(repository: Repository, package_python_
single_packages = [
Package(base=package_python_schedule.base,
version=package_python_schedule.version,
- aur_url=package_python_schedule.aur_url,
+ remote=package_python_schedule.remote,
packages={package: props})
for package, props in package_python_schedule.packages.items()
]
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")])
assert len(packages) == 1
diff --git a/tests/ahriman/core/repository/test_update_handler.py b/tests/ahriman/core/repository/test_update_handler.py
index f064ffc1..db999212 100644
--- a/tests/ahriman/core/repository/test_update_handler.py
+++ b/tests/ahriman/core/repository/test_update_handler.py
@@ -5,6 +5,7 @@ from pytest_mock import MockerFixture
from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
+from ahriman.models.remote_source import RemoteSource
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.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")
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.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")
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
"""
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")
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",
return_value=[package_ahriman, package_python_schedule])
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]
- package_load_mock.assert_called_once_with(package_ahriman.base, PackageSource.AUR,
- update_handler.pacman, update_handler.aur_url)
+ package_load_mock.assert_called_once_with(package_ahriman.base, update_handler.pacman)
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]
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)
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("ahriman.models.package.Package.is_outdated", return_value=True)
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")
assert update_handler.updates_local() == [package_ahriman]
fetch_mock.assert_called_once_with(package_ahriman.base, remote=None)
- package_load_mock.assert_called_once_with(
- package_ahriman.base, PackageSource.Local, update_handler.pacman, update_handler.aur_url)
+ package_load_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("ahriman.models.package.Package.is_outdated", return_value=True)
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")
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("ahriman.models.package.Package.is_outdated", return_value=False)
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")
assert not update_handler.updates_local()
diff --git a/tests/ahriman/core/test_tree.py b/tests/ahriman/core/test_tree.py
index 6e44855c..219215a6 100644
--- a/tests/ahriman/core/test_tree.py
+++ b/tests/ahriman/core/test_tree.py
@@ -51,7 +51,7 @@ def test_leaf_load(package_ahriman: Package, database: SQLite, mocker: MockerFix
assert leaf.dependencies == {"ahriman-dependency"}
tempdir_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))
rmtree_mock.assert_called_once_with(pytest.helpers.anyvar(int), ignore_errors=True)
diff --git a/tests/ahriman/models/conftest.py b/tests/ahriman/models/conftest.py
index 8cc65cef..fafb2252 100644
--- a/tests/ahriman/models/conftest.py
+++ b/tests/ahriman/models/conftest.py
@@ -1,14 +1,18 @@
+import datetime
import pytest
import time
from unittest.mock import MagicMock, PropertyMock
from ahriman import version
+from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.counters import Counters
from ahriman.models.internal_status import InternalStatus
from ahriman.models.package import Package
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
@@ -67,7 +71,7 @@ def package_tpacpi_bat_git() -> Package:
return Package(
base="tpacpi-bat-git",
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()})
@@ -88,22 +92,34 @@ def pyalpm_handle(pyalpm_package_ahriman: MagicMock) -> MagicMock:
@pytest.fixture
-def pyalpm_package_ahriman(package_ahriman: Package) -> MagicMock:
+def pyalpm_package_ahriman(aur_package_ahriman: AURPackage) -> MagicMock:
"""
mock object for pyalpm package
Args:
- package_ahriman(Package): package fixture
+ aur_package_ahriman(AURPackage): package fixture
Returns:
MagicMock: pyalpm package mock
"""
mock = MagicMock()
- type(mock).base = PropertyMock(return_value=package_ahriman.base)
- type(mock).depends = PropertyMock(return_value=["python-aur"])
- type(mock).name = PropertyMock(return_value=package_ahriman.base)
- type(mock).provides = PropertyMock(return_value=["python-ahriman"])
- type(mock).version = PropertyMock(return_value=package_ahriman.version)
+ db = type(mock).db = MagicMock()
+
+ type(mock).base = PropertyMock(return_value=aur_package_ahriman.package_base)
+ type(mock).builddate = PropertyMock(
+ 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
diff --git a/tests/ahriman/models/test_aur_package.py b/tests/ahriman/models/test_aur_package.py
index 00a34896..255e1d12 100644
--- a/tests/ahriman/models/test_aur_package.py
+++ b/tests/ahriman/models/test_aur_package.py
@@ -1,5 +1,6 @@
import datetime
import json
+import pyalpm # typing: ignore
from dataclasses import asdict, fields
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
+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:
"""
must load package from repository api json
diff --git a/tests/ahriman/models/test_package.py b/tests/ahriman/models/test_package.py
index 7998aa2c..0bf89816 100644
--- a/tests/ahriman/models/test_package.py
+++ b/tests/ahriman/models/test_package.py
@@ -4,10 +4,10 @@ from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock
+from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import InvalidPackageInfo
from ahriman.models.aur_package import AURPackage
from ahriman.models.package import Package
-from ahriman.models.package_source import PackageSource
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:
"""
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
-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:
"""
must construct package from alpm library
"""
mocker.patch("ahriman.models.package_description.PackageDescription.from_package",
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
"""
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.version == package.version
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
"""
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()
package_ahriman.packages = package.packages # we are not going to test PackageDescription here
+ package_ahriman.remote = None
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
"""
- 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"]))
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:
@@ -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
-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
"""
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.version == package.version
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:
"""
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"]))
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
"""
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"}
@@ -266,7 +178,7 @@ def test_dependencies_with_version_and_overlap(mocker: MockerFixture, resource_p
must load correct list of dependencies with version
"""
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"}
@@ -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
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
diff --git a/tests/ahriman/models/test_package_description.py b/tests/ahriman/models/test_package_description.py
index 6b158e56..3a84c020 100644
--- a/tests/ahriman/models/test_package_description.py
+++ b/tests/ahriman/models/test_package_description.py
@@ -1,4 +1,3 @@
-from dataclasses import asdict
from unittest.mock import MagicMock
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
"""
- 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:
"""
must construct description from json object containing unknown fields
"""
- dump = asdict(package_description_ahriman)
+ dump = package_description_ahriman.view()
dump.update(unknown_field="value")
assert PackageDescription.from_json(dump) == package_description_ahriman
diff --git a/tests/ahriman/models/test_remote_source.py b/tests/ahriman/models/test_remote_source.py
new file mode 100644
index 00000000..62d6451e
--- /dev/null
+++ b/tests/ahriman/models/test_remote_source.py
@@ -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
diff --git a/tests/ahriman/web/views/service/test_views_service_search.py b/tests/ahriman/web/views/service/test_views_service_search.py
index 13eefa50..ff3d1b3e 100644
--- a/tests/ahriman/web/views/service/test_views_service_search.py
+++ b/tests/ahriman/web/views/service/test_views_service_search.py
@@ -37,7 +37,7 @@ async def test_get_exception(client: TestClient, mocker: MockerFixture) -> None:
response = await client.get("/service-api/v1/search")
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:
@@ -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")])
assert response.ok
- search_mock.assert_called_once_with("ahriman", "maybe")
+ search_mock.assert_called_once_with("ahriman", "maybe", pacman=pytest.helpers.anyvar(int))
diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini
index 9fdda429..6f018915 100644
--- a/tests/testresources/core/ahriman.ini
+++ b/tests/testresources/core/ahriman.ini
@@ -4,7 +4,6 @@ logging = logging.ini
database = ../../../ahriman-test.db
[alpm]
-aur_url = https://aur.archlinux.org
database = /var/lib/pacman
repositories = core extra community multilib
root = /