implement local reporter mode

This commit is contained in:
Evgenii Alekseev 2024-05-07 14:54:13 +03:00
parent ac19c407d3
commit 24a5c68b08
72 changed files with 1605 additions and 466 deletions

View File

@ -446,7 +446,7 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
parser = root.add_parser("patch-list", help="list patch sets",
description="list available patches for the package", formatter_class=_formatter)
parser.add_argument("package", help="package base", nargs="?")
parser.add_argument("package", help="package base")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
action="append")

View File

@ -161,8 +161,7 @@ class Application(ApplicationPackages, ApplicationRepository):
package = Package.from_aur(package_name, username)
with_dependencies[package.base] = package
# register package in local database
self.database.package_base_update(package)
# register package in the database
self.repository.reporter.set_unknown(package)
return list(with_dependencies.values())

View File

@ -65,7 +65,7 @@ class ApplicationPackages(ApplicationProperties):
"""
package = Package.from_aur(source, username)
self.database.build_queue_insert(package)
self.database.package_base_update(package)
self.reporter.set_unknown(package)
def _add_directory(self, source: str, *_: Any) -> None:
"""
@ -139,7 +139,7 @@ class ApplicationPackages(ApplicationProperties):
"""
package = Package.from_official(source, self.repository.pacman, username)
self.database.build_queue_insert(package)
self.database.package_base_update(package)
self.reporter.set_unknown(package)
def add(self, names: Iterable[str], source: PackageSource, username: str | None = None) -> None:
"""

View File

@ -21,6 +21,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.log import LazyLogging
from ahriman.core.repository import Repository
from ahriman.core.status.client import Client
from ahriman.models.pacman_synchronization import PacmanSynchronization
from ahriman.models.repository_id import RepositoryId
@ -63,3 +64,13 @@ class ApplicationProperties(LazyLogging):
str: repository architecture
"""
return self.repository_id.architecture
@property
def reporter(self) -> Client:
"""
instance of the web/database client
Returns:
Client: repository reposter
"""
return self.repository.reporter

View File

@ -39,10 +39,8 @@ class ApplicationRepository(ApplicationProperties):
Args:
packages(Iterable[Package]): list of packages to retrieve changes
"""
last_commit_hashes = self.database.hashes_get()
for package in packages:
last_commit_sha = last_commit_hashes.get(package.base)
last_commit_sha = self.reporter.package_changes_get(package.base).last_commit_sha
if last_commit_sha is None:
continue # skip check in case if we can't calculate diff

View File

@ -50,7 +50,8 @@ class Add(Handler):
application.add(args.package, args.source, args.username)
patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else []
for package in args.package: # for each requested package insert patch
application.database.patches_insert(package, patches)
for patch in patches:
application.reporter.package_patches_add(package, patch)
if not args.now:
return

View File

@ -116,25 +116,29 @@ class Patch(Handler):
package_base(str): package base
patch(PkgbuildPatch): patch descriptor
"""
application.database.patches_insert(package_base, [patch])
application.reporter.package_patches_add(package_base, patch)
@staticmethod
def patch_set_list(application: Application, package_base: str | None, variables: list[str] | None,
def patch_set_list(application: Application, package_base: str, variables: list[str] | None,
exit_code: bool) -> None:
"""
list patches available for the package base
Args:
application(Application): application instance
package_base(str | None): package base
package_base(str): package base
variables(list[str] | None): extract patches only for specified PKGBUILD variables
exit_code(bool): exit with error on empty search result
"""
patches = application.database.patches_list(package_base, variables)
patches = []
if variables is not None:
for variable in variables:
patches.extend(application.reporter.package_patches_get(package_base, variable))
else:
patches = application.reporter.package_patches_get(package_base, variables)
Patch.check_if_empty(exit_code, not patches)
for base, patch in patches.items():
PatchPrinter(base, patch)(verbose=True, separator=" = ")
PatchPrinter(package_base, patches)(verbose=True, separator=" = ")
@staticmethod
def patch_set_remove(application: Application, package_base: str, variables: list[str] | None) -> None:
@ -146,4 +150,8 @@ class Patch(Handler):
package_base(str): package base
variables(list[str] | None): remove patches only for specified PKGBUILD variables
"""
application.database.patches_remove(package_base, variables)
if variables is not None:
for variable in variables: # iterate over single variable
application.reporter.package_patches_remove(package_base, variable)
else:
application.reporter.package_patches_remove(package_base, variables) # just pass as is

View File

@ -76,7 +76,7 @@ class Rebuild(Handler):
if from_database:
return [
package
for (package, last_status) in application.database.packages_get()
for (package, last_status) in application.reporter.package_get(None)
if status is None or last_status.status == status
]

View File

@ -56,7 +56,7 @@ class StatusUpdate(Handler):
if (local := next((package for package in packages if package.base == base), None)) is not None:
client.package_add(local, args.status)
else:
client.package_update(base, args.status)
client.package_set(base, args.status)
case Action.Update:
# update service status
client.status_update(args.status)

View File

@ -21,7 +21,6 @@ from pathlib import Path
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import BuildError
from ahriman.core.log import LazyLogging
from ahriman.core.util import check_output
@ -116,20 +115,20 @@ class Task(LazyLogging):
# e.g. in some cases packagelist command produces debug packages which were not actually built
return list(filter(lambda path: path.is_file(), map(Path, packages)))
def init(self, sources_dir: Path, database: SQLite, local_version: str | None) -> str | None:
def init(self, sources_dir: Path, patches: list[PkgbuildPatch], local_version: str | None) -> str | None:
"""
fetch package from git
Args:
sources_dir(Path): local path to fetch
database(SQLite): database instance
patches(list[PkgbuildPatch]): list of patches for the package
local_version(str | None): local version of the package. If set and equal to current version, it will
automatically bump pkgrel
Returns:
str | None: current commit sha if available
"""
last_commit_sha = Sources.load(sources_dir, self.package, database.patches_get(self.package.base), self.paths)
last_commit_sha = Sources.load(sources_dir, self.package, patches, self.paths)
if local_version is None:
return last_commit_sha # there is no local package or pkgrel increment is disabled

View File

@ -117,27 +117,3 @@ class ChangesOperations(Operations):
})
return self.with_connection(run, commit=True)
def hashes_get(self, repository_id: RepositoryId | None = None) -> dict[str, str]:
"""
extract last commit hashes if available
Args:
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
Returns:
dict[str, str]: map of package base to its last commit hash
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> dict[str, str]:
return {
row["package_base"]: row["last_commit_sha"]
for row in connection.execute(
"""select package_base, last_commit_sha from package_changes where repository = :repository""",
{"repository": repository_id.id}
)
}
return self.with_connection(run)

View File

@ -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/>.
#
from pathlib import Path
from sqlite3 import Connection
from ahriman.core.database.operations.operations import Operations
@ -31,7 +30,7 @@ class DependenciesOperations(Operations):
"""
def dependencies_get(self, package_base: str | None = None,
repository_id: RepositoryId | None = None) -> list[Dependencies]:
repository_id: RepositoryId | None = None) -> dict[str, Dependencies]:
"""
get dependencies for the specific package base if available
@ -44,15 +43,9 @@ class DependenciesOperations(Operations):
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> list[Dependencies]:
return [
Dependencies(
row["package_base"],
{
Path(path): packages
for path, packages in row["dependencies"].items()
}
)
def run(connection: Connection) -> dict[str, Dependencies]:
return {
row["package_base"]: Dependencies(row["dependencies"])
for row in connection.execute(
"""
select package_base, dependencies from package_dependencies
@ -64,15 +57,17 @@ class DependenciesOperations(Operations):
"repository": repository_id.id,
}
)
]
}
return self.with_connection(run)
def dependencies_insert(self, dependencies: Dependencies, repository_id: RepositoryId | None = None) -> None:
def dependencies_insert(self, package_base: str, dependencies: Dependencies,
repository_id: RepositoryId | None = None) -> None:
"""
insert package dependencies
Args:
package_base(str): package base
dependencies(Dependencies): package dependencies
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
@ -89,12 +84,9 @@ class DependenciesOperations(Operations):
dependencies = :dependencies
""",
{
"package_base": dependencies.package_base,
"package_base": package_base,
"repository": repository_id.id,
"dependencies": {
str(path): packages
for path, packages in dependencies.paths.items()
}
"dependencies": dependencies.paths,
})
return self.with_connection(run, commit=True)

View File

@ -25,7 +25,7 @@ from typing import Any, TypeVar
from ahriman.core.log import LazyLogging
from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
T = TypeVar("T")
@ -38,7 +38,7 @@ class Operations(LazyLogging):
path(Path): path to the database file
"""
def __init__(self, path: Path, repository_id: RepositoryId) -> None:
def __init__(self, path: Path, repository_id: RepositoryId, repository_paths: RepositoryPaths) -> None:
"""
default constructor
@ -48,6 +48,7 @@ class Operations(LazyLogging):
"""
self.path = path
self._repository_id = repository_id
self._repository_paths = repository_paths
@staticmethod
def factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> dict[str, Any]:

View File

@ -150,34 +150,6 @@ class PackageOperations(Operations):
""",
package_list)
@staticmethod
def _package_update_insert_status(connection: Connection, package_base: str, status: BuildStatus,
repository_id: RepositoryId) -> None:
"""
insert base package status into table
Args:
connection(Connection): database connection
package_base(str): package base name
status(BuildStatus): new build status
repository_id(RepositoryId): repository unique identifier
"""
connection.execute(
"""
insert into package_statuses
(package_base, status, last_updated, repository)
values
(:package_base, :status, :last_updated, :repository)
on conflict (package_base, repository) do update set
status = :status, last_updated = :last_updated
""",
{
"package_base": package_base,
"status": status.status.value,
"last_updated": status.timestamp,
"repository": repository_id.id,
})
@staticmethod
def _packages_get_select_package_bases(connection: Connection, repository_id: RepositoryId) -> dict[str, Package]:
"""
@ -277,20 +249,18 @@ class PackageOperations(Operations):
return self.with_connection(run, commit=True)
def package_update(self, package: Package, status: BuildStatus, repository_id: RepositoryId | None = None) -> None:
def package_update(self, package: Package, repository_id: RepositoryId | None = None) -> None:
"""
update package status
Args:
package(Package): package properties
status(BuildStatus): new build status
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> None:
self._package_update_insert_base(connection, package, repository_id)
self._package_update_insert_status(connection, package.base, status, repository_id)
self._package_update_insert_packages(connection, package, repository_id)
self._package_remove_packages(connection, package.base, package.packages.keys(), repository_id)
@ -336,3 +306,33 @@ class PackageOperations(Operations):
package_base: package.remote
for package_base, package in self.with_connection(run).items()
}
def status_update(self, package_base: str, status: BuildStatus, repository_id: RepositoryId | None = None) -> None:
"""
insert base package status into table
Args:
package_base(str): package base name
status(BuildStatus): new build status
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> None:
connection.execute(
"""
insert into package_statuses
(package_base, status, last_updated, repository)
values
(:package_base, :status, :last_updated, :repository)
on conflict (package_base, repository) do update set
status = :status, last_updated = :last_updated
""",
{
"package_base": package_base,
"status": status.status.value,
"last_updated": status.timestamp,
"repository": repository_id.id,
})
return self.with_connection(run, commit=True)

View File

@ -66,7 +66,7 @@ class SQLite(
path = cls.database_path(configuration)
_, repository_id = configuration.check_loaded()
database = cls(path, repository_id)
database = cls(path, repository_id, configuration.repository_paths)
database.init(configuration)
return database
@ -119,3 +119,6 @@ class SQLite(
self.logs_remove(package_base, None)
self.changes_remove(package_base)
self.dependencies_remove(package_base)
# remove local cache too
self._repository_paths.tree_clear(package_base)

View File

@ -25,9 +25,9 @@ from tempfile import TemporaryDirectory
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import GitRemoteError
from ahriman.core.log import LazyLogging
from ahriman.core.status.client import Client
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
@ -40,20 +40,20 @@ class RemotePush(LazyLogging):
Attributes:
commit_author(tuple[str, str] | None): optional commit author in form of git config
database(SQLite): database instance
remote_source(RemoteSource): repository remote source (remote pull url and branch)
reporter(Client): reporter client used for additional information retrieval
"""
def __init__(self, database: SQLite, configuration: Configuration, section: str) -> None:
def __init__(self, reporter: Client, configuration: Configuration, section: str) -> None:
"""
default constructor
Args:
database(SQLite): database instance
reporter(Client): reporter client
configuration(Configuration): configuration instance
section(str): settings section name
"""
self.database = database
self.reporter = reporter
commit_email = configuration.get(section, "commit_email", fallback="ahriman@localhost")
commit_user = configuration.get(section, "commit_user", fallback="ahriman")
@ -92,7 +92,7 @@ class RemotePush(LazyLogging):
else:
shutil.rmtree(git_file)
# ...copy all patches...
for patch in self.database.patches_get(package.base):
for patch in self.reporter.package_patches_get(package.base, None):
filename = f"ahriman-{package.base}.patch" if patch.key is None else f"ahriman-{patch.key}.patch"
patch.write(package_target_dir / filename)
# ...and finally return path to the copied directory

