unify aur.search method

due to specific of the AUR API in order to reduce the code we are using
own wrapper and work with it instead of direct library calls
This commit is contained in:
Evgenii Alekseev 2021-10-26 04:49:55 +03:00
parent 09b0f2914d
commit d3ea81d234
6 changed files with 75 additions and 67 deletions

View File

@ -20,12 +20,13 @@
import argparse
import aur # type: ignore
from typing import Callable, Dict, Iterable, List, Tuple, Type
from typing import Callable, Iterable, List, Tuple, Type
from ahriman.application.formatters.aur_printer import AurPrinter
from ahriman.application.handlers.handler import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InvalidOption
from ahriman.core.util import aur_search
class Search(Handler):
@ -46,17 +47,7 @@ class Search(Handler):
:param configuration: configuration instance
:param no_report: force disable reporting
"""
packages: Dict[str, aur.Package] = {}
# see https://bugs.archlinux.org/task/49133
for search in args.search:
portion = aur.search(search)
packages = {
package.package_base: package
for package in portion
if package.package_base in packages or not packages
}
packages_list = list(packages.values()) # explicit conversion for the tests
packages_list = aur_search(*args.search)
for package in Search.sort(packages_list, args.sort_by):
AurPrinter(package).print(args.info)

View File

@ -17,6 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import aur # type: ignore
import datetime
import os
import subprocess
@ -24,11 +25,29 @@ import requests
from logging import Logger
from pathlib import Path
from typing import Any, Dict, Generator, Iterable, Optional, Union
from typing import Any, Dict, Generator, Iterable, List, Optional, Union
from ahriman.core.exceptions import InvalidOption, UnsafeRun
def aur_search(*terms: str) -> List[aur.Package]:
"""
search in AUR 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
:param terms: search terms, e.g. "ahriman", "is", "cool"
:return: list of packages each of them matches all search terms
"""
packages: Dict[str, aur.Package] = {}
for term in filter(lambda word: len(word) > 3, terms):
portion = aur.search(term)
packages = {
package.package_base: package
for package in portion
if package.package_base in packages or not packages
}
return list(packages.values())
def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path] = None,
input_data: Optional[str] = None, logger: Optional[Logger] = None) -> str:
"""

View File

