mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-27 22:31:43 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
e414616bbd | |||
60a2e25b9a | |||
683abca9e5 | |||
5a3770b739 | |||
52cd9a0ea9 | |||
bfca7e41ab | |||
603c5449a8 | |||
5aac3db2d5 | |||
3c5bcbd172 | |||
042638d40e | |||
e6adb333b2 | |||
fa4244d21e | |||
91de1c2b8a | |||
32a4a82603 | |||
e8a10c1bb5 | |||
d480eb7bc3 | |||
8b0f9bfd78 | |||
a2639f8dbb | |||
65ba590ace |
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 406 KiB |
@ -119,7 +119,7 @@ remove user
|
||||
web server
|
||||
.SH OPTIONS 'ahriman aur-search'
|
||||
usage: ahriman aur-search [-h] [-i]
|
||||
[--sort-by {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}]
|
||||
[--sort-by {conflicts,depends,description,first_submitted,id,keywords,last_modified,license,maintainer,make_depends,name,num_votes,opt_depends,out_of_date,package_base,package_base_id,popularity,provides,url,url_path,version}]
|
||||
search [search ...]
|
||||
|
||||
search for package in AUR using API
|
||||
@ -133,13 +133,13 @@ search terms, can be specified multiple times, result will match all terms
|
||||
show additional package information
|
||||
|
||||
.TP
|
||||
\fB\-\-sort\-by\fR {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}
|
||||
\fB\-\-sort\-by\fR {conflicts,depends,description,first_submitted,id,keywords,last_modified,license,maintainer,make_depends,name,num_votes,opt_depends,out_of_date,package_base,package_base_id,popularity,provides,url,url_path,version}
|
||||
sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted
|
||||
by name
|
||||
|
||||
.SH OPTIONS 'ahriman search'
|
||||
usage: ahriman aur-search [-h] [-i]
|
||||
[--sort-by {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}]
|
||||
[--sort-by {conflicts,depends,description,first_submitted,id,keywords,last_modified,license,maintainer,make_depends,name,num_votes,opt_depends,out_of_date,package_base,package_base_id,popularity,provides,url,url_path,version}]
|
||||
search [search ...]
|
||||
|
||||
search for package in AUR using API
|
||||
@ -153,7 +153,7 @@ search terms, can be specified multiple times, result will match all terms
|
||||
show additional package information
|
||||
|
||||
.TP
|
||||
\fB\-\-sort\-by\fR {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}
|
||||
\fB\-\-sort\-by\fR {conflicts,depends,description,first_submitted,id,keywords,last_modified,license,maintainer,make_depends,name,num_votes,opt_depends,out_of_date,package_base,package_base_id,popularity,provides,url,url_path,version}
|
||||
sort field by this field. In case if two packages have the same value of the specified field, they will be always sorted
|
||||
by name
|
||||
|
||||
@ -496,7 +496,7 @@ create empty repository tree. Optional command for auto architecture support
|
||||
|
||||
|
||||
.SH OPTIONS 'ahriman repo-rebuild'
|
||||
usage: ahriman repo-rebuild [-h] [--depends-on DEPENDS_ON]
|
||||
usage: ahriman repo-rebuild [-h] [--depends-on DEPENDS_ON] [--dry-run]
|
||||
|
||||
force rebuild whole repository
|
||||
|
||||
@ -505,8 +505,12 @@ force rebuild whole repository
|
||||
\fB\-\-depends\-on\fR \fI\,DEPENDS_ON\/\fR
|
||||
only rebuild packages that depend on specified package
|
||||
|
||||
.TP
|
||||
\fB\-\-dry\-run\fR
|
||||
just perform check for packages without rebuild process itself
|
||||
|
||||
.SH OPTIONS 'ahriman rebuild'
|
||||
usage: ahriman repo-rebuild [-h] [--depends-on DEPENDS_ON]
|
||||
usage: ahriman repo-rebuild [-h] [--depends-on DEPENDS_ON] [--dry-run]
|
||||
|
||||
force rebuild whole repository
|
||||
|
||||
@ -515,6 +519,10 @@ force rebuild whole repository
|
||||
\fB\-\-depends\-on\fR \fI\,DEPENDS_ON\/\fR
|
||||
only rebuild packages that depend on specified package
|
||||
|
||||
.TP
|
||||
\fB\-\-dry\-run\fR
|
||||
just perform check for packages without rebuild process itself
|
||||
|
||||
.SH OPTIONS 'ahriman repo-remove-unknown'
|
||||
usage: ahriman repo-remove-unknown [-h] [--dry-run] [-i]
|
||||
|
||||
@ -695,7 +703,7 @@ target to sync
|
||||
|
||||
|
||||
.SH OPTIONS 'ahriman repo-update'
|
||||
usage: ahriman repo-update [-h] [--dry-run] [--no-aur] [--no-manual] [--no-vcs] [package ...]
|
||||
usage: ahriman repo-update [-h] [--dry-run] [--no-aur] [--no-local] [--no-manual] [--no-vcs] [package ...]
|
||||
|
||||
check for packages updates and run build process if requested
|
||||
|
||||
@ -711,6 +719,10 @@ just perform check for updates, same as check command
|
||||
\fB\-\-no\-aur\fR
|
||||
do not check for AUR updates. Implies \-\-no\-vcs
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-local\fR
|
||||
do not check local packages for updates
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-manual\fR
|
||||
do not include manual updates
|
||||
@ -720,7 +732,7 @@ do not include manual updates
|
||||
do not check VCS packages
|
||||
|
||||
.SH OPTIONS 'ahriman update'
|
||||
usage: ahriman repo-update [-h] [--dry-run] [--no-aur] [--no-manual] [--no-vcs] [package ...]
|
||||
usage: ahriman repo-update [-h] [--dry-run] [--no-aur] [--no-local] [--no-manual] [--no-vcs] [package ...]
|
||||
|
||||
check for packages updates and run build process if requested
|
||||
|
||||
@ -736,6 +748,10 @@ just perform check for updates, same as check command
|
||||
\fB\-\-no\-aur\fR
|
||||
do not check for AUR updates. Implies \-\-no\-vcs
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-local\fR
|
||||
do not check local packages for updates
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-manual\fR
|
||||
do not include manual updates
|
||||
|
@ -1,6 +1,13 @@
|
||||
# ahriman configuration
|
||||
|
||||
Some groups can be specified for each architecture separately. E.g. if there are `build` and `build:x86_64` groups it will use the option from `build:x86_64` for the `x86_64` architecture and `build` for any other (architecture specific group has higher priority). In case if both groups are presented, architecture specific options will be merged into global ones overriding them.
|
||||
Some groups can be specified for each architecture separately. E.g. if there are `build` and `build:x86_64` groups it will use the option from `build:x86_64` for the `x86_64` architecture and `build` for any other (architecture specific group has higher priority). In case if both groups are presented, architecture specific options will be merged into global ones overriding them.
|
||||
|
||||
Some values have list of strings type. Those values will be read in the same way as shell does:
|
||||
|
||||
* By default, it splits value by spaces excluding empty elements.
|
||||
* In case if quotation mark (`"` or `'`) will be found, any spaces inside will be ignored.
|
||||
* In order to use quotation mark inside value it is required to put it to another quotation mark, e.g. `wor"'"d "with quote"` will be parsed as `["wor'd", "with quote"]` and vice versa.
|
||||
* Unclosed quotation mark is not allowed and will rise an exception.
|
||||
|
||||
## `settings` group
|
||||
|
||||
@ -38,11 +45,11 @@ Authorization mapping. Group name must refer to user access level, i.e. it shoul
|
||||
Key is always username (case-insensitive), option value depends on authorization provider:
|
||||
|
||||
* `OAuth` - by default requires only usernames and ignores values. But in case of direct login method call (via POST request) it will act as `Mapping` authorization method.
|
||||
* `Mapping` (default) - reads salted password hashes from values, uses SHA512 in order to hash passwords. Password can be set by using `create-user` subcommand.
|
||||
* `Mapping` (default) - reads salted password hashes from values, uses SHA512 in order to hash passwords. Password can be set by using `user-add` subcommand.
|
||||
|
||||
## `build:*` groups
|
||||
|
||||
Build related configuration. Group name must refer to architecture, e.g. it should be `build:x86_64` for x86_64 architecture.
|
||||
Build related configuration. Group name can refer to architecture, e.g. `build:x86_64` can be used for x86_64 architecture specific settings.
|
||||
|
||||
* `archbuild_flags` - additional flags passed to `archbuild` command, space separated list of strings, optional.
|
||||
* `build_command` - default build command, string, required.
|
||||
@ -59,7 +66,7 @@ Base repository settings.
|
||||
|
||||
## `sign:*` groups
|
||||
|
||||
Settings for signing packages or repository. Group name must refer to architecture, e.g. it should be `sign:x86_64` for x86_64 architecture.
|
||||
Settings for signing packages or repository. Group name can refer to architecture, e.g. `sign:x86_64` can be used for x86_64 architecture specific settings.
|
||||
|
||||
* `target` - configuration flag to enable signing, space separated list of strings, required. Allowed values are `package` (sign each package separately), `repository` (sign repository database file).
|
||||
* `key` - default PGP key, string, required. This key will also be used for database signing if enabled.
|
||||
@ -69,7 +76,7 @@ Settings for signing packages or repository. Group name must refer to architectu
|
||||
|
||||
Report generation settings.
|
||||
|
||||
* `target` - list of reports to be generated, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. `somerandomname` must point to existing section, `email` must point to one of `email` of `email:x86_64` (with architecture it has higher priority).
|
||||
* `target` - list of reports to be generated, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. `somerandomname` must point to existing section, `email` must point to one of `email` of `email:x86_64` (the one with architecture has higher priority).
|
||||
|
||||
Type will be read from several ways:
|
||||
|
||||
@ -152,7 +159,7 @@ Requires `boto3` library to be installed. Section name must be either `s3` (plus
|
||||
|
||||
## `web:*` groups
|
||||
|
||||
Web server settings. If any of `host`/`port` is not set, web integration will be disabled. Group name must refer to architecture, e.g. it should be `web:x86_64` for x86_64 architecture. This feature requires `aiohttp` libraries to be installed.
|
||||
Web server settings. If any of `host`/`port` is not set, web integration will be disabled. Group name can refer to architecture, e.g. `web:x86_64` can be used for x86_64 architecture specific settings. This feature requires `aiohttp` libraries to be installed.
|
||||
|
||||
* `address` - optional address in form `proto://host:port` (`port` can be omitted in case of default `proto` ports), will be used instead of `http://{host}:{port}` in case if set, string, optional. This option is required in case if `OAuth` provider is used.
|
||||
* `debug` - enable debug toolbar, boolean, optional, default `no`.
|
||||
|
35
docs/faq.md
35
docs/faq.md
@ -163,6 +163,41 @@ Server = file:///var/lib/ahriman/repository/x86_64
|
||||
|
||||
(You might need to add `SigLevel` option according to the pacman documentation.)
|
||||
|
||||
|
||||
### I would like to serve the repository
|
||||
|
||||
Easy. For example, nginx configuration (without SSL) will look like:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name repo.example.com;
|
||||
|
||||
location / {
|
||||
autoindex on;
|
||||
root /var/lib/ahriman/repository;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example of the status page configuration is the following (status service is using 8080 port):
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name builds.example.com;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarder-Proto $scheme;
|
||||
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Remote synchronization
|
||||
|
||||
### Wait I would like to use the repository from another server
|
||||
|
@ -1,13 +1,13 @@
|
||||
# Maintainer: Evgeniy Alekseev
|
||||
|
||||
pkgname='ahriman'
|
||||
pkgver=1.6.0
|
||||
pkgver=1.8.0
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH Linux ReposItory MANager"
|
||||
arch=('any')
|
||||
url="https://github.com/arcan1s/ahriman"
|
||||
license=('GPL3')
|
||||
depends=('devtools' 'git' 'pyalpm' 'python-aur' 'python-passlib' 'python-srcinfo')
|
||||
depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-srcinfo')
|
||||
makedepends=('python-pip')
|
||||
optdepends=('breezy: -bzr packages support'
|
||||
'darcs: -darcs packages support'
|
||||
|
@ -3,3 +3,4 @@ test = pytest
|
||||
|
||||
[tool:pytest]
|
||||
addopts = --cov=ahriman --cov-report term-missing:skip-covered --pspec
|
||||
asyncio_mode = auto
|
||||
|
2
setup.py
2
setup.py
@ -29,7 +29,7 @@ setup(
|
||||
dependency_links=[
|
||||
],
|
||||
install_requires=[
|
||||
"aur",
|
||||
"inflection",
|
||||
"passlib",
|
||||
"pyalpm",
|
||||
"requests",
|
||||
|
@ -22,6 +22,7 @@ import sys
|
||||
import tempfile
|
||||
|
||||
from pathlib import Path
|
||||
from typing import TypeVar
|
||||
|
||||
from ahriman import version
|
||||
from ahriman.application import handlers
|
||||
@ -32,8 +33,11 @@ from ahriman.models.sign_settings import SignSettings
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
# pylint thinks it is bad idea, but get the fuck off
|
||||
SubParserAction = argparse._SubParsersAction # pylint: disable=protected-access
|
||||
# this workaround is for several things
|
||||
# firstly python devs don't think that is it error and asking you for workarounds https://bugs.python.org/issue41592
|
||||
# secondly linters don't like when you are importing private members
|
||||
# thirdly new mypy doesn't like _SubParsersAction and thinks it is a template
|
||||
SubParserAction = TypeVar("SubParserAction", bound="argparse._SubParsersAction[argparse.ArgumentParser]")
|
||||
|
||||
|
||||
def _formatter(prog: str) -> argparse.HelpFormatter:
|
||||
@ -285,7 +289,7 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("package", help="filter check by package base", nargs="*")
|
||||
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
|
||||
parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_manual=True)
|
||||
parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_local=False, no_manual=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -346,6 +350,8 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository",
|
||||
description="force rebuild whole repository", formatter_class=_formatter)
|
||||
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append")
|
||||
parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself",
|
||||
action="store_true")
|
||||
parser.set_defaults(handler=handlers.Rebuild)
|
||||
return parser
|
||||
|
||||
@ -461,6 +467,7 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("package", help="filter check by package base", nargs="*")
|
||||
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
|
||||
parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true")
|
||||
parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
|
||||
parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
|
||||
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
|
||||
parser.set_defaults(handler=handlers.Update)
|
||||
|
@ -64,12 +64,10 @@ class Packages(Properties):
|
||||
:param known_packages: list of packages which are known by the service
|
||||
:param without_dependencies: if set, dependency check will be disabled
|
||||
"""
|
||||
aur_url = self.configuration.get("alpm", "aur_url")
|
||||
package = Package.load(source, self.repository.pacman, aur_url)
|
||||
Sources.load(self.repository.paths.manual_for(package.base), package.git_url,
|
||||
self.repository.paths.patches_for(package.base))
|
||||
|
||||
package = Package.load(source, PackageSource.AUR, self.repository.pacman, self.repository.aur_url)
|
||||
local_path = self.repository.paths.manual_for(package.base)
|
||||
|
||||
Sources.load(local_path, package.git_url, self.repository.paths.patches_for(package.base))
|
||||
self._process_dependencies(local_path, known_packages, without_dependencies)
|
||||
|
||||
def _add_directory(self, source: str, *_: Any) -> None:
|
||||
@ -88,11 +86,9 @@ class Packages(Properties):
|
||||
:param known_packages: list of packages which are known by the service
|
||||
:param without_dependencies: if set, dependency check will be disabled
|
||||
"""
|
||||
local_path = Path(source)
|
||||
aur_url = self.configuration.get("alpm", "aur_url")
|
||||
package = Package.load(local_path, self.repository.pacman, aur_url)
|
||||
package = Package.load(source, PackageSource.Local, self.repository.pacman, self.repository.aur_url)
|
||||
cache_dir = self.repository.paths.cache_for(package.base)
|
||||
shutil.copytree(local_path, cache_dir) # copy package to store in caches
|
||||
shutil.copytree(Path(source), cache_dir) # copy package to store in caches
|
||||
Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
|
||||
|
||||
dst = self.repository.paths.manual_for(package.base)
|
||||
|
@ -23,9 +23,11 @@ from pathlib import Path
|
||||
from typing import Callable, Iterable, List
|
||||
|
||||
from ahriman.application.application.properties import Properties
|
||||
from ahriman.application.formatters.update_printer import UpdatePrinter
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.tree import Tree
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
class Repository(Properties):
|
||||
@ -103,27 +105,37 @@ class Repository(Properties):
|
||||
targets = target or None
|
||||
self.repository.process_sync(targets, built_packages)
|
||||
|
||||
def unknown(self) -> List[Package]:
|
||||
def unknown(self) -> List[str]:
|
||||
"""
|
||||
get packages which were not found in AUR
|
||||
:return: unknown package list
|
||||
:return: unknown package archive list
|
||||
"""
|
||||
def has_aur(package_base: str, aur_url: str) -> bool:
|
||||
try:
|
||||
_ = Package.from_aur(package_base, aur_url)
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def has_local(package_base: str) -> bool:
|
||||
cache_dir = self.repository.paths.cache_for(package_base)
|
||||
def has_local(probe: Package) -> bool:
|
||||
cache_dir = self.repository.paths.cache_for(probe.base)
|
||||
return cache_dir.is_dir() and not Sources.has_remotes(cache_dir)
|
||||
|
||||
return [
|
||||
package
|
||||
for package in self.repository.packages()
|
||||
if not has_aur(package.base, package.aur_url) and not has_local(package.base)
|
||||
]
|
||||
def unknown_aur(probe: Package) -> List[str]:
|
||||
packages: List[str] = []
|
||||
for single in probe.packages:
|
||||
try:
|
||||
_ = Package.from_aur(single, probe.aur_url)
|
||||
except Exception:
|
||||
packages.append(single)
|
||||
return packages
|
||||
|
||||
def unknown_local(probe: Package) -> List[str]:
|
||||
cache_dir = self.repository.paths.cache_for(probe.base)
|
||||
local = Package.from_build(cache_dir, probe.aur_url)
|
||||
packages = set(probe.packages.keys()).difference(local.packages.keys())
|
||||
return list(packages)
|
||||
|
||||
result = []
|
||||
for package in self.repository.packages():
|
||||
if has_local(package):
|
||||
result.extend(unknown_local(package)) # there is local package
|
||||
else:
|
||||
result.extend(unknown_aur(package)) # local package not found
|
||||
return result
|
||||
|
||||
def update(self, updates: Iterable[Package]) -> None:
|
||||
"""
|
||||
@ -133,7 +145,10 @@ class Repository(Properties):
|
||||
def process_update(paths: Iterable[Path]) -> None:
|
||||
if not paths:
|
||||
return # don't need to process if no update supplied
|
||||
updated = [Package.load(path, self.repository.pacman, self.repository.aur_url) for path in paths]
|
||||
updated = [
|
||||
Package.load(str(path), PackageSource.Archive, self.repository.pacman, self.repository.aur_url)
|
||||
for path in paths
|
||||
]
|
||||
self.repository.process_update(paths)
|
||||
self._finalize(updated)
|
||||
|
||||
@ -148,25 +163,31 @@ class Repository(Properties):
|
||||
packages = self.repository.process_build(level)
|
||||
process_update(packages)
|
||||
|
||||
def updates(self, filter_packages: Iterable[str], no_aur: bool, no_manual: bool, no_vcs: bool,
|
||||
def updates(self, filter_packages: Iterable[str], no_aur: bool, no_local: bool, no_manual: bool, no_vcs: bool,
|
||||
log_fn: Callable[[str], None]) -> List[Package]:
|
||||
"""
|
||||
get list of packages to run update process
|
||||
:param filter_packages: do not check every package just specified in the list
|
||||
:param no_aur: do not check for aur updates
|
||||
:param no_local: do not check local packages for updates
|
||||
:param no_manual: do not check for manual updates
|
||||
:param no_vcs: do not check VCS packages
|
||||
:param log_fn: logger function to log updates
|
||||
:return: list of out-of-dated packages
|
||||
"""
|
||||
updates = []
|
||||
updates = {}
|
||||
|
||||
if not no_aur:
|
||||
updates.extend(self.repository.updates_aur(filter_packages, no_vcs))
|
||||
updates.update({package.base: package for package in self.repository.updates_aur(filter_packages, no_vcs)})
|
||||
if not no_local:
|
||||
updates.update({package.base: package for package in self.repository.updates_local()})
|
||||
if not no_manual:
|
||||
updates.extend(self.repository.updates_manual())
|
||||
updates.update({package.base: package for package in self.repository.updates_manual()})
|
||||
|
||||
for package in updates:
|
||||
log_fn(f"{package.base} = {package.version}")
|
||||
local_versions = {package.base: package.version for package in self.repository.packages()}
|
||||
updated_packages = [package for _, package in sorted(updates.items())]
|
||||
for package in updated_packages:
|
||||
UpdatePrinter(package, local_versions.get(package.base)).print(
|
||||
verbose=True, log_fn=log_fn, separator=" -> ")
|
||||
|
||||
return updates
|
||||
return updated_packages
|
||||
|
@ -17,12 +17,11 @@
|
||||
# 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
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
@ -31,7 +30,7 @@ class AurPrinter(Printer):
|
||||
print content of the AUR package
|
||||
"""
|
||||
|
||||
def __init__(self, package: aur.Package) -> None:
|
||||
def __init__(self, package: AURPackage) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param package: AUR package description
|
||||
@ -46,12 +45,12 @@ class AurPrinter(Printer):
|
||||
return [
|
||||
Property("Package base", self.content.package_base),
|
||||
Property("Description", self.content.description, is_required=True),
|
||||
Property("Upstream URL", self.content.url),
|
||||
Property("Licenses", self.content.license), # it should be actually a list
|
||||
Property("Maintainer", self.content.maintainer or ""), # I think it is optional
|
||||
Property("Upstream URL", self.content.url or ""),
|
||||
Property("Licenses", ",".join(self.content.license)),
|
||||
Property("Maintainer", self.content.maintainer or ""),
|
||||
Property("First submitted", pretty_datetime(self.content.first_submitted)),
|
||||
Property("Last updated", pretty_datetime(self.content.last_modified)),
|
||||
# more fields coming https://github.com/cdown/aur/pull/29
|
||||
Property("Keywords", ",".join(self.content.keywords)),
|
||||
]
|
||||
|
||||
def title(self) -> Optional[str]:
|
||||
|
@ -17,11 +17,10 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class StatusPrinter(Printer):
|
||||
@ -36,13 +35,6 @@ class StatusPrinter(Printer):
|
||||
"""
|
||||
self.content = status
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return []
|
||||
|
||||
def title(self) -> Optional[str]:
|
||||
"""
|
||||
generate entry title from content
|
||||
|
42
src/ahriman/application/formatters/string_printer.py
Normal file
42
src/ahriman/application/formatters/string_printer.py
Normal file
@ -0,0 +1,42 @@
|
||||
#
|
||||
# Copyright (c) 2021 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
|
||||
|
||||
class StringPrinter(Printer):
|
||||
"""
|
||||
print content of the random string
|
||||
"""
|
||||
|
||||
def __init__(self, content: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param content: any content string
|
||||
"""
|
||||
self.content = content
|
||||
|
||||
def title(self) -> Optional[str]:
|
||||
"""
|
||||
generate entry title from content
|
||||
:return: content title if it can be generated and None otherwise
|
||||
"""
|
||||
return self.content
|
53
src/ahriman/application/formatters/update_printer.py
Normal file
53
src/ahriman/application/formatters/update_printer.py
Normal file
@ -0,0 +1,53 @@
|
||||
#
|
||||
# Copyright (c) 2021 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class UpdatePrinter(Printer):
|
||||
"""
|
||||
print content of the package update
|
||||
"""
|
||||
|
||||
def __init__(self, remote: Package, local_version: Optional[str]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param remote: remote (new) package object
|
||||
:param local_version: local version of the package if any
|
||||
"""
|
||||
self.content = remote
|
||||
self.local_version = local_version or "N/A"
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return [Property(self.local_version, self.content.version, is_required=True)]
|
||||
|
||||
def title(self) -> Optional[str]:
|
||||
"""
|
||||
generate entry title from content
|
||||
:return: content title if it can be generated and None otherwise
|
||||
"""
|
||||
return self.content.base
|
@ -46,5 +46,5 @@ class Add(Handler):
|
||||
if not args.now:
|
||||
return
|
||||
|
||||
packages = application.updates(args.package, True, False, True, application.logger.info)
|
||||
packages = application.updates(args.package, True, True, False, True, application.logger.info)
|
||||
application.update(packages)
|
||||
|
@ -29,6 +29,7 @@ from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
class Patch(Handler):
|
||||
@ -55,23 +56,24 @@ class Patch(Handler):
|
||||
elif args.action == Action.Remove:
|
||||
Patch.patch_set_remove(application, args.package)
|
||||
elif args.action == Action.Update:
|
||||
Patch.patch_set_create(application, Path(args.package), args.track)
|
||||
Patch.patch_set_create(application, args.package, args.track)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_create(application: Application, sources_dir: Path, track: List[str]) -> None:
|
||||
def patch_set_create(application: Application, sources_dir: str, track: List[str]) -> None:
|
||||
"""
|
||||
create patch set for the package base
|
||||
:param application: application instance
|
||||
:param sources_dir: path to directory with the package sources
|
||||
:param track: track files which match the glob before creating the patch
|
||||
"""
|
||||
package = Package.load(sources_dir, application.repository.pacman, application.repository.aur_url)
|
||||
package = Package.load(sources_dir, PackageSource.Local, application.repository.pacman,
|
||||
application.repository.aur_url)
|
||||
patch_dir = application.repository.paths.patches_for(package.base)
|
||||
|
||||
Patch.patch_set_remove(application, package.base) # remove old patches
|
||||
patch_dir.mkdir(mode=0o755, parents=True)
|
||||
|
||||
Sources.patch_create(sources_dir, patch_dir / "00-main.patch", *track)
|
||||
Sources.patch_create(Path(sources_dir), patch_dir / "00-main.patch", *track)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_list(application: Application, package_base: str) -> None:
|
||||
|
@ -22,6 +22,7 @@ import argparse
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.formatters.update_printer import UpdatePrinter
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
@ -44,9 +45,10 @@ class Rebuild(Handler):
|
||||
depends_on = set(args.depends_on) if args.depends_on else None
|
||||
|
||||
application = Application(architecture, configuration, no_report)
|
||||
packages = [
|
||||
package
|
||||
for package in application.repository.packages()
|
||||
if depends_on is None or depends_on.intersection(package.depends)
|
||||
] # we have to use explicit list here for testing purpose
|
||||
application.update(packages)
|
||||
updates = application.repository.packages_depends_on(depends_on)
|
||||
if args.dry_run:
|
||||
for package in updates:
|
||||
UpdatePrinter(package, package.version).print(verbose=True)
|
||||
return
|
||||
|
||||
application.update(updates)
|
||||
|
@ -22,10 +22,9 @@ import argparse
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.formatters.package_printer import PackagePrinter
|
||||
from ahriman.application.formatters.string_printer import StringPrinter
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
|
||||
|
||||
class RemoveUnknown(Handler):
|
||||
@ -46,8 +45,8 @@ class RemoveUnknown(Handler):
|
||||
application = Application(architecture, configuration, no_report)
|
||||
unknown_packages = application.unknown()
|
||||
if args.dry_run:
|
||||
for package in unknown_packages:
|
||||
PackagePrinter(package, BuildStatus()).print(args.info)
|
||||
for package in sorted(unknown_packages):
|
||||
StringPrinter(package).print(args.info)
|
||||
return
|
||||
|
||||
application.remove(package.base for package in unknown_packages)
|
||||
application.remove(unknown_packages)
|
||||
|
@ -18,24 +18,27 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import aur # type: ignore
|
||||
|
||||
from dataclasses import fields
|
||||
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.alpm.aur import AUR
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InvalidOption
|
||||
from ahriman.core.util import aur_search
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
class Search(Handler):
|
||||
"""
|
||||
packages search handler
|
||||
:cvar SORT_FIELDS: allowed fields to sort the package list
|
||||
"""
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||
SORT_FIELDS = set(aur.Package._fields) # later we will have to remove some fields from here (lists)
|
||||
# later we will have to remove some fields from here (lists)
|
||||
SORT_FIELDS = {pair.name for pair in fields(AURPackage)}
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
@ -47,12 +50,12 @@ class Search(Handler):
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
packages_list = aur_search(*args.search)
|
||||
packages_list = AUR.multisearch(*args.search)
|
||||
for package in Search.sort(packages_list, args.sort_by):
|
||||
AurPrinter(package).print(args.info)
|
||||
|
||||
@staticmethod
|
||||
def sort(packages: Iterable[aur.Package], sort_by: str) -> List[aur.Package]:
|
||||
def sort(packages: Iterable[AURPackage], sort_by: str) -> List[AURPackage]:
|
||||
"""
|
||||
sort package list by specified field
|
||||
:param packages: packages list to sort
|
||||
@ -63,6 +66,6 @@ class Search(Handler):
|
||||
raise InvalidOption(sort_by)
|
||||
# always sort by package name at the last
|
||||
# well technically it is not a string, but we can deal with it
|
||||
comparator: Callable[[aur.Package], Tuple[str, str]] =\
|
||||
comparator: Callable[[AURPackage], Tuple[str, str]] =\
|
||||
lambda package: (getattr(package, sort_by), package.name)
|
||||
return sorted(packages, key=comparator)
|
||||
|
@ -42,7 +42,7 @@ class Update(Handler):
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
application = Application(architecture, configuration, no_report)
|
||||
packages = application.updates(args.package, args.no_aur, args.no_manual, args.no_vcs,
|
||||
packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
|
||||
Update.log_fn(application, args.dry_run))
|
||||
if args.dry_run:
|
||||
return
|
||||
|
152
src/ahriman/core/alpm/aur.py
Normal file
152
src/ahriman/core/alpm/aur.py
Normal file
@ -0,0 +1,152 @@
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
from ahriman.core.exceptions import InvalidPackageInfo
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
class AUR:
|
||||
"""
|
||||
AUR RPC wrapper
|
||||
:cvar DEFAULT_RPC_URL: default AUR RPC url
|
||||
:cvar DEFAULT_RPC_VERSION: default AUR RPC version
|
||||
:ivar logger: class logger
|
||||
:ivar rpc_url: AUR RPC url
|
||||
:ivar rpc_version: AUR RPC version
|
||||
"""
|
||||
|
||||
DEFAULT_RPC_URL = "https://aur.archlinux.org/rpc"
|
||||
DEFAULT_RPC_VERSION = "5"
|
||||
|
||||
def __init__(self, rpc_url: Optional[str] = None, rpc_version: Optional[str] = None) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param rpc_url: AUR RPC url
|
||||
:param rpc_version: AUR RPC version
|
||||
"""
|
||||
self.rpc_url = rpc_url or self.DEFAULT_RPC_URL
|
||||
self.rpc_version = rpc_version or self.DEFAULT_RPC_VERSION
|
||||
self.logger = logging.getLogger("build_details")
|
||||
|
||||
@classmethod
|
||||
def info(cls: Type[AUR], package_name: str) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
:param package_name: package name to search
|
||||
:return: package which match the package name
|
||||
"""
|
||||
return cls().package_info(package_name)
|
||||
|
||||
@classmethod
|
||||
def multisearch(cls: Type[AUR], *keywords: str) -> List[AURPackage]:
|
||||
"""
|
||||
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 keywords: search terms, e.g. "ahriman", "is", "cool"
|
||||
:return: list of packages each of them matches all search terms
|
||||
"""
|
||||
instance = cls()
|
||||
packages: Dict[str, AURPackage] = {}
|
||||
for term in filter(lambda word: len(word) > 3, keywords):
|
||||
portion = instance.search(term)
|
||||
packages = {
|
||||
package.package_base: package
|
||||
for package in portion
|
||||
if package.package_base in packages or not packages
|
||||
}
|
||||
return list(packages.values())
|
||||
|
||||
@classmethod
|
||||
def search(cls: Type[AUR], *keywords: str) -> List[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
:param keywords: keywords to search
|
||||
:return: list of packages which match the criteria
|
||||
"""
|
||||
return cls().package_search(*keywords)
|
||||
|
||||
@staticmethod
|
||||
def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
|
||||
"""
|
||||
parse RPC response to package list
|
||||
:param response: RPC response json
|
||||
:return: list of parsed packages
|
||||
"""
|
||||
response_type = response["type"]
|
||||
if response_type == "error":
|
||||
error_details = response.get("error", "Unknown API error")
|
||||
raise InvalidPackageInfo(error_details)
|
||||
return [AURPackage.from_json(package) for package in response["results"]]
|
||||
|
||||
def make_request(self, request_type: str, *args: str, **kwargs: str) -> List[AURPackage]:
|
||||
"""
|
||||
perform request to AUR RPC
|
||||
:param request_type: AUR request type, e.g. search, info
|
||||
:param args: list of arguments to be passed as args query parameter
|
||||
:param kwargs: list of additional named parameters like by
|
||||
:return: response parsed to package list
|
||||
"""
|
||||
query: Dict[str, Any] = {
|
||||
"type": request_type,
|
||||
"v": self.rpc_version
|
||||
}
|
||||
|
||||
arg_query = "arg[]" if len(args) > 1 else "arg"
|
||||
query[arg_query] = list(args)
|
||||
|
||||
for key, value in kwargs.items():
|
||||
query[key] = value
|
||||
|
||||
try:
|
||||
response = requests.get(self.rpc_url, params=query)
|
||||
response.raise_for_status()
|
||||
return self.parse_response(response.json())
|
||||
except requests.HTTPError as e:
|
||||
self.logger.exception(
|
||||
"could not perform request by using type %s: %s",
|
||||
request_type,
|
||||
exception_response_text(e))
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.exception("could not perform request by using type %s", request_type)
|
||||
raise
|
||||
|
||||
def package_info(self, package_name: str) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
:param package_name: package name to search
|
||||
:return: package which match the package name
|
||||
"""
|
||||
packages = self.make_request("info", package_name)
|
||||
return next(package for package in packages if package.name == package_name)
|
||||
|
||||
def package_search(self, *keywords: str, by: str = "name-desc") -> List[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
:param keywords: keywords to search
|
||||
:param by: search by the field
|
||||
:return: list of packages which match the criteria
|
||||
"""
|
||||
return self.make_request("search", *keywords, by=by)
|
@ -20,7 +20,7 @@
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.core.util import check_output
|
||||
|
||||
@ -64,7 +64,7 @@ class Sources:
|
||||
patch_path.write_text(patch)
|
||||
|
||||
@staticmethod
|
||||
def fetch(sources_dir: Path, remote: str) -> None:
|
||||
def fetch(sources_dir: Path, remote: Optional[str]) -> None:
|
||||
"""
|
||||
either clone repository or update it to origin/`branch`
|
||||
:param sources_dir: local path to fetch
|
||||
@ -81,6 +81,8 @@ class Sources:
|
||||
Sources.logger.info("update HEAD to remote at %s", sources_dir)
|
||||
Sources._check_output("git", "fetch", "origin", Sources._branch,
|
||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
||||
elif remote is None:
|
||||
Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
|
||||
else:
|
||||
Sources.logger.info("clone remote %s to %s", remote, sources_dir)
|
||||
Sources._check_output("git", "clone", remote, str(sources_dir), exception=None, logger=Sources.logger)
|
||||
|
@ -24,7 +24,7 @@ import logging
|
||||
|
||||
from logging.config import fileConfig
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple, Type
|
||||
|
||||
from ahriman.core.exceptions import InitializeException
|
||||
|
||||
@ -49,7 +49,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
default constructor. In the most cases must not be called directly
|
||||
"""
|
||||
configparser.RawConfigParser.__init__(self, allow_no_value=True, converters={
|
||||
"list": lambda value: value.split(),
|
||||
"list": self.__convert_list,
|
||||
"path": self.__convert_path,
|
||||
})
|
||||
self.architecture: Optional[str] = None
|
||||
@ -84,6 +84,32 @@ class Configuration(configparser.RawConfigParser):
|
||||
config.load_logging(quiet)
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def __convert_list(value: str) -> List[str]:
|
||||
"""
|
||||
convert string value to list of strings
|
||||
:param value: string configuration value
|
||||
:return: list of string from the parsed string
|
||||
"""
|
||||
def generator() -> Generator[str, None, None]:
|
||||
quote_mark = None
|
||||
word = ""
|
||||
for char in value:
|
||||
if char in ("'", "\"") and quote_mark is None: # quoted part started, store quote and do nothing
|
||||
quote_mark = char
|
||||
elif char == quote_mark: # quoted part ended, reset quotation
|
||||
quote_mark = None
|
||||
elif char == " " and quote_mark is None: # found space outside of the quotation, yield the word
|
||||
yield word
|
||||
word = ""
|
||||
else: # append character to the buffer
|
||||
word += char
|
||||
if quote_mark: # there is unmatched quote
|
||||
raise ValueError(f"unmatched quote in {value}")
|
||||
yield word # sequence done, return whatever we found
|
||||
|
||||
return [word for word in generator() if word]
|
||||
|
||||
@staticmethod
|
||||
def section_name(section: str, suffix: str) -> str:
|
||||
"""
|
||||
@ -204,6 +230,8 @@ class Configuration(configparser.RawConfigParser):
|
||||
"""
|
||||
if self.path is None or self.architecture is None:
|
||||
raise InitializeException("Configuration path and/or architecture are not set")
|
||||
for section in self.sections(): # clear current content
|
||||
self.remove_section(section)
|
||||
self.load(self.path)
|
||||
self.merge_sections(self.architecture)
|
||||
|
||||
|
@ -153,8 +153,12 @@ class UnknownPackage(ValueError):
|
||||
exception for status watcher which will be thrown on unknown package
|
||||
"""
|
||||
|
||||
def __init__(self, base: str) -> None:
|
||||
ValueError.__init__(self, f"Package base {base} is unknown")
|
||||
def __init__(self, package_base: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param package_base: package base name
|
||||
"""
|
||||
ValueError.__init__(self, f"Package base {package_base} is unknown")
|
||||
|
||||
|
||||
class UnsafeRun(RuntimeError):
|
||||
@ -165,9 +169,9 @@ class UnsafeRun(RuntimeError):
|
||||
def __init__(self, current_uid: int, root_uid: int) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param current_uid: current user ID
|
||||
:param root_uid: ID of the owner of root directory
|
||||
"""
|
||||
RuntimeError.__init__(
|
||||
self,
|
||||
f"""Current UID {current_uid} differs from root owner {root_uid}.
|
||||
Note that for the most actions it is unsafe to run application as different user.
|
||||
If you are 100% sure that it must be there try --unsafe option""")
|
||||
RuntimeError.__init__(self, f"Current UID {current_uid} differs from root owner {root_uid}. "
|
||||
f"Note that for the most actions it is unsafe to run application as different user."
|
||||
f" If you are 100% sure that it must be there try --unsafe option")
|
||||
|
@ -20,7 +20,7 @@
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Optional
|
||||
from typing import Iterable, List, Optional, Set
|
||||
|
||||
from ahriman.core.build_tools.task import Task
|
||||
from ahriman.core.report.report import Report
|
||||
@ -34,6 +34,14 @@ class Executor(Cleaner):
|
||||
trait for common repository update processes
|
||||
"""
|
||||
|
||||
def load_archives(self, packages: Iterable[Path]) -> List[Package]:
|
||||
"""
|
||||
load packages from list of archives
|
||||
:param packages: paths to package archives
|
||||
:return: list of read packages
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def packages(self) -> List[Package]:
|
||||
"""
|
||||
generate list of repository packages
|
||||
@ -151,23 +159,24 @@ class Executor(Cleaner):
|
||||
package_path = self.paths.repository / name
|
||||
self.repo.add(package_path)
|
||||
|
||||
# we are iterating over bases, not single packages
|
||||
updates: Dict[str, Package] = {}
|
||||
for filename in packages:
|
||||
try:
|
||||
local = Package.load(filename, self.pacman, self.aur_url)
|
||||
updates.setdefault(local.base, local).packages.update(local.packages)
|
||||
except Exception:
|
||||
self.logger.exception("could not load package from %s", filename)
|
||||
current_packages = self.packages()
|
||||
removed_packages: List[str] = [] # list of packages which have been removed from the base
|
||||
updates = self.load_archives(packages)
|
||||
|
||||
for local in updates.values():
|
||||
for local in updates:
|
||||
try:
|
||||
for description in local.packages.values():
|
||||
update_single(description.filename, local.base)
|
||||
self.reporter.set_success(local)
|
||||
|
||||
current_package_archives: Set[str] = next(
|
||||
(set(current.packages) for current in current_packages if current.base == local.base), set())
|
||||
removed_packages.extend(current_package_archives.difference(local.packages))
|
||||
except Exception:
|
||||
self.reporter.set_failed(local.base)
|
||||
self.logger.exception("could not process %s", local.base)
|
||||
self.clear_packages()
|
||||
|
||||
self.process_remove(removed_packages)
|
||||
|
||||
return self.repo.repo_path
|
||||
|
@ -18,12 +18,13 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
from typing import Dict, Iterable, List, Optional
|
||||
|
||||
from ahriman.core.repository.executor import Executor
|
||||
from ahriman.core.repository.update_handler import UpdateHandler
|
||||
from ahriman.core.util import package_like
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
class Repository(Executor, UpdateHandler):
|
||||
@ -31,20 +32,35 @@ class Repository(Executor, UpdateHandler):
|
||||
base repository control class
|
||||
"""
|
||||
|
||||
def load_archives(self, packages: Iterable[Path]) -> List[Package]:
|
||||
"""
|
||||
load packages from list of archives
|
||||
:param packages: paths to package archives
|
||||
:return: list of read packages
|
||||
"""
|
||||
result: Dict[str, Package] = {}
|
||||
# we are iterating over bases, not single packages
|
||||
for full_path in packages:
|
||||
try:
|
||||
local = Package.load(str(full_path), PackageSource.Archive, self.pacman, self.aur_url)
|
||||
current = result.setdefault(local.base, local)
|
||||
if current.version != local.version:
|
||||
# force version to max of them
|
||||
self.logger.warning("version of %s differs, found %s and %s",
|
||||
current.base, current.version, local.version)
|
||||
if current.is_outdated(local, self.paths, calculate_version=False):
|
||||
current.version = local.version
|
||||
current.packages.update(local.packages)
|
||||
except Exception:
|
||||
self.logger.exception("could not load package from %s", full_path)
|
||||
return list(result.values())
|
||||
|
||||
def packages(self) -> List[Package]:
|
||||
"""
|
||||
generate list of repository packages
|
||||
:return: list of packages properties
|
||||
"""
|
||||
result: Dict[str, Package] = {}
|
||||
for full_path in filter(package_like, self.paths.repository.iterdir()):
|
||||
try:
|
||||
local = Package.load(full_path, self.pacman, self.aur_url)
|
||||
result.setdefault(local.base, local).packages.update(local.packages)
|
||||
except Exception:
|
||||
self.logger.exception("could not load package from %s", full_path)
|
||||
continue
|
||||
return list(result.values())
|
||||
return self.load_archives(filter(package_like, self.paths.repository.iterdir()))
|
||||
|
||||
def packages_built(self) -> List[Path]:
|
||||
"""
|
||||
@ -52,3 +68,20 @@ class Repository(Executor, UpdateHandler):
|
||||
:return: list of filenames from the directory
|
||||
"""
|
||||
return list(filter(package_like, self.paths.packages.iterdir()))
|
||||
|
||||
def packages_depends_on(self, depends_on: Optional[Iterable[str]]) -> List[Package]:
|
||||
"""
|
||||
extract list of packages which depends on specified package
|
||||
:param: depends_on: dependencies of the packages
|
||||
:return: list of repository packages which depend on specified packages
|
||||
"""
|
||||
packages = self.packages()
|
||||
if depends_on is None:
|
||||
return packages # no list provided extract everything by default
|
||||
depends_on = set(depends_on)
|
||||
|
||||
return [
|
||||
package
|
||||
for package in packages
|
||||
if depends_on is None or depends_on.intersection(package.full_depends(self.pacman, packages))
|
||||
]
|
||||
|
@ -19,8 +19,10 @@
|
||||
#
|
||||
from typing import Iterable, List
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
class UpdateHandler(Cleaner):
|
||||
@ -53,7 +55,7 @@ class UpdateHandler(Cleaner):
|
||||
continue
|
||||
|
||||
try:
|
||||
remote = Package.load(local.base, self.pacman, self.aur_url)
|
||||
remote = Package.load(local.base, PackageSource.AUR, self.pacman, self.aur_url)
|
||||
if local.is_outdated(remote, self.paths):
|
||||
self.reporter.set_pending(local.base)
|
||||
result.append(remote)
|
||||
@ -64,6 +66,31 @@ class UpdateHandler(Cleaner):
|
||||
|
||||
return result
|
||||
|
||||
def updates_local(self) -> List[Package]:
|
||||
"""
|
||||
check local packages for updates
|
||||
:return: list of local packages which are out-of-dated
|
||||
"""
|
||||
result: List[Package] = []
|
||||
packages = {local.base: local for local in self.packages()}
|
||||
|
||||
for dirname in self.paths.cache.iterdir():
|
||||
try:
|
||||
Sources.fetch(dirname, remote=None)
|
||||
remote = Package.load(str(dirname), PackageSource.Local, self.pacman, self.aur_url)
|
||||
|
||||
local = packages.get(remote.base)
|
||||
if local is None:
|
||||
self.reporter.set_unknown(remote)
|
||||
result.append(remote)
|
||||
elif local.is_outdated(remote, self.paths):
|
||||
self.reporter.set_pending(local.base)
|
||||
result.append(remote)
|
||||
except Exception:
|
||||
self.logger.exception("could not procees package at %s", dirname)
|
||||
|
||||
return result
|
||||
|
||||
def updates_manual(self) -> List[Package]:
|
||||
"""
|
||||
check for packages for which manual update has been requested
|
||||
@ -72,16 +99,16 @@ class UpdateHandler(Cleaner):
|
||||
result: List[Package] = []
|
||||
known_bases = {package.base for package in self.packages()}
|
||||
|
||||
for filename in self.paths.manual.iterdir():
|
||||
for dirname in self.paths.manual.iterdir():
|
||||
try:
|
||||
local = Package.load(filename, self.pacman, self.aur_url)
|
||||
local = Package.load(str(dirname), PackageSource.Local, self.pacman, self.aur_url)
|
||||
result.append(local)
|
||||
if local.base not in known_bases:
|
||||
self.reporter.set_unknown(local)
|
||||
else:
|
||||
self.reporter.set_pending(local.base)
|
||||
except Exception:
|
||||
self.logger.exception("could not add package from %s", filename)
|
||||
self.logger.exception("could not add package from %s", dirname)
|
||||
self.clear_manual()
|
||||
|
||||
return result
|
||||
|
@ -17,7 +17,6 @@
|
||||
# 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
|
||||
@ -25,29 +24,11 @@ import requests
|
||||
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Generator, Iterable, List, Optional, Union
|
||||
from typing import Any, Dict, Generator, Iterable, 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:
|
||||
"""
|
||||
|
109
src/ahriman/models/aur_package.py
Normal file
109
src/ahriman/models/aur_package.py
Normal file
@ -0,0 +1,109 @@
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import inflection
|
||||
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import Any, Callable, Dict, List, Optional, Type
|
||||
|
||||
from ahriman.core.util import filter_json
|
||||
|
||||
|
||||
@dataclass
|
||||
class AURPackage:
|
||||
"""
|
||||
AUR package descriptor
|
||||
:ivar id: package ID
|
||||
:ivar name: package name
|
||||
:ivar package_base_id: package base ID
|
||||
:ivar version: package base version
|
||||
:ivar description: package base description
|
||||
:ivar url: package upstream URL
|
||||
:ivar num_votes: number of votes for the package
|
||||
:ivar polularity: package popularity
|
||||
:ivar out_of_date: package out of date timestamp if any
|
||||
:ivar maintainer: package maintainer
|
||||
:ivar first_submitted: timestamp of the first package submission
|
||||
:ivar last_modified: timestamp of the last package submission
|
||||
:ivar url_path: AUR package path
|
||||
:ivar depends: list of package dependencies
|
||||
:ivar make_depends: list of package make dependencies
|
||||
:ivar opt_depends: list of package optional dependencies
|
||||
:ivar conflicts: conflicts list for the package
|
||||
:ivar provides: list of packages which this package provides
|
||||
:ivar license: list of package licenses
|
||||
:ivar keywords: list of package keywords
|
||||
"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
package_base_id: int
|
||||
package_base: str
|
||||
version: str
|
||||
description: str
|
||||
num_votes: int
|
||||
popularity: float
|
||||
first_submitted: datetime.datetime
|
||||
last_modified: datetime.datetime
|
||||
url_path: str
|
||||
url: Optional[str] = None
|
||||
out_of_date: Optional[datetime.datetime] = None
|
||||
maintainer: Optional[str] = None
|
||||
depends: List[str] = field(default_factory=list)
|
||||
make_depends: List[str] = field(default_factory=list)
|
||||
opt_depends: List[str] = field(default_factory=list)
|
||||
conflicts: List[str] = field(default_factory=list)
|
||||
provides: List[str] = field(default_factory=list)
|
||||
license: List[str] = field(default_factory=list)
|
||||
keywords: List[str] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls: Type[AURPackage], dump: Dict[str, Any]) -> AURPackage:
|
||||
"""
|
||||
construct package descriptor from RPC properties
|
||||
:param dump: json dump body
|
||||
:return: AUR package descriptor
|
||||
"""
|
||||
# filter to only known fields
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
properties = cls.convert(dump)
|
||||
return cls(**filter_json(properties, known_fields))
|
||||
|
||||
@staticmethod
|
||||
def convert(descriptor: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
covert AUR RPC key names to package keys
|
||||
:param descriptor: RPC package descriptor
|
||||
:return: package descriptor with names converted to snake case
|
||||
"""
|
||||
identity_mapper: Callable[[Any], Any] = lambda value: value
|
||||
value_mapper: Dict[str, Callable[[Any], Any]] = {
|
||||
"out_of_date": lambda value: datetime.datetime.utcfromtimestamp(value) if value is not None else None,
|
||||
"first_submitted": datetime.datetime.utcfromtimestamp,
|
||||
"last_modified": datetime.datetime.utcfromtimestamp,
|
||||
}
|
||||
|
||||
result: Dict[str, Any] = {}
|
||||
for api_key, api_value in descriptor.items():
|
||||
property_key = inflection.underscore(api_key)
|
||||
mapper = value_mapper.get(property_key, identity_mapper)
|
||||
result[property_key] = mapper(api_value)
|
||||
|
||||
return result
|
@ -21,7 +21,7 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from dataclasses import dataclass, fields
|
||||
from dataclasses import dataclass, field, fields
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Type
|
||||
|
||||
@ -84,7 +84,7 @@ class BuildStatus:
|
||||
"""
|
||||
|
||||
status: BuildStatusEnum = BuildStatusEnum.Unknown
|
||||
timestamp: int = int(datetime.datetime.utcnow().timestamp())
|
||||
timestamp: int = field(default_factory=lambda: int(datetime.datetime.utcnow().timestamp()))
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
|
@ -19,19 +19,21 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import aur # type: ignore
|
||||
import copy
|
||||
import logging
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from pyalpm import vercmp # type: ignore
|
||||
from srcinfo.parse import parse_srcinfo # type: ignore
|
||||
from typing import Any, Dict, List, Optional, Set, Type, Union
|
||||
from typing import Any, Dict, Iterable, List, Optional, Set, Type
|
||||
|
||||
from ahriman.core.alpm.aur import AUR
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.exceptions import InvalidPackageInfo
|
||||
from ahriman.core.util import check_output
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@ -127,7 +129,7 @@ class Package:
|
||||
:param aur_url: AUR root url
|
||||
:return: package properties
|
||||
"""
|
||||
package = aur.info(name)
|
||||
package = AUR.info(name)
|
||||
return cls(package.package_base, package.version, aur_url, {package.name: PackageDescription()})
|
||||
|
||||
@classmethod
|
||||
@ -164,21 +166,24 @@ class Package:
|
||||
packages=packages)
|
||||
|
||||
@classmethod
|
||||
def load(cls: Type[Package], path: Union[Path, str], pacman: Pacman, aur_url: str) -> Package:
|
||||
def load(cls: Type[Package], package: str, source: PackageSource, pacman: Pacman, aur_url: str) -> Package:
|
||||
"""
|
||||
package constructor from available sources
|
||||
:param path: one of path to sources directory, path to archive or package name/base
|
||||
:param package: one of path to sources directory, path to archive or package name/base
|
||||
:param source: source of the package required to define the load method
|
||||
:param pacman: alpm wrapper instance (required to load from archive)
|
||||
:param aur_url: AUR root url
|
||||
:return: package properties
|
||||
"""
|
||||
try:
|
||||
maybe_path = Path(path)
|
||||
if maybe_path.is_dir():
|
||||
return cls.from_build(maybe_path, aur_url)
|
||||
if maybe_path.is_file():
|
||||
return cls.from_archive(maybe_path, pacman, aur_url)
|
||||
return cls.from_aur(str(path), aur_url)
|
||||
resolved_source = source.resolve(package)
|
||||
if resolved_source == PackageSource.Archive:
|
||||
return cls.from_archive(Path(package), pacman, aur_url)
|
||||
if resolved_source == PackageSource.AUR:
|
||||
return cls.from_aur(package, aur_url)
|
||||
if resolved_source == PackageSource.Local:
|
||||
return cls.from_build(Path(package), aur_url)
|
||||
raise InvalidPackageInfo(f"Unsupported local package source {resolved_source}")
|
||||
except InvalidPackageInfo:
|
||||
raise
|
||||
except Exception as e:
|
||||
@ -253,14 +258,45 @@ class Package:
|
||||
|
||||
return self.version
|
||||
|
||||
def is_outdated(self, remote: Package, paths: RepositoryPaths) -> bool:
|
||||
def full_depends(self, pacman: Pacman, packages: Iterable[Package]) -> List[str]:
|
||||
"""
|
||||
generate full dependencies list including transitive dependencies
|
||||
:param pacman: alpm wrapper instance
|
||||
:param packages: repository package list
|
||||
:return: all dependencies of the package
|
||||
"""
|
||||
dependencies = {}
|
||||
# load own package dependencies
|
||||
for package_base in packages:
|
||||
for name, repo_package in package_base.packages.items():
|
||||
dependencies[name] = repo_package.depends
|
||||
for provides in repo_package.provides:
|
||||
dependencies[provides] = repo_package.depends
|
||||
# load repository dependencies
|
||||
for database in pacman.handle.get_syncdbs():
|
||||
for pacman_package in database.pkgcache:
|
||||
dependencies[pacman_package.name] = pacman_package.depends
|
||||
for provides in pacman_package.provides:
|
||||
dependencies[provides] = pacman_package.depends
|
||||
|
||||
result = set(self.depends)
|
||||
current_depends: Set[str] = set()
|
||||
while result != current_depends:
|
||||
current_depends = copy.deepcopy(result)
|
||||
for package in current_depends:
|
||||
result.update(dependencies.get(package, []))
|
||||
|
||||
return sorted(result)
|
||||
|
||||
def is_outdated(self, remote: Package, paths: RepositoryPaths, calculate_version: bool = True) -> bool:
|
||||
"""
|
||||
check if package is out-of-dated
|
||||
:param remote: package properties from remote source
|
||||
:param paths: repository paths instance. Required for VCS packages cache
|
||||
:param calculate_version: expand version to actual value (by calculating git versions)
|
||||
:return: True if the package is out-of-dated and False otherwise
|
||||
"""
|
||||
remote_version = remote.actual_version(paths) # either normal version or updated VCS
|
||||
remote_version = remote.actual_version(paths) if calculate_version else remote.version
|
||||
result: int = vercmp(self.version, remote_version)
|
||||
return result < 0
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
#
|
||||
# Copyright (c) 2021 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
@ -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__ = "1.6.0"
|
||||
__version__ = "1.8.0"
|
||||
|
@ -25,8 +25,8 @@ from aiohttp import web
|
||||
from aiohttp.web import middleware, Request
|
||||
from aiohttp.web_response import StreamResponse
|
||||
from aiohttp.web_urldispatcher import StaticResource
|
||||
from aiohttp_session import setup as setup_session # type: ignore
|
||||
from aiohttp_session.cookie_storage import EncryptedCookieStorage # type: ignore
|
||||
from aiohttp_session import setup as setup_session
|
||||
from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
||||
from cryptography import fernet
|
||||
from typing import Optional
|
||||
|
||||
|
@ -17,12 +17,11 @@
|
||||
# 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
|
||||
|
||||
from aiohttp.web import HTTPNotFound, Response, json_response
|
||||
from typing import Callable, List
|
||||
|
||||
from ahriman.core.util import aur_search
|
||||
from ahriman.core.alpm.aur import AUR
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
@ -45,11 +44,11 @@ class SearchView(BaseView):
|
||||
:return: 200 with found package bases and descriptions sorted by base
|
||||
"""
|
||||
search: List[str] = self.request.query.getall("for", default=[])
|
||||
packages = aur_search(*search)
|
||||
packages = AUR.multisearch(*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)
|
||||
comparator: Callable[[AURPackage], str] = lambda item: str(item.package_base)
|
||||
response = [
|
||||
{
|
||||
"package": package.package_base,
|
||||
|
@ -147,6 +147,7 @@ def test_unknown_no_aur(application_repository: Repository, package_ahriman: Pac
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
|
||||
|
||||
@ -163,7 +164,7 @@ def test_unknown_no_aur_no_local(application_repository: Repository, package_ahr
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
|
||||
packages = application_repository.unknown()
|
||||
assert packages == [package_ahriman]
|
||||
assert packages == list(package_ahriman.packages.keys())
|
||||
|
||||
|
||||
def test_unknown_no_local(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -201,12 +202,15 @@ def test_updates_all(application_repository: Repository, package_ahriman: Packag
|
||||
"""
|
||||
must get updates for all
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[])
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur",
|
||||
return_value=[package_ahriman])
|
||||
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
application_repository.updates([], no_aur=False, no_local=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], False)
|
||||
updates_local_mock.assert_called_once()
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
@ -214,11 +218,14 @@ def test_updates_disabled(application_repository: Repository, mocker: MockerFixt
|
||||
"""
|
||||
must get updates without anything
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[])
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=True, no_manual=True, no_vcs=False, log_fn=print)
|
||||
application_repository.updates([], no_aur=True, no_local=True, no_manual=True, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_not_called()
|
||||
updates_local_mock.assert_not_called()
|
||||
updates_manual_mock.assert_not_called()
|
||||
|
||||
|
||||
@ -226,11 +233,29 @@ def test_updates_no_aur(application_repository: Repository, mocker: MockerFixtur
|
||||
"""
|
||||
must get updates without aur
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[])
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=True, no_manual=False, no_vcs=False, log_fn=print)
|
||||
application_repository.updates([], no_aur=True, no_local=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_not_called()
|
||||
updates_local_mock.assert_called_once()
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_updates_no_local(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without local packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[])
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=False, no_local=True, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], False)
|
||||
updates_local_mock.assert_not_called()
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
@ -238,11 +263,14 @@ def test_updates_no_manual(application_repository: Repository, mocker: MockerFix
|
||||
"""
|
||||
must get updates without manual
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[])
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=False, no_manual=True, no_vcs=False, log_fn=print)
|
||||
application_repository.updates([], no_aur=False, no_local=False, no_manual=True, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], False)
|
||||
updates_local_mock.assert_called_once()
|
||||
updates_manual_mock.assert_not_called()
|
||||
|
||||
|
||||
@ -250,21 +278,28 @@ def test_updates_no_vcs(application_repository: Repository, mocker: MockerFixtur
|
||||
"""
|
||||
must get updates without VCS
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[])
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=False, no_manual=False, no_vcs=True, log_fn=print)
|
||||
application_repository.updates([], no_aur=False, no_local=False, no_manual=False, no_vcs=True, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], True)
|
||||
updates_local_mock.assert_called_once()
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_updates_with_filter(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without VCS
|
||||
must get updates with filter
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[])
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_local_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_local")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates(["filter"], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
application_repository.updates(["filter"], no_aur=False, no_local=False, no_manual=False, no_vcs=False,
|
||||
log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with(["filter"], False)
|
||||
updates_local_mock.assert_called_once()
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
@ -1,16 +1,18 @@
|
||||
import aur
|
||||
import pytest
|
||||
|
||||
from ahriman.application.formatters.aur_printer import AurPrinter
|
||||
from ahriman.application.formatters.configuration_printer import ConfigurationPrinter
|
||||
from ahriman.application.formatters.package_printer import PackagePrinter
|
||||
from ahriman.application.formatters.status_printer import StatusPrinter
|
||||
from ahriman.application.formatters.string_printer import StringPrinter
|
||||
from ahriman.application.formatters.update_printer import UpdatePrinter
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def aur_package_ahriman_printer(aur_package_ahriman: aur.Package) -> AurPrinter:
|
||||
def aur_package_ahriman_printer(aur_package_ahriman: AURPackage) -> AurPrinter:
|
||||
"""
|
||||
fixture for AUR package printer
|
||||
:param aur_package_ahriman: AUR package fixture
|
||||
@ -39,9 +41,27 @@ def package_ahriman_printer(package_ahriman: Package) -> PackagePrinter:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def status_printer(package_ahriman: Package) -> StatusPrinter:
|
||||
def status_printer() -> StatusPrinter:
|
||||
"""
|
||||
fixture for build status printer
|
||||
:return: build status printer test instance
|
||||
"""
|
||||
return StatusPrinter(BuildStatus())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def string_printer() -> StringPrinter:
|
||||
"""
|
||||
fixture for any string printer
|
||||
:return: any string printer test instance
|
||||
"""
|
||||
return StringPrinter("hello, world")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def update_printer(package_ahriman: Package) -> UpdatePrinter:
|
||||
"""
|
||||
fixture for build status printer
|
||||
:return: build status printer test instance
|
||||
"""
|
||||
return UpdatePrinter(package_ahriman, None)
|
||||
|
15
tests/ahriman/application/formatters/test_string_printer.py
Normal file
15
tests/ahriman/application/formatters/test_string_printer.py
Normal file
@ -0,0 +1,15 @@
|
||||
from ahriman.application.formatters.string_printer import StringPrinter
|
||||
|
||||
|
||||
def test_properties(string_printer: StringPrinter) -> None:
|
||||
"""
|
||||
must return empty properties list
|
||||
"""
|
||||
assert not string_printer.properties()
|
||||
|
||||
|
||||
def test_title(string_printer: StringPrinter) -> None:
|
||||
"""
|
||||
must return non empty title
|
||||
"""
|
||||
assert string_printer.title() is not None
|
15
tests/ahriman/application/formatters/test_update_printer.py
Normal file
15
tests/ahriman/application/formatters/test_update_printer.py
Normal file
@ -0,0 +1,15 @@
|
||||
from ahriman.application.formatters.update_printer import UpdatePrinter
|
||||
|
||||
|
||||
def test_properties(update_printer: UpdatePrinter) -> None:
|
||||
"""
|
||||
must return empty properties list
|
||||
"""
|
||||
assert update_printer.properties()
|
||||
|
||||
|
||||
def test_title(update_printer: UpdatePrinter) -> None:
|
||||
"""
|
||||
must return non empty title
|
||||
"""
|
||||
assert update_printer.title() is not None
|
@ -14,6 +14,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
:return: generated arguments for these test cases
|
||||
"""
|
||||
args.depends_on = []
|
||||
args.dry_run = False
|
||||
return args
|
||||
|
||||
|
||||
@ -23,7 +24,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages")
|
||||
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
|
||||
Rebuild.run(args, "x86_64", configuration, True)
|
||||
@ -31,34 +32,43 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
application_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_run_filter(args: argparse.Namespace, configuration: Configuration,
|
||||
package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
def test_run_dry_run(args: argparse.Namespace, configuration: Configuration,
|
||||
package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with depends filter
|
||||
must run command without update itself
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.depends_on = ["python-aur"]
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
args.dry_run = True
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman])
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
|
||||
Rebuild.run(args, "x86_64", configuration, True)
|
||||
application_mock.assert_called_once_with([package_ahriman])
|
||||
application_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_run_without_filter(args: argparse.Namespace, configuration: Configuration,
|
||||
package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
def test_run_filter(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with depends on filter
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.depends_on = ["python-aur"]
|
||||
mocker.patch("ahriman.application.application.Application.update")
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on")
|
||||
|
||||
Rebuild.run(args, "x86_64", configuration, True)
|
||||
application_packages_mock.assert_called_once_with({"python-aur"})
|
||||
|
||||
|
||||
def test_run_without_filter(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command for all packages if no filter supplied
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
mocker.patch("ahriman.application.application.Application.update")
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on")
|
||||
|
||||
Rebuild.run(args, "x86_64", configuration, True)
|
||||
application_mock.assert_called_once_with([package_ahriman, package_python_schedule])
|
||||
application_packages_mock.assert_called_once_with(None)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import argparse
|
||||
import aur
|
||||
import dataclasses
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
@ -7,6 +7,7 @@ from pytest_mock import MockerFixture
|
||||
from ahriman.application.handlers import Search
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InvalidOption
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
@ -21,13 +22,13 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
return args
|
||||
|
||||
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
search_mock = mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
|
||||
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman])
|
||||
print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
|
||||
|
||||
Search.run(args, "x86_64", configuration, True)
|
||||
@ -35,38 +36,38 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
|
||||
print_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
|
||||
def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with sorting
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
|
||||
mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman])
|
||||
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
|
||||
|
||||
Search.run(args, "x86_64", configuration, True)
|
||||
sort_mock.assert_called_once_with([aur_package_ahriman], "name")
|
||||
|
||||
|
||||
def test_run_sort_by(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
|
||||
def test_run_sort_by(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with sorting by specified field
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.sort_by = "field"
|
||||
mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
|
||||
mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman])
|
||||
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
|
||||
|
||||
Search.run(args, "x86_64", configuration, True)
|
||||
sort_mock.assert_called_once_with([aur_package_ahriman], "field")
|
||||
|
||||
|
||||
def test_sort(aur_package_ahriman: aur.Package) -> None:
|
||||
def test_sort(aur_package_ahriman: AURPackage) -> None:
|
||||
"""
|
||||
must sort package list
|
||||
"""
|
||||
another = aur_package_ahriman._replace(name="1", package_base="base")
|
||||
another = dataclasses.replace(aur_package_ahriman, name="1", package_base="base")
|
||||
# sort by name
|
||||
assert Search.sort([aur_package_ahriman, another], "name") == [another, aur_package_ahriman]
|
||||
# sort by another field
|
||||
@ -75,7 +76,7 @@ def test_sort(aur_package_ahriman: aur.Package) -> None:
|
||||
assert Search.sort([aur_package_ahriman, another], "version") == [another, aur_package_ahriman]
|
||||
|
||||
|
||||
def test_sort_exception(aur_package_ahriman: aur.Package) -> None:
|
||||
def test_sort_exception(aur_package_ahriman: AURPackage) -> None:
|
||||
"""
|
||||
must raise an exception on unknown sorting field
|
||||
"""
|
||||
@ -94,4 +95,5 @@ def test_sort_fields() -> None:
|
||||
"""
|
||||
must store valid field list which are allowed to be used for sorting
|
||||
"""
|
||||
assert all(field in aur.Package._fields for field in Search.SORT_FIELDS)
|
||||
expected = {pair.name for pair in dataclasses.fields(AURPackage)}
|
||||
assert all(field in expected for field in Search.SORT_FIELDS)
|
||||
|
@ -16,6 +16,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
args.package = []
|
||||
args.dry_run = False
|
||||
args.no_aur = False
|
||||
args.no_local = False
|
||||
args.no_manual = False
|
||||
args.no_vcs = False
|
||||
return args
|
||||
|
@ -1,4 +1,4 @@
|
||||
import aur
|
||||
import datetime
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
@ -10,6 +10,7 @@ from ahriman.core.auth.auth import Auth
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@ -48,28 +49,56 @@ def anyvar(cls: Type[T], strict: bool = False) -> T:
|
||||
|
||||
# generic fixtures
|
||||
@pytest.fixture
|
||||
def aur_package_ahriman(package_ahriman: Package) -> aur.Package:
|
||||
def aur_package_ahriman() -> AURPackage:
|
||||
"""
|
||||
fixture for AUR package
|
||||
:param package_ahriman: package fixture
|
||||
:return: AUR package test instance
|
||||
"""
|
||||
return aur.Package(
|
||||
num_votes=None,
|
||||
description=package_ahriman.packages[package_ahriman.base].description,
|
||||
url_path=package_ahriman.web_url,
|
||||
last_modified=None,
|
||||
name=package_ahriman.base,
|
||||
return AURPackage(
|
||||
id=1009791,
|
||||
name="ahriman",
|
||||
package_base_id=165427,
|
||||
package_base="ahriman",
|
||||
version="1.7.0-1",
|
||||
description="ArcH Linux ReposItory MANager",
|
||||
num_votes=0,
|
||||
popularity=0,
|
||||
first_submitted=datetime.datetime(2021, 4, 9, 22, 44, 45),
|
||||
last_modified=datetime.datetime(2021, 12, 25, 23, 11, 11),
|
||||
url_path="/cgit/aur.git/snapshot/ahriman.tar.gz",
|
||||
url="https://github.com/arcan1s/ahriman",
|
||||
out_of_date=None,
|
||||
id=None,
|
||||
first_submitted=None,
|
||||
maintainer=None,
|
||||
version=package_ahriman.version,
|
||||
license=package_ahriman.packages[package_ahriman.base].licenses,
|
||||
url=None,
|
||||
package_base=package_ahriman.base,
|
||||
package_base_id=None,
|
||||
category_id=None)
|
||||
maintainer="arcanis",
|
||||
depends=[
|
||||
"devtools",
|
||||
"git",
|
||||
"pyalpm",
|
||||
"python-aur",
|
||||
"python-passlib",
|
||||
"python-srcinfo",
|
||||
],
|
||||
make_depends=["python-pip"],
|
||||
opt_depends=[
|
||||
"breezy",
|
||||
"darcs",
|
||||
"mercurial",
|
||||
"python-aioauth-client",
|
||||
"python-aiohttp",
|
||||
"python-aiohttp-debugtoolbar",
|
||||
"python-aiohttp-jinja2",
|
||||
"python-aiohttp-security",
|
||||
"python-aiohttp-session",
|
||||
"python-boto3",
|
||||
"python-cryptography",
|
||||
"python-jinja",
|
||||
"rsync",
|
||||
"subversion",
|
||||
],
|
||||
conflicts=[],
|
||||
provides=[],
|
||||
license=["GPL3"],
|
||||
keywords=[],
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -103,7 +132,7 @@ def package_ahriman(package_description_ahriman: PackageDescription) -> Package:
|
||||
packages = {"ahriman": package_description_ahriman}
|
||||
return Package(
|
||||
base="ahriman",
|
||||
version="0.12.1-1",
|
||||
version="1.7.0-1",
|
||||
aur_url="https://aur.archlinux.org",
|
||||
packages=packages)
|
||||
|
||||
@ -139,9 +168,16 @@ def package_description_ahriman() -> PackageDescription:
|
||||
architecture="x86_64",
|
||||
archive_size=4200,
|
||||
build_date=42,
|
||||
depends=["devtools", "git", "pyalpm", "python-aur", "python-srcinfo"],
|
||||
depends=[
|
||||
"devtools",
|
||||
"git",
|
||||
"pyalpm",
|
||||
"python-aur",
|
||||
"python-passlib",
|
||||
"python-srcinfo",
|
||||
],
|
||||
description="ArcH Linux ReposItory MANager",
|
||||
filename="ahriman-0.12.1-1-any.pkg.tar.zst",
|
||||
filename="ahriman-1.7.0-1-any.pkg.tar.zst",
|
||||
groups=[],
|
||||
installed_size=4200000,
|
||||
licenses=["GPL3"],
|
||||
|
12
tests/ahriman/core/alpm/conftest.py
Normal file
12
tests/ahriman/core/alpm/conftest.py
Normal file
@ -0,0 +1,12 @@
|
||||
import pytest
|
||||
|
||||
from ahriman.core.alpm.aur import AUR
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def aur() -> AUR:
|
||||
"""
|
||||
aur helper fixture
|
||||
:return: aur helper instance
|
||||
"""
|
||||
return AUR()
|
173
tests/ahriman/core/alpm/test_aur.py
Normal file
173
tests/ahriman/core/alpm/test_aur.py
Normal file
@ -0,0 +1,173 @@
|
||||
import json
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.core.alpm.aur import AUR
|
||||
from ahriman.core.exceptions import InvalidPackageInfo
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
def _get_response(resource_path_root: Path) -> str:
|
||||
"""
|
||||
load response from resource file
|
||||
:param resource_path_root: path to resource root
|
||||
:return: response text
|
||||
"""
|
||||
return (resource_path_root / "models" / "package_ahriman_aur").read_text()
|
||||
|
||||
|
||||
def test_info(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call info method
|
||||
"""
|
||||
info_mock = mocker.patch("ahriman.core.alpm.aur.AUR.package_info")
|
||||
AUR.info("ahriman")
|
||||
info_mock.assert_called_once_with("ahriman")
|
||||
|
||||
|
||||
def test_multisearch(aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must search in AUR with multiple words
|
||||
"""
|
||||
terms = ["ahriman", "is", "cool"]
|
||||
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.search", return_value=[aur_package_ahriman])
|
||||
|
||||
assert AUR.multisearch(*terms) == [aur_package_ahriman]
|
||||
search_mock.assert_has_calls([mock.call("ahriman"), mock.call("cool")])
|
||||
|
||||
|
||||
def test_multisearch_empty(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty list if no long terms supplied
|
||||
"""
|
||||
terms = ["it", "is"]
|
||||
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.search")
|
||||
|
||||
assert AUR.multisearch(*terms) == []
|
||||
search_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_multisearch_single(aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must search in AUR with one word
|
||||
"""
|
||||
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.search", return_value=[aur_package_ahriman])
|
||||
assert AUR.multisearch("ahriman") == [aur_package_ahriman]
|
||||
search_mock.assert_called_once_with("ahriman")
|
||||
|
||||
|
||||
def test_search(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call search method
|
||||
"""
|
||||
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.package_search")
|
||||
AUR.search("ahriman")
|
||||
search_mock.assert_called_once_with("ahriman")
|
||||
|
||||
|
||||
def test_parse_response(aur_package_ahriman: AURPackage, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must parse success response
|
||||
"""
|
||||
response = _get_response(resource_path_root)
|
||||
assert AUR.parse_response(json.loads(response)) == [aur_package_ahriman]
|
||||
|
||||
|
||||
def test_parse_response_error(resource_path_root: Path) -> None:
|
||||
"""
|
||||
must raise exception on invalid response
|
||||
"""
|
||||
response = (resource_path_root / "models" / "aur_error").read_text()
|
||||
with pytest.raises(InvalidPackageInfo, match="Incorrect request type specified."):
|
||||
AUR.parse_response(json.loads(response))
|
||||
|
||||
|
||||
def test_parse_response_unknown_error(resource_path_root: Path) -> None:
|
||||
"""
|
||||
must raise exception on invalid response with empty error message
|
||||
"""
|
||||
with pytest.raises(InvalidPackageInfo, match="Unknown API error"):
|
||||
AUR.parse_response({"type": "error"})
|
||||
|
||||
|
||||
def test_make_request(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must perform request to AUR
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
|
||||
assert aur.make_request("info", "ahriman") == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with(
|
||||
"https://aur.archlinux.org/rpc", params={"v": "5", "type": "info", "arg": ["ahriman"]})
|
||||
|
||||
|
||||
def test_make_request_multi_arg(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must perform request to AUR with multiple args
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
|
||||
assert aur.make_request("search", "ahriman", "is", "cool") == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with(
|
||||
"https://aur.archlinux.org/rpc", params={"v": "5", "type": "search", "arg[]": ["ahriman", "is", "cool"]})
|
||||
|
||||
|
||||
def test_make_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must perform request to AUR with named parameters
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
|
||||
assert aur.make_request("search", "ahriman", by="name") == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with(
|
||||
"https://aur.archlinux.org/rpc", params={"v": "5", "type": "search", "arg": ["ahriman"], "by": "name"})
|
||||
|
||||
|
||||
def test_make_request_failed(aur: AUR, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise generic exception
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=Exception())
|
||||
with pytest.raises(Exception):
|
||||
aur.make_request("info", "ahriman")
|
||||
|
||||
|
||||
def test_make_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise http exception
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=requests.exceptions.HTTPError())
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
aur.make_request("info", "ahriman")
|
||||
|
||||
|
||||
def test_package_info(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must make request for info
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.alpm.aur.AUR.make_request", return_value=[aur_package_ahriman])
|
||||
assert aur.package_info(aur_package_ahriman.name) == aur_package_ahriman
|
||||
request_mock.assert_called_once_with("info", aur_package_ahriman.name)
|
||||
|
||||
|
||||
def test_package_search(aur: AUR, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must make request for search
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.alpm.aur.AUR.make_request", return_value=[aur_package_ahriman])
|
||||
assert aur.package_search(aur_package_ahriman.name, by="name") == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with("search", aur_package_ahriman.name, by="name")
|
@ -86,6 +86,23 @@ def test_fetch_new(mocker: MockerFixture) -> None:
|
||||
])
|
||||
|
||||
|
||||
def test_fetch_new_without_remote(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must fetch nothing in case if no remote set
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
|
||||
|
||||
local = Path("local")
|
||||
Sources.fetch(local, None)
|
||||
check_output_mock.assert_has_calls([
|
||||
mock.call("git", "checkout", "--force", Sources._branch,
|
||||
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
|
||||
mock.call("git", "reset", "--hard", f"origin/{Sources._branch}",
|
||||
exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
|
||||
])
|
||||
|
||||
|
||||
def test_has_remotes(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must ask for remotes
|
||||
|
@ -11,6 +11,14 @@ from ahriman.core.upload.upload import Upload
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_load_archives(executor: Executor) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing load_archives method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
executor.load_archives([])
|
||||
|
||||
|
||||
def test_packages(executor: Executor) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing method
|
||||
@ -182,11 +190,13 @@ def test_process_update(executor: Executor, package_ahriman: Package, mocker: Mo
|
||||
"""
|
||||
must run update process
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
sign_package_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_package", side_effect=lambda fn, _: [fn])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
|
||||
# must return complete
|
||||
assert executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
@ -201,6 +211,8 @@ def test_process_update(executor: Executor, package_ahriman: Package, mocker: Mo
|
||||
# must clear directory
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
Cleaner.clear_packages.assert_called_once()
|
||||
# clear removed packages
|
||||
remove_mock.assert_called_once_with([])
|
||||
|
||||
|
||||
def test_process_update_group(executor: Executor, package_python_schedule: Package,
|
||||
@ -209,9 +221,11 @@ def test_process_update_group(executor: Executor, package_python_schedule: Packa
|
||||
must group single packages under one base
|
||||
"""
|
||||
mocker.patch("shutil.move")
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_python_schedule)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_python_schedule])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
|
||||
executor.process_update([package.filepath for package in package_python_schedule.packages.values()])
|
||||
repo_add_mock.assert_has_calls([
|
||||
@ -219,6 +233,7 @@ def test_process_update_group(executor: Executor, package_python_schedule: Packa
|
||||
for package in package_python_schedule.packages.values()
|
||||
], any_order=True)
|
||||
status_client_mock.assert_called_once_with(package_python_schedule)
|
||||
remove_mock.assert_called_once_with([])
|
||||
|
||||
|
||||
def test_process_empty_filename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -226,7 +241,8 @@ def test_process_empty_filename(executor: Executor, package_ahriman: Package, mo
|
||||
must skip update for package which does not have path
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
|
||||
|
||||
@ -235,18 +251,27 @@ def test_process_update_failed(executor: Executor, package_ahriman: Package, moc
|
||||
must process update for failed package
|
||||
"""
|
||||
mocker.patch("shutil.move", side_effect=Exception())
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed")
|
||||
|
||||
executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
status_client_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_process_update_failed_on_load(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_process_update_removed_package(executor: Executor, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process update even with failed package load
|
||||
must remove packages which have been removed from the new base
|
||||
"""
|
||||
mocker.patch("shutil.move")
|
||||
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception())
|
||||
without_python2 = Package.from_json(package_python_schedule.view())
|
||||
del without_python2.packages["python2-schedule"]
|
||||
|
||||
assert executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
mocker.patch("shutil.move")
|
||||
mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[without_python2])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
|
||||
executor.process_update([package.filepath for package in without_python2.packages.values()])
|
||||
remove_mock.assert_called_once_with(["python2-schedule"])
|
||||
|
@ -5,8 +5,8 @@ from ahriman.core.repository import Repository
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_packages(package_ahriman: Package, package_python_schedule: Package,
|
||||
repository: Repository, mocker: MockerFixture) -> None:
|
||||
def test_load_archives(package_ahriman: Package, package_python_schedule: Package,
|
||||
repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return all packages grouped by package base
|
||||
"""
|
||||
@ -17,12 +17,9 @@ def test_packages(package_ahriman: Package, package_python_schedule: Package,
|
||||
packages={package: props})
|
||||
for package, props in package_python_schedule.packages.items()
|
||||
] + [package_ahriman]
|
||||
|
||||
mocker.patch("pathlib.Path.iterdir",
|
||||
return_value=[Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
|
||||
mocker.patch("ahriman.models.package.Package.load", side_effect=single_packages)
|
||||
|
||||
packages = repository.packages()
|
||||
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
|
||||
assert len(packages) == 2
|
||||
assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base}
|
||||
|
||||
@ -33,21 +30,48 @@ def test_packages(package_ahriman: Package, package_python_schedule: Package,
|
||||
assert set(archives) == expected
|
||||
|
||||
|
||||
def test_packages_failed(repository: Repository, mocker: MockerFixture) -> None:
|
||||
def test_load_archives_failed(repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip packages which cannot be loaded
|
||||
"""
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.pkg.tar.xz")])
|
||||
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception())
|
||||
assert not repository.packages()
|
||||
assert not repository.load_archives([Path("a.pkg.tar.xz")])
|
||||
|
||||
|
||||
def test_packages_not_package(repository: Repository, mocker: MockerFixture) -> None:
|
||||
def test_load_archives_not_package(repository: Repository) -> None:
|
||||
"""
|
||||
must skip not packages from iteration
|
||||
"""
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz")])
|
||||
assert not repository.packages()
|
||||
assert not repository.load_archives([Path("a.tar.xz")])
|
||||
|
||||
|
||||
def test_load_archives_different_version(repository: Repository, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load packages with different versions choosing maximal
|
||||
"""
|
||||
single_packages = [
|
||||
Package(base=package_python_schedule.base,
|
||||
version=package_python_schedule.version,
|
||||
aur_url=package_python_schedule.aur_url,
|
||||
packages={package: props})
|
||||
for package, props in package_python_schedule.packages.items()
|
||||
]
|
||||
single_packages[0].version = "0.0.1-1"
|
||||
mocker.patch("ahriman.models.package.Package.load", side_effect=single_packages)
|
||||
|
||||
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")])
|
||||
assert len(packages) == 1
|
||||
assert packages[0].version == package_python_schedule.version
|
||||
|
||||
|
||||
def test_packages(repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return repository packages
|
||||
"""
|
||||
load_mock = mocker.patch("ahriman.core.repository.repository.Repository.load_archives")
|
||||
repository.packages()
|
||||
load_mock.assert_called_once() # it uses filter object so we cannot verity argument list =/
|
||||
|
||||
|
||||
def test_packages_built(repository: Repository, mocker: MockerFixture) -> None:
|
||||
@ -56,3 +80,23 @@ def test_packages_built(repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz"), Path("b.pkg.tar.xz")])
|
||||
assert repository.packages_built() == [Path("b.pkg.tar.xz")]
|
||||
|
||||
|
||||
def test_packages_depends_on(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must filter packages by depends list
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
assert repository.packages_depends_on(["python-aur"]) == [package_ahriman]
|
||||
|
||||
|
||||
def test_packages_depends_on_empty(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return all packages in case if no filter is provided
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
assert repository.packages_depends_on(None) == [package_ahriman, package_python_schedule]
|
||||
|
@ -81,6 +81,50 @@ def test_updates_aur_ignore_vcs(update_handler: UpdateHandler, package_ahriman:
|
||||
package_is_outdated_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must check for updates for locally stored packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
package_load_mock = mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending")
|
||||
|
||||
assert update_handler.updates_local() == [package_ahriman]
|
||||
fetch_mock.assert_called_once_with(package_ahriman.base, remote=None)
|
||||
package_load_mock.assert_called_once()
|
||||
status_client_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_updates_local_unknown(update_handler: UpdateHandler, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return unknown package as out-dated
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[])
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown")
|
||||
|
||||
assert update_handler.updates_local() == [package_ahriman]
|
||||
status_client_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_updates_local_with_failures(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process local through the packages with failure
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages")
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception())
|
||||
|
||||
assert not update_handler.updates_local()
|
||||
|
||||
|
||||
def test_updates_manual_clear(update_handler: UpdateHandler, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
requesting manual updates must clear packages directory
|
||||
@ -125,7 +169,7 @@ def test_updates_manual_status_unknown(update_handler: UpdateHandler, package_ah
|
||||
def test_updates_manual_with_failures(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process through the packages with failure
|
||||
must process manual through the packages with failure
|
||||
"""
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[])
|
||||
|
@ -4,6 +4,7 @@ import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InitializeException
|
||||
@ -54,6 +55,64 @@ def test_section_name(configuration: Configuration) -> None:
|
||||
assert configuration.section_name("build", "x86_64") == "build:x86_64"
|
||||
|
||||
|
||||
def test_getlist(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of string correctly
|
||||
"""
|
||||
configuration.set_option("build", "test_list", "a b c")
|
||||
assert configuration.getlist("build", "test_list") == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_getlist_empty(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of string correctly for non-existing option
|
||||
"""
|
||||
assert configuration.getlist("build", "test_list", fallback=[]) == []
|
||||
configuration.set_option("build", "test_list", "")
|
||||
assert configuration.getlist("build", "test_list") == []
|
||||
|
||||
|
||||
def test_getlist_single(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of strings for single string
|
||||
"""
|
||||
configuration.set_option("build", "test_list", "a")
|
||||
assert configuration.getlist("build", "test_list") == ["a"]
|
||||
assert configuration.getlist("build", "test_list") == ["a"]
|
||||
|
||||
|
||||
def test_getlist_with_spaces(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of string if there is string with spaces in quotes
|
||||
"""
|
||||
configuration.set_option("build", "test_list", """"ahriman is" cool""")
|
||||
assert configuration.getlist("build", "test_list") == ["""ahriman is""", """cool"""]
|
||||
configuration.set_option("build", "test_list", """'ahriman is' cool""")
|
||||
assert configuration.getlist("build", "test_list") == ["""ahriman is""", """cool"""]
|
||||
|
||||
|
||||
def test_getlist_with_quotes(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of string if there is string with quote inside quote
|
||||
"""
|
||||
configuration.set_option("build", "test_list", """"ahriman is" c"'"ool""")
|
||||
assert configuration.getlist("build", "test_list") == ["""ahriman is""", """c'ool"""]
|
||||
configuration.set_option("build", "test_list", """'ahriman is' c'"'ool""")
|
||||
assert configuration.getlist("build", "test_list") == ["""ahriman is""", """c"ool"""]
|
||||
|
||||
|
||||
def test_getlist_unmatched_quote(configuration: Configuration) -> None:
|
||||
"""
|
||||
must raise exception on unmatched quote in string value
|
||||
"""
|
||||
configuration.set_option("build", "test_list", """ahri"man is cool""")
|
||||
with pytest.raises(ValueError):
|
||||
configuration.getlist("build", "test_list")
|
||||
configuration.set_option("build", "test_list", """ahri'man is cool""")
|
||||
with pytest.raises(ValueError):
|
||||
configuration.getlist("build", "test_list")
|
||||
|
||||
|
||||
def test_getpath_absolute_to_absolute(configuration: Configuration) -> None:
|
||||
"""
|
||||
must not change path for absolute path in settings
|
||||
@ -94,32 +153,6 @@ def test_getpath_without_fallback(configuration: Configuration) -> None:
|
||||
assert configuration.getpath("build", "option")
|
||||
|
||||
|
||||
def test_getlist(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of string correctly
|
||||
"""
|
||||
configuration.set_option("build", "test_list", "a b c")
|
||||
assert configuration.getlist("build", "test_list") == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_getlist_empty(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of string correctly for non-existing option
|
||||
"""
|
||||
assert configuration.getlist("build", "test_list", fallback=[]) == []
|
||||
configuration.set_option("build", "test_list", "")
|
||||
assert configuration.getlist("build", "test_list") == []
|
||||
|
||||
|
||||
def test_getlist_single(configuration: Configuration) -> None:
|
||||
"""
|
||||
must return list of strings for single string
|
||||
"""
|
||||
configuration.set_option("build", "test_list", "a")
|
||||
assert configuration.getlist("build", "test_list") == ["a"]
|
||||
assert configuration.getlist("build", "test_list") == ["a"]
|
||||
|
||||
|
||||
def test_gettype(configuration: Configuration) -> None:
|
||||
"""
|
||||
must extract type from variable
|
||||
@ -222,6 +255,17 @@ def test_reload(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
merge_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_reload_clear(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clear current settings before configuration reload
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.configuration.Configuration.remove_section")
|
||||
sections = configuration.sections()
|
||||
|
||||
configuration.reload()
|
||||
clear_mock.assert_has_calls([mock.call(section) for section in sections])
|
||||
|
||||
|
||||
def test_reload_no_architecture(configuration: Configuration) -> None:
|
||||
"""
|
||||
must raise exception on reload if no architecture set
|
||||
|
@ -1,4 +1,3 @@
|
||||
import aur
|
||||
import datetime
|
||||
import logging
|
||||
import pytest
|
||||
@ -6,45 +5,12 @@ 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 aur_search, check_output, check_user, filter_json, package_like, pretty_datetime, \
|
||||
pretty_size, walk
|
||||
from ahriman.core.util import 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
|
||||
@ -127,7 +93,7 @@ def test_filter_json(package_ahriman: Package) -> None:
|
||||
|
||||
def test_filter_json_empty_value(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return empty values from object
|
||||
must filter empty values from object
|
||||
"""
|
||||
probe = package_ahriman.view()
|
||||
probe["base"] = None
|
||||
@ -238,8 +204,10 @@ def test_walk(resource_path_root: Path) -> None:
|
||||
expected = sorted([
|
||||
resource_path_root / "core/ahriman.ini",
|
||||
resource_path_root / "core/logging.ini",
|
||||
resource_path_root / "models/aur_error",
|
||||
resource_path_root / "models/big_file_checksum",
|
||||
resource_path_root / "models/empty_file_checksum",
|
||||
resource_path_root / "models/package_ahriman_aur",
|
||||
resource_path_root / "models/package_ahriman_srcinfo",
|
||||
resource_path_root / "models/package_tpacpi-bat-git_srcinfo",
|
||||
resource_path_root / "models/package_yay_srcinfo",
|
||||
|
@ -22,7 +22,7 @@ def test_calculate_hash_small(resource_path_root: Path) -> None:
|
||||
must calculate checksum for path which is single chunk
|
||||
"""
|
||||
path = resource_path_root / "models" / "package_ahriman_srcinfo"
|
||||
assert HttpUpload.calculate_hash(path) == "a55f82198e56061295d405aeb58f4062"
|
||||
assert HttpUpload.calculate_hash(path) == "c0aaf6ebf95ca9206dc8ba1d8ff10af3"
|
||||
|
||||
|
||||
def test_get_body_get_hashes() -> None:
|
||||
|
@ -31,7 +31,7 @@ def test_calculate_etag_small(resource_path_root: Path) -> None:
|
||||
must calculate checksum for path which is single chunk
|
||||
"""
|
||||
path = resource_path_root / "models" / "package_ahriman_srcinfo"
|
||||
assert S3.calculate_etag(path, _chunk_size) == "a55f82198e56061295d405aeb58f4062"
|
||||
assert S3.calculate_etag(path, _chunk_size) == "c0aaf6ebf95ca9206dc8ba1d8ff10af3"
|
||||
|
||||
|
||||
def test_files_remove(s3_remote_objects: List[Any]) -> None:
|
||||
|
@ -82,7 +82,9 @@ def pyalpm_package_ahriman(package_ahriman: Package) -> MagicMock:
|
||||
"""
|
||||
mock = MagicMock()
|
||||
type(mock).base = PropertyMock(return_value=package_ahriman.base)
|
||||
type(mock).depends = PropertyMock(return_value=["python-aur"])
|
||||
type(mock).name = PropertyMock(return_value=package_ahriman.base)
|
||||
type(mock).provides = PropertyMock(return_value=["python-ahriman"])
|
||||
type(mock).version = PropertyMock(return_value=package_ahriman.version)
|
||||
return mock
|
||||
|
||||
|
47
tests/ahriman/models/test_aur_package.py
Normal file
47
tests/ahriman/models/test_aur_package.py
Normal file
@ -0,0 +1,47 @@
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from dataclasses import asdict, fields
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any, Dict
|
||||
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
def _get_data(resource_path_root: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
load package description from resource file
|
||||
:param resource_path_root: path to resource root
|
||||
:return: json descriptor
|
||||
"""
|
||||
response = (resource_path_root / "models" / "package_ahriman_aur").read_text()
|
||||
return json.loads(response)["results"][0]
|
||||
|
||||
|
||||
def test_from_json(aur_package_ahriman: AURPackage, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must load package from json
|
||||
"""
|
||||
model = _get_data(resource_path_root)
|
||||
assert AURPackage.from_json(model) == aur_package_ahriman
|
||||
|
||||
|
||||
def test_from_json_2(aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load the same package from json
|
||||
"""
|
||||
mocker.patch("ahriman.models.aur_package.AURPackage.convert", side_effect=lambda v: v)
|
||||
assert AURPackage.from_json(asdict(aur_package_ahriman)) == aur_package_ahriman
|
||||
|
||||
|
||||
def test_convert(aur_package_ahriman: AURPackage, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must convert fields to snakecase and also apply converters
|
||||
"""
|
||||
model = _get_data(resource_path_root)
|
||||
converted = AURPackage.convert(model)
|
||||
known_fields = [pair.name for pair in fields(AURPackage)]
|
||||
assert all(field in known_fields for field in converted)
|
||||
assert isinstance(converted.get("first_submitted"), datetime.datetime)
|
||||
assert isinstance(converted.get("last_modified"), datetime.datetime)
|
@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import time
|
||||
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
|
||||
@ -45,6 +46,17 @@ def test_build_status_init_2(build_status_failed: BuildStatus) -> None:
|
||||
assert status == build_status_failed
|
||||
|
||||
|
||||
def test_build_status_init_empty_timestamp() -> None:
|
||||
"""
|
||||
must st current timestamp when not set
|
||||
"""
|
||||
first = BuildStatus()
|
||||
time.sleep(1)
|
||||
second = BuildStatus()
|
||||
# well technically it just should increase
|
||||
assert first.timestamp < second.timestamp
|
||||
|
||||
|
||||
def test_build_status_from_json_view(build_status_failed: BuildStatus) -> None:
|
||||
"""
|
||||
must construct same object from json
|
||||
|
@ -2,10 +2,12 @@ import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.core.exceptions import InvalidPackageInfo
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@ -95,15 +97,11 @@ def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker
|
||||
assert Package.from_archive(Path("path"), pyalpm_handle, package_ahriman.aur_url) == package_ahriman
|
||||
|
||||
|
||||
def test_from_aur(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_from_aur(package_ahriman: Package, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must construct package from aur
|
||||
"""
|
||||
mock = MagicMock()
|
||||
type(mock).name = PropertyMock(return_value=package_ahriman.base)
|
||||
type(mock).package_base = PropertyMock(return_value=package_ahriman.base)
|
||||
type(mock).version = PropertyMock(return_value=package_ahriman.version)
|
||||
mocker.patch("aur.info", return_value=mock)
|
||||
mocker.patch("ahriman.core.alpm.aur.AUR.info", return_value=aur_package_ahriman)
|
||||
|
||||
package = Package.from_aur(package_ahriman.base, package_ahriman.aur_url)
|
||||
assert package_ahriman.base == package.base
|
||||
@ -156,14 +154,24 @@ def test_from_json_view_3(package_tpacpi_bat_git: Package) -> None:
|
||||
assert Package.from_json(package_tpacpi_bat_git.view()) == package_tpacpi_bat_git
|
||||
|
||||
|
||||
def test_load_resolve(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must resolve source before package loading
|
||||
"""
|
||||
resolve_mock = mocker.patch("ahriman.models.package_source.PackageSource.resolve",
|
||||
return_value=PackageSource.Archive)
|
||||
mocker.patch("ahriman.models.package.Package.from_archive")
|
||||
|
||||
Package.load("path", PackageSource.Archive, pyalpm_handle, package_ahriman.aur_url)
|
||||
resolve_mock.assert_called_once_with("path")
|
||||
|
||||
|
||||
def test_load_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load package from package archive
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
load_mock = mocker.patch("ahriman.models.package.Package.from_archive")
|
||||
|
||||
Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url)
|
||||
Package.load("path", PackageSource.Archive, pyalpm_handle, package_ahriman.aur_url)
|
||||
load_mock.assert_called_once()
|
||||
|
||||
|
||||
@ -172,8 +180,7 @@ def test_load_from_aur(package_ahriman: Package, pyalpm_handle: MagicMock, mocke
|
||||
must load package from AUR
|
||||
"""
|
||||
load_mock = mocker.patch("ahriman.models.package.Package.from_aur")
|
||||
|
||||
Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url)
|
||||
Package.load("path", PackageSource.AUR, pyalpm_handle, package_ahriman.aur_url)
|
||||
load_mock.assert_called_once()
|
||||
|
||||
|
||||
@ -181,10 +188,8 @@ def test_load_from_build(package_ahriman: Package, pyalpm_handle: MagicMock, moc
|
||||
"""
|
||||
must load package from build directory
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
load_mock = mocker.patch("ahriman.models.package.Package.from_build")
|
||||
|
||||
Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url)
|
||||
Package.load("path", PackageSource.Local, pyalpm_handle, package_ahriman.aur_url)
|
||||
load_mock.assert_called_once()
|
||||
|
||||
|
||||
@ -192,13 +197,26 @@ def test_load_failure(package_ahriman: Package, pyalpm_handle: MagicMock, mocker
|
||||
"""
|
||||
must raise InvalidPackageInfo on exception
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", side_effect=InvalidPackageInfo("exception!"))
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=InvalidPackageInfo("exception!"))
|
||||
with pytest.raises(InvalidPackageInfo):
|
||||
Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url)
|
||||
Package.load("path", PackageSource.AUR, pyalpm_handle, package_ahriman.aur_url)
|
||||
|
||||
mocker.patch("pathlib.Path.is_dir", side_effect=Exception())
|
||||
|
||||
def test_load_failure_exception(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise InvalidPackageInfo on random eexception
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||
with pytest.raises(InvalidPackageInfo):
|
||||
Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url)
|
||||
Package.load("path", PackageSource.AUR, pyalpm_handle, package_ahriman.aur_url)
|
||||
|
||||
|
||||
def test_load_invalid_source(package_ahriman: Package, pyalpm_handle: MagicMock) -> None:
|
||||
"""
|
||||
must raise InvalidPackageInfo on unsupported source
|
||||
"""
|
||||
with pytest.raises(InvalidPackageInfo):
|
||||
Package.load("path", PackageSource.Remote, pyalpm_handle, package_ahriman.aur_url)
|
||||
|
||||
|
||||
def test_dependencies_failed(mocker: MockerFixture) -> None:
|
||||
@ -273,6 +291,24 @@ def test_actual_version_vcs_failed(package_tpacpi_bat_git: Package, repository_p
|
||||
assert package_tpacpi_bat_git.actual_version(repository_paths) == package_tpacpi_bat_git.version
|
||||
|
||||
|
||||
def test_full_depends(package_ahriman: Package, package_python_schedule: Package, pyalpm_package_ahriman: MagicMock,
|
||||
pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must extract all dependencies from the package
|
||||
"""
|
||||
package_python_schedule.packages[package_python_schedule.base].provides = ["python3-schedule"]
|
||||
|
||||
database_mock = MagicMock()
|
||||
database_mock.pkgcache = [pyalpm_package_ahriman]
|
||||
pyalpm_handle.handle.get_syncdbs.return_value = [database_mock]
|
||||
|
||||
assert package_ahriman.full_depends(pyalpm_handle, [package_python_schedule]) == package_ahriman.depends
|
||||
|
||||
package_python_schedule.packages[package_python_schedule.base].depends = [package_ahriman.base]
|
||||
expected = sorted(set(package_python_schedule.depends + ["python-aur"]))
|
||||
assert package_python_schedule.full_depends(pyalpm_handle, [package_python_schedule]) == expected
|
||||
|
||||
|
||||
def test_is_outdated_false(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
must be not outdated for the same package
|
||||
|
@ -2,7 +2,8 @@ import pytest
|
||||
|
||||
from aiohttp import web
|
||||
from asyncio import BaseEventLoop
|
||||
from pytest_aiohttp import TestClient
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import aur
|
||||
import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.service.search import SearchView
|
||||
|
||||
@ -17,11 +17,11 @@ async def test_get_permission() -> None:
|
||||
assert await SearchView.get_permission(request) == UserAccess.Read
|
||||
|
||||
|
||||
async def test_get(client: TestClient, aur_package_ahriman: aur.Package, mocker: MockerFixture) -> None:
|
||||
async def test_get(client: TestClient, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call get request correctly
|
||||
"""
|
||||
mocker.patch("ahriman.web.views.service.search.aur_search", return_value=[aur_package_ahriman])
|
||||
mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman])
|
||||
response = await client.get("/service-api/v1/search", params={"for": "ahriman"})
|
||||
|
||||
assert response.ok
|
||||
@ -33,7 +33,7 @@ async def test_get_exception(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise 400 on empty search string
|
||||
"""
|
||||
search_mock = mocker.patch("ahriman.web.views.service.search.aur_search", return_value=[])
|
||||
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[])
|
||||
response = await client.get("/service-api/v1/search")
|
||||
|
||||
assert response.status == 404
|
||||
@ -44,7 +44,7 @@ async def test_get_join(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must join search args with space
|
||||
"""
|
||||
search_mock = mocker.patch("ahriman.web.views.service.search.aur_search")
|
||||
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch")
|
||||
response = await client.get("/service-api/v1/search", params=[("for", "ahriman"), ("for", "maybe")])
|
||||
|
||||
assert response.ok
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from pytest_aiohttp import TestClient
|
||||
from aiohttp.test_utils import TestClient
|
||||
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from pytest_aiohttp import TestClient
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from pytest_aiohttp import TestClient
|
||||
from aiohttp.test_utils import TestClient
|
||||
|
||||
import ahriman.version as version
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from pytest_aiohttp import TestClient
|
||||
from aiohttp.test_utils import TestClient
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.index import IndexView
|
||||
|
@ -1,4 +1,5 @@
|
||||
import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock
|
||||
@ -31,7 +32,7 @@ async def test_get_redirect_to_oauth(client_with_auth: TestClient) -> None:
|
||||
must redirect to OAuth service provider in case if no code is supplied
|
||||
"""
|
||||
oauth = client_with_auth.app["validator"] = MagicMock(spec=OAuth)
|
||||
oauth.get_oauth_url.return_value = "https://example.com"
|
||||
oauth.get_oauth_url.return_value = "https://httpbin.org"
|
||||
|
||||
get_response = await client_with_auth.get("/user-api/v1/login")
|
||||
assert get_response.ok
|
||||
@ -43,7 +44,7 @@ async def test_get_redirect_to_oauth_empty_code(client_with_auth: TestClient) ->
|
||||
must redirect to OAuth service provider in case if empty code is supplied
|
||||
"""
|
||||
oauth = client_with_auth.app["validator"] = MagicMock(spec=OAuth)
|
||||
oauth.get_oauth_url.return_value = "https://example.com"
|
||||
oauth.get_oauth_url.return_value = "https://httpbin.org"
|
||||
|
||||
get_response = await client_with_auth.get("/user-api/v1/login", params={"code": ""})
|
||||
assert get_response.ok
|
||||
|
7
tests/testresources/models/aur_error
Normal file
7
tests/testresources/models/aur_error
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"error": "Incorrect request type specified.",
|
||||
"resultcount": 0,
|
||||
"results": [],
|
||||
"type": "error",
|
||||
"version": 5
|
||||
}
|
54
tests/testresources/models/package_ahriman_aur
Normal file
54
tests/testresources/models/package_ahriman_aur
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"resultcount": 1,
|
||||
"results": [
|
||||
{
|
||||
"Depends": [
|
||||
"devtools",
|
||||
"git",
|
||||
"pyalpm",
|
||||
"python-aur",
|
||||
"python-passlib",
|
||||
"python-srcinfo"
|
||||
],
|
||||
"Description": "ArcH Linux ReposItory MANager",
|
||||
"FirstSubmitted": 1618008285,
|
||||
"ID": 1009791,
|
||||
"Keywords": [],
|
||||
"LastModified": 1640473871,
|
||||
"License": [
|
||||
"GPL3"
|
||||
],
|
||||
"Maintainer": "arcanis",
|
||||
"MakeDepends": [
|
||||
"python-pip"
|
||||
],
|
||||
"Name": "ahriman",
|
||||
"NumVotes": 0,
|
||||
"OptDepends": [
|
||||
"breezy",
|
||||
"darcs",
|
||||
"mercurial",
|
||||
"python-aioauth-client",
|
||||
"python-aiohttp",
|
||||
"python-aiohttp-debugtoolbar",
|
||||
"python-aiohttp-jinja2",
|
||||
"python-aiohttp-security",
|
||||
"python-aiohttp-session",
|
||||
"python-boto3",
|
||||
"python-cryptography",
|
||||
"python-jinja",
|
||||
"rsync",
|
||||
"subversion"
|
||||
],
|
||||
"OutOfDate": null,
|
||||
"PackageBase": "ahriman",
|
||||
"PackageBaseID": 165427,
|
||||
"Popularity": 0,
|
||||
"URL": "https://github.com/arcan1s/ahriman",
|
||||
"URLPath": "/cgit/aur.git/snapshot/ahriman.tar.gz",
|
||||
"Version": "1.7.0-1"
|
||||
}
|
||||
],
|
||||
"type": "multiinfo",
|
||||
"version": 5
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
pkgbase = ahriman
|
||||
pkgdesc = ArcH Linux ReposItory MANager
|
||||
pkgver = 0.12.1
|
||||
pkgver = 1.7.0
|
||||
pkgrel = 1
|
||||
url = https://github.com/arcan1s/ahriman
|
||||
arch = any
|
||||
@ -10,6 +10,7 @@ pkgbase = ahriman
|
||||
depends = git
|
||||
depends = pyalpm
|
||||
depends = python-aur
|
||||
depends = python-passlib
|
||||
depends = python-srcinfo
|
||||
optdepends = aws-cli: sync to s3
|
||||
optdepends = breezy: -bzr packages support
|
||||
@ -24,7 +25,7 @@ pkgbase = ahriman
|
||||
optdepends = subversion: -svn packages support
|
||||
backup = etc/ahriman.ini
|
||||
backup = etc/ahriman.ini.d/logging.ini
|
||||
source = https://github.com/arcan1s/ahriman/releases/download/0.12.1/ahriman-0.12.1-src.tar.xz
|
||||
source = https://github.com/arcan1s/ahriman/releases/download/1.7.0/ahriman-1.7.0-src.tar.xz
|
||||
source = ahriman.sysusers
|
||||
source = ahriman.tmpfiles
|
||||
sha512sums = 8acc57f937d587ca665c29092cadddbaf3ba0b80e870b80d1551e283aba8f21306f9030a26fec8c71ab5863316f5f5f061b7ddc63cdff9e6d5a885f28ef1893d
|
||||
|
Reference in New Issue
Block a user