View File

@ -19,8 +19,8 @@
#
from ahriman.core import context
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.gitremote.remote_push import RemotePush
from ahriman.core.status.client import Client
from ahriman.core.triggers import Trigger
from ahriman.models.package import Package
from ahriman.models.repository_id import RepositoryId
@ -110,10 +110,10 @@ class RemotePushTrigger(Trigger):
GitRemoteError: if database is not set in context
"""
ctx = context.get()
database = ctx.get(SQLite)
reporter = ctx.get(Client)
for target in self.targets:
section, _ = self.configuration.gettype(
target, self.repository_id, fallback=self.CONFIGURATION_SCHEMA_FALLBACK)
runner = RemotePush(database, self.configuration, section)
runner = RemotePush(reporter, self.configuration, section)
runner.run(result)

View File

@ -92,7 +92,7 @@ class HttpLogHandler(logging.Handler):
return # in case if no package base supplied we need just skip log message
try:
self.reporter.package_logs(log_record_id, record)
self.reporter.package_logs_add(log_record_id, record.created, record.getMessage())
except Exception:
if self.suppress_errors:
return

View File

@ -58,7 +58,8 @@ class Executor(PackageInfo, Cleaner):
self.reporter.set_building(package.base)
task = Task(package, self.configuration, self.architecture, self.paths)
local_version = local_versions.get(package.base) if bump_pkgrel else None
commit_sha = task.init(local_path, self.database, local_version)
patches = self.reporter.package_patches_get(package.base, None)
commit_sha = task.init(local_path, patches, local_version)
built = task.build(local_path, PACKAGER=packager_id)
for src in built:
dst = self.paths.packages / src.name
@ -80,7 +81,7 @@ class Executor(PackageInfo, Cleaner):
self.reporter.package_changes_set(single.base, Changes(last_commit_sha))
# update dependencies list
dependencies = PackageArchive(self.paths.build_directory, single).depends_on()
self.database.dependencies_insert(dependencies)
self.reporter.package_dependencies_set(single.base, dependencies)
# update result set
result.add_updated(single)
except Exception:
@ -102,9 +103,7 @@ class Executor(PackageInfo, Cleaner):
"""
def remove_base(package_base: str) -> None:
try:
self.paths.tree_clear(package_base) # remove all internal files
self.database.package_clear(package_base)
self.reporter.package_remove(package_base) # we only update status page in case of base removal
self.reporter.package_remove(package_base)
except Exception:
self.logger.exception("could not remove base %s", package_base)

View File

@ -43,15 +43,14 @@ class PackageInfo(RepositoryProperties):
Returns:
list[Package]: list of read packages
"""
sources = self.database.remotes_get()
result: dict[str, Package] = {}
# we are iterating over bases, not single packages
for full_path in packages:
try:
local = Package.from_archive(full_path, self.pacman)
if (source := sources.get(local.base)) is not None:
local.remote = source
remote, _ = next(iter(self.reporter.package_get(local.base)), (None, None))
if remote is not None: # update source with remote
local.remote = remote.remote
current = result.setdefault(local.base, local)
if current.version != local.version:
@ -78,7 +77,8 @@ class PackageInfo(RepositoryProperties):
"""
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
dir_path = Path(dir_name)
current_commit_sha = Sources.load(dir_path, package, self.database.patches_get(package.base), self.paths)
patches = self.reporter.package_patches_get(package.base, None)
current_commit_sha = Sources.load(dir_path, package, patches, self.paths)
changes: str | None = None
if current_commit_sha != last_commit_sha:

View File

@ -26,6 +26,7 @@ from ahriman.core.database import SQLite
from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.core.sign.gpg import GPG
from ahriman.core.status.client import Client
from ahriman.models.pacman_synchronization import PacmanSynchronization
from ahriman.models.repository_id import RepositoryId
@ -92,6 +93,7 @@ class Repository(Executor, UpdateHandler):
ctx.set(Configuration, self.configuration)
ctx.set(Pacman, self.pacman)
ctx.set(GPG, self.sign)
ctx.set(Client, self.reporter)
ctx.set(type(self), self)

View File

@ -75,7 +75,7 @@ class RepositoryProperties(LazyLogging):
self.pacman = Pacman(repository_id, configuration, refresh_database=refresh_pacman_database)
self.sign = GPG(configuration)
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
self.reporter = Client.load(repository_id, configuration, report=report)
self.reporter = Client.load(repository_id, configuration, database, report=report)
self.triggers = TriggerLoader.load(repository_id, configuration)
@property

View File

@ -98,19 +98,17 @@ class UpdateHandler(PackageInfo, Cleaner):
return files
dependencies = {dependency.package_base: dependency for dependency in self.database.dependencies_get()}
result: list[Package] = []
for package in self.packages(filter_packages):
if package.base not in dependencies:
dependencies = self.reporter.package_dependencies_get(package.base)
if not dependencies.paths:
continue # skip check if no package dependencies found
required = dependencies[package.base].paths
required_packages = {dep for dep_packages in required.values() for dep in dep_packages}
required_packages = {dep for dep_packages in dependencies.paths.values() for dep in dep_packages}
filesystem = extract_files(required_packages)
for path, packages in required.items():
found = filesystem.get(path, set())
for path, packages in dependencies.paths.items():
found = filesystem.get(Path(path), set())
if found.intersection(packages):
continue

View File

@ -17,16 +17,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# pylint: disable=too-many-public-methods
from __future__ import annotations
import logging
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.repository_id import RepositoryId
@ -36,22 +38,31 @@ class Client:
"""
@staticmethod
def load(repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Client:
def load(repository_id: RepositoryId, configuration: Configuration, database: SQLite | None = None, *,
report: bool = True) -> Client:
"""
load client from settings
Args:
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
database(SQLite | None, optional): database instance (Default value = None)
report(bool, optional): force enable or disable reporting (Default value = True)
Returns:
Client: client according to current settings
"""
def make_local_client() -> Client:
if database is None:
return Client()
from ahriman.core.status.local_client import LocalClient
return LocalClient(repository_id, database)
if not report:
return Client()
return make_local_client()
if not configuration.getboolean("status", "enabled", fallback=True): # global switch
return Client()
return make_local_client()
# new-style section
address = configuration.get("status", "address", fallback=None)
@ -65,7 +76,8 @@ class Client:
if address or legacy_address or (host and port) or socket:
from ahriman.core.status.web_client import WebClient
return WebClient(repository_id, configuration)
return Client()
return make_local_client()
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
"""
@ -74,7 +86,11 @@ class Client:
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_changes_get(self, package_base: str) -> Changes:
"""
@ -85,9 +101,11 @@ class Client:
Returns:
Changes: package changes if available and empty object otherwise
Raises:
NotImplementedError: not implemented method
"""
del package_base
return Changes()
raise NotImplementedError
def package_changes_set(self, package_base: str, changes: Changes) -> None:
"""
@ -96,7 +114,39 @@ class Client:
Args:
package_base(str): package base to update
changes(Changes): changes descriptor
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_dependencies_get(self, package_base: str) -> Dependencies:
"""
get package dependencies
Args:
package_base(str): package base to retrieve
Returns:
list[Dependencies]: package implicit dependencies if available
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_dependencies_set(self, package_base: str, dependencies: Dependencies) -> None:
"""
update package dependencies
Args:
package_base(str): package base to update
dependencies(Dependencies): dependencies descriptor
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
"""
@ -107,18 +157,94 @@ class Client:
Returns:
list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found
"""
del package_base
return []
def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None:
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None:
"""
post log record
Args:
log_record_id(LogRecordId): log record id
record(logging.LogRecord): log record to post to api
created(float): log created timestamp
message(str): log message
"""
# this method does not raise NotImplementedError because it is actively used as dummy client for http log
def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
"""
get package logs
Args:
package_base(str): package base
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0)
Returns:
list[tuple[float, str]]: package logs
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_logs_remove(self, package_base: str, version: str | None) -> None:
"""
remove package logs
Args:
package_base(str): package base
version(str | None): package version to remove logs. If None set, all logs will be removed
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_patches_add(self, package_base: str, patch: PkgbuildPatch) -> None:
"""
create or update package patch
Args:
package_base(str): package base to update
patch(PkgbuildPatch): package patch
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
"""
get package patches
Args:
package_base(str): package base to retrieve
variable(str | None): optional filter by patch variable
Returns:
list[PkgbuildPatch]: list of patches for the specified package
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_patches_remove(self, package_base: str, variable: str | None) -> None:
"""
remove package patch
Args:
package_base(str): package base to update
variable(str | None): patch name. If None set, all patches will be removed
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_remove(self, package_base: str) -> None:
"""
@ -126,16 +252,24 @@ class Client:
Args:
package_base(str): package base to remove
"""
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_set(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package build status. Unlike :func:`package_add()` it does not update package properties
Args:
package_base(str): package base to update
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def set_building(self, package_base: str) -> None:
"""
@ -144,7 +278,7 @@ class Client:
Args:
package_base(str): package base to update
"""
return self.package_update(package_base, BuildStatusEnum.Building)
return self.package_set(package_base, BuildStatusEnum.Building)
def set_failed(self, package_base: str) -> None:
"""
@ -153,7 +287,7 @@ class Client:
Args:
package_base(str): package base to update
"""
return self.package_update(package_base, BuildStatusEnum.Failed)
return self.package_set(package_base, BuildStatusEnum.Failed)
def set_pending(self, package_base: str) -> None:
"""
@ -162,7 +296,7 @@ class Client:
Args:
package_base(str): package base to update
"""
return self.package_update(package_base, BuildStatusEnum.Pending)
return self.package_set(package_base, BuildStatusEnum.Pending)
def set_success(self, package: Package) -> None:
"""

View File

@ -0,0 +1,208 @@
#
# Copyright (c) 2021-2024 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 ahriman.core.database import SQLite
from ahriman.core.status.client import Client
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.repository_id import RepositoryId
class LocalClient(Client):
"""
local database handler
Attributes:
database(SQLite): database instance
repository_id(RepositoryId): repository unique identifier
"""
def __init__(self, repository_id: RepositoryId, database: SQLite) -> None:
"""
default constructor
Args:
repository_id(RepositoryId): repository unique identifier
database(SQLite): database instance:
"""
self.database = database
self.repository_id = repository_id
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
"""
add new package with status
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
"""
self.database.package_update(package, self.repository_id)
self.database.status_update(package.base, BuildStatus(status), self.repository_id)
def package_changes_get(self, package_base: str) -> Changes:
"""
get package changes
Args:
package_base(str): package base to retrieve
Returns:
Changes: package changes if available and empty object otherwise
"""
return self.database.changes_get(package_base, self.repository_id)
def package_changes_set(self, package_base: str, changes: Changes) -> None:
"""
update package changes
Args:
package_base(str): package base to update
changes(Changes): changes descriptor
"""
self.database.changes_insert(package_base, changes, self.repository_id)
def package_dependencies_get(self, package_base: str) -> Dependencies:
"""
get package dependencies
Args:
package_base(str): package base to retrieve
Returns:
list[Dependencies]: package implicit dependencies if available
"""
return self.database.dependencies_get(package_base, self.repository_id).get(package_base, Dependencies())
def package_dependencies_set(self, package_base: str, dependencies: Dependencies) -> None:
"""
update package dependencies
Args:
package_base(str): package base to update
dependencies(Dependencies): dependencies descriptor
"""
self.database.dependencies_insert(package_base, dependencies, self.repository_id)
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
"""
get package status
Args:
package_base(str | None): package base to get
Returns:
list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found
"""
packages = self.database.packages_get(self.repository_id)
if package_base is None:
return packages
return [(package, status) for package, status in packages if package.base == package_base]
def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None:
"""
post log record
Args:
log_record_id(LogRecordId): log record id
created(float): log created timestamp
message(str): log message
"""
self.database.logs_insert(log_record_id, created, message, self.repository_id)
def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
"""
get package logs
Args:
package_base(str): package base
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0)
Returns:
list[tuple[float, str]]: package logs
"""
return self.database.logs_get(package_base, limit, offset, self.repository_id)
def package_logs_remove(self, package_base: str, version: str | None) -> None:
"""
remove package logs
Args:
package_base(str): package base
version(str | None): package version to remove logs. If None set, all logs will be removed
"""
self.database.logs_remove(package_base, version, self.repository_id)
def package_patches_add(self, package_base: str, patch: PkgbuildPatch) -> None:
"""
create or update package patch
Args:
package_base(str): package base to update
patch(PkgbuildPatch): package patch
"""
self.database.patches_insert(package_base, [patch])
def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
"""
get package patches
Args:
package_base(str): package base to retrieve
variable(str | None): optional filter by patch variable
Returns:
list[PkgbuildPatch]: list of patches for the specified package
"""
variables = [variable] if variable is not None else None
return self.database.patches_list(package_base, variables).get(package_base, [])
def package_patches_remove(self, package_base: str, variable: str | None) -> None:
"""
remove package patch
Args:
package_base(str): package base to update
variable(str | None): patch name. If None set, all patches will be removed
"""
variables = [variable] if variable is not None else None
self.database.patches_remove(package_base, variables)
def package_remove(self, package_base: str) -> None:
"""
remove packages from watcher
Args:
package_base(str): package base to remove
"""
self.database.package_clear(package_base)
def package_set(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package build status. Unlike :func:`package_add()` it does not update package properties
Args:
package_base(str): package base to update
status(BuildStatusEnum): current package build status
"""
self.database.status_update(package_base, BuildStatus(status), self.repository_id)

