fix(spec): enforce read-time budget and re-render invalid comment submissions
All checks were successful
CI / nightly-e2e (pull_request) Has been skipped
CI / ci (pull_request) Successful in 1m1s

This commit is contained in:
Mark
2026-02-28 17:36:34 +00:00
parent cfe0cbca62
commit c4fde90a9c
4 changed files with 52 additions and 9 deletions

View File

@@ -63,6 +63,34 @@ def test_comment_post_rejected_when_comments_disabled(client, home_page):
assert Comment.objects.count() == 0
@pytest.mark.django_db
def test_invalid_comment_post_rerenders_form_with_errors(client, home_page):
cache.clear()
index = ArticleIndexPage(title="Articles", slug="articles")
home_page.add_child(instance=index)
author = AuthorFactory()
article = ArticlePage(title="A", slug="a", author=author, summary="s", body=[("rich_text", "<p>body</p>")])
index.add_child(instance=article)
article.save_revision().publish()
resp = client.post(
"/comments/post/",
{
"article_id": article.id,
"author_name": "Test",
"author_email": "test@example.com",
"body": " ",
"honeypot": "",
},
)
assert resp.status_code == 200
assert b'aria-label="Comment form errors"' in resp.content
assert b'value="Test"' in resp.content
assert b"test@example.com" in resp.content
assert Comment.objects.count() == 0
@pytest.mark.django_db
def test_comment_reply_depth_is_enforced(client, home_page):
cache.clear()
@@ -100,7 +128,8 @@ def test_comment_reply_depth_is_enforced(client, home_page):
"honeypot": "",
},
)
assert resp.status_code == 302
assert resp.status_code == 200
assert b"Reply depth exceeds the allowed limit" in resp.content
assert Comment.objects.count() == 2

View File

@@ -5,7 +5,7 @@ from django.contrib import messages
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.shortcuts import get_object_or_404, redirect, render
from django.views import View
from apps.blog.models import ArticlePage
@@ -23,6 +23,12 @@ def client_ip_from_request(request) -> str:
class CommentCreateView(View):
def _render_article_with_errors(self, request, article, form):
context = article.get_context(request)
context["page"] = article
context["comment_form"] = form
return render(request, "blog/article_page.html", context, status=200)
def post(self, request):
ip = client_ip_from_request(request)
key = f"comment-rate:{ip}"
@@ -48,11 +54,10 @@ class CommentCreateView(View):
try:
comment.full_clean()
except ValidationError:
messages.error(request, "Reply depth exceeds the allowed limit")
return redirect(article.url)
form.add_error(None, "Reply depth exceeds the allowed limit")
return self._render_article_with_errors(request, article, form)
comment.save()
messages.success(request, "Your comment is awaiting moderation")
return redirect(f"{article.url}?commented=1")
messages.error(request, "Please correct the form errors")
return redirect(article.url)
return self._render_article_with_errors(request, article, form)

View File

@@ -72,3 +72,4 @@ def test_read_time_benchmark(benchmark):
result = benchmark(article._compute_read_time)
assert result >= 1
assert benchmark.stats.stats.mean < 0.05

View File

@@ -69,12 +69,20 @@
{% empty %}
<p>No comments yet.</p>
{% endfor %}
{% if comment_form and comment_form.errors %}
<div aria-label="Comment form errors">
{{ comment_form.non_field_errors }}
{% for field in comment_form %}
{{ field.errors }}
{% endfor %}
</div>
{% endif %}
<form method="post" action="{% url 'comment_post' %}">
{% csrf_token %}
<input type="hidden" name="article_id" value="{{ page.id }}" />
<input type="text" name="author_name" required />
<input type="email" name="author_email" required />
<textarea name="body" required></textarea>
<input type="text" name="author_name" value="{% if comment_form %}{{ comment_form.author_name.value|default:'' }}{% endif %}" required />
<input type="email" name="author_email" value="{% if comment_form %}{{ comment_form.author_email.value|default:'' }}{% endif %}" required />
<textarea name="body" required>{% if comment_form %}{{ comment_form.body.value|default:'' }}{% endif %}</textarea>
<input type="text" name="honeypot" style="display:none" />
<button type="submit">Post comment</button>
</form>