From d38abd783250d13f93294944f3897a38e1209d31 Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Wed, 6 Oct 2021 13:54:14 -0700 Subject: [PATCH] feat(FastAPI): add /pkgbase/{name}/delete (get, post) In addition, we've had to add cascade arguments to backref so sqlalchemy treats the relationships as proper cascades. Furthermore, our pkgbase actions template was not rendering actions properly based on TU credentials. Signed-off-by: Kevin Morris --- aurweb/routers/packages.py | 37 ++++++++++++++++++++++ templates/packages/delete.html | 56 ++++++++++++++++++++++++++++++++++ test/test_packages_routes.py | 46 ++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 templates/packages/delete.html diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index 40322785..4426d0be 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -940,3 +940,40 @@ async def pkgbase_disown_post(request: Request, name: str, disown_pkgbase(pkgbase, request.user) return RedirectResponse(f"/pkgbase/{name}", status_code=int(HTTPStatus.SEE_OTHER)) + + +@router.get("/pkgbase/{name}/delete") +@auth_required(True) +async def pkgbase_delete_get(request: Request, name: str): + if not request.user.has_credential("CRED_PKGBASE_DELETE"): + return RedirectResponse(f"/pkgbase/{name}", + status_code=int(HTTPStatus.SEE_OTHER)) + + context = make_context(request, "Package Deletion") + context["pkgbase"] = get_pkg_or_base(name, PackageBase) + return render_template(request, "packages/delete.html", context) + + +@router.post("/pkgbase/{name}/delete") +@auth_required(True) +async def pkgbase_delete_post(request: Request, name: str, + confirm: bool = Form(default=False)): + pkgbase = get_pkg_or_base(name, PackageBase) + + if not request.user.has_credential("CRED_PKGBASE_DELETE"): + return RedirectResponse(f"/pkgbase/{name}", + status_code=int(HTTPStatus.SEE_OTHER)) + + if not confirm: + context = make_context(request, "Package Deletion") + context["pkgbase"] = pkgbase + context["errors"] = [("The selected packages have not been deleted, " + "check the confirmation checkbox.")] + return render_template(request, "packages/delete.html", context, + status_code=int(HTTPStatus.EXPECTATION_FAILED)) + + packages = pkgbase.packages.all() + for package in packages: + delete_package(request.user, package) + + return RedirectResponse("/packages", status_code=int(HTTPStatus.SEE_OTHER)) diff --git a/templates/packages/delete.html b/templates/packages/delete.html new file mode 100644 index 00000000..6e882d05 --- /dev/null +++ b/templates/packages/delete.html @@ -0,0 +1,56 @@ +{% extends "partials/layout.html" %} + +{% block pageContent %} + + {% if errors %} + + {% endif %} + +
+

{{ "Delete Package" | tr }}: {{ pkgbase.Name }}

+ +

+ {{ + "Use this form to delete the package base %s%s%s and " + "the following packages from the AUR: " + | tr | format("", pkgbase.Name, "") | safe + }} +

+ +
    + {% for package in pkgbase.packages.all() %} +
  • {{ package.Name }}
  • + {% endfor %} +
+ +

+ {{ + "Deletion of a package is permanent. " + "Select the checkbox to confirm action." | tr + }} +

+ +
+
+

+ +

+ +

+ +

+
+
+ +
+{% endblock %} diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py index c9622431..1f258497 100644 --- a/test/test_packages_routes.py +++ b/test/test_packages_routes.py @@ -1845,3 +1845,49 @@ def test_pkgbase_disown(client: TestClient, user: User, maintainer: User, with client as request: resp = request.post(endpoint, data={"confirm": True}, cookies=cookies) assert resp.status_code == int(HTTPStatus.SEE_OTHER) + + +def test_pkgbase_delete_unauthorized(client: TestClient, user: User, + package: Package): + pkgbase = package.PackageBase + cookies = {"AURSID": user.login(Request(), "testPassword")} + endpoint = f"/pkgbase/{pkgbase.Name}/delete" + + # Test GET. + with client as request: + resp = request.get(endpoint, cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.SEE_OTHER) + assert resp.headers.get("location") == f"/pkgbase/{pkgbase.Name}" + + # Test POST. + with client as request: + resp = request.post(endpoint, cookies=cookies) + assert resp.status_code == int(HTTPStatus.SEE_OTHER) + assert resp.headers.get("location") == f"/pkgbase/{pkgbase.Name}" + + +def test_pkgbase_delete(client: TestClient, tu_user: User, package: Package): + pkgbase = package.PackageBase + + # Test that the GET request works. + cookies = {"AURSID": tu_user.login(Request(), "testPassword")} + endpoint = f"/pkgbase/{pkgbase.Name}/delete" + with client as request: + resp = request.get(endpoint, cookies=cookies) + assert resp.status_code == int(HTTPStatus.OK) + + # Test that POST works and denies us because we haven't confirmed. + with client as request: + resp = request.post(endpoint, cookies=cookies) + assert resp.status_code == int(HTTPStatus.EXPECTATION_FAILED) + + # Test that we can actually delete the pkgbase. + with client as request: + resp = request.post(endpoint, data={"confirm": True}, cookies=cookies) + assert resp.status_code == int(HTTPStatus.SEE_OTHER) + + # Let's assert that the package base record got removed. + record = db.query(PackageBase).filter( + PackageBase.Name == pkgbase.Name + ).first() + assert record is None