View File

@ -19,15 +19,15 @@
#
from threading import Lock
from ahriman.core.database import SQLite
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.log import LazyLogging
from ahriman.core.status.client import Client
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.repository_id import RepositoryId
class Watcher(LazyLogging):
@ -35,21 +35,18 @@ class Watcher(LazyLogging):
package status watcher
Attributes:
database(SQLite): database instance
repository_id(RepositoryId): repository unique identifier
client(Client): reporter instance
status(BuildStatus): daemon status
"""
def __init__(self, repository_id: RepositoryId, database: SQLite) -> None:
def __init__(self, client: Client) -> None:
"""
default constructor
Args:
repository_id(RepositoryId): repository unique identifier
database(SQLite): database instance
client(Client): reporter instance
"""
self.repository_id = repository_id
self.database = database
self.client = client
self._lock = Lock()
self._known: dict[str, tuple[Package, BuildStatus]] = {}
@ -76,48 +73,20 @@ class Watcher(LazyLogging):
with self._lock:
self._known = {
package.base: (package, status)
for package, status in self.database.packages_get(self.repository_id)
for package, status in self.client.package_get(None)
}
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
"""
extract logs for the package base
update package
Args:
package_base(str): package base
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0)
Returns:
list[tuple[float, str]]: package logs
package(Package): package description
status(BuildStatusEnum): new build status
"""
self.package_get(package_base)
return self.database.logs_get(package_base, limit, offset, self.repository_id)
def logs_remove(self, package_base: str, version: str | None) -> None:
"""
remove package related logs
Args:
package_base(str): package base
version(str): package versio
"""
self.database.logs_remove(package_base, version, self.repository_id)
def logs_update(self, log_record_id: LogRecordId, created: float, record: str) -> None:
"""
make new log record into database
Args:
log_record_id(LogRecordId): log record id
created(float): log created timestamp
record(str): log record
"""
if self._last_log_record_id != log_record_id:
# there is new log record, so we remove old ones
self.logs_remove(log_record_id.package_base, log_record_id.version)
self._last_log_record_id = log_record_id
self.database.logs_insert(log_record_id, created, record, self.repository_id)
with self._lock:
self._known[package.base] = (package, BuildStatus(status))
self.client.package_add(package, status)
def package_changes_get(self, package_base: str) -> Changes:
"""
@ -129,8 +98,43 @@ class Watcher(LazyLogging):
Returns:
Changes: package changes if available
"""
self.package_get(package_base)
return self.database.changes_get(package_base, self.repository_id)
_ = self.package_get(package_base)
return self.client.package_changes_get(package_base)
def package_changes_set(self, package_base: str, changes: Changes) -> None:
"""
update package changes
Args:
package_base(str): package base
changes(Changes): package changes
"""
_ = self.package_get(package_base)
self.client.package_changes_set(package_base, changes)
def package_dependencies_get(self, package_base: str) -> Dependencies:
"""
retrieve package dependencies
Args:
package_base(str): package base
Returns:
Dependencies: package dependencies if available
"""
_ = self.package_get(package_base)
return self.client.package_dependencies_get(package_base)
def package_dependencies_set(self, package_base: str, dependencies: Dependencies) -> None:
"""
update package dependencies
Args:
package_base(str): package base
dependencies(Dependencies): package dependencies
"""
_ = self.package_get(package_base)
self.client.package_dependencies_set(package_base, dependencies)
def package_get(self, package_base: str) -> tuple[Package, BuildStatus]:
"""
@ -151,35 +155,47 @@ class Watcher(LazyLogging):
except KeyError:
raise UnknownPackageError(package_base) from None
def package_remove(self, package_base: str) -> None:
def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
"""
remove package base from known list if any
extract logs for the package base
Args:
package_base(str): package base
"""
with self._lock:
self._known.pop(package_base, None)
self.database.package_remove(package_base, self.repository_id)
self.logs_remove(package_base, None)
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0)
def package_update(self, package_base: str, status: BuildStatusEnum, package: Package | None) -> None:
Returns:
list[tuple[float, str]]: package logs
"""
update package status and description
_ = self.package_get(package_base)
return self.client.package_logs_get(package_base, limit, offset)
def package_logs_remove(self, package_base: str, version: str | None) -> None:
"""
remove package related logs
Args:
package_base(str): package base to update
status(BuildStatusEnum): new build status
package(Package | None): optional package description. In case if not set current properties will be used
package_base(str): package base
version(str): package version
"""
if package is None:
package, _ = self.package_get(package_base)
full_status = BuildStatus(status)
with self._lock:
self._known[package_base] = (package, full_status)
self.database.package_update(package, full_status, self.repository_id)
self.client.package_logs_remove(package_base, version)
def patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
def package_logs_update(self, log_record_id: LogRecordId, created: float, message: str) -> None:
"""
make new log record into database
Args:
log_record_id(LogRecordId): log record id
created(float): log created timestamp
message(str): log message
"""
if self._last_log_record_id != log_record_id:
# there is new log record, so we remove old ones
self.package_logs_remove(log_record_id.package_base, log_record_id.version)
self._last_log_record_id = log_record_id
self.client.package_logs_add(log_record_id, created, message)
def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
"""
get patches for the package
@ -192,10 +208,9 @@ class Watcher(LazyLogging):
"""
# patches are package base based, we don't know (and don't differentiate) to which package does them belong
# so here we skip checking if package exists or not
variables = [variable] if variable is not None else None
return self.database.patches_list(package_base, variables).get(package_base, [])
return self.client.package_patches_get(package_base, variable)
def patches_remove(self, package_base: str, variable: str) -> None:
def package_patches_remove(self, package_base: str, variable: str) -> None:
"""
remove package patch
@ -203,9 +218,9 @@ class Watcher(LazyLogging):
package_base(str): package base
variable(str): patch variable name
"""
self.database.patches_remove(package_base, [variable])
self.client.package_patches_remove(package_base, variable)
def patches_update(self, package_base: str, patch: PkgbuildPatch) -> None:
def package_patches_update(self, package_base: str, patch: PkgbuildPatch) -> None:
"""
update package patch
@ -213,7 +228,32 @@ class Watcher(LazyLogging):
package_base(str): package base
patch(PkgbuildPatch): package patch
"""
self.database.patches_insert(package_base, [patch])
self.client.package_patches_add(package_base, patch)
def package_remove(self, package_base: str) -> None:
"""
remove package base from known list if any
Args:
package_base(str): package base
"""
with self._lock:
self._known.pop(package_base, None)
self.client.package_remove(package_base)
self.package_logs_remove(package_base, None)
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package status
Args:
package_base(str): package base to update
status(BuildStatusEnum): new build status
"""
package, _ = self.package_get(package_base)
with self._lock:
self._known[package_base] = (package, BuildStatus(status))
self.client.package_set(package_base, status)
def status_update(self, status: BuildStatusEnum) -> None:
"""

View File

@ -18,7 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import contextlib
import logging
from urllib.parse import quote_plus as urlencode
@ -27,9 +26,11 @@ from ahriman.core.http import SyncAhrimanClient
from ahriman.core.status.client import Client
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.repository_id import RepositoryId
@ -92,10 +93,22 @@ class WebClient(Client, SyncAhrimanClient):
package_base(str): package base
Returns:
str: full url for web service for logs
str: full url for web service for changes
"""
return f"{self.address}/api/v1/packages/{urlencode(package_base)}/changes"
def _dependencies_url(self, package_base: str) -> str:
"""
get url for the dependencies api
Args:
package_base(str): package base
Returns:
str: full url for web service for dependencies
"""
return f"{self.address}/api/v1/packages/{urlencode(package_base)}/dependencies"
def _logs_url(self, package_base: str) -> str:
"""
get url for the logs api
@ -110,7 +123,7 @@ class WebClient(Client, SyncAhrimanClient):
def _package_url(self, package_base: str = "") -> str:
"""
url generator
package url generator
Args:
package_base(str, optional): package base to generate url (Default value = "")
@ -121,6 +134,20 @@ class WebClient(Client, SyncAhrimanClient):
suffix = f"/{urlencode(package_base)}" if package_base else ""
return f"{self.address}/api/v1/packages{suffix}"
def _patches_url(self, package_base: str, variable: str = "") -> str:
"""
patches url generator
Args:
package_base(str): package base
variable(str, optional): patch variable name to generate url (Default value = "")
Returns:
str: full url of web service for the package patch
"""
suffix = f"/{urlencode(variable)}" if variable else ""
return f"{self.address}/api/v1/packages/{urlencode(package_base)}/patches{suffix}"
def _status_url(self) -> str:
"""
get url for the status api
@ -177,6 +204,37 @@ class WebClient(Client, SyncAhrimanClient):
self.make_request("POST", self._changes_url(package_base),
params=self.repository_id.query(), json=changes.view())
def package_dependencies_get(self, package_base: str) -> Dependencies:
"""
get package dependencies
Args:
package_base(str): package base to retrieve
Returns:
list[Dependencies]: package implicit dependencies if available
"""
with contextlib.suppress(Exception):
response = self.make_request("GET", self._dependencies_url(package_base),
params=self.repository_id.query())
response_json = response.json()
return Dependencies.from_json(response_json)
return Dependencies()
def package_dependencies_set(self, package_base: str, dependencies: Dependencies) -> None:
"""
update package dependencies
Args:
package_base(str): package base to update
dependencies(Dependencies): dependencies descriptor
"""
with contextlib.suppress(Exception):
self.make_request("POST", self._dependencies_url(package_base),
params=self.repository_id.query(), json=dependencies.view())
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
"""
get package status
@ -199,17 +257,18 @@ class WebClient(Client, SyncAhrimanClient):
return []
def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None:
def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None:
"""
post log record
Args:
log_record_id(LogRecordId): log record id
record(logging.LogRecord): log record to post to api
created(float): log created timestamp
message(str): log message
"""
payload = {
"created": record.created,
"message": record.getMessage(),
"created": created,
"message": message,
"version": log_record_id.version,
}
@ -219,6 +278,83 @@ class WebClient(Client, SyncAhrimanClient):
self.make_request("POST", self._logs_url(log_record_id.package_base),
params=self.repository_id.query(), json=payload, suppress_errors=True)
def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
"""
get package logs
Args:
package_base(str): package base
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0)
Returns:
list[tuple[float, str]]: package logs
"""
with contextlib.suppress(Exception):
query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
response = self.make_request("GET", self._logs_url(package_base), params=query)
response_json = response.json()
return [(record["created"], record["message"]) for record in response_json]
return []
def package_logs_remove(self, package_base: str, version: str | None) -> None:
"""
remove package logs
Args:
package_base(str): package base
version(str | None): package version to remove logs. If None set, all logs will be removed
"""
with contextlib.suppress(Exception):
query = self.repository_id.query()
if version is not None:
query += [("version", version)]
self.make_request("DELETE", self._logs_url(package_base), params=query)
def package_patches_add(self, package_base: str, patch: PkgbuildPatch) -> None:
"""
create or update package patch
Args:
package_base(str): package base to update
patch(PkgbuildPatch): package patch
"""
with contextlib.suppress(Exception):
self.make_request("POST", self._patches_url(package_base), json=patch.view())
def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
"""
get package patches
Args:
package_base(str): package base to retrieve
variable(str | None): optional filter by patch variable
Returns:
list[PkgbuildPatch]: list of patches for the specified package
"""
with contextlib.suppress(Exception):
response = self.make_request("GET", self._patches_url(package_base, variable or ""))
response_json = response.json()
patches = response_json if variable is None else [response_json]
return [PkgbuildPatch.from_json(patch) for patch in patches]
return []
def package_patches_remove(self, package_base: str, variable: str | None) -> None:
"""
remove package patch
Args:
package_base(str): package base to update
variable(str | None): patch name. If None set, all patches will be removed
"""
with contextlib.suppress(Exception):
self.make_request("DELETE", self._patches_url(package_base, variable or ""))
def package_remove(self, package_base: str) -> None:
"""
remove packages from watcher
@ -229,7 +365,7 @@ class WebClient(Client, SyncAhrimanClient):
with contextlib.suppress(Exception):
self.make_request("DELETE", self._package_url(package_base), params=self.repository_id.query())
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
def package_set(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package build status. Unlike :func:`package_add()` it does not update package properties

View File

@ -18,13 +18,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import shutil
from pathlib import Path
from ahriman.core import context
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.status.client import Client
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
@ -48,23 +48,39 @@ class PackageCreator:
self.configuration = configuration
self.generator = generator
def package_create(self, path: Path) -> None:
"""
create package files
Args:
path(Path): path to directory with package files
"""
# clear old tree if any
shutil.rmtree(path, ignore_errors=True)
# create local tree
path.mkdir(mode=0o755, parents=True, exist_ok=True)
self.generator.write_pkgbuild(path)
Sources.init(path)
def package_register(self, path: Path) -> None:
"""
register package in build worker
Args:
path(Path): path to directory with package files
"""
ctx = context.get()
reporter = ctx.get(Client)
_, repository_id = self.configuration.check_loaded()
package = Package.from_build(path, repository_id.architecture, None)
reporter.set_unknown(package)
def run(self) -> None:
"""
create new local package
"""
local_path = self.configuration.repository_paths.cache_for(self.generator.pkgname)
# clear old tree if any
shutil.rmtree(local_path, ignore_errors=True)
# create local tree
local_path.mkdir(mode=0o755, parents=True, exist_ok=True)
self.generator.write_pkgbuild(local_path)
Sources.init(local_path)
# register package
ctx = context.get()
database = ctx.get(SQLite)
_, repository_id = self.configuration.check_loaded()
package = Package.from_build(local_path, repository_id.architecture, None)
database.package_update(package, BuildStatus())
self.package_create(local_path)
self.package_register(local_path)

View File

@ -17,8 +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 dataclasses import dataclass, field
from pathlib import Path
from dataclasses import dataclass, field, fields
from typing import Any, Self
from ahriman.core.util import dataclass_view, filter_json
@dataclass(frozen=True)
@ -27,9 +29,31 @@ class Dependencies:
package paths dependencies
Attributes:
package_base(str): package base
paths(dict[Path, list[str]]): map of the paths used by this package to set of packages in which they were found
paths(dict[str, list[str]]): map of the paths used by this package to set of packages in which they were found
"""
package_base: str
paths: dict[Path, list[str]] = field(default_factory=dict)
paths: dict[str, list[str]] = field(default_factory=dict)
@classmethod
def from_json(cls, dump: dict[str, Any]) -> Self:
"""
construct dependencies from the json dump
Args:
dump(dict[str, Any]): json dump body
Returns:
Self: dependencies object
"""
# filter to only known fields
known_fields = [pair.name for pair in fields(cls)]
return cls(**filter_json(dump, known_fields))
def view(self) -> dict[str, Any]:
"""
generate json dependencies view
Returns:
dict[str, Any]: json-friendly dictionary
"""
return dataclass_view(self)

