feat(FastAPI): add /pkgbase/{name}/request (post)

This change implements the FastAPI version of the
/pkgbase/{name}/request form's action.

Changes from PHP:

- Additional errors are now displayed for the **merge_into** field,
  which are only displayed when the Merge type is selected.
    - If the **merge_into** field is empty, a new error is displayed:
      'The "Merge into" field must not be empty.'
    - If the **merge_into** field is given the name of a package base
      which does not exist, a new error is displayed:
      "The package base you want to merge into does not exist."
    - If the **merge_into** field is given the name of the package
      base that a request is being created for, a new error is
      displayed: "You cannot merge a package base into itself."
- When an error is encountered, users are now brought back to
  the request form which they submitted and an error is displayed
  at the top of the page.
- If an invalid type is provided, users are returned to a BAD_REQUEST
  status rendering of the request form.

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-09-13 17:26:25 -07:00
parent ad8369395e
commit 1c031638c6
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
4 changed files with 247 additions and 1 deletions

View file

@ -20,6 +20,12 @@ class RequestType(Base):
name = self.Name
return name[0].upper() + name[1:]
def title(self) -> str:
return self.name_display()
def __getitem__(self, n: int) -> str:
return self.Name[n]
DELETION_ID = db.query(RequestType, RequestType.Name == DELETION).first().ID
ORPHAN_ID = db.query(RequestType, RequestType.Name == ORPHAN).first().ID

View file

@ -582,3 +582,72 @@ async def package_request(request: Request, name: str):
context["pkgbase"] = pkgbase
return render_template(request, "pkgbase/request.html", context)
@router.post("/pkgbase/{name}/request")
@auth_required(True)
async def pkgbase_request_post(request: Request, name: str,
type: str = Form(...),
merge_into: str = Form(default=None),
comments: str = Form(default=str())):
pkgbase = get_pkg_or_base(name, PackageBase)
# Create our render context.
context = make_context(request, "Submit Request")
context["pkgbase"] = pkgbase
if type not in {"deletion", "merge", "orphan"}:
# In the case that someone crafted a POST request with an invalid
# type, just return them to the request form with BAD_REQUEST status.
return render_template(request, "pkgbase/request.html", context,
status_code=HTTPStatus.BAD_REQUEST)
if not comments:
context["errors"] = ["The comment field must not be empty."]
return render_template(request, "pkgbase/request.html", context)
if type == "merge":
# Perform merge-related checks.
if not merge_into:
# TODO: This error needs to be translated.
context["errors"] = ['The "Merge into" field must not be empty.']
return render_template(request, "pkgbase/request.html", context)
target = db.query(PackageBase).filter(
PackageBase.Name == merge_into
).first()
if not target:
# TODO: This error needs to be translated.
context["errors"] = [
"The package base you want to merge into does not exist."
]
return render_template(request, "pkgbase/request.html", context)
if target.ID == pkgbase.ID:
# TODO: This error needs to be translated.
context["errors"] = [
"You cannot merge a package base into itself."
]
return render_template(request, "pkgbase/request.html", context)
# All good. Create a new PackageRequest based on the given type.
now = int(datetime.utcnow().timestamp())
reqtype = db.query(RequestType, RequestType.Name == type).first()
conn = db.ConnectionExecutor(db.get_engine().raw_connection())
notify_ = None
with db.begin():
pkgreq = db.create(PackageRequest, RequestType=reqtype, RequestTS=now,
PackageBase=pkgbase, PackageBaseName=pkgbase.Name,
MergeBaseName=merge_into, User=request.user,
Comments=comments, ClosureComment=str())
# Prepare notification object.
notify_ = notify.RequestOpenNotification(
conn, request.user.ID, pkgreq.ID, reqtype,
pkgreq.PackageBase.ID, merge_into=merge_into or None)
# Send the notification now that we're out of the DB scope.
notify_.send()
# Redirect the submitting user to /packages.
return RedirectResponse("/packages",
status_code=int(HTTPStatus.SEE_OTHER))

View file

