diff --git a/src/ahriman/application/application/application.py b/src/ahriman/application/application/application.py index f6032a5c..2ae9874c 100644 --- a/src/ahriman/application/application/application.py +++ b/src/ahriman/application/application/application.py @@ -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 diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py index 9f616699..4865e35a 100644 --- a/src/ahriman/core/alpm/pacman.py +++ b/src/ahriman/core/alpm/pacman.py @@ -255,3 +255,19 @@ 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: + return package_name in package.provides + + for database in self.handle.get_syncdbs(): + yield from filter(is_package_provided, database.search(package_name)) diff --git a/src/ahriman/core/alpm/remote/aur.py b/src/ahriman/core/alpm/remote/aur.py index 374a0783..f6cb0b08 100644 --- a/src/ahriman/core/alpm/remote/aur.py +++ b/src/ahriman/core/alpm/remote/aur.py @@ -130,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.package_base, 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) diff --git a/src/ahriman/core/alpm/remote/official.py b/src/ahriman/core/alpm/remote/official.py index e931b1f9..466f29e4 100644 --- a/src/ahriman/core/alpm/remote/official.py +++ b/src/ahriman/core/alpm/remote/official.py @@ -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) diff --git a/src/ahriman/core/alpm/remote/official_syncdb.py b/src/ahriman/core/alpm/remote/official_syncdb.py index 95abeb8b..e1f954e0 100644 --- a/src/ahriman/core/alpm/remote/official_syncdb.py +++ b/src/ahriman/core/alpm/remote/official_syncdb.py @@ -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) + ] diff --git a/src/ahriman/core/alpm/remote/remote.py b/src/ahriman/core/alpm/remote/remote.py index 6e11b896..370e0b29 100644 --- a/src/ahriman/core/alpm/remote/remote.py +++ b/src/ahriman/core/alpm/remote/remote.py @@ -18,6 +18,7 @@ # along with this program. If not, see . # 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 diff --git a/src/ahriman/core/tree.py b/src/ahriman/core/tree.py index 7b5fae49..074a03af 100644 --- a/src/ahriman/core/tree.py +++ b/src/ahriman/core/tree.py @@ -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: """ diff --git a/src/ahriman/models/package.py b/src/ahriman/models/package.py index 27d7d6a3..4a74a893 100644 --- a/src/ahriman/models/package.py +++ b/src/ahriman/models/package.py @@ -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, diff --git a/tests/ahriman/application/application/test_application.py b/tests/ahriman/application/application/test_application.py index ec15b97f..5df929c4 100644 --- a/tests/ahriman/application/application/test_application.py +++ b/tests/ahriman/application/application/test_application.py @@ -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), diff --git a/tests/ahriman/core/alpm/remote/test_aur.py b/tests/ahriman/core/alpm/remote/test_aur.py index 50b50183..216f43b9 100644 --- a/tests/ahriman/core/alpm/remote/test_aur.py +++ b/tests/ahriman/core/alpm/remote/test_aur.py @@ -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 @@ -132,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") diff --git a/tests/ahriman/core/alpm/remote/test_official.py b/tests/ahriman/core/alpm/remote/test_official.py index bb178377..5e1fa82a 100644 --- a/tests/ahriman/core/alpm/remote/test_official.py +++ b/tests/ahriman/core/alpm/remote/test_official.py @@ -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") diff --git a/tests/ahriman/core/alpm/remote/test_official_syncdb.py b/tests/ahriman/core/alpm/remote/test_official_syncdb.py index c9accfd5..e0eefd67 100644 --- a/tests/ahriman/core/alpm/remote/test_official_syncdb.py +++ b/tests/ahriman/core/alpm/remote/test_official_syncdb.py @@ -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) == [] diff --git a/tests/ahriman/core/alpm/remote/test_remote.py b/tests/ahriman/core/alpm/remote/test_remote.py index 8b5ddb38..7654c8ed 100644 --- a/tests/ahriman/core/alpm/remote/test_remote.py +++ b/tests/ahriman/core/alpm/remote/test_remote.py @@ -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) diff --git a/tests/ahriman/core/alpm/test_pacman.py b/tests/ahriman/core/alpm/test_pacman.py index f3d0f48e..2070301e 100644 --- a/tests/ahriman/core/alpm/test_pacman.py +++ b/tests/ahriman/core/alpm/test_pacman.py @@ -282,3 +282,10 @@ 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")) diff --git a/tests/ahriman/core/test_tree.py b/tests/ahriman/core/test_tree.py index 42775f88..0d65ae78 100644 --- a/tests/ahriman/core/test_tree.py +++ b/tests/ahriman/core/test_tree.py @@ -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 diff --git a/tests/ahriman/models/test_package.py b/tests/ahriman/models/test_package.py index a3815381..d72eec1e 100644 --- a/tests/ahriman/models/test_package.py +++ b/tests/ahriman/models/test_package.py @@ -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()