View File

@ -99,7 +99,7 @@ class PackageArchive:
"""
dependencies, roots = self.depends_on_paths()
result: dict[Path, list[str]] = {}
result: dict[str, list[str]] = {}
for package, (directories, files) in self.installed_packages().items():
if package in self.package.packages:
continue # skip package itself
@ -108,9 +108,9 @@ class PackageArchive:
required_by.extend(library for library in files if library.name in dependencies)
for path in required_by:
result.setdefault(path, []).append(package)
result.setdefault(str(path), []).append(package)
return Dependencies(self.package.base, result)
return Dependencies(result)
def depends_on_paths(self) -> tuple[set[str], set[Path]]:
"""

View File

@ -19,11 +19,11 @@
#
import shlex
from dataclasses import dataclass
from dataclasses import dataclass, fields
from pathlib import Path
from typing import Any, Generator, Self
from ahriman.core.util import dataclass_view
from ahriman.core.util import dataclass_view, filter_json
@dataclass(frozen=True)
@ -84,6 +84,21 @@ class PkgbuildPatch:
raw_value = next(iter(value_parts), "") # extract raw value
return cls(key, cls.parse(raw_value))
@classmethod
def from_json(cls, dump: dict[str, Any]) -> Self:
"""
construct patch descriptor from the json dump
Args:
dump(dict[str, Any]): json dump body
Returns:
Self: patch object
"""
# filter to only known fields
known_fields = [pair.name for pair in fields(cls)]
return cls(**filter_json(dump, known_fields))
@staticmethod
def parse(source: str) -> str | list[str]:
"""

View File

@ -22,6 +22,7 @@ from ahriman.web.schemas.auth_schema import AuthSchema
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema
from ahriman.web.schemas.changes_schema import ChangesSchema
from ahriman.web.schemas.counters_schema import CountersSchema
from ahriman.web.schemas.dependencies_schema import DependenciesSchema
from ahriman.web.schemas.error_schema import ErrorSchema
from ahriman.web.schemas.file_schema import FileSchema
from ahriman.web.schemas.info_schema import InfoSchema
@ -36,6 +37,7 @@ from ahriman.web.schemas.package_patch_schema import PackagePatchSchema
from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema
from ahriman.web.schemas.package_schema import PackageSchema
from ahriman.web.schemas.package_status_schema import PackageStatusSchema, PackageStatusSimplifiedSchema
from ahriman.web.schemas.package_version_schema import PackageVersionSchema
from ahriman.web.schemas.pagination_schema import PaginationSchema
from ahriman.web.schemas.patch_name_schema import PatchNameSchema
from ahriman.web.schemas.patch_schema import PatchSchema

View File

@ -0,0 +1,31 @@
#
# Copyright (c) 2021-2024 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 marshmallow import Schema, fields
class DependenciesSchema(Schema):
"""
request/response package dependencies schema
"""
paths = fields.Dict(
keys=fields.String(), values=fields.List(fields.String()), required=True, metadata={
"description": "Map of filesystem paths to packages which contain this path",
})

View File

@ -0,0 +1,34 @@
#
# Copyright (c) 2021-2024 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 marshmallow import fields
from ahriman import __version__
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
class PackageVersionSchema(RepositoryIdSchema):
"""
request package name schema
"""
version = fields.String(metadata={
"description": "Package version",
"example": __version__,
})

View File

@ -113,7 +113,6 @@ class ChangesView(StatusViewGuard, BaseView):
raise HTTPBadRequest(reason=str(ex))
changes = Changes(last_commit_sha, change)
repository_id = self.repository_id()
self.service(repository_id).database.changes_insert(package_base, changes, repository_id)
self.service().package_changes_set(package_base, changes)
raise HTTPNoContent

View File

@ -0,0 +1,120 @@
#
# Copyright (c) 2021-2024 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/>.
#
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.dependencies import Dependencies
from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, DependenciesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
class DependencyView(StatusViewGuard, BaseView):
"""
package dependencies web view
Attributes:
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
"""
GET_PERMISSION = UserAccess.Reporter
POST_PERMISSION = UserAccess.Full
ROUTES = ["/api/v1/packages/{package}/dependencies"]
@aiohttp_apispec.docs(
tags=["Build"],
summary="Get package dependencies",
description="Retrieve package implicit dependencies",
responses={
200: {"description": "Success response", "schema": DependenciesSchema},
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [GET_PERMISSION]}],
)
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
async def get(self) -> Response:
"""
get package dependencies
Returns:
Response: 200 with package implicit dependencies on success
Raises:
HTTPNotFound: if package base is unknown
"""
package_base = self.request.match_info["package"]
try:
dependencies = self.service().package_dependencies_get(package_base)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
return json_response(dependencies.view())
@aiohttp_apispec.docs(
tags=["Build"],
summary="Update package dependencies",
description="Set package implicit dependencies",
responses={
204: {"description": "Success response"},
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
)
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.json_schema(DependenciesSchema)
async def post(self) -> None:
"""
insert new package dependencies
Raises:
HTTPBadRequest: if bad data is supplied
HTTPNoContent: in case of success response
"""
package_base = self.request.match_info["package"]
try:
data = await self.request.json()
data["package_base"] = package_base # read from path instead of object
dependencies = Dependencies.from_json(data)
except Exception as ex:
raise HTTPBadRequest(reason=str(ex))
try:
self.service().package_dependencies_set(package_base, dependencies)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
raise HTTPNoContent

View File

@ -25,8 +25,8 @@ from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.util import pretty_datetime
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogsSchema, PackageNameSchema, RepositoryIdSchema, \
VersionedLogSchema
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogsSchema, PackageNameSchema, PackageVersionSchema, \
RepositoryIdSchema, VersionedLogSchema
from ahriman.web.views.base import BaseView
from ahriman.web.views.status_view_guard import StatusViewGuard
@ -60,7 +60,7 @@ class LogsView(StatusViewGuard, BaseView):
)
@aiohttp_apispec.cookies_schema(AuthSchema)
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
@aiohttp_apispec.querystring_schema(PackageVersionSchema)
async def delete(self) -> None:
"""
delete package logs
@ -69,7 +69,8 @@ class LogsView(StatusViewGuard, BaseView):
HTTPNoContent: on success response
"""
package_base = self.request.match_info["package"]
self.service().logs_remove(package_base, None)
version = self.request.query.get("version")
self.service().package_logs_remove(package_base, version)
raise HTTPNoContent
@ -103,7 +104,7 @@ class LogsView(StatusViewGuard, BaseView):
try:
_, status = self.service().package_get(package_base)
logs = self.service().logs_get(package_base)
logs = self.service().package_logs_get(package_base)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
@ -149,6 +150,6 @@ class LogsView(StatusViewGuard, BaseView):
except Exception as ex:
raise HTTPBadRequest(reason=str(ex))
self.service().logs_update(LogRecordId(package_base, version), created, record)
self.service().package_logs_update(LogRecordId(package_base, version), created, record)
raise HTTPNoContent

View File

@ -152,7 +152,10 @@ class PackageView(StatusViewGuard, BaseView):
raise HTTPBadRequest(reason=str(ex))
try:
self.service().package_update(package_base, status, package)
if package is None:
self.service().package_update(package_base, status)
else:
self.service().package_add(package, status)
except UnknownPackageError:
raise HTTPBadRequest(reason=f"Package {package_base} is unknown, but no package body set")

View File

@ -63,7 +63,7 @@ class PatchView(StatusViewGuard, BaseView):
"""
package_base = self.request.match_info["package"]
variable = self.request.match_info["patch"]
self.service().patches_remove(package_base, variable)
self.service().package_patches_remove(package_base, variable)
raise HTTPNoContent
@ -95,7 +95,7 @@ class PatchView(StatusViewGuard, BaseView):
package_base = self.request.match_info["package"]
variable = self.request.match_info["patch"]
patches = self.service().patches_get(package_base, variable)
patches = self.service().package_patches_get(package_base, variable)
selected = next((patch for patch in patches if patch.key == variable), None)
if selected is None:

