Compare commits

...

11 Commits

Author SHA1 Message Date
6280c9dbe6 Release 2.19.1 2025-07-14 21:41:02 +03:00
3d1fdd5517 fix: fix migrations on empty repositories 2025-07-14 21:37:43 +03:00
ab022071e8 fix: trim provides/depends versions and lookup provides through pkgname
(#150)

Current implementation did it in wrong way. First of all, there was a
lookup through pkgbase instead of pkgname, which lead to errors, because
aur api doesn't allow to search by pkgbase (as well as provides is
basically pkgname instead)

It also was found that dependencies resolution lookup has been performed
by using raw packages array, which can include versions, descriptions
etc
2025-07-14 21:37:35 +03:00
a01f76df42 fix: separate ua by spaces 2025-07-14 21:37:25 +03:00
2b1b17a1a3 Release 2.19.0 2025-06-29 03:00:41 +03:00
9e6705056a build: use archlinux images for release build 2025-06-29 02:59:28 +03:00
b3a3a81f70 feat: add ability to refresh databases through web interface 2025-06-29 02:44:57 +03:00
3e5dbbd6cd feat: extend user-agent 2025-06-28 23:08:31 +03:00
f41e44895d fix: support provides in aur (#146)
* support provides in aur

* process provides during tree resolution

* stylish
2025-06-28 22:39:54 +03:00
765bbf486f feat: port to new AUR API 2025-06-28 22:07:59 +03:00
a3c54afb82 fix: process unicode errors in command execution 2025-06-28 20:26:47 +03:00
34 changed files with 455 additions and 115 deletions

View File

@ -13,7 +13,15 @@ jobs:
runs-on: ubuntu-latest
container:
image: archlinux:base
options: -w /build
volumes:
- ${{ github.workspace }}:/build
steps:
- run: pacman --noconfirm -Syu base-devel git python-tox
- uses: actions/checkout@v4
- name: Extract version
@ -27,10 +35,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
filter: 'Release \d+\.\d+\.\d+'
- uses: ConorMacBride/install-package@v1.1.0
with:
apt: tox
- name: Create archive
run: tox -e archive
env:

View File

@ -64,7 +64,7 @@ digraph G {
ahriman_core_alpm_remote_aur [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nalpm\.\nremote\.\naur",shape="box"];
ahriman_core_alpm_remote_official [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nalpm\.\nremote\.\nofficial",shape="box"];
ahriman_core_alpm_remote_official_syncdb [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nalpm\.\nremote\.\nofficial_syncdb",shape="box"];
ahriman_core_alpm_remote_remote [fillcolor="#ae441e",fontcolor="#ffffff",label="ahriman\.\ncore\.\nalpm\.\nremote\.\nremote"];
ahriman_core_alpm_remote_remote [fillcolor="#a5401d",fontcolor="#ffffff",label="ahriman\.\ncore\.\nalpm\.\nremote\.\nremote"];
ahriman_core_alpm_repo [fillcolor="#994d33",fontcolor="#ffffff",label="ahriman\.\ncore\.\nalpm\.\nrepo"];
ahriman_core_auth [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nauth",shape="box"];
ahriman_core_auth_auth [fillcolor="blue",fontcolor="white",label="ahriman\.\ncore\.\nauth\.\nauth",shape="box"];
@ -509,9 +509,9 @@ digraph G {
ahriman_core_alpm_remote_official -> ahriman_core_alpm_remote [fillcolor="blue",minlen="0",weight="4"];
ahriman_core_alpm_remote_official -> ahriman_core_alpm_remote_official_syncdb [fillcolor="blue",minlen="0",weight="4"];
ahriman_core_alpm_remote_official_syncdb -> ahriman_core_alpm_remote [fillcolor="blue",minlen="0",weight="4"];
ahriman_core_alpm_remote_remote -> ahriman_core_alpm_remote [fillcolor="#ae441e",minlen="0",weight="4"];
ahriman_core_alpm_remote_remote -> ahriman_core_alpm_remote_aur [fillcolor="#ae441e",minlen="0",weight="4"];
ahriman_core_alpm_remote_remote -> ahriman_core_alpm_remote_official [fillcolor="#ae441e",minlen="0",weight="4"];
ahriman_core_alpm_remote_remote -> ahriman_core_alpm_remote [fillcolor="#a5401d",minlen="0",weight="4"];
ahriman_core_alpm_remote_remote -> ahriman_core_alpm_remote_aur [fillcolor="#a5401d",minlen="0",weight="4"];
ahriman_core_alpm_remote_remote -> ahriman_core_alpm_remote_official [fillcolor="#a5401d",minlen="0",weight="4"];
ahriman_core_alpm_repo -> ahriman_core_repository_repository_properties [fillcolor="#994d33",minlen="2",weight="2"];
ahriman_core_auth -> ahriman_web_keys [fillcolor="blue",minlen="2"];
ahriman_core_auth -> ahriman_web_middlewares_auth_handler [fillcolor="blue",minlen="3"];
@ -710,6 +710,7 @@ digraph G {
ahriman_core_exceptions -> ahriman_core_alpm_remote_aur [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_exceptions -> ahriman_core_alpm_remote_official [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_exceptions -> ahriman_core_alpm_remote_official_syncdb [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_exceptions -> ahriman_core_alpm_remote_remote [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_exceptions -> ahriman_core_alpm_repo [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_exceptions -> ahriman_core_auth_oauth [fillcolor="#ef4306",minlen="2",weight="2"];
ahriman_core_exceptions -> ahriman_core_auth_pam [fillcolor="#ef4306",minlen="2",weight="2"];

View File

@ -2,7 +2,7 @@
pkgbase='ahriman'
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
pkgver=2.18.3
pkgver=2.19.1
pkgrel=1
pkgdesc="ArcH linux ReposItory MANager"
arch=('any')

View File

@ -55,6 +55,11 @@
<i class="bi bi-play"></i> update
</button>
</li>
<li>
<button id="update-repositories-button" class="btn dropdown-item" onclick="refreshDatabases()">
<i class="bi bi-arrow-down-circle"></i> update pacman databases
</button>
</li>
<li>
<button id="package-rebuild-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal">
<i class="bi bi-arrow-clockwise"></i> rebuild

View File

@ -24,6 +24,13 @@
<datalist id="package-add-known-packages-dlist"></datalist>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label"></label>
<div class="col-9">
<input id="package-add-refresh-input" type="checkbox" class="form-check-input" value="" checked>
<label for="package-add-refresh-input" class="form-check-label">update pacman databases</label>
</div>
</div>
<div class="form-group row">
<div class="col-12">
<button id="package-add-variable-button" type="button" class="form-control btn btn-light rounded" onclick="packageAddVariableInputCreate()"><i class="bi bi-plus"></i> add environment variable </button>
@ -50,6 +57,8 @@
const packageAddVariablesDiv = document.getElementById("package-add-variables-div");
const packageAddRefreshInput = document.getElementById("package-add-refresh-input");
function packageAddVariableInputCreate() {
const variableInput = document.createElement("div");
variableInput.classList.add("input-group");
@ -99,16 +108,18 @@
return {patches: patches};
}
function packagesAdd(packages, patches, repository) {
function packagesAdd(packages, patches, repository, data) {
packages = packages ?? packageAddInput.value;
patches = patches ?? patchesParse();
repository = repository ?? getRepositorySelector(packageAddRepositoryInput);
data = data ?? {refresh: packageAddRefreshInput.checked};
if (packages) {
bootstrap.Modal.getOrCreateInstance(packageAddModal).hide();
const onSuccess = update => `Packages ${update} have been added`;
const onFailure = error => `Package addition failed: ${error}`;
doPackageAction("/api/v1/service/add", [packages], repository, onSuccess, onFailure, patches);
const parameters = Object.assign({}, data, patches);
doPackageAction("/api/v1/service/add", [packages], repository, onSuccess, onFailure, parameters);
}
}

View File

@ -95,6 +95,9 @@
</div>
<div class="modal-footer">
{% if not auth.enabled or auth.username is not none %}
<input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked>
<label for="package-info-refresh-input" class="form-check-label">update pacman databases</label>
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
{% endif %}
@ -135,6 +138,8 @@
const packageInfoVariablesBlock = document.getElementById("package-info-variables-block");
const packageInfoVariablesDiv = document.getElementById("package-info-variables-div");
const packageInfoRefreshInput = document.getElementById("package-info-refresh-input");
function clearChart() {
packageInfoEventsUpdateChartCanvas.hidden = true;
if (packageInfoEventsUpdateChart) {
@ -404,7 +409,7 @@
function packageInfoUpdate() {
const packageBase = packageInfoModal.package;
packagesAdd(packageBase, [], repository);
packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
}
function showPackageInfo(packageBase) {

View File

@ -73,6 +73,19 @@
doPackageAction(url, currentSelection, repository, onSuccess, onFailure);
}
function refreshDatabases() {
const onSuccess = _ => "Pacman database update has been requested";
const onFailure = error => `Could not update pacman databases: ${error}`;
const parameters = {
refresh: true,
aur: false,
local: false,
manual: false,
};
doPackageAction("/api/v1/service/update", [], repository, onSuccess, onFailure, parameters);
}
function reload() {
table.bootstrapTable("showLoading");

View File

@ -1,4 +1,4 @@
.TH AHRIMAN "1" "2025\-06\-23" "ahriman 2.18.3" "ArcH linux ReposItory MANager"
.TH AHRIMAN "1" "2025\-07\-14" "ahriman 2.19.1" "ArcH linux ReposItory MANager"
.SH NAME
ahriman \- ArcH linux ReposItory MANager
.SH SYNOPSIS

View File

@ -17,4 +17,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__version__ = "2.18.3"
__version__ = "2.19.1"

View File

@ -133,18 +133,18 @@ class Application(ApplicationPackages, ApplicationRepository):
if not process_dependencies or not packages:
return packages
def missing_dependencies(source: Iterable[Package]) -> dict[str, str | None]:
def missing_dependencies(sources: Iterable[Package]) -> dict[str, str | None]:
# append list of known packages with packages which are in current sources
satisfied_packages = known_packages | {
single
for package in source
for single in package.packages_full
for source in sources
for single in source.packages_full
}
return {
dependency: package.packager
for package in source
for dependency in package.depends_build
dependency: source.packager
for source in sources
for dependency in source.depends_build
if dependency not in satisfied_packages
}
@ -156,7 +156,7 @@ class Application(ApplicationPackages, ApplicationRepository):
# there is local cache, load package from it
leaf = Package.from_build(source_dir, self.repository.architecture, packager)
else:
leaf = Package.from_aur(package_name, packager)
leaf = Package.from_aur(package_name, packager, include_provides=True)
portion[leaf.base] = leaf
# register package in the database

View File

@ -255,3 +255,20 @@ class Pacman(LazyLogging):
result.update(trim_package(provides) for provides in package.provides)
return result
def provided_by(self, package_name: str) -> Generator[Package, None, None]:
"""
search through databases and emit packages which provides the ``package_name``
Args:
package_name(str): package name to search
Yields:
Package: list of packages which were returned by the query
"""
def is_package_provided(package: Package) -> bool:
provides = [trim_package(name) for name in package.provides]
return package_name in provides
for database in self.handle.get_syncdbs():
yield from filter(is_package_provided, database.search(package_name))

View File

@ -97,20 +97,17 @@ class AUR(Remote):
Returns:
list[AURPackage]: response parsed to package list
Raises:
PackageInfoError: if multiple arguments are passed
"""
query: list[tuple[str, str]] = [
("type", request_type),
("v", self.DEFAULT_RPC_VERSION),
]
if len(args) != 1:
raise PackageInfoError("AUR API requires exactly one argument to search")
arg_query = "arg[]" if len(args) > 1 else "arg"
for arg in args:
query.append((arg_query, arg))
url = f"{self.DEFAULT_RPC_URL}/v{self.DEFAULT_RPC_VERSION}/{request_type}/{args[0]}"
query = list(kwargs.items())
for key, value in kwargs.items():
query.append((key, value))
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
response = self.make_request("GET", url, params=query)
return self.parse_response(response.json())
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
@ -133,15 +130,36 @@ class AUR(Remote):
except StopIteration:
raise UnknownPackageError(package_name) from None
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
return [
package
# search api provides reduced models
for stub in self.package_search(package_name, pacman=pacman, search_by="provides")
# verity that found package actually provides it
if package_name in (package := self.package_info(stub.name, pacman=pacman)).provides
]
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
"""
search package in AUR web
Args:
*keywords(str): keywords to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
search_by(str | None): search by keywords
Returns:
list[AURPackage]: list of packages which match the criteria
"""
return self.aur_request("search", *keywords, by="name-desc")
search_by = search_by or "name-desc"
return self.aur_request("search", *keywords, by=search_by)

View File

@ -127,15 +127,17 @@ class Official(Remote):
except StopIteration:
raise UnknownPackageError(package_name) from None
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
"""
search package in AUR web
Args:
*keywords(str): keywords to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
search_by(str | None): search by keywords
Returns:
list[AURPackage]: list of packages which match the criteria
"""
return self.arch_request(*keywords, by="q")
search_by = search_by or "q"
return self.arch_request(*keywords, by=search_by)

View File

@ -59,3 +59,22 @@ class OfficialSyncdb(Official):
return next(AURPackage.from_pacman(package) for package in pacman.package(package_name))
except StopIteration:
raise UnknownPackageError(package_name) from None
def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
if pacman is None:
return []
return [
AURPackage.from_pacman(package)
for package in pacman.provided_by(package_name)
]

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.http import SyncHttpClient
from ahriman.models.aur_package import AURPackage
@ -41,22 +42,36 @@ class Remote(SyncHttpClient):
"""
@classmethod
def info(cls, package_name: str, *, pacman: Pacman | None = None) -> AURPackage:
def info(cls, package_name: str, *, pacman: Pacman | None = None, include_provides: bool = False) -> AURPackage:
"""
get package info by its name
get package info by its name. If ``include_provides`` is set to ``True``, then, in addition, this method
will perform search by :attr:`ahriman.models.aur_package.AURPackage.provides` and return first package found.
Note, however, that in this case some implementation might not provide this method and search result will might
not be stable
Args:
package_name(str): package name to search
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
(Default value = None)
include_provides(bool, optional): search by provides if no exact match found (Default value = False)
Returns:
AURPackage: package which match the package name
Raises:
UnknownPackageError: if requested package not found
"""
return cls().package_info(package_name, pacman=pacman)
instance = cls()
try:
return instance.package_info(package_name, pacman=pacman)
except UnknownPackageError:
if include_provides and (provided_by := instance.package_provided_by(package_name, pacman=pacman)):
return next(iter(provided_by))
raise
@classmethod
def multisearch(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]:
def multisearch(cls, *keywords: str, pacman: Pacman | None = None,
search_by: str | None = None) -> 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
@ -65,6 +80,7 @@ class Remote(SyncHttpClient):
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
(Default value = None)
search_by(str | None, optional): search by keywords (Default value = None)
Returns:
list[AURPackage]: list of packages each of them matches all search terms
@ -72,7 +88,7 @@ class Remote(SyncHttpClient):
instance = cls()
packages: dict[str, AURPackage] = {}
for term in filter(lambda word: len(word) >= 3, keywords):
portion = instance.search(term, pacman=pacman)
portion = instance.package_search(term, pacman=pacman, search_by=search_by)
packages = {
package.name: package # not mistake to group them by name
for package in portion
@ -114,7 +130,7 @@ class Remote(SyncHttpClient):
raise NotImplementedError
@classmethod
def search(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]:
def search(cls, *keywords: str, pacman: Pacman | None = None, search_by: str | None = None) -> list[AURPackage]:
"""
search package in AUR web
@ -122,11 +138,12 @@ class Remote(SyncHttpClient):
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
(Default value = None)
search_by(str | None, optional): search by keywords (Default value = None)
Returns:
list[AURPackage]: list of packages which match the criteria
"""
return cls().package_search(*keywords, pacman=pacman)
return cls().package_search(*keywords, pacman=pacman, search_by=search_by)
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
"""
@ -144,13 +161,28 @@ class Remote(SyncHttpClient):
"""
raise NotImplementedError
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
del package_name, pacman
return []
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
"""
search package in AUR web
Args:
*keywords(str): keywords to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
search_by(str | None): search by keywords
Returns:
list[AURPackage]: list of packages which match the criteria

View File

@ -203,6 +203,8 @@ def migrate_package_repository(connection: Connection, configuration: Configurat
configuration(Configuration): configuration instance
"""
_, repository_id = configuration.check_loaded()
if repository_id.is_empty:
return # no repository available yet
connection.execute("""update build_queue set repository = :repository""", {"repository": repository_id.id})
connection.execute("""update package_bases set repository = :repository""", {"repository": repository_id.id})

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import requests
import sys
from functools import cached_property
from typing import Any, IO, Literal
@ -70,7 +71,10 @@ class SyncHttpClient(LazyLogging):
request.Session: created session object
"""
session = requests.Session()
session.headers["User-Agent"] = f"ahriman/{__version__}"
python_version = ".".join(map(str, sys.version_info[:3])) # just major.minor.patch
session.headers["User-Agent"] = f"ahriman/{__version__} " \
f"{requests.utils.default_user_agent()} " \
f"python/{python_version}"
return session

View File

@ -33,6 +33,7 @@ class Leaf:
Attributes:
dependencies(set[str]): list of package dependencies
items(list[str]): list of packages in this leaf including provides
package(Package): leaf package properties
"""
@ -42,17 +43,9 @@ class Leaf:
package(Package): package properties
"""
self.package = package
# store frequently used properties
self.dependencies = package.depends_build
@property
def items(self) -> Iterable[str]:
"""
extract all packages from the leaf
Returns:
Iterable[str]: packages containing in this leaf
"""
return self.package.packages.keys()
self.items = self.package.packages_full
def is_dependency(self, packages: Iterable[Leaf]) -> bool:
"""

View File

@ -136,7 +136,8 @@ def check_output(*args: str, exception: Exception | Callable[[int, list[str], st
} | environment
with subprocess.Popen(args, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
user=user, env=full_environment, text=True, encoding="utf8", bufsize=1) as process:
user=user, env=full_environment, text=True, encoding="utf8", errors="backslashreplace",
bufsize=1) as process:
if input_data is not None:
input_channel = get_io(process, "stdin")
input_channel.write(input_data)

View File

@ -25,7 +25,7 @@ from dataclasses import dataclass, field, fields
from pyalpm import Package # type: ignore[import-not-found]
from typing import Any, Self
from ahriman.core.utils import filter_json, full_version
from ahriman.core.utils import filter_json, full_version, trim_package
@dataclass(frozen=True, kw_only=True)
@ -103,6 +103,17 @@ class AURPackage:
keywords: list[str] = field(default_factory=list)
groups: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
"""
update packages lists accordingly
"""
object.__setattr__(self, "depends", [trim_package(package) for package in self.depends])
object.__setattr__(self, "make_depends", [trim_package(package) for package in self.make_depends])
object.__setattr__(self, "opt_depends", [trim_package(package) for package in self.opt_depends])
object.__setattr__(self, "check_depends", [trim_package(package) for package in self.check_depends])
object.__setattr__(self, "conflicts", [trim_package(package) for package in self.conflicts])
object.__setattr__(self, "provides", [trim_package(package) for package in self.provides])
@classmethod
def from_json(cls, dump: dict[str, Any]) -> Self:
"""

View File

@ -213,18 +213,19 @@ class Package(LazyLogging):
)
@classmethod
def from_aur(cls, name: str, packager: str | None = None) -> Self:
def from_aur(cls, name: str, packager: str | None = None, *, include_provides: bool = False) -> Self:
"""
construct package properties from AUR page
Args:
name(str): package name (either base or normal name)
packager(str | None, optional): packager to be used for this build (Default value = None)
include_provides(bool, optional): search by provides if no exact match found (Default value = False)
Returns:
Self: package properties
"""
package = AUR.info(name)
package = AUR.info(name, include_provides=include_provides)
remote = RemoteSource(
source=PackageSource.AUR,
@ -310,7 +311,8 @@ class Package(LazyLogging):
)
@classmethod
def from_official(cls, name: str, pacman: Pacman, packager: str | None = None, *, use_syncdb: bool = True) -> Self:
def from_official(cls, name: str, pacman: Pacman, packager: str | None = None, *, use_syncdb: bool = True,
include_provides: bool = False) -> Self:
"""
construct package properties from official repository page
@ -319,11 +321,13 @@ class Package(LazyLogging):
pacman(Pacman): alpm wrapper instance
packager(str | None, optional): packager to be used for this build (Default value = None)
use_syncdb(bool, optional): use pacman databases instead of official repositories RPC (Default value = True)
include_provides(bool, optional): search by provides if no exact match found (Default value = False)
Returns:
Self: package properties
"""
package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name)
impl = OfficialSyncdb if use_syncdb else Official
package = impl.info(name, pacman=pacman, include_provides=include_provides)
remote = RemoteSource(
source=PackageSource.Repository,

View File

@ -83,12 +83,13 @@ class PackageDescription:
def __post_init__(self) -> None:
"""
update dependencies list accordingly
update packages lists accordingly
"""
self.depends = [trim_package(package) for package in self.depends]
self.opt_depends = [trim_package(package) for package in self.opt_depends]
self.make_depends = [trim_package(package) for package in self.make_depends]
self.opt_depends = [trim_package(package) for package in self.opt_depends]
self.check_depends = [trim_package(package) for package in self.check_depends]
self.provides = [trim_package(package) for package in self.provides]
@property
def filepath(self) -> Path | None:

View File

@ -1,4 +1,6 @@
from pathlib import Path
from pytest_mock import MockerFixture
from typing import Any
from unittest.mock import MagicMock, call as MockCall
from ahriman.application.application import Application
@ -73,6 +75,10 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
mock.packages_full = [package_base]
return mock
def get_package(name: str | Path, *args: Any, **kwargs: Any) -> Package:
name = name if isinstance(name, str) else name.name
return packages[name]
package_python_schedule.packages = {
package_python_schedule.base: package_python_schedule.packages[package_python_schedule.base]
}
@ -87,10 +93,8 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
}
mocker.patch("pathlib.Path.is_dir", autospec=True, side_effect=lambda p: p.name == "python")
package_aur_mock = mocker.patch("ahriman.models.package.Package.from_aur",
side_effect=lambda *args: packages[args[0]])
package_local_mock = mocker.patch("ahriman.models.package.Package.from_build",
side_effect=lambda *args: packages[args[0].name])
package_aur_mock = mocker.patch("ahriman.models.package.Package.from_aur", side_effect=get_package)
package_local_mock = mocker.patch("ahriman.models.package.Package.from_build", side_effect=get_package)
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages",
return_value={"devtools", "python-build", "python-pytest"})
status_client_mock = mocker.patch("ahriman.core.status.Client.set_unknown")
@ -98,8 +102,8 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
result = application.with_dependencies([package_ahriman], process_dependencies=True)
assert {package.base: package for package in result} == packages
package_aur_mock.assert_has_calls([
MockCall(package_python_schedule.base, package_ahriman.packager),
MockCall("python-installer", package_ahriman.packager),
MockCall(package_python_schedule.base, package_ahriman.packager, include_provides=True),
MockCall("python-installer", package_ahriman.packager, include_provides=True),
], any_order=True)
package_local_mock.assert_has_calls([
MockCall(application.repository.paths.cache_for("python"), "x86_64", package_ahriman.packager),

View File

@ -4,7 +4,7 @@ import requests
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock
from unittest.mock import MagicMock, call as MockCall
from ahriman.core.alpm.remote import AUR
from ahriman.core.exceptions import PackageInfoError, UnknownPackageError
@ -76,24 +76,18 @@ def test_aur_request(aur: AUR, aur_package_ahriman: AURPackage,
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=response_mock)
assert aur.aur_request("info", "ahriman") == [aur_package_ahriman]
request_mock.assert_called_once_with(
"GET", "https://aur.archlinux.org/rpc",
params=[("type", "info"), ("v", "5"), ("arg", "ahriman")])
request_mock.assert_called_once_with("GET", "https://aur.archlinux.org/rpc/v5/info/ahriman", params=[])
def test_aur_request_multi_arg(aur: AUR, aur_package_ahriman: AURPackage,
mocker: MockerFixture, resource_path_root: Path) -> None:
def test_aur_request_multi_arg(aur: AUR) -> None:
"""
must perform request to AUR with multiple args
must raise PackageInfoError if invalid amount of arguments supplied
"""
response_mock = MagicMock()
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=response_mock)
with pytest.raises(PackageInfoError):
aur.aur_request("search", "ahriman", "is", "cool")
assert aur.aur_request("search", "ahriman", "is", "cool") == [aur_package_ahriman]
request_mock.assert_called_once_with(
"GET", "https://aur.archlinux.org/rpc",
params=[("type", "search"), ("v", "5"), ("arg[]", "ahriman"), ("arg[]", "is"), ("arg[]", "cool")])
with pytest.raises(PackageInfoError):
aur.aur_request("search")
def test_aur_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage,
@ -106,9 +100,8 @@ def test_aur_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage,
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=response_mock)
assert aur.aur_request("search", "ahriman", by="name") == [aur_package_ahriman]
request_mock.assert_called_once_with(
"GET", "https://aur.archlinux.org/rpc",
params=[("type", "search"), ("v", "5"), ("arg", "ahriman"), ("by", "name")])
request_mock.assert_called_once_with("GET", "https://aur.archlinux.org/rpc/v5/search/ahriman",
params=[("by", "name")])
def test_aur_request_failed(aur: AUR, mocker: MockerFixture) -> None:
@ -139,17 +132,46 @@ def test_package_info(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerF
def test_package_info_not_found(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
"""
must raise UnknownPackage exception in case if no package was found
must raise UnknownPackageError in case if no package was found
"""
mocker.patch("ahriman.core.alpm.remote.AUR.aur_request", return_value=[])
with pytest.raises(UnknownPackageError, match=aur_package_ahriman.name):
assert aur.package_info(aur_package_ahriman.name, pacman=None)
def test_package_provided_by(aur: AUR, aur_package_ahriman: AURPackage, aur_package_akonadi: AURPackage,
mocker: MockerFixture) -> None:
"""
must search for packages which provide required one
"""
aur_package_ahriman.provides.append(aur_package_ahriman.name)
search_mock = mocker.patch("ahriman.core.alpm.remote.AUR.package_search", return_value=[
aur_package_ahriman, aur_package_akonadi
])
info_mock = mocker.patch("ahriman.core.alpm.remote.AUR.package_info", side_effect=[
aur_package_ahriman, aur_package_akonadi
])
assert aur.package_provided_by(aur_package_ahriman.name, pacman=None) == [aur_package_ahriman]
search_mock.assert_called_once_with(aur_package_ahriman.name, pacman=None, search_by="provides")
info_mock.assert_has_calls([
MockCall(aur_package_ahriman.name, pacman=None), MockCall(aur_package_akonadi.name, pacman=None)
])
def test_package_search(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
"""
must make request for search
"""
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.aur_request", return_value=[aur_package_ahriman])
assert aur.package_search(aur_package_ahriman.name, pacman=None) == [aur_package_ahriman]
assert aur.package_search(aur_package_ahriman.name, pacman=None, search_by=None) == [aur_package_ahriman]
request_mock.assert_called_once_with("search", aur_package_ahriman.name, by="name-desc")
def test_package_search_provides(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
"""
must make request for search with custom field
"""
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.aur_request")
aur.package_search(aur_package_ahriman.name, pacman=None, search_by="provides")
request_mock.assert_called_once_with("search", aur_package_ahriman.name, by="provides")

View File

@ -106,7 +106,7 @@ def test_package_info(official: Official, aur_package_akonadi: AURPackage, mocke
def test_package_info_not_found(official: Official, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
"""
must raise UnknownPackage exception in case if no package was found
must raise UnknownPackageError in case if no package was found
"""
mocker.patch("ahriman.core.alpm.remote.Official.arch_request", return_value=[])
with pytest.raises(UnknownPackageError, match=aur_package_ahriman.name):
@ -119,5 +119,16 @@ def test_package_search(official: Official, aur_package_akonadi: AURPackage, moc
"""
request_mock = mocker.patch("ahriman.core.alpm.remote.Official.arch_request",
return_value=[aur_package_akonadi])
assert official.package_search(aur_package_akonadi.name, pacman=None) == [aur_package_akonadi]
assert official.package_search(aur_package_akonadi.name, pacman=None, search_by=None) == [
aur_package_akonadi,
]
request_mock.assert_called_once_with(aur_package_akonadi.name, by="q")
def test_package_search_name(official: Official, aur_package_akonadi: AURPackage, mocker: MockerFixture) -> None:
"""
must make request for search with custom field
"""
request_mock = mocker.patch("ahriman.core.alpm.remote.Official.arch_request")
official.package_search(aur_package_akonadi.name, pacman=None, search_by="name")
request_mock.assert_called_once_with(aur_package_akonadi.name, by="name")

View File

@ -16,18 +16,14 @@ def test_package_info(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURP
mocker.patch("ahriman.models.aur_package.AURPackage.from_pacman", return_value=aur_package_akonadi)
get_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.package", return_value=[aur_package_akonadi])
package = official_syncdb.package_info(aur_package_akonadi.name, pacman=pacman)
assert official_syncdb.package_info(aur_package_akonadi.name, pacman=pacman) == aur_package_akonadi
get_mock.assert_called_once_with(aur_package_akonadi.name)
assert package == aur_package_akonadi
def test_package_info_no_pacman(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURPackage,
mocker: MockerFixture) -> None:
def test_package_info_no_pacman(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURPackage) -> None:
"""
must raise UnknownPackageError if no pacman set
"""
mocker.patch("ahriman.core.alpm.pacman.Pacman.package", return_value=[aur_package_akonadi])
with pytest.raises(UnknownPackageError, match=aur_package_akonadi.name):
official_syncdb.package_info(aur_package_akonadi.name, pacman=None)
@ -40,3 +36,22 @@ def test_package_info_not_found(official_syncdb: OfficialSyncdb, aur_package_ako
mocker.patch("ahriman.core.alpm.pacman.Pacman.package", return_value=[])
with pytest.raises(UnknownPackageError, match=aur_package_akonadi.name):
assert official_syncdb.package_info(aur_package_akonadi.name, pacman=pacman)
def test_package_provided_by(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURPackage, pacman: Pacman,
mocker: MockerFixture) -> None:
"""
must search by provides in database
"""
mocker.patch("ahriman.models.aur_package.AURPackage.from_pacman", return_value=aur_package_akonadi)
get_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.provided_by", return_value=[aur_package_akonadi])
assert official_syncdb.package_provided_by(aur_package_akonadi.name, pacman=pacman) == [aur_package_akonadi]
get_mock.assert_called_once_with(aur_package_akonadi.name)
def test_package_provided_by_no_pacman(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURPackage) -> None:
"""
must return empty list if no pacman set
"""
assert official_syncdb.package_provided_by(aur_package_akonadi.name, pacman=None) == []

View File

@ -5,16 +5,53 @@ from unittest.mock import call as MockCall
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote import Remote
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.aur_package import AURPackage
def test_info(pacman: Pacman, mocker: MockerFixture) -> None:
def test_info(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
"""
must call info method
"""
info_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_info")
Remote.info("ahriman", pacman=pacman)
info_mock.assert_called_once_with("ahriman", pacman=pacman)
info_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_info", return_value=aur_package_ahriman)
assert Remote.info(aur_package_ahriman.name, pacman=pacman) == aur_package_ahriman
info_mock.assert_called_once_with(aur_package_ahriman.name, pacman=pacman)
def test_info_not_found(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
"""
must raise UnknownPackageError if no package found and search by provides is disabled
"""
mocker.patch("ahriman.core.alpm.remote.Remote.package_info",
side_effect=UnknownPackageError(aur_package_ahriman.name))
with pytest.raises(UnknownPackageError):
Remote.info(aur_package_ahriman.name, pacman=pacman)
def test_info_include_provides(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
"""
must perform search through provides list is set
"""
mocker.patch("ahriman.core.alpm.remote.Remote.package_info",
side_effect=UnknownPackageError(aur_package_ahriman.name))
provided_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_provided_by",
return_value=[aur_package_ahriman])
assert Remote.info(aur_package_ahriman.name, pacman=pacman, include_provides=True) == aur_package_ahriman
provided_mock.assert_called_once_with(aur_package_ahriman.name, pacman=pacman)
def test_info_include_provides_not_found(aur_package_ahriman: AURPackage, pacman: Pacman,
mocker: MockerFixture) -> None:
"""
must raise UnknownPackageError if no package found and search by provides returns empty list
"""
mocker.patch("ahriman.core.alpm.remote.Remote.package_info",
side_effect=UnknownPackageError(aur_package_ahriman.name))
mocker.patch("ahriman.core.alpm.remote.Remote.package_provided_by", return_value=[])
with pytest.raises(UnknownPackageError):
Remote.info("ahriman", pacman=pacman, include_provides=True)
def test_multisearch(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
@ -22,10 +59,13 @@ def test_multisearch(aur_package_ahriman: AURPackage, pacman: Pacman, mocker: Mo
must search in AUR with multiple words
"""
terms = ["ahriman", "is", "cool"]
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.search", return_value=[aur_package_ahriman])
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search", return_value=[aur_package_ahriman])
assert Remote.multisearch(*terms, pacman=pacman) == [aur_package_ahriman]
search_mock.assert_has_calls([MockCall("ahriman", pacman=pacman), MockCall("cool", pacman=pacman)])
assert Remote.multisearch(*terms, pacman=pacman, search_by="name") == [aur_package_ahriman]
search_mock.assert_has_calls([
MockCall("ahriman", pacman=pacman, search_by="name"),
MockCall("cool", pacman=pacman, search_by="name"),
])
def test_multisearch_empty(pacman: Pacman, mocker: MockerFixture) -> None:
@ -33,7 +73,7 @@ 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.search")
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search")
assert Remote.multisearch(*terms, pacman=pacman) == []
search_mock.assert_not_called()
@ -43,9 +83,9 @@ def test_multisearch_single(aur_package_ahriman: AURPackage, pacman: Pacman, moc
"""
must search in AUR with one word
"""
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.search", return_value=[aur_package_ahriman])
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search", return_value=[aur_package_ahriman])
assert Remote.multisearch("ahriman", pacman=pacman) == [aur_package_ahriman]
search_mock.assert_called_once_with("ahriman", pacman=pacman)
search_mock.assert_called_once_with("ahriman", pacman=pacman, search_by=None)
def test_remote_git_url(remote: Remote) -> None:
@ -69,8 +109,8 @@ def test_search(pacman: Pacman, mocker: MockerFixture) -> None:
must call search method
"""
search_mock = mocker.patch("ahriman.core.alpm.remote.Remote.package_search")
Remote.search("ahriman", pacman=pacman)
search_mock.assert_called_once_with("ahriman", pacman=pacman)
Remote.search("ahriman", pacman=pacman, search_by="name")
search_mock.assert_called_once_with("ahriman", pacman=pacman, search_by="name")
def test_package_info(remote: Remote, pacman: Pacman) -> None:
@ -81,9 +121,16 @@ def test_package_info(remote: Remote, pacman: Pacman) -> None:
remote.package_info("package", pacman=pacman)
def test_package_provided_by(remote: Remote, pacman: Pacman) -> None:
"""
must return empty list for provides method
"""
assert remote.package_provided_by("package", pacman=pacman) == []
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", pacman=pacman)
remote.package_search("package", pacman=pacman, search_by=None)

View File

@ -282,3 +282,11 @@ def test_packages_with_provides(pacman: Pacman) -> None:
"""
assert "sh" in pacman.packages()
assert "mysql" in pacman.packages() # mariadb
def test_package_provided_by(pacman: Pacman) -> None:
"""
must search through the provides lists
"""
assert list(pacman.provided_by("sh"))
assert list(pacman.provided_by("libacl.so")) # case with exact version

View File

@ -6,6 +6,7 @@ from unittest.mock import call as MockCall
from ahriman.core.configuration import Configuration
from ahriman.core.database.migrations.m011_repository_name import migrate_data, migrate_package_repository, steps
from ahriman.models.repository_id import RepositoryId
def test_migration_repository_name() -> None:
@ -37,3 +38,13 @@ def test_migrate_package_repository(connection: Connection, configuration: Confi
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
])
def test_migrate_package_repository_empty_id(connection: Connection, configuration: Configuration,
mocker: MockerFixture) -> None:
"""
must do nothing on empty repository id
"""
mocker.patch("ahriman.core.configuration.Configuration.check_loaded", return_value=("", RepositoryId("", "")))
migrate_package_repository(connection, configuration)
connection.execute.assert_not_called()

View File

@ -195,6 +195,32 @@ def test_tree_levels_sorted() -> None:
assert third == [leaf2.package, leaf4.package]
def test_tree_levels_provides() -> None:
"""
must build tree according to provides list
"""
leaf1 = Leaf(
Package(
base="package1",
version="1.0.0",
remote=RemoteSource(source=PackageSource.AUR),
packages={"package1": PackageDescription(depends=["package3"])},
)
)
leaf2 = Leaf(
Package(
base="package2",
version="1.0.0",
remote=RemoteSource(source=PackageSource.AUR),
packages={"package2": PackageDescription(provides=["package3"])},
)
)
first, second = Tree([leaf1, leaf2]).levels()
assert first == [leaf2.package]
assert second == [leaf1.package]
def test_tree_partitions() -> None:
"""
must divide tree into partitions

View File

@ -150,6 +150,13 @@ def test_check_output_empty_line(mocker: MockerFixture) -> None:
logger_mock.assert_has_calls([MockCall(""), MockCall("hello")])
def test_check_output_encoding_error(resource_path_root: Path) -> None:
"""
must correctly process unicode encoding error in command output
"""
assert check_output("cat", str(resource_path_root / "models" / "package_pacman-static_pkgbuild"))
def test_check_user(repository_id: RepositoryId, mocker: MockerFixture) -> None:
"""
must check user correctly

View File

@ -2,7 +2,7 @@ import datetime
import json
import pyalpm # typing: ignore
from dataclasses import asdict, fields
from dataclasses import asdict, fields, replace
from pathlib import Path
from pytest_mock import MockerFixture
from typing import Any
@ -38,6 +38,25 @@ def _get_official_data(resource_path_root: Path) -> dict[str, Any]:
return json.loads(response)["results"][0]
def test_post_init(aur_package_ahriman: AURPackage) -> None:
"""
must trim versions and descriptions from packages list
"""
package = replace(
aur_package_ahriman,
depends=["a=1"],
make_depends=["b>=3"],
opt_depends=["c: a description"],
check_depends=["d=4"],
provides=["e=5"],
)
assert package.depends == ["a"]
assert package.make_depends == ["b"]
assert package.opt_depends == ["c"]
assert package.check_depends == ["d"]
assert package.provides == ["e"]
def test_from_json(aur_package_ahriman: AURPackage, resource_path_root: Path) -> None:
"""
must load package from json

View File

@ -167,15 +167,26 @@ def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, moc
"""
must construct package from aur
"""
mocker.patch("ahriman.core.alpm.remote.AUR.info", return_value=aur_package_ahriman)
info_mock = mocker.patch("ahriman.core.alpm.remote.AUR.info", return_value=aur_package_ahriman)
package = Package.from_aur(package_ahriman.base, package_ahriman.packager)
info_mock.assert_called_once_with(package_ahriman.base, include_provides=False)
assert package_ahriman.base == package.base
assert package_ahriman.version == package.version
assert package_ahriman.packages.keys() == package.packages.keys()
assert package_ahriman.packager == package.packager
def test_from_aur_include_provides(package_ahriman: Package, aur_package_ahriman: AURPackage,
mocker: MockerFixture) -> None:
"""
must construct package from aur by using provides list
"""
info_mock = mocker.patch("ahriman.core.alpm.remote.AUR.info", return_value=aur_package_ahriman)
Package.from_aur(package_ahriman.base, package_ahriman.packager, include_provides=True)
info_mock.assert_called_once_with(package_ahriman.base, include_provides=True)
def test_from_build(package_ahriman: Package, mocker: MockerFixture, resource_path_root: Path) -> None:
"""
must construct package from PKGBUILD
@ -269,14 +280,25 @@ 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_include_provides(package_ahriman: Package, aur_package_ahriman: AURPackage, pacman: Pacman,
mocker: MockerFixture) -> None:
"""
must construct package from official repository
"""
info_mock = mocker.patch("ahriman.core.alpm.remote.Official.info", return_value=aur_package_ahriman)
Package.from_official(package_ahriman.base, pacman, package_ahriman.packager, include_provides=True)
info_mock.assert_called_once_with(package_ahriman.base, pacman=pacman, include_provides=True)
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.info", return_value=aur_package_ahriman)
info_mock = mocker.patch("ahriman.core.alpm.remote.Official.info", return_value=aur_package_ahriman)
package = Package.from_official(package_ahriman.base, pacman, package_ahriman.packager)
info_mock.assert_called_once_with(package_ahriman.base, pacman=pacman, include_provides=False)
assert package_ahriman.base == package.base
assert package_ahriman.version == package.version
assert package_ahriman.packages.keys() == package.packages.keys()

View File

@ -6,10 +6,15 @@ from ahriman.models.package_description import PackageDescription
def test_post_init() -> None:
"""
must trim versions and descriptions from dependencies list
must trim versions and descriptions from packages list
"""
assert PackageDescription(depends=["a=1"], make_depends=["b>=3"], opt_depends=["c: a description"]) == \
PackageDescription(depends=["a"], make_depends=["b"], opt_depends=["c"])
assert PackageDescription(
depends=["a=1"],
make_depends=["b>=3"],
opt_depends=["c: a description"],
check_depends=["d=4"],
provides=["e=5"]
) == PackageDescription(depends=["a"], make_depends=["b"], opt_depends=["c"], check_depends=["d"], provides=["e"])
def test_filepath(package_description_ahriman: PackageDescription) -> None: