mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 07:17:17 +00:00
always return json in responses
This commit is contained in:
parent
be017ed102
commit
d8523bd83b
@ -18,8 +18,8 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import middleware, Request
|
from aiohttp.web import middleware, Request
|
||||||
from aiohttp.web_exceptions import HTTPException
|
from aiohttp.web_exceptions import HTTPClientError, HTTPException, HTTPServerError
|
||||||
from aiohttp.web_response import StreamResponse
|
from aiohttp.web_response import json_response, StreamResponse
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
from ahriman.web.middlewares import HandlerType, MiddlewareType
|
from ahriman.web.middlewares import HandlerType, MiddlewareType
|
||||||
@ -35,10 +35,15 @@ def exception_handler(logger: Logger) -> MiddlewareType:
|
|||||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
||||||
try:
|
try:
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
|
except HTTPClientError as e:
|
||||||
|
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||||
|
except HTTPServerError as e:
|
||||||
|
logger.exception("server exception during performing request to %s", request.path)
|
||||||
|
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise # we do not raise 5xx exceptions actually so it should be fine
|
raise # just raise 2xx and 3xx codes
|
||||||
except Exception:
|
except Exception as e:
|
||||||
logger.exception("exception during performing request to %s", request.path)
|
logger.exception("unknown exception during performing request to %s", request.path)
|
||||||
raise
|
return json_response(data={"error": str(e)}, status=500)
|
||||||
|
|
||||||
return handle
|
return handle
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import HTTPFound, Response, json_response
|
from aiohttp.web import HTTPBadRequest, HTTPFound, Response
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
@ -42,12 +42,11 @@ class AddView(BaseView):
|
|||||||
|
|
||||||
:return: redirect to main page on success
|
:return: redirect to main page on success
|
||||||
"""
|
"""
|
||||||
data = await self.extract_data(["packages"])
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
data = await self.extract_data(["packages"])
|
||||||
packages = data["packages"]
|
packages = data["packages"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json_response(data=str(e), status=400)
|
raise HTTPBadRequest(reason=str(e))
|
||||||
|
|
||||||
self.spawner.packages_add(packages, now=True)
|
self.spawner.packages_add(packages, now=True)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import HTTPFound, Response, json_response
|
from aiohttp.web import HTTPBadRequest, HTTPFound, Response
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
@ -42,12 +42,11 @@ class RemoveView(BaseView):
|
|||||||
|
|
||||||
:return: redirect to main page on success
|
:return: redirect to main page on success
|
||||||
"""
|
"""
|
||||||
data = await self.extract_data(["packages"])
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
data = await self.extract_data(["packages"])
|
||||||
packages = data["packages"]
|
packages = data["packages"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json_response(data=str(e), status=400)
|
raise HTTPBadRequest(reason=str(e))
|
||||||
|
|
||||||
self.spawner.packages_remove(packages)
|
self.spawner.packages_remove(packages)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import HTTPFound, Response, json_response
|
from aiohttp.web import HTTPBadRequest, HTTPFound, Response
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
@ -42,12 +42,11 @@ class RequestView(BaseView):
|
|||||||
|
|
||||||
:return: redirect to main page on success
|
:return: redirect to main page on success
|
||||||
"""
|
"""
|
||||||
data = await self.extract_data(["packages"])
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
data = await self.extract_data(["packages"])
|
||||||
packages = data["packages"]
|
packages = data["packages"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json_response(data=str(e), status=400)
|
raise HTTPBadRequest(reason=str(e))
|
||||||
|
|
||||||
self.spawner.packages_add(packages, now=False)
|
self.spawner.packages_add(packages, now=False)
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
import aur # type: ignore
|
import aur # type: ignore
|
||||||
|
|
||||||
from aiohttp.web import Response, json_response
|
from aiohttp.web import HTTPBadRequest, Response, json_response
|
||||||
from typing import Callable, Iterator
|
from typing import Callable, Iterator
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
@ -47,7 +47,7 @@ class SearchView(BaseView):
|
|||||||
search_string = " ".join(search)
|
search_string = " ".join(search)
|
||||||
|
|
||||||
if not search_string:
|
if not search_string:
|
||||||
return json_response(data="Search string must not be empty", status=400)
|
raise HTTPBadRequest(reason="Search string must not be empty")
|
||||||
packages = aur.search(search_string)
|
packages = aur.search(search_string)
|
||||||
|
|
||||||
comparator: Callable[[aur.Package], str] = lambda item: str(item.package_base)
|
comparator: Callable[[aur.Package], str] = lambda item: str(item.package_base)
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import HTTPNoContent, Response, json_response
|
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
|
||||||
|
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
@ -53,12 +53,11 @@ class AhrimanView(BaseView):
|
|||||||
|
|
||||||
:return: 204 on success
|
:return: 204 on success
|
||||||
"""
|
"""
|
||||||
data = await self.extract_data()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
data = await self.extract_data()
|
||||||
status = BuildStatusEnum(data["status"])
|
status = BuildStatusEnum(data["status"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json_response(data=str(e), status=400)
|
raise HTTPBadRequest(reason=str(e))
|
||||||
|
|
||||||
self.service.update_self(status)
|
self.service.update_self(status)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
|
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
|
||||||
|
|
||||||
from ahriman.core.exceptions import UnknownPackage
|
from ahriman.core.exceptions import UnknownPackage
|
||||||
from ahriman.models.build_status import BuildStatusEnum
|
from ahriman.models.build_status import BuildStatusEnum
|
||||||
@ -88,11 +88,11 @@ class PackageView(BaseView):
|
|||||||
package = Package.from_json(data["package"]) if "package" in data else None
|
package = Package.from_json(data["package"]) if "package" in data else None
|
||||||
status = BuildStatusEnum(data["status"])
|
status = BuildStatusEnum(data["status"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json_response(data=str(e), status=400)
|
raise HTTPBadRequest(reason=str(e))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.service.update(base, status, package)
|
self.service.update(base, status, package)
|
||||||
except UnknownPackage:
|
except UnknownPackage:
|
||||||
return json_response(data=f"Package {base} is unknown, but no package body set", status=400)
|
raise HTTPBadRequest(reason=f"Package {base} is unknown, but no package body set")
|
||||||
|
|
||||||
raise HTTPNoContent()
|
raise HTTPNoContent()
|
||||||
|
@ -1,13 +1,24 @@
|
|||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aiohttp.web_exceptions import HTTPBadRequest, HTTPNoContent
|
from aiohttp.web_exceptions import HTTPBadRequest, HTTPInternalServerError, HTTPNoContent
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
from ahriman.web.middlewares.exception_handler import exception_handler
|
from ahriman.web.middlewares.exception_handler import exception_handler
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_body(response: Any) -> Any:
|
||||||
|
"""
|
||||||
|
extract json body from given object
|
||||||
|
:param response: response (any actually) object
|
||||||
|
:return: body key from the object converted to json
|
||||||
|
"""
|
||||||
|
return json.loads(getattr(response, "body"))
|
||||||
|
|
||||||
|
|
||||||
async def test_exception_handler(mocker: MockerFixture) -> None:
|
async def test_exception_handler(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must pass success response
|
must pass success response
|
||||||
@ -23,7 +34,7 @@ async def test_exception_handler(mocker: MockerFixture) -> None:
|
|||||||
|
|
||||||
async def test_exception_handler_success(mocker: MockerFixture) -> None:
|
async def test_exception_handler_success(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must pass client exception
|
must pass 2xx and 3xx codes
|
||||||
"""
|
"""
|
||||||
request = pytest.helpers.request("", "", "")
|
request = pytest.helpers.request("", "", "")
|
||||||
request_handler = AsyncMock(side_effect=HTTPNoContent())
|
request_handler = AsyncMock(side_effect=HTTPNoContent())
|
||||||
@ -37,27 +48,41 @@ async def test_exception_handler_success(mocker: MockerFixture) -> None:
|
|||||||
|
|
||||||
async def test_exception_handler_client_error(mocker: MockerFixture) -> None:
|
async def test_exception_handler_client_error(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must pass client exception
|
must handle client exception
|
||||||
"""
|
"""
|
||||||
request = pytest.helpers.request("", "", "")
|
request = pytest.helpers.request("", "", "")
|
||||||
request_handler = AsyncMock(side_effect=HTTPBadRequest())
|
request_handler = AsyncMock(side_effect=HTTPBadRequest())
|
||||||
logging_mock = mocker.patch("logging.Logger.exception")
|
logging_mock = mocker.patch("logging.Logger.exception")
|
||||||
|
|
||||||
handler = exception_handler(logging.getLogger())
|
handler = exception_handler(logging.getLogger())
|
||||||
with pytest.raises(HTTPBadRequest):
|
response = await handler(request, request_handler)
|
||||||
await handler(request, request_handler)
|
assert _extract_body(response) == {"error": HTTPBadRequest().reason}
|
||||||
logging_mock.assert_not_called()
|
logging_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
async def test_exception_handler_server_error(mocker: MockerFixture) -> None:
|
async def test_exception_handler_server_error(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must log server exception and re-raise it
|
must handle server exception
|
||||||
"""
|
"""
|
||||||
request = pytest.helpers.request("", "", "")
|
request = pytest.helpers.request("", "", "")
|
||||||
request_handler = AsyncMock(side_effect=Exception())
|
request_handler = AsyncMock(side_effect=HTTPInternalServerError())
|
||||||
logging_mock = mocker.patch("logging.Logger.exception")
|
logging_mock = mocker.patch("logging.Logger.exception")
|
||||||
|
|
||||||
handler = exception_handler(logging.getLogger())
|
handler = exception_handler(logging.getLogger())
|
||||||
with pytest.raises(Exception):
|
response = await handler(request, request_handler)
|
||||||
await handler(request, request_handler)
|
assert _extract_body(response) == {"error": HTTPInternalServerError().reason}
|
||||||
|
logging_mock.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_exception_handler_unknown_error(mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must log server exception and re-raise it
|
||||||
|
"""
|
||||||
|
request = pytest.helpers.request("", "", "")
|
||||||
|
request_handler = AsyncMock(side_effect=Exception("An error"))
|
||||||
|
logging_mock = mocker.patch("logging.Logger.exception")
|
||||||
|
|
||||||
|
handler = exception_handler(logging.getLogger())
|
||||||
|
response = await handler(request, request_handler)
|
||||||
|
assert _extract_body(response) == {"error": "An error"}
|
||||||
logging_mock.assert_called_once()
|
logging_mock.assert_called_once()
|
||||||
|
Loading…
Reference in New Issue
Block a user