View File

@ -63,7 +63,7 @@ class PatchesView(StatusViewGuard, BaseView):
Response: 200 with package patches on success
"""
package_base = self.request.match_info["package"]
patches = self.service().patches_get(package_base, None)
patches = self.service().package_patches_get(package_base, None)
response = [patch.view() for patch in patches]
return json_response(response)
@ -101,6 +101,6 @@ class PatchesView(StatusViewGuard, BaseView):
except Exception as ex:
raise HTTPBadRequest(reason=str(ex))
self.service().patches_update(package_base, PkgbuildPatch(key, value))
self.service().package_patches_update(package_base, PkgbuildPatch(key, value))
raise HTTPNoContent

View File

@ -69,7 +69,7 @@ class LogsView(StatusViewGuard, BaseView):
package_base = self.request.match_info["package"]
limit, offset = self.page()
try:
logs = self.service().logs_get(package_base, limit, offset)
logs = self.service().package_logs_get(package_base, limit, offset)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")

View File

@ -30,6 +30,7 @@ from ahriman.core.database import SQLite
from ahriman.core.distributed import WorkersCache
from ahriman.core.exceptions import InitializeError
from ahriman.core.spawn import Spawn
from ahriman.core.status.client import Client
from ahriman.core.status.watcher import Watcher
from ahriman.models.repository_id import RepositoryId
from ahriman.web.apispec import setup_apispec
@ -167,7 +168,8 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
watchers: dict[RepositoryId, Watcher] = {}
for repository_id in repositories:
application.logger.info("load repository %s", repository_id)
watchers[repository_id] = Watcher(repository_id, database)
client = Client.load(repository_id, configuration, database, report=False) # explicitly load local client
watchers[repository_id] = Watcher(client)
application[WatcherKey] = watchers
# workers cache
application[WorkersKey] = WorkersCache(configuration)

View File

@ -93,7 +93,6 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
side_effect=lambda *args: packages[args[0].name])
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages",
return_value={"devtools", "python-build", "python-pytest"})
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown")
result = application.with_dependencies([package_ahriman], process_dependencies=True)
@ -107,11 +106,6 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
], any_order=True)
packages_mock.assert_called_once_with()
update_remote_mock.assert_has_calls([
MockCall(package_python_schedule),
MockCall(packages["python"]),
MockCall(packages["python-installer"]),
], any_order=True)
status_client_mock.assert_has_calls([
MockCall(package_python_schedule),
MockCall(packages["python"]),

View File

@ -41,11 +41,11 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac
"""
mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown")
application_packages._add_aur(package_ahriman.base, "packager")
build_queue_mock.assert_called_once_with(package_ahriman)
update_remote_mock.assert_called_once_with(package_ahriman)
status_client_mock.assert_called_once_with(package_ahriman)
def test_add_directory(application_packages: ApplicationPackages, package_ahriman: Package,
@ -153,11 +153,11 @@ def test_add_repository(application_packages: ApplicationPackages, package_ahrim
"""
mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman)
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown")
application_packages._add_repository(package_ahriman.base, "packager")
build_queue_mock.assert_called_once_with(package_ahriman)
update_remote_mock.assert_called_once_with(package_ahriman)
status_client_mock.assert_called_once_with(package_ahriman)
def test_add_add_archive(application_packages: ApplicationPackages, package_ahriman: Package,

View File

@ -1,15 +1,15 @@
from ahriman.application.application.application_properties import ApplicationProperties
def test_create_tree(application_properties: ApplicationProperties) -> None:
"""
must have repository attribute
"""
assert application_properties.repository
def test_architecture(application_properties: ApplicationProperties) -> None:
"""
must return repository architecture
"""
assert application_properties.architecture == application_properties.repository_id.architecture
def test_reporter(application_properties: ApplicationProperties) -> None:
"""
must have reporter attribute
"""
assert application_properties.reporter

View File

@ -17,14 +17,12 @@ def test_changes(application_repository: ApplicationRepository, package_ahriman:
must generate changes for the packages
"""
changes = Changes("hash", "change")
hashes_mock = mocker.patch("ahriman.core.database.SQLite.hashes_get", return_value={
package_ahriman.base: changes.last_commit_sha,
})
hashes_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get", return_value=changes)
changes_mock = mocker.patch("ahriman.core.repository.Repository.package_changes", return_value=changes)
report_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set")
report_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_set")
application_repository.changes([package_ahriman])
hashes_mock.assert_called_once_with()
hashes_mock.assert_called_once_with(package_ahriman.base)
changes_mock.assert_called_once_with(package_ahriman, changes.last_commit_sha)
report_mock.assert_called_once_with(package_ahriman.base, changes)
@ -34,9 +32,8 @@ def test_changes_skip(application_repository: ApplicationRepository, package_ahr
"""
must skip change generation if no last commit sha has been found
"""
mocker.patch("ahriman.core.database.SQLite.hashes_get", return_value={})
changes_mock = mocker.patch("ahriman.core.repository.Repository.package_changes")
report_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set")
report_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_set")
application_repository.changes([package_ahriman])
changes_mock.assert_not_called()

View File

@ -62,11 +62,11 @@ def test_run_with_patches(args: argparse.Namespace, configuration: Configuration
args.variable = ["KEY=VALUE"]
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.application.application.Application.add")
application_mock = mocker.patch("ahriman.core.database.SQLite.patches_insert")
application_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_add")
_, repository_id = configuration.check_loaded()
Add.run(args, repository_id, configuration, report=False)
application_mock.assert_called_once_with(args.package[0], [PkgbuildPatch("KEY", "VALUE")])
application_mock.assert_called_once_with(args.package[0], PkgbuildPatch("KEY", "VALUE"))
def test_run_with_updates(args: argparse.Namespace, configuration: Configuration, repository: Repository,

View File

@ -160,13 +160,13 @@ def test_patch_set_list(application: Application, mocker: MockerFixture) -> None
"""
must list available patches for the command
"""
get_mock = mocker.patch("ahriman.core.database.SQLite.patches_list",
return_value={"ahriman": PkgbuildPatch(None, "patch")})
get_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get",
return_value=[PkgbuildPatch(None, "patch")])
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
Patch.patch_set_list(application, "ahriman", ["version"], False)
get_mock.assert_called_once_with("ahriman", ["version"])
get_mock.assert_called_once_with("ahriman", "version")
print_mock.assert_called_once_with(verbose=True, log_fn=pytest.helpers.anyvar(int), separator=" = ")
check_mock.assert_called_once_with(False, False)

View File

@ -185,7 +185,7 @@ def test_extract_packages_by_status(application: Application, mocker: MockerFixt
("package2", BuildStatus(BuildStatusEnum.Failed)),
])
assert Rebuild.extract_packages(application, BuildStatusEnum.Failed, from_database=True) == ["package2"]
packages_mock.assert_called_once_with()
packages_mock.assert_called_once_with(application.repository_id)
def test_extract_packages_from_database(application: Application, mocker: MockerFixture) -> None:
@ -194,4 +194,4 @@ def test_extract_packages_from_database(application: Application, mocker: Mocker
"""
packages_mock = mocker.patch("ahriman.core.database.SQLite.packages_get")
Rebuild.extract_packages(application, None, from_database=True)
packages_mock.assert_called_once_with()
packages_mock.assert_called_once_with(application.repository_id)

View File

@ -50,13 +50,11 @@ def test_run_packages(args: argparse.Namespace, configuration: Configuration, re
args.package = [package_ahriman.base, "package"]
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
add_mock = mocker.patch("ahriman.core.status.client.Client.package_add")
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
update_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_add")
_, repository_id = configuration.check_loaded()
StatusUpdate.run(args, repository_id, configuration, report=False)
add_mock.assert_called_once_with(package_ahriman, args.status)
update_mock.assert_called_once_with("package", args.status)
update_mock.assert_called_once_with(package_ahriman, args.status)
def test_run_remove(args: argparse.Namespace, configuration: Configuration, repository: Repository,
@ -68,7 +66,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo
args.package = [package_ahriman.base]
args.action = Action.Remove
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
update_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
update_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
_, repository_id = configuration.check_loaded()
StatusUpdate.run(args, repository_id, configuration, report=False)

View File

@ -13,6 +13,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository
from ahriman.core.spawn import Spawn
from ahriman.core.status.client import Client
from ahriman.core.status.watcher import Watcher
from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
@ -276,6 +277,21 @@ def database(configuration: Configuration) -> SQLite:
database.path.unlink()
@pytest.fixture
def local_client(database: SQLite, configuration: Configuration) -> Client:
"""
local status client
Args:
database(SQLite): database fixture
Returns:
Client: local status client test instance
"""
_, repository_id = configuration.check_loaded()
return Client.load(repository_id, configuration, database, report=False)
@pytest.fixture
def package_ahriman(package_description_ahriman: PackageDescription, remote_source: RemoteSource) -> Package:
"""
@ -559,15 +575,14 @@ def user() -> User:
@pytest.fixture
def watcher(repository_id: RepositoryId, database: SQLite) -> Watcher:
def watcher(local_client: Client) -> Watcher:
"""
package status watcher fixture
Args:
repository_id(RepositoryId): repository identifier fixture
database(SQLite): database fixture
local_client(Client): local status client fixture
Returns:
Watcher: package status watcher test instance
"""
return Watcher(repository_id, database)
return Watcher(local_client)

View File

@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
from unittest.mock import call as MockCall
from ahriman.core.build_tools.task import Task
from ahriman.core.database import SQLite
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_build(task_ahriman: Task, mocker: MockerFixture) -> None:
@ -91,18 +91,19 @@ def test_build_no_debug(task_ahriman: Task, mocker: MockerFixture) -> None:
])
def test_init(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None:
def test_init(task_ahriman: Task, mocker: MockerFixture) -> None:
"""
must copy tree instead of fetch
"""
patches = [PkgbuildPatch("hash", "patch")]
mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package)
load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha")
assert task_ahriman.init(Path("ahriman"), database, None) == "sha"
load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, [], task_ahriman.paths)
assert task_ahriman.init(Path("ahriman"), patches, None) == "sha"
load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, patches, task_ahriman.paths)
def test_init_bump_pkgrel(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None:
def test_init_bump_pkgrel(task_ahriman: Task, mocker: MockerFixture) -> None:
"""
must bump pkgrel if it is same as provided
"""
@ -111,11 +112,11 @@ def test_init_bump_pkgrel(task_ahriman: Task, database: SQLite, mocker: MockerFi
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
local = Path("ahriman")
assert task_ahriman.init(local, database, task_ahriman.package.version) == "sha"
assert task_ahriman.init(local, [], task_ahriman.package.version) == "sha"
write_mock.assert_called_once_with(local / "PKGBUILD")
def test_init_bump_pkgrel_skip(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None:
def test_init_bump_pkgrel_skip(task_ahriman: Task, mocker: MockerFixture) -> None:
"""
must keep pkgrel if version is different from provided
"""
@ -123,5 +124,5 @@ def test_init_bump_pkgrel_skip(task_ahriman: Task, database: SQLite, mocker: Moc
mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha")
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
assert task_ahriman.init(Path("ahriman"), database, f"2:{task_ahriman.package.version}") == "sha"
assert task_ahriman.init(Path("ahriman"), [], f"2:{task_ahriman.package.version}") == "sha"
write_mock.assert_not_called()

View File

@ -53,13 +53,3 @@ def test_changes_insert_remove_full(database: SQLite, package_ahriman: Package,
assert database.changes_get(package_python_schedule.base).changes is None
assert database.changes_get(
package_ahriman.base, RepositoryId("i686", database._repository_id.name)).changes == "change2"
def test_hashes_get(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must return non-empty hashes for packages
"""
database.changes_insert(package_ahriman.base, Changes("sha1", "change1"))
database.changes_insert(package_python_schedule.base, Changes())
assert database.hashes_get() == {package_ahriman.base: "sha1"}

View File

@ -1,5 +1,3 @@
from pathlib import Path
from ahriman.core.database import SQLite
from ahriman.models.dependencies import Dependencies
from ahriman.models.package import Package
@ -10,16 +8,19 @@ def test_dependencies_insert_get(database: SQLite, package_ahriman: Package) ->
"""
must insert and get dependencies
"""
dependencies = Dependencies(package_ahriman.base, {Path("usr/lib/python3.11/site-packages"): ["python"]})
database.dependencies_insert(dependencies)
assert database.dependencies_get(package_ahriman.base) == [dependencies]
dependencies = Dependencies({"usr/lib/python3.11/site-packages": ["python"]})
database.dependencies_insert(package_ahriman.base, dependencies)
assert database.dependencies_get(package_ahriman.base) == {package_ahriman.base: dependencies}
dependencies2 = Dependencies(package_ahriman.base, {Path("usr/lib/python3.11/site-packages"): ["python3"]})
database.dependencies_insert(dependencies2, RepositoryId("i686", database._repository_id.name))
assert database.dependencies_get() == [dependencies]
assert database.dependencies_get(package_ahriman.base) == [dependencies]
assert database.dependencies_get(
package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == [dependencies2]
dependencies2 = Dependencies({"usr/lib/python3.11/site-packages": ["python3"]})
database.dependencies_insert(
package_ahriman.base, dependencies2, RepositoryId(
"i686", database._repository_id.name))
assert database.dependencies_get() == {package_ahriman.base: dependencies}
assert database.dependencies_get(package_ahriman.base) == {package_ahriman.base: dependencies}
assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == {
package_ahriman.base: dependencies2
}
def test_dependencies_insert_remove(database: SQLite, package_ahriman: Package,
@ -27,23 +28,28 @@ def test_dependencies_insert_remove(database: SQLite, package_ahriman: Package,
"""
must remove dependencies for the package
"""
dependencies1 = Dependencies(package_ahriman.base, {Path("usr"): ["python"]})
database.dependencies_insert(dependencies1)
dependencies2 = Dependencies(package_python_schedule.base, {Path("usr"): ["filesystem"]})
database.dependencies_insert(dependencies2)
dependencies3 = Dependencies(package_ahriman.base, {Path("usr"): ["python3"]})
database.dependencies_insert(dependencies3, RepositoryId("i686", database._repository_id.name))
dependencies1 = Dependencies({"usr": ["python"]})
database.dependencies_insert(package_ahriman.base, dependencies1)
dependencies2 = Dependencies({"usr": ["filesystem"]})
database.dependencies_insert(package_python_schedule.base, dependencies2)
dependencies3 = Dependencies({"usr": ["python3"]})
database.dependencies_insert(
package_ahriman.base, dependencies3, RepositoryId(
"i686", database._repository_id.name))
assert database.dependencies_get() == [dependencies1, dependencies2]
assert database.dependencies_get() == {
package_ahriman.base: dependencies1,
package_python_schedule.base: dependencies2,
}
database.dependencies_remove(package_ahriman.base)
assert database.dependencies_get(package_ahriman.base) == []
assert database.dependencies_get(package_python_schedule.base) == [dependencies2]
assert database.dependencies_get(package_ahriman.base) == {}
assert database.dependencies_get(package_python_schedule.base) == {package_python_schedule.base: dependencies2}
# insert null
database.dependencies_remove(package_ahriman.base, RepositoryId("i686", database._repository_id.name))
assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == []
assert database.dependencies_get(package_python_schedule.base) == [dependencies2]
assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == {}
assert database.dependencies_get(package_python_schedule.base) == {package_python_schedule.base: dependencies2}
def test_dependencies_insert_remove_full(database: SQLite, package_ahriman: Package,
@ -51,11 +57,11 @@ def test_dependencies_insert_remove_full(database: SQLite, package_ahriman: Pack
"""
must remove all dependencies for the repository
"""
database.dependencies_insert(Dependencies(package_ahriman.base, {Path("usr"): ["python"]}))
database.dependencies_insert(Dependencies(package_python_schedule.base, {Path("usr"): ["filesystem"]}))
database.dependencies_insert(Dependencies(package_ahriman.base, {Path("usr"): ["python3"]}),
database.dependencies_insert(package_ahriman.base, Dependencies({"usr": ["python"]}))
database.dependencies_insert(package_python_schedule.base, Dependencies({"usr": ["filesystem"]}))
database.dependencies_insert(package_ahriman.base, Dependencies({"usr": ["python3"]}),
RepositoryId("i686", database._repository_id.name))
database.dependencies_remove(None)
assert database.dependencies_get() == []
assert database.dependencies_get() == {}
assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name))

View File

@ -5,7 +5,7 @@ from sqlite3 import Connection
from unittest.mock import call as MockCall
from ahriman.core.database import SQLite
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
@ -66,14 +66,6 @@ def test_package_update_insert_packages_no_arch(database: SQLite, connection: Co
connection.executemany(pytest.helpers.anyvar(str, strict=True), [])
def test_package_update_insert_status(database: SQLite, connection: Connection, package_ahriman: Package) -> None:
"""
must insert single package status
"""
database._package_update_insert_status(connection, package_ahriman.base, BuildStatus(), database._repository_id)
connection.execute(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
def test_packages_get_select_package_bases(database: SQLite, connection: Connection) -> None:
"""
must select all bases
@ -131,16 +123,12 @@ def test_package_update(database: SQLite, package_ahriman: Package, mocker: Mock
"""
must update package status
"""
status = BuildStatus()
insert_base_mock = mocker.patch("ahriman.core.database.SQLite._package_update_insert_base")
insert_status_mock = mocker.patch("ahriman.core.database.SQLite._package_update_insert_status")
insert_packages_mock = mocker.patch("ahriman.core.database.SQLite._package_update_insert_packages")
remove_packages_mock = mocker.patch("ahriman.core.database.SQLite._package_remove_packages")
database.package_update(package_ahriman, status)
database.package_update(package_ahriman)
insert_base_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman, database._repository_id)
insert_status_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base, status,
database._repository_id)
insert_packages_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman,
database._repository_id)
remove_packages_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base,
@ -168,7 +156,8 @@ def test_package_update_get(database: SQLite, package_ahriman: Package) -> None:
must insert and retrieve package
"""
status = BuildStatus()
database.package_update(package_ahriman, status)
database.package_update(package_ahriman)
database.status_update(package_ahriman.base, status)
assert next((db_package, db_status)
for db_package, db_status in database.packages_get()
if db_package.base == package_ahriman.base) == (package_ahriman, status)
@ -179,7 +168,7 @@ def test_package_update_remove_get(database: SQLite, package_ahriman: Package) -
must insert, remove and retrieve package
"""
status = BuildStatus()
database.package_update(package_ahriman, status)
database.package_update(package_ahriman)
database.package_remove(package_ahriman.base)
assert not database.packages_get()
@ -188,11 +177,12 @@ def test_package_update_update(database: SQLite, package_ahriman: Package) -> No
"""
must perform update for existing package
"""
database.package_update(package_ahriman, BuildStatus())
database.package_update(package_ahriman, BuildStatus(BuildStatusEnum.Failed))
assert next(db_status.status
for db_package, db_status in database.packages_get()
if db_package.base == package_ahriman.base) == BuildStatusEnum.Failed
database.package_update(package_ahriman)
package_ahriman.version = "1.0.0"
database.package_update(package_ahriman)
assert next(db_package.version
for db_package, _ in database.packages_get()
if db_package.base == package_ahriman.base) == package_ahriman.version
def test_remote_update_get(database: SQLite, package_ahriman: Package) -> None:
@ -213,3 +203,14 @@ def test_remote_update_update(database: SQLite, package_ahriman: Package) -> Non
database.package_base_update(package_ahriman)
assert database.remotes_get()[package_ahriman.base] == remote_source
def test_status_update(database: SQLite, package_ahriman: Package) -> None:
"""
must insert single package status
"""
status = BuildStatus()
database.package_update(package_ahriman, database._repository_id)
database.status_update(package_ahriman.base, status, database._repository_id)
assert database.packages_get(database._repository_id) == [(package_ahriman, status)]

View File

@ -44,6 +44,7 @@ def test_package_clear(database: SQLite, mocker: MockerFixture) -> None:
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_remove")
dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_remove")
tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear")
database.package_clear("package")
build_queue_mock.assert_called_once_with("package")
@ -51,3 +52,4 @@ def test_package_clear(database: SQLite, mocker: MockerFixture) -> None:
logs_mock.assert_called_once_with("package", None)
changes_mock.assert_called_once_with("package")
dependencies_mock.assert_called_once_with("package")
tree_clear_mock.assert_called_once_with("package")

View File

@ -5,15 +5,15 @@ from pytest_mock import MockerFixture
from unittest.mock import call as MockCall
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import GitRemoteError
from ahriman.core.gitremote.remote_push import RemotePush
from ahriman.core.status.client import Client
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.result import Result
def test_package_update(database: SQLite, configuration: Configuration, package_ahriman: Package,
def test_package_update(local_client: Client, configuration: Configuration, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must update single package
@ -27,9 +27,10 @@ def test_package_update(database: SQLite, configuration: Configuration, package_
rmtree_mock = mocker.patch("shutil.rmtree")
unlink_mock = mocker.patch("pathlib.Path.unlink")
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_get", return_value=[patch1, patch2])
patches_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get",
return_value=[patch1, patch2])
patches_write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
runner = RemotePush(database, configuration, "gitremote")
runner = RemotePush(local_client, configuration, "gitremote")
assert runner.package_update(package_ahriman, local) == package_ahriman.base
glob_mock.assert_called_once_with(".git*")
@ -39,28 +40,28 @@ def test_package_update(database: SQLite, configuration: Configuration, package_
])
unlink_mock.assert_called_once_with()
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.remote)
patches_mock.assert_called_once_with(package_ahriman.base)
patches_mock.assert_called_once_with(package_ahriman.base, None)
patches_write_mock.assert_has_calls([
MockCall(local / package_ahriman.base / f"ahriman-{package_ahriman.base}.patch"),
MockCall(local / package_ahriman.base / f"ahriman-{patch2.key}.patch"),
])
def test_packages_update(database: SQLite, configuration: Configuration, result: Result, package_ahriman: Package,
def test_packages_update(local_client: Client, configuration: Configuration, result: Result, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must generate packages update
"""
update_mock = mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.package_update",
return_value=[package_ahriman.base])
runner = RemotePush(database, configuration, "gitremote")
runner = RemotePush(local_client, configuration, "gitremote")
local = Path("local")
assert list(runner.packages_update(result, local))
update_mock.assert_called_once_with(package_ahriman, local)
def test_run(database: SQLite, configuration: Configuration, result: Result, package_ahriman: Package,
def test_run(local_client: Client, configuration: Configuration, result: Result, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must push changes on result
@ -68,7 +69,7 @@ def test_run(database: SQLite, configuration: Configuration, result: Result, pac
mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.packages_update", return_value=[package_ahriman.base])
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
push_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.push")
runner = RemotePush(database, configuration, "gitremote")
runner = RemotePush(local_client, configuration, "gitremote")
runner.run(result)
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source)
@ -77,12 +78,12 @@ def test_run(database: SQLite, configuration: Configuration, result: Result, pac
)
def test_run_failed(database: SQLite, configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
def test_run_failed(local_client: Client, configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
"""
must reraise exception on error occurred
"""
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception())
runner = RemotePush(database, configuration, "gitremote")
runner = RemotePush(local_client, configuration, "gitremote")
with pytest.raises(GitRemoteError):
runner.run(result)

View File

@ -1,8 +1,8 @@
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.gitremote import RemotePushTrigger
from ahriman.core.status.client import Client
from ahriman.models.package import Package
from ahriman.models.result import Result
@ -19,15 +19,15 @@ def test_configuration_sections(configuration: Configuration) -> None:
def test_on_result(configuration: Configuration, result: Result, package_ahriman: Package,
database: SQLite, mocker: MockerFixture) -> None:
local_client: Client, mocker: MockerFixture) -> None:
"""
must push changes on result
"""
database_mock = mocker.patch("ahriman.core._Context.get", return_value=database)
database_mock = mocker.patch("ahriman.core._Context.get", return_value=local_client)
run_mock = mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.run")
_, repository_id = configuration.check_loaded()
trigger = RemotePushTrigger(repository_id, configuration)
trigger.on_result(result, [package_ahriman])
database_mock.assert_called_once_with(SQLite)
database_mock.assert_called_once_with(Client)
run_mock.assert_called_once_with(result)

View File

@ -43,13 +43,13 @@ def test_emit(configuration: Configuration, log_record: logging.LogRecord, packa
must emit log record to reporter
"""
log_record_id = log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs")
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs_add")
_, repository_id = configuration.check_loaded()
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False)
handler.emit(log_record)
log_mock.assert_called_once_with(log_record_id, log_record)
log_mock.assert_called_once_with(log_record_id, log_record.created, log_record.getMessage())
def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord, package_ahriman: Package,
@ -58,7 +58,7 @@ def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord
must call handle error on exception
"""
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
mocker.patch("ahriman.core.status.client.Client.package_logs", side_effect=Exception())
mocker.patch("ahriman.core.status.client.Client.package_logs_add", side_effect=Exception())
handle_error_mock = mocker.patch("logging.Handler.handleError")
_, repository_id = configuration.check_loaded()
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False)
@ -73,7 +73,7 @@ def test_emit_suppress_failed(configuration: Configuration, log_record: logging.
must not call handle error on exception if suppress flag is set
"""
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
mocker.patch("ahriman.core.status.client.Client.package_logs", side_effect=Exception())
mocker.patch("ahriman.core.status.client.Client.package_logs_add", side_effect=Exception())
handle_error_mock = mocker.patch("logging.Handler.handleError")
_, repository_id = configuration.check_loaded()
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=True)
@ -86,7 +86,7 @@ def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord,
"""
must skip log record posting if no package base set
"""
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs")
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs_add")
_, repository_id = configuration.check_loaded()
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False)

View File

@ -17,22 +17,21 @@ def test_process_build(executor: Executor, package_ahriman: Package, passwd: Any
"""
must run build process
"""
dependencies = Dependencies(package_ahriman.base)
mocker.patch("ahriman.models.repository_paths.getpwuid", return_value=passwd)
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init", return_value="sha")
move_mock = mocker.patch("shutil.move")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building")
commit_sha_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set")
commit_sha_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_set")
depends_on_mock = mocker.patch("ahriman.models.package_archive.PackageArchive.depends_on",
return_value=dependencies)
dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_insert")
return_value=Dependencies())
dependencies_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_set")
executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=False)
init_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), None)
depends_on_mock.assert_called_once_with()
dependencies_mock.assert_called_once_with(dependencies)
dependencies_mock.assert_called_once_with(package_ahriman.base, Dependencies())
# must move files (once)
move_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base)
# must update status
@ -77,18 +76,14 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke
must run remove process for whole base
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear")
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
database_mock = mocker.patch("ahriman.core.database.SQLite.package_clear")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
executor.process_remove([package_ahriman.base])
# must remove via alpm wrapper
repo_remove_mock.assert_called_once_with(
package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath)
# must update status and remove package files
tree_clear_mock.assert_called_once_with(package_ahriman.base)
database_mock.assert_called_once_with(package_ahriman.base)
status_client_mock.assert_called_once_with(package_ahriman.base)
@ -101,9 +96,7 @@ def test_process_remove_with_debug(executor: Executor, package_ahriman: Package,
f"{package_ahriman.base}-debug": package_ahriman.packages[package_ahriman.base],
}
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear")
mocker.patch("ahriman.core.database.SQLite.package_clear")
mocker.patch("ahriman.core.status.client.Client.package_remove")
mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
executor.process_remove([package_ahriman.base])
@ -121,7 +114,7 @@ def test_process_remove_base_multiple(executor: Executor, package_python_schedul
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
executor.process_remove([package_python_schedule.base])
# must remove via alpm wrapper
@ -140,7 +133,7 @@ def test_process_remove_base_single(executor: Executor, package_python_schedule:
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
executor.process_remove(["python2-schedule"])
# must remove via alpm wrapper
@ -155,7 +148,7 @@ def test_process_remove_failed(executor: Executor, package_ahriman: Package, moc
must suppress tree clear errors during package base removal
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear", side_effect=Exception())
mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove", side_effect=Exception())
executor.process_remove([package_ahriman.base])
@ -186,7 +179,7 @@ def test_process_remove_unknown(executor: Executor, package_ahriman: Package, mo
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[])
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
executor.process_remove([package_ahriman.base])
repo_remove_mock.assert_not_called()

View File

@ -21,9 +21,9 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag
for package, props in package_python_schedule.packages.items()
] + [package_ahriman]
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages)
mocker.patch("ahriman.core.database.SQLite.remotes_get", return_value={
package_ahriman.base: package_ahriman.base
})
mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[
(package_ahriman, None),
])
packages = package_info.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
assert len(packages) == 2

