diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index 231f953b..a3effb36 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -22,7 +22,7 @@ from aurweb.models.package_dependency import PackageDependency from aurweb.models.package_license import PackageLicense from aurweb.models.package_notification import PackageNotification from aurweb.models.package_relation import PackageRelation -from aurweb.models.package_request import PENDING_ID, PackageRequest +from aurweb.models.package_request import ACCEPTED_ID, PENDING_ID, REJECTED_ID, PackageRequest from aurweb.models.package_source import PackageSource from aurweb.models.package_vote import PackageVote from aurweb.models.relation_type import CONFLICTS_ID @@ -651,3 +651,53 @@ async def pkgbase_request_post(request: Request, name: str, # Redirect the submitting user to /packages. return RedirectResponse("/packages", status_code=int(HTTPStatus.SEE_OTHER)) + + +@router.get("/requests/{id}/close") +@auth_required(True) +async def requests_close(request: Request, id: int): + pkgreq = db.query(PackageRequest).filter(PackageRequest.ID == id).first() + if not request.user.is_elevated() and request.user != pkgreq.User: + # Request user doesn't have permission here: redirect to '/'. + return RedirectResponse("/", status_code=int(HTTPStatus.SEE_OTHER)) + + context = make_context(request, "Close Request") + context["pkgreq"] = pkgreq + return render_template(request, "requests/close.html", context) + + +@router.post("/requests/{id}/close") +@auth_required(True) +async def requests_close_post(request: Request, id: int, + reason: int = Form(default=0), + comments: str = Form(default=str())): + pkgreq = db.query(PackageRequest).filter(PackageRequest.ID == id).first() + if not request.user.is_elevated() and request.user != pkgreq.User: + # Request user doesn't have permission here: redirect to '/'. + return RedirectResponse("/", status_code=int(HTTPStatus.SEE_OTHER)) + + context = make_context(request, "Close Request") + context["pkgreq"] = pkgreq + + if reason not in {ACCEPTED_ID, REJECTED_ID}: + # If the provided reason is not valid, send the user back to + # the closure form with a BAD_REQUEST status. + return render_template(request, "requests/close.html", context, + status_code=HTTPStatus.BAD_REQUEST) + + if not request.user.is_elevated(): + # If we're closing the request as the user who created it, + # the reason should just be a REJECTION. + reason = REJECTED_ID + + with db.begin(): + pkgreq.Closer = request.user + pkgreq.Status = reason + pkgreq.ClosureComment = comments + + conn = db.ConnectionExecutor(db.get_engine().raw_connection()) + notify_ = notify.RequestCloseNotification( + conn, request.user.ID, pkgreq.ID, pkgreq.status_display()) + notify_.send() + + return RedirectResponse("/requests", status_code=int(HTTPStatus.SEE_OTHER)) diff --git a/templates/requests/close.html b/templates/requests/close.html new file mode 100644 index 00000000..7862064a --- /dev/null +++ b/templates/requests/close.html @@ -0,0 +1,60 @@ +{% extends "partials/layout.html" %} + +{% block pageContent %} +
+

{{ "Close Request" | tr }}: {{ pkgreq.PackageBaseName }}

+ +

+ {{ + "Use this form to close the request for package base %s%s%s." + | tr | format("", pkgreq.PackageBaseName, "") + | safe + }} +

+ +

+ {{ "Note" | tr }}: + {{ + "The comments field can be left empty. However, it is highly " + "recommended to add a comment when rejecting a request." + | tr + }} +

+ +
+
+

+ + +

+ +

+ + +

+ +

+ +

+ +
+
+ +
+{% endblock %} diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py index 5353d3bf..5afe011a 100644 --- a/test/test_packages_routes.py +++ b/test/test_packages_routes.py @@ -20,7 +20,7 @@ from aurweb.models.package_dependency import PackageDependency from aurweb.models.package_keyword import PackageKeyword from aurweb.models.package_notification import PackageNotification from aurweb.models.package_relation import PackageRelation -from aurweb.models.package_request import PackageRequest +from aurweb.models.package_request import ACCEPTED_ID, REJECTED_ID, PackageRequest from aurweb.models.package_vote import PackageVote from aurweb.models.relation_type import PROVIDES_ID, RelationType from aurweb.models.request_type import DELETION_ID, RequestType @@ -1581,3 +1581,87 @@ def test_pkgbase_request_post_merge_self_error(client: TestClient, user: User, error = root.xpath('//ul[@class="errorlist"]/li')[0] expected = "You cannot merge a package base into itself." assert error.text.strip() == expected + + +@pytest.fixture +def pkgreq(user: User, package: Package) -> PackageRequest: + reqtype = db.query(RequestType).filter( + RequestType.ID == DELETION_ID + ).first() + with db.begin(): + pkgreq = db.create(PackageRequest, + RequestType=reqtype, + User=user, + PackageBase=package.PackageBase, + PackageBaseName=package.PackageBase.Name, + Comments=str(), + ClosureComment=str()) + yield pkgreq + + +def test_requests_close(client: TestClient, user: User, + pkgreq: PackageRequest): + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.get(f"/requests/{pkgreq.ID}/close", cookies=cookies, + allow_redirects=False) + assert resp.status_code == int(HTTPStatus.OK) + + +def test_requests_close_unauthorized(client: TestClient, maintainer: User, + pkgreq: PackageRequest): + cookies = {"AURSID": maintainer.login(Request(), "testPassword")} + with client as request: + resp = request.get(f"/requests/{pkgreq.ID}/close", cookies=cookies, + allow_redirects=False) + assert resp.status_code == int(HTTPStatus.SEE_OTHER) + assert resp.headers.get("location") == "/" + + +def test_requests_close_post_invalid_reason(client: TestClient, user: User, + pkgreq: PackageRequest): + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.post(f"/requests/{pkgreq.ID}/close", data={ + "reason": 0 + }, cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + + +def test_requests_close_post_unauthorized(client: TestClient, maintainer: User, + pkgreq: PackageRequest): + cookies = {"AURSID": maintainer.login(Request(), "testPassword")} + with client as request: + resp = request.post(f"/requests/{pkgreq.ID}/close", data={ + "reason": ACCEPTED_ID + }, cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.SEE_OTHER) + assert resp.headers.get("location") == "/" + + +def test_requests_close_post(client: TestClient, user: User, + pkgreq: PackageRequest): + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.post(f"/requests/{pkgreq.ID}/close", data={ + "reason": REJECTED_ID + }, cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.SEE_OTHER) + + assert pkgreq.Status == REJECTED_ID + assert pkgreq.Closer == user + assert pkgreq.ClosureComment == str() + + +def test_requests_close_post_rejected(client: TestClient, user: User, + pkgreq: PackageRequest): + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + resp = request.post(f"/requests/{pkgreq.ID}/close", data={ + "reason": REJECTED_ID + }, cookies=cookies, allow_redirects=False) + assert resp.status_code == int(HTTPStatus.SEE_OTHER) + + assert pkgreq.Status == REJECTED_ID + assert pkgreq.Closer == user + assert pkgreq.ClosureComment == str()