diff --git a/aurweb/models/request_type.py b/aurweb/models/request_type.py index a26dcf9a..48ace3a3 100644 --- a/aurweb/models/request_type.py +++ b/aurweb/models/request_type.py @@ -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 diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index 9c9a41e3..231f953b 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -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)) diff --git a/templates/pkgbase/request.html b/templates/pkgbase/request.html index 66d69f07..bb9b5aba 100644 --- a/templates/pkgbase/request.html +++ b/templates/pkgbase/request.html @@ -1,8 +1,17 @@ {% extends "partials/layout.html" %} {% block pageContent %} + {% if errors %} + + {% endif %} +

{{ "Submit Request" | tr }}: {{ pkgbase.Name }}

+

{{ "Use this form to file a request against package base " "%s%s%s which includes the following packages:" @@ -15,7 +24,8 @@ {# Request form #} -

+

diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py index 8704d702..5353d3bf 100644 --- a/test/test_packages_routes.py +++ b/test/test_packages_routes.py @@ -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