From c588a4e82ed2559463d050475d485e18b0f680e6 Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Fri, 8 Oct 2021 17:52:13 -0700 Subject: [PATCH] feat(FastAPI): add /packages (post) The POST /packages route takes an `action`, `merge_into` and `confirm` form data arguments. It then routes over to `action`'s callback provided by `PACKAGE_ACTIONS`. This commit does not implement actions, but mocks out the flow we would expect from the POST route. Signed-off-by: Kevin Morris --- aurweb/routers/packages.py | 45 +++++++++++++++++++++++++++++++-- templates/packages.html | 7 +++--- test/test_packages_routes.py | 49 +++++++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index 58c89192..d2002188 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -23,7 +23,8 @@ from aurweb.templates import make_context, render_raw_template, render_template router = APIRouter() -async def packages_get(request: Request, context: Dict[str, Any]): +async def packages_get(request: Request, context: Dict[str, Any], + status_code: HTTPStatus = HTTPStatus.OK): # Query parameters used in this request. context["q"] = dict(request.query_params) @@ -94,7 +95,8 @@ async def packages_get(request: Request, context: Dict[str, Any]): context.get("packages"), request.user) context["packages_count"] = search.total_count - return render_template(request, "packages.html", context) + return render_template(request, "packages.html", context, + status_code=status_code) @router.get("/packages") @@ -1024,3 +1026,42 @@ async def pkgbase_delete_post(request: Request, name: str, delete_package(request.user, package) return RedirectResponse("/packages", status_code=HTTPStatus.SEE_OTHER) + +# A mapping of action string -> callback functions used within the +# `packages_post` route below. We expect any action callback to +# return a tuple in the format: (succeeded: bool, message: List[str]). +PACKAGE_ACTIONS = {} + + +@router.post("/packages") +@auth_required(redirect="/packages") +async def packages_post(request: Request, + IDs: List[int] = Form(default=[]), + action: str = Form(default=str()), + merge_into: str = Form(default=str()), + confirm: bool = Form(default=False)): + + # If an invalid action is specified, just render GET /packages + # with an BAD_REQUEST status_code. + if action not in PACKAGE_ACTIONS: + context = make_context(request, "Packages") + return await packages_get(request, context, HTTPStatus.BAD_REQUEST) + + context = make_context(request, "Packages") + + # We deal with `IDs`, `merge_into` and `confirm` arguments + # within action callbacks. + callback = PACKAGE_ACTIONS.get(action) + retval = await callback(request, package_ids=IDs, merge_into=merge_into, + confirm=confirm) + if retval: # If *anything* was returned: + success, messages = retval + if not success: + # If the first element was False: + context["errors"] = messages + return await packages_get(request, context, HTTPStatus.BAD_REQUEST) + else: + # Otherwise: + context["success"] = messages + + return await packages_get(request, context) diff --git a/templates/packages.html b/templates/packages.html index 8b5b06d1..40a4414b 100644 --- a/templates/packages.html +++ b/templates/packages.html @@ -55,11 +55,10 @@ {% include "partials/widgets/pager.html" %} {% endwith %} - {# Package action form #} + {# Package action form: persists query parameters. #}
- + action="/packages?{{ q | urlencode }}" method="post" + > {# Search results #} {% with voted = packages_voted, notified = packages_notified %} {% include "partials/packages/search_results.html" %} diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py index f29c6c31..032f2571 100644 --- a/test/test_packages_routes.py +++ b/test/test_packages_routes.py @@ -3,6 +3,7 @@ import re from datetime import datetime from http import HTTPStatus from typing import List +from unittest import mock import pytest @@ -26,7 +27,7 @@ from aurweb.models.relation_type import PROVIDES_ID, RelationType from aurweb.models.request_type import DELETION_ID, RequestType from aurweb.models.user import User from aurweb.testing import setup_test_db -from aurweb.testing.html import parse_root +from aurweb.testing.html import get_errors, get_successes, parse_root from aurweb.testing.requests import Request @@ -1987,3 +1988,49 @@ def test_pkgbase_delete(client: TestClient, tu_user: User, package: Package): PackageBase.Name == pkgbase.Name ).first() assert record is None + + +def test_packages_post_unknown_action(client: TestClient, user: User, + package: Package): + + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.post("/packages", data={"action": "unknown"}, + cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + + +def test_packages_post_error(client: TestClient, user: User, package: Package): + + async def stub_action(request: Request, **kwargs): + return (False, ["Some error."]) + + actions = {"stub": stub_action} + with mock.patch.dict("aurweb.routers.packages.PACKAGE_ACTIONS", actions): + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.post("/packages", data={"action": "stub"}, + cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + + errors = get_errors(resp.text) + expected = "Some error." + assert errors[0].text.strip() == expected + + +def test_packages_post(client: TestClient, user: User, package: Package): + + async def stub_action(request: Request, **kwargs): + return (True, ["Some success."]) + + actions = {"stub": stub_action} + with mock.patch.dict("aurweb.routers.packages.PACKAGE_ACTIONS", actions): + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.post("/packages", data={"action": "stub"}, + cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.OK) + + errors = get_successes(resp.text) + expected = "Some success." + assert errors[0].text.strip() == expected