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 <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-10-08 17:52:13 -07:00
parent 927f5e8567
commit c588a4e82e
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
3 changed files with 94 additions and 7 deletions

View file

@ -23,7 +23,8 @@ from aurweb.templates import make_context, render_raw_template, render_template
router = APIRouter() 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. # Query parameters used in this request.
context["q"] = dict(request.query_params) 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.get("packages"), request.user)
context["packages_count"] = search.total_count 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") @router.get("/packages")
@ -1024,3 +1026,42 @@ async def pkgbase_delete_post(request: Request, name: str,
delete_package(request.user, package) delete_package(request.user, package)
return RedirectResponse("/packages", status_code=HTTPStatus.SEE_OTHER) 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)

View file

@ -55,11 +55,10 @@
{% include "partials/widgets/pager.html" %} {% include "partials/widgets/pager.html" %}
{% endwith %} {% endwith %}
{# Package action form #} {# Package action form: persists query parameters. #}
<form id="pkglist-results-form" <form id="pkglist-results-form"
action="/packages/?{{ q | urlencode }}" action="/packages?{{ q | urlencode }}" method="post"
method="post"> >
{# Search results #} {# Search results #}
{% with voted = packages_voted, notified = packages_notified %} {% with voted = packages_voted, notified = packages_notified %}
{% include "partials/packages/search_results.html" %} {% include "partials/packages/search_results.html" %}

View file

@ -3,6 +3,7 @@ import re
from datetime import datetime from datetime import datetime
from http import HTTPStatus from http import HTTPStatus
from typing import List from typing import List
from unittest import mock
import pytest 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.request_type import DELETION_ID, RequestType
from aurweb.models.user import User from aurweb.models.user import User
from aurweb.testing import setup_test_db 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 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 PackageBase.Name == pkgbase.Name
).first() ).first()
assert record is None 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