View File

@ -6,6 +6,7 @@ from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository
from ahriman.core.sign.gpg import GPG
from ahriman.core.status.client import Client
def test_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None:
@ -32,5 +33,6 @@ def test_set_context(configuration: Configuration, database: SQLite, mocker: Moc
MockCall(Configuration, instance.configuration),
MockCall(Pacman, instance.pacman),
MockCall(GPG, instance.sign),
MockCall(Client, instance.reporter),
MockCall(Repository, instance),
])

View File

@ -153,11 +153,12 @@ def test_updates_dependencies(update_handler: UpdateHandler, package_ahriman: Pa
"""
packages_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages",
return_value=[package_ahriman, package_python_schedule])
dependencies = [
Dependencies(package_ahriman.base, {Path("usr/lib/python3.11/site-packages"): ["python"]}),
Dependencies(package_python_schedule.base, {Path("usr/lib/python3.12/site-packages"): ["python"]}),
]
mocker.patch("ahriman.core.database.SQLite.dependencies_get", return_value=dependencies)
dependencies = {
package_ahriman.base: Dependencies({"usr/lib/python3.11/site-packages": ["python"]}),
package_python_schedule.base: Dependencies({"usr/lib/python3.12/site-packages": ["python"]}),
}
mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get",
side_effect=lambda base: dependencies[base])
mocker.patch("ahriman.core.alpm.pacman.Pacman.files",
return_value={"python": {Path("usr/lib/python3.12/site-packages")}})
@ -171,7 +172,8 @@ def test_updates_dependencies_skip_unknown(update_handler: UpdateHandler, packag
must skip unknown package dependencies
"""
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.core.database.SQLite.dependencies_get", return_value=[])
mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get",
return_value=Dependencies())
mocker.patch("ahriman.core.alpm.pacman.Pacman.files",
return_value={"python": {Path("usr/lib/python3.12/site-packages")}})
@ -184,10 +186,8 @@ def test_updates_dependencies_partial(update_handler: UpdateHandler, package_ahr
must skip broken dependencies update if at least one package provides file
"""
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
dependencies = [
Dependencies(package_ahriman.base, {Path("usr"): ["filesystem", "python"]}),
]
mocker.patch("ahriman.core.database.SQLite.dependencies_get", return_value=dependencies)
dependencies = Dependencies({"usr": ["filesystem", "python"]})
mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", return_value=dependencies)
mocker.patch("ahriman.core.alpm.pacman.Pacman.files", return_value={
"filesystem": {Path("usr")},
"python": {Path("usr")},

View File

@ -1,26 +1,39 @@
import logging
import pytest
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.status.client import Client
from ahriman.core.status.local_client import LocalClient
from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_load_dummy_client(configuration: Configuration) -> None:
"""
must load dummy client if no settings and database set
"""
_, repository_id = configuration.check_loaded()
assert isinstance(Client.load(repository_id, configuration, report=True), Client)
def test_load_local_client(configuration: Configuration, database: SQLite) -> None:
"""
must load dummy client if no settings set
"""
_, repository_id = configuration.check_loaded()
assert not isinstance(Client.load(repository_id, configuration, report=True), WebClient)
assert isinstance(Client.load(repository_id, configuration, database, report=True), LocalClient)
def test_load_dummy_client_disabled(configuration: Configuration) -> None:
def test_load_local_client_disabled(configuration: Configuration, database: SQLite) -> None:
"""
must load dummy client if report is set to False
"""
@ -28,10 +41,10 @@ def test_load_dummy_client_disabled(configuration: Configuration) -> None:
configuration.set_option("web", "port", "8080")
_, repository_id = configuration.check_loaded()
assert not isinstance(Client.load(repository_id, configuration, report=False), WebClient)
assert isinstance(Client.load(repository_id, configuration, database, report=False), LocalClient)
def test_load_dummy_client_disabled_in_configuration(configuration: Configuration) -> None:
def test_load_local_client_disabled_in_configuration(configuration: Configuration, database: SQLite) -> None:
"""
must load dummy client if disabled in configuration
"""
@ -40,19 +53,19 @@ def test_load_dummy_client_disabled_in_configuration(configuration: Configuratio
configuration.set_option("status", "enabled", "no")
_, repository_id = configuration.check_loaded()
assert not isinstance(Client.load(repository_id, configuration, report=True), WebClient)
assert isinstance(Client.load(repository_id, configuration, database, report=True), LocalClient)
def test_load_full_client_from_address(configuration: Configuration) -> None:
def test_load_web_client_from_address(configuration: Configuration, database: SQLite) -> None:
"""
must load full client by using address
"""
configuration.set_option("status", "address", "http://localhost:8080")
_, repository_id = configuration.check_loaded()
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient)
def test_load_full_client_from_legacy_host(configuration: Configuration) -> None:
def test_load_web_client_from_legacy_host(configuration: Configuration, database: SQLite) -> None:
"""
must load full client if host and port settings set
"""
@ -60,82 +73,144 @@ def test_load_full_client_from_legacy_host(configuration: Configuration) -> None
configuration.set_option("web", "port", "8080")
_, repository_id = configuration.check_loaded()
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient)
def test_load_full_client_from_legacy_address(configuration: Configuration) -> None:
def test_load_web_client_from_legacy_address(configuration: Configuration, database: SQLite) -> None:
"""
must load full client by using legacy address
"""
configuration.set_option("web", "address", "http://localhost:8080")
_, repository_id = configuration.check_loaded()
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient)
def test_load_full_client_from_legacy_unix_socket(configuration: Configuration) -> None:
def test_load_web_client_from_legacy_unix_socket(configuration: Configuration, database: SQLite) -> None:
"""
must load full client by using unix socket
"""
configuration.set_option("web", "unix_socket", "/var/lib/ahriman/ahriman-web.sock")
_, repository_id = configuration.check_loaded()
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient)
def test_package_add(client: Client, package_ahriman: Package) -> None:
"""
must process package addition without errors
must raise not implemented on package addition
"""
client.package_add(package_ahriman, BuildStatusEnum.Unknown)
with pytest.raises(NotImplementedError):
client.package_add(package_ahriman, BuildStatusEnum.Unknown)
def test_package_changes_get(client: Client, package_ahriman: Package) -> None:
"""
must return null changes
must raise not implemented on package changes request
"""
assert client.package_changes_get(package_ahriman.base) == Changes()
with pytest.raises(NotImplementedError):
client.package_changes_get(package_ahriman.base)
def test_package_changes_set(client: Client, package_ahriman: Package) -> None:
"""
must process changes update without errors
must raise not implemented on changes update
"""
client.package_changes_set(package_ahriman.base, Changes())
with pytest.raises(NotImplementedError):
client.package_changes_set(package_ahriman.base, Changes())
def test_package_dependencies_get(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on package dependencies request
"""
with pytest.raises(NotImplementedError):
client.package_dependencies_get(package_ahriman.base)
def test_package_dependencies_set(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on dependencies update
"""
with pytest.raises(NotImplementedError):
client.package_dependencies_set(package_ahriman.base, Dependencies())
def test_package_get(client: Client, package_ahriman: Package) -> None:
"""
must return empty package list
must raise not implemented on packages get
"""
assert client.package_get(package_ahriman.base) == []
assert client.package_get(None) == []
with pytest.raises(NotImplementedError):
assert client.package_get(package_ahriman.base)
def test_package_logs(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None:
def test_package_logs_add(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None:
"""
must process log record without errors
must process log record addition without exception
"""
client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
log_record_id = LogRecordId(package_ahriman.base, package_ahriman.version)
client.package_logs_add(log_record_id, log_record.created, log_record.getMessage())
def test_package_logs_get(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on logs retrieval
"""
with pytest.raises(NotImplementedError):
client.package_logs_get(package_ahriman.base)
def test_package_logs_remove(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on logs removal
"""
with pytest.raises(NotImplementedError):
client.package_logs_remove(package_ahriman.base, package_ahriman.version)
def test_package_patches_add(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on patches addition
"""
with pytest.raises(NotImplementedError):
client.package_patches_add(package_ahriman.base, PkgbuildPatch(None, ""))
def test_package_patches_get(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on patches retrieval
"""
with pytest.raises(NotImplementedError):
client.package_patches_get(package_ahriman.base, None)
def test_package_patches_remove(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on patches removal
"""
with pytest.raises(NotImplementedError):
client.package_patches_remove(package_ahriman.base, None)
def test_package_remove(client: Client, package_ahriman: Package) -> None:
"""
must process remove without errors
must raise not implemented on package removal
"""
client.package_remove(package_ahriman.base)
with pytest.raises(NotImplementedError):
client.package_remove(package_ahriman.base)
def test_package_update(client: Client, package_ahriman: Package) -> None:
"""
must update package status without errors
must raise not implemented on package update
"""
client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
with pytest.raises(NotImplementedError):
client.package_set(package_ahriman.base, BuildStatusEnum.Unknown)
def test_set_building(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set building status to the package
"""
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
update_mock = mocker.patch("ahriman.core.status.client.Client.package_set")
client.set_building(package_ahriman.base)
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Building)
@ -145,7 +220,7 @@ def test_set_failed(client: Client, package_ahriman: Package, mocker: MockerFixt
"""
must set failed status to the package
"""
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
update_mock = mocker.patch("ahriman.core.status.client.Client.package_set")
client.set_failed(package_ahriman.base)
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Failed)
@ -155,7 +230,7 @@ def test_set_pending(client: Client, package_ahriman: Package, mocker: MockerFix
"""
must set building status to the package
"""
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
update_mock = mocker.patch("ahriman.core.status.client.Client.package_set")
client.set_pending(package_ahriman.base)
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Pending)

View File

@ -0,0 +1,179 @@
import logging
import pytest
from pytest_mock import MockerFixture
from ahriman.core.status.local_client import LocalClient
from ahriman.models.build_status import BuildStatusEnum, BuildStatus
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_package_add(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package addition
"""
package_mock = mocker.patch("ahriman.core.database.SQLite.package_update")
status_mock = mocker.patch("ahriman.core.database.SQLite.status_update")
local_client.package_add(package_ahriman, BuildStatusEnum.Success)
package_mock.assert_called_once_with(package_ahriman, local_client.repository_id)
status_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int), local_client.repository_id)
def test_package_changes_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package changes
"""
changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_get")
local_client.package_changes_get(package_ahriman.base)
changes_mock.assert_called_once_with(package_ahriman.base, local_client.repository_id)
def test_package_changes_set(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package changes
"""
changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_insert")
changes = Changes()
local_client.package_changes_set(package_ahriman.base, changes)
changes_mock.assert_called_once_with(package_ahriman.base, changes, local_client.repository_id)
def test_package_dependencies_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package dependencies
"""
dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_get")
local_client.package_dependencies_get(package_ahriman.base)
dependencies_mock.assert_called_once_with(package_ahriman.base, local_client.repository_id)
def test_package_dependencies_set(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package dependencies
"""
dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_insert")
local_client.package_dependencies_set(package_ahriman.base, Dependencies())
dependencies_mock.assert_called_once_with(package_ahriman.base, Dependencies(), local_client.repository_id)
def test_package_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve packages
"""
result = [(package_ahriman, BuildStatus())]
package_mock = mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=result)
assert local_client.package_get(None) == result
package_mock.assert_called_once_with(local_client.repository_id)
def test_package_get_package(local_client: LocalClient, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must retrieve specific package
"""
result = [(package_ahriman, BuildStatus()), (package_python_schedule, BuildStatus())]
package_mock = mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=result)
assert local_client.package_get(package_ahriman.base) == [result[0]]
package_mock.assert_called_once_with(local_client.repository_id)
def test_package_logs_add(local_client: LocalClient, package_ahriman: Package, log_record: logging.LogRecord,
mocker: MockerFixture) -> None:
"""
must add package logs
"""
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert")
log_record_id = LogRecordId(package_ahriman.base, package_ahriman.version)
local_client.package_logs_add(log_record_id, log_record.created, log_record.getMessage())
logs_mock.assert_called_once_with(log_record_id, log_record.created, log_record.getMessage(),
local_client.repository_id)
def test_package_logs_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package logs
"""
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get")
local_client.package_logs_get(package_ahriman.base, 1, 2)
logs_mock.assert_called_once_with(package_ahriman.base, 1, 2, local_client.repository_id)
def test_package_logs_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must remove package logs
"""
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
local_client.package_logs_remove(package_ahriman.base, package_ahriman.version)
logs_mock.assert_called_once_with(package_ahriman.base, package_ahriman.version, local_client.repository_id)
def test_package_patches_add(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add package patches
"""
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_insert")
patch = PkgbuildPatch("key", "value")
local_client.package_patches_add(package_ahriman.base, patch)
patches_mock.assert_called_once_with(package_ahriman.base, [patch])
def test_package_patches_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package patches
"""
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_list")
local_client.package_patches_get(package_ahriman.base, None)
patches_mock.assert_called_once_with(package_ahriman.base, None)
def test_package_patches_get_key(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package patches for specific patch name
"""
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_list")
local_client.package_patches_get(package_ahriman.base, "key")
patches_mock.assert_called_once_with(package_ahriman.base, ["key"])
def test_package_patches_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must remove package patches
"""
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove")
local_client.package_patches_remove(package_ahriman.base, None)
patches_mock.assert_called_once_with(package_ahriman.base, None)
def test_package_patches_remove_key(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must remove package specific package patch
"""
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove")
local_client.package_patches_remove(package_ahriman.base, "key")
patches_mock.assert_called_once_with(package_ahriman.base, ["key"])
def test_package_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must remove package
"""
package_mock = mocker.patch("ahriman.core.database.SQLite.package_clear")
local_client.package_remove(package_ahriman.base)
package_mock.assert_called_once_with(package_ahriman.base)
def test_package_set(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package status
"""
status_mock = mocker.patch("ahriman.core.database.SQLite.status_update")
local_client.package_set(package_ahriman.base, BuildStatusEnum.Success)
status_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int), local_client.repository_id)

View File

@ -7,20 +7,31 @@ from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.status.watcher import Watcher
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_packages(watcher: Watcher, package_ahriman: Package) -> None:
"""
must return list of available packages
"""
assert not watcher.packages
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
assert watcher.packages
def test_load(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must correctly load packages
"""
cache_mock = mocker.patch("ahriman.core.database.SQLite.packages_get",
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_get",
return_value=[(package_ahriman, BuildStatus())])
watcher.load()
cache_mock.assert_called_once_with(watcher.repository_id)
cache_mock.assert_called_once_with(None)
package, status = watcher._known[package_ahriman.base]
assert package == package_ahriman
assert status.status == BuildStatusEnum.Unknown
@ -31,7 +42,7 @@ def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFi
must correctly load packages with known statuses
"""
status = BuildStatus(BuildStatusEnum.Success)
mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=[(package_ahriman, status)])
mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[(package_ahriman, status)])
watcher._known = {package_ahriman.base: (package_ahriman, status)}
watcher.load()
@ -39,6 +50,105 @@ def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFi
assert status.status == BuildStatusEnum.Success
def test_package_add(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add package to cache
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_add")
watcher.package_add(package_ahriman, BuildStatusEnum.Unknown)
assert watcher.packages
cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int))
def test_package_changes_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package changes
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_changes_get(package_ahriman.base)
cache_mock.assert_called_once_with(package_ahriman.base)
def test_package_changes_get_failed(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must fail if package is unknown during fetching changes
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get")
with pytest.raises(UnknownPackageError):
watcher.package_changes_get(package_ahriman.base)
cache_mock.assert_not_called()
def test_package_changes_set(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package changes
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_set")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_changes_set(package_ahriman.base, Changes())
cache_mock.assert_called_once_with(package_ahriman.base, Changes())
def test_package_changes_set_failed(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must fail if package is unknown during updating changes
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_set")
with pytest.raises(UnknownPackageError):
watcher.package_changes_set(package_ahriman.base, Changes())
cache_mock.assert_not_called()
def test_package_dependencies_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package dependencies
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_dependencies_get(package_ahriman.base)
cache_mock.assert_called_once_with(package_ahriman.base)
def test_package_dependencies_get_failed(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must fail if package is unknown during fetching dependencies
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get")
with pytest.raises(UnknownPackageError):
watcher.package_dependencies_get(package_ahriman.base)
cache_mock.assert_not_called()
def test_package_dependencies_set(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package dependencies
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_set")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_dependencies_set(package_ahriman.base, Dependencies())
cache_mock.assert_called_once_with(package_ahriman.base, Dependencies())
def test_package_dependencies_set_failed(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must fail if package is unknown during updating dependencies
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_set")
with pytest.raises(UnknownPackageError):
watcher.package_dependencies_set(package_ahriman.base, Dependencies())
cache_mock.assert_not_called()
def test_logs_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return package logs

View File

@ -0,0 +1,9 @@
from ahriman.models.dependencies import Dependencies
def test_from_json_view() -> None:
"""
must construct and serialize dependencies to json
"""
dependencies = Dependencies({"/usr/bin/python3": ["python"]})
assert Dependencies.from_json(dependencies.view()) == dependencies

View File

@ -65,7 +65,6 @@ def test_depends_on(package_archive_ahriman: PackageArchive, mocker: MockerFixtu
))
result = package_archive_ahriman.depends_on()
assert result.package_base == package_archive_ahriman.package.base
assert result.paths == {
Path("package1") / "file1": ["package1"],
Path("package2") / "file3": ["package2"],

View File

@ -51,6 +51,14 @@ def test_from_env() -> None:
assert PkgbuildPatch.from_env("KEY") == PkgbuildPatch("KEY", "")
def test_from_json_view() -> None:
"""
must correctly serialize to json
"""
patch = PkgbuildPatch("key", "value")
assert PkgbuildPatch.from_json(patch.view()) == patch
def test_parse() -> None:
"""
must parse string correctly
@ -124,13 +132,6 @@ def test_serialize_list() -> None:
assert PkgbuildPatch("key", ["val'ue", "val\"ue2"]).serialize() == """key=('val'"'"'ue' 'val"ue2')"""
def test_view() -> None:
"""
must correctly serialize to json
"""
assert PkgbuildPatch("key", "value").view() == {"key": "key", "value": "value"}
def test_write(mocker: MockerFixture) -> None:
"""
must write serialized value to the file

View File

@ -0,0 +1 @@
# schema testing goes in view class tests

View File

@ -0,0 +1 @@
# schema testing goes in view class tests

View File

@ -40,6 +40,18 @@ async def test_delete(client: TestClient, package_ahriman: Package, package_pyth
json={"created": 42.0, "message": "message", "version": "42"})
await client.post(f"/api/v1/packages/{package_python_schedule.base}/logs",
json={"created": 42.0, "message": "message", "version": "42"})
request_schema = pytest.helpers.schema_request(LogsView.delete, location="querystring")
payload = {}
assert not request_schema.validate(payload)
payload = {"version": "42"}
response = await client.delete(f"/api/v1/packages/{package_ahriman.base}/logs", params=payload)
assert response.status == 204
response = await client.get(f"/api/v1/packages/{package_ahriman.base}/logs")
logs = await response.json()
assert logs["logs"]
response = await client.delete(f"/api/v1/packages/{package_ahriman.base}/logs")
assert response.status == 204