@ -1,8 +1,17 @@
{% extends "partials/layout.html" %}
{% block pageContent %}
{% if errors %}
<ul class="errorlist">
{% for error in errors %}
<li>{{ error | tr }}</li>
{% endfor %}
</ul>
{% endif %}
<div class="box">
<h2>{{ "Submit Request" | tr }}: {{ pkgbase.Name }}</h2>
<p>
{{ "Use this form to file a request against package base "
"%s%s%s which includes the following packages:"
@ -15,7 +24,8 @@
</ul>
{# Request form #}
<form id="request-form" action="/pkgbase/{{ pkgbase.Name }}/request">
<form id="request-form" action="/pkgbase/{{ pkgbase.Name }}/request"
method="post">
<fieldset>
<p>
<label for="id_type">{{ "Request type" | tr }}:</label>

View file

@ -1420,3 +1420,164 @@ def test_pkgbase_request(client: TestClient, user: User, package: Package):
with client as request:
resp = request.get(endpoint, cookies=cookies)
assert resp.status_code == int(HTTPStatus.OK)
def test_pkgbase_request_post_deletion(client: TestClient, user: User,
package: Package):
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={
"type": "deletion",
"comments": "We want to delete this."
}, cookies=cookies, allow_redirects=False)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
pkgreq = db.query(PackageRequest).filter(
PackageRequest.PackageBaseID == package.PackageBase.ID
).first()
assert pkgreq is not None
assert pkgreq.RequestType.Name == "deletion"
assert pkgreq.PackageBaseName == package.PackageBase.Name
assert pkgreq.Comments == "We want to delete this."
def test_pkgbase_request_post_orphan(client: TestClient, user: User,
package: Package):
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={
"type": "orphan",
"comments": "We want to disown this."
}, cookies=cookies, allow_redirects=False)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
pkgreq = db.query(PackageRequest).filter(
PackageRequest.PackageBaseID == package.PackageBase.ID
).first()
assert pkgreq is not None
assert pkgreq.RequestType.Name == "orphan"
assert pkgreq.PackageBaseName == package.PackageBase.Name
assert pkgreq.Comments == "We want to disown this."
def test_pkgbase_request_post_merge(client: TestClient, user: User,
package: Package):
with db.begin():
pkgbase2 = db.create(PackageBase, Name="new-pkgbase",
Submitter=user, Maintainer=user, Packager=user)
target = db.create(Package, PackageBase=pkgbase2,
Name=pkgbase2.Name, Version="1.0.0")
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={
"type": "merge",
"merge_into": target.PackageBase.Name,
"comments": "We want to merge this."
}, cookies=cookies, allow_redirects=False)
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
pkgreq = db.query(PackageRequest).filter(
PackageRequest.PackageBaseID == package.PackageBase.ID
).first()
assert pkgreq is not None
assert pkgreq.RequestType.Name == "merge"
assert pkgreq.PackageBaseName == package.PackageBase.Name
assert pkgreq.MergeBaseName == target.PackageBase.Name
assert pkgreq.Comments == "We want to merge this."
def test_pkgbase_request_post_not_found(client: TestClient, user: User):
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post("/pkgbase/fake/request", data={
"type": "fake"
}, cookies=cookies)
assert resp.status_code == int(HTTPStatus.NOT_FOUND)
def test_pkgbase_request_post_invalid_type(client: TestClient,
user: User,
package: Package):
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={"type": "fake"}, cookies=cookies)
assert resp.status_code == int(HTTPStatus.BAD_REQUEST)
def test_pkgbase_request_post_no_comment_error(client: TestClient,
user: User,
package: Package):
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={
"type": "deletion",
"comments": "" # An empty comment field causes an error.
}, cookies=cookies)
assert resp.status_code == int(HTTPStatus.OK)
root = parse_root(resp.text)
error = root.xpath('//ul[@class="errorlist"]/li')[0]
expected = "The comment field must not be empty."
assert error.text.strip() == expected
def test_pkgbase_request_post_merge_not_found_error(client: TestClient,
user: User,
package: Package):
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={
"type": "merge",
"merge_into": "fake", # There is no PackageBase.Name "fake"
"comments": "We want to merge this."
}, cookies=cookies, allow_redirects=False)
assert resp.status_code == int(HTTPStatus.OK)
root = parse_root(resp.text)
error = root.xpath('//ul[@class="errorlist"]/li')[0]
expected = "The package base you want to merge into does not exist."
assert error.text.strip() == expected
def test_pkgbase_request_post_merge_no_merge_into_error(client: TestClient,
user: User,
package: Package):
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={
"type": "merge",
"merge_into": "", # There is no PackageBase.Name "fake"
"comments": "We want to merge this."
}, cookies=cookies, allow_redirects=False)
assert resp.status_code == int(HTTPStatus.OK)
root = parse_root(resp.text)
error = root.xpath('//ul[@class="errorlist"]/li')[0]
expected = 'The "Merge into" field must not be empty.'
assert error.text.strip() == expected
def test_pkgbase_request_post_merge_self_error(client: TestClient, user: User,
package: Package):
endpoint = f"/pkgbase/{package.PackageBase.Name}/request"
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
resp = request.post(endpoint, data={
"type": "merge",
"merge_into": package.PackageBase.Name,
"comments": "We want to merge this."
}, cookies=cookies, allow_redirects=False)
assert resp.status_code == int(HTTPStatus.OK)
root = parse_root(resp.text)
error = root.xpath('//ul[@class="errorlist"]/li')[0]
expected = "You cannot merge a package base into itself."
assert error.text.strip() == expected