@ -19,9 +19,10 @@
#
import aur # type: ignore
from aiohttp.web import HTTPBadRequest, Response, json_response
from typing import Callable, Iterator
from aiohttp.web import HTTPNotFound, Response, json_response
from typing import Callable, List
from ahriman.core.util import aur_search
from ahriman.models.user_access import UserAccess
from ahriman.web.views.base import BaseView
@ -43,12 +44,10 @@ class SearchView(BaseView):
:return: 200 with found package bases and descriptions sorted by base
"""
search: Iterator[str] = filter(lambda s: len(s) > 3, self.request.query.getall("for", default=[]))
search_string = " ".join(search)
if not search_string:
raise HTTPBadRequest(reason="Search string must not be empty")
packages = aur.search(search_string)
search: List[str] = self.request.query.getall("for", default=[])
packages = aur_search(*search)
if not packages:
raise HTTPNotFound(reason=f"No packages found for terms: {search}")
comparator: Callable[[aur.Package], str] = lambda item: str(item.package_base)
response = [

View File

@ -3,7 +3,6 @@ import aur
import pytest
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.application.handlers import Search
from ahriman.core.configuration import Configuration
@ -28,33 +27,21 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
must run command
"""
args = _default_args(args)
mocker.patch("aur.search", return_value=[aur_package_ahriman])
search_mock = mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
Search.run(args, "x86_64", configuration, True)
search_mock.assert_called_once_with("ahriman")
print_mock.assert_called_once()
def test_run_multiple_search(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
mocker: MockerFixture) -> None:
"""
must run command with multiple search arguments
"""
args = _default_args(args)
args.search = ["ahriman", "is", "cool"]
search_mock = mocker.patch("aur.search", return_value=[aur_package_ahriman])
Search.run(args, "x86_64", configuration, True)
search_mock.assert_has_calls([mock.call(term) for term in args.search])
def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
mocker: MockerFixture) -> None:
"""
must run command with sorting
"""
args = _default_args(args)
mocker.patch("aur.search", return_value=[aur_package_ahriman])
mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
Search.run(args, "x86_64", configuration, True)
@ -68,7 +55,7 @@ def test_run_sort_by(args: argparse.Namespace, configuration: Configuration, aur
"""
args = _default_args(args)
args.sort_by = "field"
mocker.patch("aur.search", return_value=[aur_package_ahriman])
mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
Search.run(args, "x86_64", configuration, True)

View File

@ -1,3 +1,4 @@
import aur
import datetime
import logging
import pytest
@ -5,12 +6,45 @@ import subprocess
from pathlib import Path
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.core.exceptions import InvalidOption, UnsafeRun
from ahriman.core.util import check_output, check_user, filter_json, package_like, pretty_datetime, pretty_size, walk
from ahriman.core.util import aur_search, check_output, check_user, filter_json, package_like, pretty_datetime, \
pretty_size, walk
from ahriman.models.package import Package
def test_aur_search(aur_package_ahriman: aur.Package, mocker: MockerFixture) -> None:
"""
must search in AUR with multiple words
"""
terms = ["ahriman", "is", "cool"]
search_mock = mocker.patch("aur.search", return_value=[aur_package_ahriman])
assert aur_search(*terms) == [aur_package_ahriman]
search_mock.assert_has_calls([mock.call("ahriman"), mock.call("cool")])
def test_aur_search_empty(mocker: MockerFixture) -> None:
"""
must return empty list if no long terms supplied
"""
terms = ["it", "is"]
search_mock = mocker.patch("aur.search")
assert aur_search(*terms) == []
search_mock.assert_not_called()
def test_aur_search_single(aur_package_ahriman: aur.Package, mocker: MockerFixture) -> None:
"""
must search in AUR with one word
"""
search_mock = mocker.patch("aur.search", return_value=[aur_package_ahriman])
assert aur_search("ahriman") == [aur_package_ahriman]
search_mock.assert_called_once_with("ahriman")
def test_check_output(mocker: MockerFixture) -> None:
"""
must run command and log result

View File

@ -21,7 +21,7 @@ async def test_get(client: TestClient, aur_package_ahriman: aur.Package, mocker:
"""
must call get request correctly
"""
mocker.patch("aur.search", return_value=[aur_package_ahriman])
mocker.patch("ahriman.web.views.service.search.aur_search", return_value=[aur_package_ahriman])
response = await client.get("/service-api/v1/search", params={"for": "ahriman"})
assert response.ok
@ -33,41 +33,19 @@ async def test_get_exception(client: TestClient, mocker: MockerFixture) -> None:
"""
must raise 400 on empty search string
"""
search_mock = mocker.patch("aur.search")
search_mock = mocker.patch("ahriman.web.views.service.search.aur_search", return_value=[])
response = await client.get("/service-api/v1/search")
assert response.status == 400
search_mock.assert_not_called()
assert response.status == 404
search_mock.assert_called_once_with()
async def test_get_join(client: TestClient, mocker: MockerFixture) -> None:
"""
must join search args with space
"""
search_mock = mocker.patch("aur.search")
search_mock = mocker.patch("ahriman.web.views.service.search.aur_search")
response = await client.get("/service-api/v1/search", params=[("for", "ahriman"), ("for", "maybe")])
assert response.ok
search_mock.assert_called_once_with("ahriman maybe")
async def test_get_join_filter(client: TestClient, mocker: MockerFixture) -> None:
"""
must filter search parameters with less than 3 symbols
"""
search_mock = mocker.patch("aur.search")
response = await client.get("/service-api/v1/search", params=[("for", "ah"), ("for", "maybe")])
assert response.ok
search_mock.assert_called_once_with("maybe")
async def test_get_join_filter_empty(client: TestClient, mocker: MockerFixture) -> None:
"""
must filter search parameters with less than 3 symbols (empty result)
"""
search_mock = mocker.patch("aur.search")
response = await client.get("/service-api/v1/search", params=[("for", "ah"), ("for", "ma")])
assert response.status == 400
search_mock.assert_not_called()
search_mock.assert_called_once_with("ahriman", "maybe")