Add tests (#1) (#5)

* add models tests (#1)

also replace single quote to double one to confort PEP docstring
+ move _check_output to class properties to make it available for
mocking

* alpm tests implementation

* try to replace os with pathlib

* update tests for pathlib

* fix includes glob and trim version from dependencies

* build_tools package tests

* repository component tests

* add sign tests

* complete status tests

* handle exceptions in actual_version calls

* complete core tests

* move configuration to root conftest

* application tests

* complete application tests

* change copyright to more generic one

* base web tests

* complete web tests

* complete testkit

also add argument parsers test
This commit is contained in:
2021-03-28 15:30:51 +03:00
committed by GitHub
parent 69499b2d0a
commit 74a244f06c
139 changed files with 4606 additions and 1124 deletions

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -28,11 +28,11 @@ HandlerType = Callable[[Request], Awaitable[StreamResponse]]
def exception_handler(logger: Logger) -> Callable[[Request, HandlerType], Awaitable[StreamResponse]]:
'''
"""
exception handler middleware. Just log any exception (except for client ones)
:param logger: class logger
:return: built middleware
'''
"""
@middleware
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
try:
@ -40,7 +40,7 @@ def exception_handler(logger: Logger) -> Callable[[Request, HandlerType], Awaita
except HTTPClientError:
raise
except Exception:
logger.exception(f'exception during performing request to {request.path}', exc_info=True)
logger.exception(f"exception during performing request to {request.path}")
raise
return handle

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -26,7 +26,7 @@ from ahriman.web.views.packages import PackagesView
def setup_routes(application: Application) -> None:
'''
"""
setup all defined routes
Available routes are:
@ -45,16 +45,16 @@ def setup_routes(application: Application) -> None:
POST /api/v1/package/:base update package base status
:param application: web application instance
'''
application.router.add_get('/', IndexView)
application.router.add_get('/index.html', IndexView)
"""
application.router.add_get("/", IndexView)
application.router.add_get("/index.html", IndexView)
application.router.add_get('/api/v1/ahriman', AhrimanView)
application.router.add_post('/api/v1/ahriman', AhrimanView)
application.router.add_get("/api/v1/ahriman", AhrimanView)
application.router.add_post("/api/v1/ahriman", AhrimanView)
application.router.add_get('/api/v1/packages', PackagesView)
application.router.add_post('/api/v1/packages', PackagesView)
application.router.add_get("/api/v1/packages", PackagesView)
application.router.add_post("/api/v1/packages", PackagesView)
application.router.add_delete('/api/v1/packages/{package}', PackageView)
application.router.add_get('/api/v1/packages/{package}', PackageView)
application.router.add_post('/api/v1/packages/{package}', PackageView)
application.router.add_delete("/api/v1/packages/{package}", PackageView)
application.router.add_get("/api/v1/packages/{package}", PackageView)
application.router.add_post("/api/v1/packages/{package}", PackageView)

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -24,19 +24,19 @@ from ahriman.web.views.base import BaseView
class AhrimanView(BaseView):
'''
"""
service status web view
'''
"""
async def get(self) -> Response:
'''
"""
get current service status
:return: 200 with service status object
'''
"""
return json_response(self.service.status.view())
async def post(self) -> Response:
'''
"""
update service status
JSON body must be supplied, the following model is used:
@ -45,11 +45,11 @@ class AhrimanView(BaseView):
}
:return: 204 on success
'''
"""
data = await self.request.json()
try:
status = BuildStatusEnum(data['status'])
status = BuildStatusEnum(data["status"])
except Exception as e:
raise HTTPBadRequest(text=str(e))

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -19,18 +19,18 @@
#
from aiohttp.web import View
from ahriman.core.watcher.watcher import Watcher
from ahriman.core.status.watcher import Watcher
class BaseView(View):
'''
"""
base web view to make things typed
'''
"""
@property
def service(self) -> Watcher:
'''
"""
:return: build status watcher instance
'''
watcher: Watcher = self.request.app['watcher']
"""
watcher: Watcher = self.request.app["watcher"]
return watcher

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -28,7 +28,7 @@ from ahriman.web.views.base import BaseView
class IndexView(BaseView):
'''
"""
root view
It uses jinja2 templates for report generation, the following variables are allowed:
@ -39,35 +39,35 @@ class IndexView(BaseView):
repository - repository name, string, required
service - service status properties: status, status_color, timestamp. Required
version - ahriman version, string, required
'''
"""
@aiohttp_jinja2.template('build-status.jinja2')
@aiohttp_jinja2.template("build-status.jinja2")
async def get(self) -> Dict[str, Any]:
'''
"""
process get request. No parameters supported here
:return: parameters for jinja template
'''
"""
# some magic to make it jinja-friendly
packages = [
{
'base': package.base,
'packages': list(sorted(package.packages)),
'status': status.status.value,
'timestamp': pretty_datetime(status.timestamp),
'version': package.version,
'web_url': package.web_url
"base": package.base,
"packages": list(sorted(package.packages)),
"status": status.status.value,
"timestamp": pretty_datetime(status.timestamp),
"version": package.version,
"web_url": package.web_url
} for package, status in sorted(self.service.packages, key=lambda item: item[0].base)
]
service = {
'status': self.service.status.status.value,
'status_color': self.service.status.status.badges_color(),
'timestamp': pretty_datetime(self.service.status.timestamp)
"status": self.service.status.status.value,
"status_color": self.service.status.status.badges_color(),
"timestamp": pretty_datetime(self.service.status.timestamp)
}
return {
'architecture': self.service.architecture,
'packages': packages,
'repository': self.service.repository.name,
'service': service,
'version': version.__version__,
"architecture": self.service.architecture,
"packages": packages,
"repository": self.service.repository.name,
"service": service,
"version": version.__version__,
}

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -19,48 +19,49 @@
#
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from ahriman.core.exceptions import UnknownPackage
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package
from ahriman.web.views.base import BaseView
class PackageView(BaseView):
'''
"""
package base specific web view
'''
"""
async def get(self) -> Response:
'''
"""
get current package base status
:return: 200 with package description on success
'''
base = self.request.match_info['package']
"""
base = self.request.match_info["package"]
try:
package, status = self.service.get(base)
except KeyError:
except UnknownPackage:
raise HTTPNotFound()
response = [
{
'package': package.view(),
'status': status.view()
"package": package.view(),
"status": status.view()
}
]
return json_response(response)
async def delete(self) -> Response:
'''
"""
delete package base from status page
:return: 204 on success
'''
base = self.request.match_info['package']
"""
base = self.request.match_info["package"]
self.service.remove(base)
return HTTPNoContent()
async def post(self) -> Response:
'''
"""
update package build status
JSON body must be supplied, the following model is used:
@ -71,19 +72,19 @@ class PackageView(BaseView):
}
:return: 204 on success
'''
base = self.request.match_info['package']
"""
base = self.request.match_info["package"]
data = await self.request.json()
try:
package = Package.from_json(data['package']) if 'package' in data else None
status = BuildStatusEnum(data['status'])
package = Package.from_json(data["package"]) if "package" in data else None
status = BuildStatusEnum(data["status"])
except Exception as e:
raise HTTPBadRequest(text=str(e))
try:
self.service.update(base, status, package)
except KeyError:
raise HTTPBadRequest(text=f'Package {base} is unknown, but no package body set')
except UnknownPackage:
raise HTTPBadRequest(text=f"Package {base} is unknown, but no package body set")
return HTTPNoContent()

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -23,28 +23,28 @@ from ahriman.web.views.base import BaseView
class PackagesView(BaseView):
'''
"""
global watcher view
'''
"""
async def get(self) -> Response:
'''
"""
get current packages status
:return: 200 with package description on success
'''
"""
response = [
{
'package': package.view(),
'status': status.view()
"package": package.view(),
"status": status.view()
} for package, status in self.service.packages
]
return json_response(response)
async def post(self) -> Response:
'''
"""
reload all packages from repository. No parameters supported here
:return: 204 on success
'''
"""
self.service.load()
return HTTPNoContent()

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
# Copyright (c) 2021 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
@ -25,71 +25,72 @@ from aiohttp import web
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import InitializeException
from ahriman.core.watcher.watcher import Watcher
from ahriman.core.status.watcher import Watcher
from ahriman.web.middlewares.exception_handler import exception_handler
from ahriman.web.routes import setup_routes
async def on_shutdown(application: web.Application) -> None:
'''
"""
web application shutdown handler
:param application: web application instance
'''
application.logger.warning('server terminated')
"""
application.logger.warning("server terminated")
async def on_startup(application: web.Application) -> None:
'''
"""
web application start handler
:param application: web application instance
'''
application.logger.info('server started')
"""
application.logger.info("server started")
try:
application['watcher'].load()
application["watcher"].load()
except Exception:
application.logger.exception('could not load packages', exc_info=True)
application.logger.exception("could not load packages")
raise InitializeException()
def run_server(application: web.Application, architecture: str) -> None:
'''
def run_server(application: web.Application) -> None:
"""
run web application
:param application: web application instance
:param architecture: repository architecture
'''
application.logger.info('start server')
"""
application.logger.info("start server")
section = application['config'].get_section_name('web', architecture)
host = application['config'].get(section, 'host')
port = application['config'].getint(section, 'port')
section = application["config"].get_section_name("web", application["architecture"])
host = application["config"].get(section, "host")
port = application["config"].getint(section, "port")
web.run_app(application, host=host, port=port, handle_signals=False,
access_log=logging.getLogger('http'))
access_log=logging.getLogger("http"))
def setup_service(architecture: str, config: Configuration) -> web.Application:
'''
"""
create web application
:param architecture: repository architecture
:param config: configuration instance
:return: web application instance
'''
application = web.Application(logger=logging.getLogger('http'))
"""
application = web.Application(logger=logging.getLogger("http"))
application.on_shutdown.append(on_shutdown)
application.on_startup.append(on_startup)
application.middlewares.append(web.normalize_path_middleware(append_slash=False, remove_slash=True))
application.middlewares.append(exception_handler(application.logger))
application.logger.info('setup routes')
application.logger.info("setup routes")
setup_routes(application)
application.logger.info('setup templates')
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(config.get('web', 'templates')))
application.logger.info('setup configuration')
application['config'] = config
application.logger.info("setup templates")
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(config.getpath("web", "templates")))
application.logger.info('setup watcher')
application['watcher'] = Watcher(architecture, config)
application.logger.info("setup configuration")
application["config"] = config
application["architecture"] = architecture
application.logger.info("setup watcher")
application["watcher"] = Watcher(architecture, config)
return application