mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
feat(FastAPI): add pkgbase comments (new, edit)
In PHP, this was implemented using an /rpc type 'get-comment-form'. With FastAPI, we've decided to reorganize this into a non-RPC route: `/pkgbase/{name}/comments/{id}/form`, rendered via the new `templates/partials/packages/comment_form.html` template. When the comment_form.html template is provided a `comment` object, it will produce an edit comment form. Otherwise, it will produce a new comment form. A few new FastAPI routes have been introduced: - GET `/pkgbase/{name}/comments/{id}/form` - Produces a JSON response based on {"form": "<form_markup>"}. - POST `/pkgbase/{name}/comments' - Creates a new comment. - POST `/pkgbase/{name}/comments/{id}` - Edits an existing comment. In addition, some Javascript has been modified for our new routes. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
0d8216e8ea
commit
fc28aad245
6 changed files with 333 additions and 79 deletions
|
@ -11,6 +11,7 @@ from aurweb import db
|
||||||
from aurweb.models.official_provider import OFFICIAL_BASE, OfficialProvider
|
from aurweb.models.official_provider import OFFICIAL_BASE, OfficialProvider
|
||||||
from aurweb.models.package import Package
|
from aurweb.models.package import Package
|
||||||
from aurweb.models.package_base import PackageBase
|
from aurweb.models.package_base import PackageBase
|
||||||
|
from aurweb.models.package_comment import PackageComment
|
||||||
from aurweb.models.package_dependency import PackageDependency
|
from aurweb.models.package_dependency import PackageDependency
|
||||||
from aurweb.models.package_notification import PackageNotification
|
from aurweb.models.package_notification import PackageNotification
|
||||||
from aurweb.models.package_relation import PackageRelation
|
from aurweb.models.package_relation import PackageRelation
|
||||||
|
@ -121,6 +122,13 @@ def get_pkg_or_base(name: str, cls: Union[Package, PackageBase] = PackageBase):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
def get_pkgbase_comment(pkgbase: PackageBase, id: int) -> PackageComment:
|
||||||
|
comment = pkgbase.comments.filter(PackageComment.ID == id).first()
|
||||||
|
if not comment:
|
||||||
|
raise HTTPException(status_code=int(HTTPStatus.NOT_FOUND))
|
||||||
|
return comment
|
||||||
|
|
||||||
|
|
||||||
@register_filter("out_of_date")
|
@register_filter("out_of_date")
|
||||||
def out_of_date(packages: orm.Query) -> orm.Query:
|
def out_of_date(packages: orm.Query) -> orm.Query:
|
||||||
return packages.filter(PackageBase.OutOfDateTS.isnot(None))
|
return packages.filter(PackageBase.OutOfDateTS.isnot(None))
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
from datetime import datetime
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, Response
|
from fastapi import APIRouter, Form, HTTPException, Request, Response
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import JSONResponse, RedirectResponse
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
|
|
||||||
import aurweb.filters
|
import aurweb.filters
|
||||||
|
@ -11,9 +12,11 @@ import aurweb.models.package_keyword
|
||||||
import aurweb.packages.util
|
import aurweb.packages.util
|
||||||
|
|
||||||
from aurweb import db
|
from aurweb import db
|
||||||
|
from aurweb.auth import auth_required
|
||||||
from aurweb.models.license import License
|
from aurweb.models.license import License
|
||||||
from aurweb.models.package import Package
|
from aurweb.models.package import Package
|
||||||
from aurweb.models.package_base import PackageBase
|
from aurweb.models.package_base import PackageBase
|
||||||
|
from aurweb.models.package_comment import PackageComment
|
||||||
from aurweb.models.package_dependency import PackageDependency
|
from aurweb.models.package_dependency import PackageDependency
|
||||||
from aurweb.models.package_license import PackageLicense
|
from aurweb.models.package_license import PackageLicense
|
||||||
from aurweb.models.package_notification import PackageNotification
|
from aurweb.models.package_notification import PackageNotification
|
||||||
|
@ -23,8 +26,9 @@ from aurweb.models.package_source import PackageSource
|
||||||
from aurweb.models.package_vote import PackageVote
|
from aurweb.models.package_vote import PackageVote
|
||||||
from aurweb.models.relation_type import CONFLICTS_ID
|
from aurweb.models.relation_type import CONFLICTS_ID
|
||||||
from aurweb.packages.search import PackageSearch
|
from aurweb.packages.search import PackageSearch
|
||||||
from aurweb.packages.util import get_pkg_or_base, query_notified, query_voted
|
from aurweb.packages.util import get_pkg_or_base, get_pkgbase_comment, query_notified, query_voted
|
||||||
from aurweb.templates import make_context, render_template
|
from aurweb.scripts.rendercomment import update_comment_render
|
||||||
|
from aurweb.templates import make_context, render_raw_template, render_template
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
@ -124,7 +128,9 @@ async def make_single_context(request: Request,
|
||||||
context["pkgbase"] = pkgbase
|
context["pkgbase"] = pkgbase
|
||||||
context["packages_count"] = pkgbase.packages.count()
|
context["packages_count"] = pkgbase.packages.count()
|
||||||
context["keywords"] = pkgbase.keywords
|
context["keywords"] = pkgbase.keywords
|
||||||
context["comments"] = pkgbase.comments
|
context["comments"] = pkgbase.comments.order_by(
|
||||||
|
PackageComment.CommentTS.desc()
|
||||||
|
)
|
||||||
context["is_maintainer"] = (request.user.is_authenticated()
|
context["is_maintainer"] = (request.user.is_authenticated()
|
||||||
and request.user.ID == pkgbase.MaintainerUID)
|
and request.user.ID == pkgbase.MaintainerUID)
|
||||||
context["notified"] = request.user.notified(pkgbase)
|
context["notified"] = request.user.notified(pkgbase)
|
||||||
|
@ -201,7 +207,94 @@ async def package_base(request: Request, name: str) -> Response:
|
||||||
@router.get("/pkgbase/{name}/voters")
|
@router.get("/pkgbase/{name}/voters")
|
||||||
async def package_base_voters(request: Request, name: str) -> Response:
|
async def package_base_voters(request: Request, name: str) -> Response:
|
||||||
# Get the PackageBase.
|
# Get the PackageBase.
|
||||||
pkgbase = get_pkgbase(name)
|
pkgbase = get_pkg_or_base(name, PackageBase)
|
||||||
context = make_context(request, "Voters")
|
context = make_context(request, "Voters")
|
||||||
context["pkgbase"] = pkgbase
|
context["pkgbase"] = pkgbase
|
||||||
return render_template(request, "pkgbase/voters.html", context)
|
return render_template(request, "pkgbase/voters.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/pkgbase/{name}/comments")
|
||||||
|
@auth_required(True)
|
||||||
|
async def pkgbase_comments_post(
|
||||||
|
request: Request, name: str,
|
||||||
|
comment: str = Form(default=str()),
|
||||||
|
enable_notifications: bool = Form(default=False)):
|
||||||
|
""" Add a new comment. """
|
||||||
|
pkgbase = get_pkg_or_base(name, PackageBase)
|
||||||
|
|
||||||
|
if not comment:
|
||||||
|
raise HTTPException(status_code=int(HTTPStatus.EXPECTATION_FAILED))
|
||||||
|
|
||||||
|
# If the provided comment is different than the record's version,
|
||||||
|
# update the db record.
|
||||||
|
now = int(datetime.utcnow().timestamp())
|
||||||
|
with db.begin():
|
||||||
|
comment = db.create(PackageComment, User=request.user,
|
||||||
|
PackageBase=pkgbase,
|
||||||
|
Comments=comment, RenderedComment=str(),
|
||||||
|
CommentTS=now)
|
||||||
|
|
||||||
|
if enable_notifications and not request.user.notified(pkgbase):
|
||||||
|
db.create(PackageNotification,
|
||||||
|
User=request.user,
|
||||||
|
PackageBase=pkgbase)
|
||||||
|
update_comment_render(comment.ID)
|
||||||
|
|
||||||
|
# Redirect to the pkgbase page.
|
||||||
|
return RedirectResponse(f"/pkgbase/{pkgbase.Name}#comment-{comment.ID}",
|
||||||
|
status_code=int(HTTPStatus.SEE_OTHER))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/pkgbase/{name}/comments/{id}/form")
|
||||||
|
@auth_required(True)
|
||||||
|
async def pkgbase_comment_form(request: Request, name: str, id: int):
|
||||||
|
""" Produce a comment form for comment {id}. """
|
||||||
|
pkgbase = get_pkg_or_base(name, PackageBase)
|
||||||
|
comment = pkgbase.comments.filter(PackageComment.ID == id).first()
|
||||||
|
if not comment:
|
||||||
|
return JSONResponse({}, status_code=int(HTTPStatus.NOT_FOUND))
|
||||||
|
|
||||||
|
if not request.user.is_elevated() and request.user != comment.User:
|
||||||
|
return JSONResponse({}, status_code=int(HTTPStatus.UNAUTHORIZED))
|
||||||
|
|
||||||
|
context = await make_single_context(request, pkgbase)
|
||||||
|
context["comment"] = comment
|
||||||
|
|
||||||
|
form = render_raw_template(
|
||||||
|
request, "partials/packages/comment_form.html", context)
|
||||||
|
return JSONResponse({"form": form})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/pkgbase/{name}/comments/{id}")
|
||||||
|
@auth_required(True)
|
||||||
|
async def pkgbase_comment_post(
|
||||||
|
request: Request, name: str, id: int,
|
||||||
|
comment: str = Form(default=str()),
|
||||||
|
enable_notifications: bool = Form(default=False)):
|
||||||
|
pkgbase = get_pkg_or_base(name, PackageBase)
|
||||||
|
db_comment = get_pkgbase_comment(pkgbase, id)
|
||||||
|
|
||||||
|
if not comment:
|
||||||
|
raise HTTPException(status_code=int(HTTPStatus.EXPECTATION_FAILED))
|
||||||
|
|
||||||
|
# If the provided comment is different than the record's version,
|
||||||
|
# update the db record.
|
||||||
|
now = int(datetime.utcnow().timestamp())
|
||||||
|
if db_comment.Comments != comment:
|
||||||
|
with db.begin():
|
||||||
|
db_comment.Comments = comment
|
||||||
|
db_comment.Editor = request.user
|
||||||
|
db_comment.EditedTS = now
|
||||||
|
|
||||||
|
db_notif = request.user.notifications.filter(
|
||||||
|
PackageNotification.PackageBaseID == pkgbase.ID
|
||||||
|
).first()
|
||||||
|
if enable_notifications and not db_notif:
|
||||||
|
db.create(PackageNotification,
|
||||||
|
User=request.user,
|
||||||
|
PackageBase=pkgbase)
|
||||||
|
update_comment_render(db_comment.ID)
|
||||||
|
|
||||||
|
# Redirect to the pkgbase page anchored to the updated comment.
|
||||||
|
return RedirectResponse(f"/pkgbase/{pkgbase.Name}#comment-{db_comment.ID}",
|
||||||
|
status_code=int(HTTPStatus.SEE_OTHER))
|
||||||
|
|
|
@ -16,26 +16,38 @@
|
||||||
)
|
)
|
||||||
| safe
|
| safe
|
||||||
}}
|
}}
|
||||||
{% if is_maintainer %}
|
{% if comment.Editor %}
|
||||||
<form class="delete-comment-form" method="post" action="/pkgbase/{{ pkgname }}/">
|
{% set edited_on = comment.EditedTS | dt | as_timezone(timezone) %}
|
||||||
|
<span class="edited">
|
||||||
|
({{ "edited on %s by %s" | tr
|
||||||
|
| format(edited_on.strftime('%Y-%m-%d %H:%M'),
|
||||||
|
'<a href="/account/%s">%s</a>' | format(
|
||||||
|
comment.Editor.Username, comment.Editor.Username))
|
||||||
|
| safe
|
||||||
|
}})
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user.is_elevated() or pkgbase.Maintainer == request.user %}
|
||||||
|
<form class="delete-comment-form" method="post" action="/pkgbase/{{ name }}/">
|
||||||
<fieldset style="display:inline;">
|
<fieldset style="display:inline;">
|
||||||
<input type="hidden" name="action" value="do_DeleteComment" />
|
<input type="hidden" name="action" value="do_DeleteComment" />
|
||||||
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
|
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
|
||||||
<input type="hidden" name="return_to" value="/pkgbase/{{ pkgname }}/"/>
|
<input type="hidden" name="return_to" value="/pkgbase/{{ name }}/"/>
|
||||||
<input type="image" class="delete-comment" src="/images/x.min.svg" width="11" height="11" alt="{{ 'Delete comment' | tr }}" title="{{ 'Delete comment' | tr }}" name="submit" value="1" />
|
<input type="image" class="delete-comment" src="/images/x.min.svg" width="11" height="11" alt="{{ 'Delete comment' | tr }}" title="{{ 'Delete comment' | tr }}" name="submit" value="1" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<a href="/pkgbase/{{ pkgname }}/edit-comment/?comment_id={{ comment.ID }}" class="edit-comment" title="Edit comment"><img src="/images/pencil.min.svg" alt="Edit comment" width="11" height="11"></a>
|
<a href="/pkgbase/{{ pkgname }}/edit-comment/?comment_id={{ comment.ID }}" class="edit-comment" title="Edit comment"><img src="/images/pencil.min.svg" alt="Edit comment" width="11" height="11"></a>
|
||||||
{% endif %}
|
|
||||||
<form class="pin-comment-form" method="post" action="/pkgbase/{{ pkgname }}/">
|
<form class="pin-comment-form" method="post" action="/pkgbase/{{ name }}/">
|
||||||
<fieldset style="display:inline;">
|
<fieldset style="display:inline;">
|
||||||
<input type="hidden" name="action" value="do_PinComment"/>
|
<input type="hidden" name="action" value="do_PinComment"/>
|
||||||
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
|
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
|
||||||
<input type="hidden" name="package_base" value="{{ pkgbase_id }}"/>
|
<input type="hidden" name="package_base" value="{{ pkgbase.ID }}"/>
|
||||||
<input type="hidden" name="return_to" value="/pkgbase/{{ pkgname }}/"/>
|
<input type="hidden" name="return_to" value="/pkgbase/{{ name }}/"/>
|
||||||
<input type="image" class="pin-comment" src="/images/pin.min.svg" width="11" height="11" alt="{{ 'Pin comment' | tr }}" title="{{ 'Pin comment' | tr }}" name="submit" value="1"/>
|
<input type="image" class="pin-comment" src="/images/pin.min.svg" width="11" height="11" alt="{{ 'Pin comment' | tr }}" title="{{ 'Pin comment' | tr }}" name="submit" value="1"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
<div id="comment-{{ comment.ID }}-content" class="article-content">
|
<div id="comment-{{ comment.ID }}-content" class="article-content">
|
||||||
<div>
|
<div>
|
||||||
|
|
46
templates/partials/packages/comment_form.html
Normal file
46
templates/partials/packages/comment_form.html
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{# `action` is assigned the proper route to use for the form action.
|
||||||
|
When `comment` is provided (PackageComment), we display an edit form
|
||||||
|
for the comment. Otherwise, we display a new form.
|
||||||
|
|
||||||
|
Routes:
|
||||||
|
new comment - /pkgbase/{name}/comments
|
||||||
|
edit comment - /pkgbase/{name}/comments/{id}
|
||||||
|
#}
|
||||||
|
{% set action = "/pkgbase/%s/comments" | format(pkgbase.Name) %}
|
||||||
|
{% if comment %}
|
||||||
|
{% set action = "/pkgbase/%s/comments/%d" | format(pkgbase.Name, comment.ID) %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form action="{{ action }}" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<p>
|
||||||
|
{{ "Git commit identifiers referencing commits in the AUR package "
|
||||||
|
"repository and URLs are converted to links automatically." | tr }}
|
||||||
|
{{ "%sMarkdown syntax%s is partially supported." | tr
|
||||||
|
| format('<a href="https://daringfireball.net/projects/markdown/syntax">',
|
||||||
|
"</a>")
|
||||||
|
| safe }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<textarea id="id_comment"
|
||||||
|
name="comment"
|
||||||
|
cols="80"
|
||||||
|
rows="10"
|
||||||
|
>{% if comment %}{{ comment.Comments or '' }}{% endif %}</textarea>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="button">
|
||||||
|
{{ ("Save" if comment else "Add Comment") | tr }}
|
||||||
|
</button>
|
||||||
|
{% if comment and not request.user.notified(pkgbase) %}
|
||||||
|
<span class="comment-enable-notifications">
|
||||||
|
<input type="checkbox" name="enable_notifications"
|
||||||
|
id="id_enable_notifications" />
|
||||||
|
<label for="id_enable_notifications">
|
||||||
|
{{ "Enable notifications" | tr }}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
|
@ -8,44 +8,7 @@
|
||||||
{% if request.user.is_authenticated() %}
|
{% if request.user.is_authenticated() %}
|
||||||
<div id="generic-form" class="box">
|
<div id="generic-form" class="box">
|
||||||
<h2>Add Comment</h2>
|
<h2>Add Comment</h2>
|
||||||
<form action="/pkgbase/{{ pkgname }}/" method="post">
|
{% include "partials/packages/comment_form.html" %}
|
||||||
<fieldset>
|
|
||||||
<div>
|
|
||||||
<input type="hidden" name="action" value="do_AddComment"/>
|
|
||||||
<input type="hidden" name="ID" value="{{ pkgbase_id }}"/>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
{{
|
|
||||||
"Git commit identifiers referencing commits in the AUR package"
|
|
||||||
" repository and URLs are converted to links automatically."
|
|
||||||
| tr
|
|
||||||
}}
|
|
||||||
{{
|
|
||||||
"%sMarkdown Syntax%s is partially supported."
|
|
||||||
| tr
|
|
||||||
| format('<a href="https://daringfireball.net/projects/markdown/syntax">', '</a>')
|
|
||||||
| safe
|
|
||||||
}}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<textarea id="id_comment" name="comment" cols="80" rows="10"></textarea>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="{{ 'Add Comment' | tr }}"/>
|
|
||||||
{% if not notifications_enabled %}
|
|
||||||
<span class="comment-enable-notifications">
|
|
||||||
<input id="id_enable_notifications"
|
|
||||||
type="checkbox"
|
|
||||||
name="enable_notifications"
|
|
||||||
/>
|
|
||||||
<label for="id_enable_notifications">
|
|
||||||
{{ "Enable notifications" | tr }}
|
|
||||||
</label>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -99,29 +62,28 @@ function handleEditCommentClick(event) {
|
||||||
// The div class="article-content" which contains the comment
|
// The div class="article-content" which contains the comment
|
||||||
const edit_form = parent_element.nextElementSibling;
|
const edit_form = parent_element.nextElementSibling;
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const url = "/pkgbase/{{ pkgbase.Name }}/comments/" + comment_id + "/form";
|
||||||
type: "get-comment-form",
|
|
||||||
arg: comment_id,
|
|
||||||
base_id: {{ pkgbase.ID }},
|
|
||||||
pkgbase_name: {{ pkgbase.Name }}
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = '/rpc?' + params.toString();
|
|
||||||
|
|
||||||
add_busy_indicator(event.target);
|
add_busy_indicator(event.target);
|
||||||
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: 'GET'
|
method: 'GET',
|
||||||
|
credentials: 'same-origin'
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw Error(response.statusText);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(function(response) { return response.json(); })
|
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
remove_busy_indicator(event.target);
|
remove_busy_indicator(event.target);
|
||||||
if (data.success) {
|
|
||||||
edit_form.innerHTML = data.form;
|
edit_form.innerHTML = data.form;
|
||||||
edit_form.querySelector('textarea').focus();
|
edit_form.querySelector('textarea').focus();
|
||||||
} else {
|
})
|
||||||
alert(data.error);
|
.catch(function(error) {
|
||||||
}
|
remove_busy_indicator(event.target);
|
||||||
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,7 @@ def setup():
|
||||||
PackageVote.__tablename__,
|
PackageVote.__tablename__,
|
||||||
PackageNotification.__tablename__,
|
PackageNotification.__tablename__,
|
||||||
PackageComaintainer.__tablename__,
|
PackageComaintainer.__tablename__,
|
||||||
|
PackageComment.__tablename__,
|
||||||
OfficialProvider.__tablename__
|
OfficialProvider.__tablename__
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -930,3 +931,135 @@ def test_pkgbase_voters(client: TestClient, maintainer: User, package: Package):
|
||||||
root = parse_root(resp.text)
|
root = parse_root(resp.text)
|
||||||
rows = root.xpath('//div[@class="box"]//ul/li')
|
rows = root.xpath('//div[@class="box"]//ul/li')
|
||||||
assert len(rows) == 1
|
assert len(rows) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_pkgbase_comment_not_found(client: TestClient, maintainer: User,
|
||||||
|
package: Package):
|
||||||
|
cookies = {"AURSID": maintainer.login(Request(), "testPassword")}
|
||||||
|
comment_id = 12345 # A non-existing comment.
|
||||||
|
endpoint = f"/pkgbase/{package.PackageBase.Name}/comments/{comment_id}"
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(endpoint, data={
|
||||||
|
"comment": "Failure"
|
||||||
|
}, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pkgbase_comment_form_unauthorized(client: TestClient, user: User,
|
||||||
|
maintainer: User, package: Package):
|
||||||
|
now = int(datetime.utcnow().timestamp())
|
||||||
|
with db.begin():
|
||||||
|
comment = db.create(PackageComment, PackageBase=package.PackageBase,
|
||||||
|
User=maintainer, Comments="Test",
|
||||||
|
RenderedComment=str(), CommentTS=now)
|
||||||
|
|
||||||
|
cookies = {"AURSID": user.login(Request(), "testPassword")}
|
||||||
|
pkgbasename = package.PackageBase.Name
|
||||||
|
endpoint = f"/pkgbase/{pkgbasename}/comments/{comment.ID}/form"
|
||||||
|
with client as request:
|
||||||
|
resp = request.get(endpoint, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.UNAUTHORIZED)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pkgbase_comment_form_not_found(client: TestClient, maintainer: User,
|
||||||
|
package: Package):
|
||||||
|
cookies = {"AURSID": maintainer.login(Request(), "testPassword")}
|
||||||
|
comment_id = 12345 # A non-existing comment.
|
||||||
|
pkgbasename = package.PackageBase.Name
|
||||||
|
endpoint = f"/pkgbase/{pkgbasename}/comments/{comment_id}/form"
|
||||||
|
with client as request:
|
||||||
|
resp = request.get(endpoint, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pkgbase_comments_missing_comment(client: TestClient, maintainer: User,
|
||||||
|
package: Package):
|
||||||
|
cookies = {"AURSID": maintainer.login(Request(), "testPassword")}
|
||||||
|
endpoint = f"/pkgbase/{package.PackageBase.Name}/comments"
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(endpoint, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.EXPECTATION_FAILED)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pkgbase_comments(client: TestClient, maintainer: User,
|
||||||
|
package: Package):
|
||||||
|
cookies = {"AURSID": maintainer.login(Request(), "testPassword")}
|
||||||
|
pkgbasename = package.PackageBase.Name
|
||||||
|
endpoint = f"/pkgbase/{pkgbasename}/comments"
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(endpoint, data={
|
||||||
|
"comment": "Test comment.",
|
||||||
|
"enable_notifications": True
|
||||||
|
}, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
|
expected_prefix = f"/pkgbase/{pkgbasename}"
|
||||||
|
prefix_len = len(expected_prefix)
|
||||||
|
assert resp.headers.get("location")[:prefix_len] == expected_prefix
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
resp = request.get(resp.headers.get("location"))
|
||||||
|
assert resp.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
root = parse_root(resp.text)
|
||||||
|
headers = root.xpath('//h4[@class="comment-header"]')
|
||||||
|
bodies = root.xpath('//div[@class="article-content"]/div/p')
|
||||||
|
|
||||||
|
assert len(headers) == 1
|
||||||
|
assert len(bodies) == 1
|
||||||
|
|
||||||
|
assert bodies[0].text.strip() == "Test comment."
|
||||||
|
|
||||||
|
# Clear up the PackageNotification. This doubles as testing
|
||||||
|
# that the notification was created and clears it up so we can
|
||||||
|
# test enabling it during edit.
|
||||||
|
pkgbase = package.PackageBase
|
||||||
|
db_notif = pkgbase.notifications.filter(
|
||||||
|
PackageNotification.UserID == maintainer.ID
|
||||||
|
).first()
|
||||||
|
with db.begin():
|
||||||
|
db.session.delete(db_notif)
|
||||||
|
|
||||||
|
# Now, let's edit the comment we just created.
|
||||||
|
comment_id = int(headers[0].attrib["id"].split("-")[-1])
|
||||||
|
endpoint = f"/pkgbase/{pkgbasename}/comments/{comment_id}"
|
||||||
|
with client as request:
|
||||||
|
resp = request.post(endpoint, data={
|
||||||
|
"comment": "Edited comment.",
|
||||||
|
"enable_notifications": True
|
||||||
|
}, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.SEE_OTHER)
|
||||||
|
|
||||||
|
with client as request:
|
||||||
|
resp = request.get(resp.headers.get("location"))
|
||||||
|
assert resp.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
root = parse_root(resp.text)
|
||||||
|
headers = root.xpath('//h4[@class="comment-header"]')
|
||||||
|
bodies = root.xpath('//div[@class="article-content"]/div/p')
|
||||||
|
|
||||||
|
assert len(headers) == 1
|
||||||
|
assert len(bodies) == 1
|
||||||
|
|
||||||
|
assert bodies[0].text.strip() == "Edited comment."
|
||||||
|
|
||||||
|
# Ensure that a notification was created.
|
||||||
|
db_notif = pkgbase.notifications.filter(
|
||||||
|
PackageNotification.UserID == maintainer.ID
|
||||||
|
).first()
|
||||||
|
assert db_notif is not None
|
||||||
|
|
||||||
|
# Don't supply a comment; should return EXPECTATION_FAILED.
|
||||||
|
with client as request:
|
||||||
|
fail_resp = request.post(endpoint, cookies=cookies)
|
||||||
|
assert fail_resp.status_code == int(HTTPStatus.EXPECTATION_FAILED)
|
||||||
|
|
||||||
|
# Now, test the form route, which should return form markup
|
||||||
|
# via JSON.
|
||||||
|
endpoint = f"{endpoint}/form"
|
||||||
|
with client as request:
|
||||||
|
resp = request.get(endpoint, cookies=cookies)
|
||||||
|
assert resp.status_code == int(HTTPStatus.OK)
|
||||||
|
|
||||||
|
data = resp.json()
|
||||||
|
assert "form" in data
|
||||||
|
|
Loading…
Add table
Reference in a new issue