Compare commits

..
Author SHA1 Message Date
Typosaurus 0650c697eb ticket #76 attempt 1 2026-07-19 18:50:44 +00:00
Typosaurus 3d7b655a45 ticket #76 attempt 1 2026-07-19 18:23:56 +00:00
20 changed files with 68 additions and 96 deletions
File diff suppressed because one or more lines are too long
-4
View File
@@ -39,10 +39,6 @@ NGINX_MAX_BODY_SIZE=50m
NGINX_CACHE_ENABLED=false NGINX_CACHE_ENABLED=false
NGINX_CACHE_MAX_SIZE=1g NGINX_CACHE_MAX_SIZE=1g
# Max length of comment text (applied server-side on write and client-side on input).
# Change at any time via the env var; the DB has no length constraint.
DEVPLACE_COMMENT_MAX_LENGTH=1000
# Run the app container as this host user so shared files keep dev ownership. # Run the app container as this host user so shared files keep dev ownership.
DEVPLACE_UID=1000 DEVPLACE_UID=1000
DEVPLACE_GID=1000 DEVPLACE_GID=1000
-1
View File
@@ -169,7 +169,6 @@ The log is **administrator-only**. `/admin/audit-log` is a paginated, filterable
| `DEVPLACE_PRESENCE_TIMEOUT_SECONDS` | `60` | Online-presence window: a user counts as online for this many seconds after their last activity. `last_seen` is refreshed by a throttled in-place update at most once per half this interval per worker (no per-load inserts, no data growth) | | `DEVPLACE_PRESENCE_TIMEOUT_SECONDS` | `60` | Online-presence window: a user counts as online for this many seconds after their last activity. `last_seen` is refreshed by a throttled in-place update at most once per half this interval per worker (no per-load inserts, no data growth) |
| `DEVPLACE_PRESENCE_ONLINE_LIMIT` | `30` | Maximum avatars shown in the feed's live "Online now" panel (ordered alphabetically by username) | | `DEVPLACE_PRESENCE_ONLINE_LIMIT` | `30` | Maximum avatars shown in the feed's live "Online now" panel (ordered alphabetically by username) |
| `DEVPLACE_PRESENCE_ONLINE_MARGIN_SECONDS` | `20` | Grace margin before an online user drops to offline (hysteresis): online at the timeout, offline only after timeout + this. Prevents online/offline flicker for users hovering at the boundary | | `DEVPLACE_PRESENCE_ONLINE_MARGIN_SECONDS` | `20` | Grace margin before an online user drops to offline (hysteresis): online at the timeout, offline only after timeout + this. Prevents online/offline flicker for users hovering at the boundary |
| `DEVPLACE_COMMENT_MAX_LENGTH` | `1000` | Maximum character length for comment body text. Applied server-side via Pydantic validation and client-side via `maxLength` on textareas. The database has no column-length constraint, so this can be changed at any time via the environment variable (requires restart) |
### Runtime settings ### Runtime settings
+3 -1
View File
@@ -37,9 +37,10 @@ from devplacepy.cli.backups import (
) )
from devplacepy.cli.containers import ( from devplacepy.cli.containers import (
cmd_containers_list, cmd_containers_list,
cmd_containers_reconcile,
cmd_containers_prune, cmd_containers_prune,
cmd_containers_prune_builds, cmd_containers_prune_builds,
cmd_containers_prune_stopped,
cmd_containers_reconcile,
cmd_containers_gc_workspaces, cmd_containers_gc_workspaces,
) )
from devplacepy.cli.migrate import cmd_emoji_sync, cmd_migrate_data from devplacepy.cli.migrate import cmd_emoji_sync, cmd_migrate_data
@@ -83,6 +84,7 @@ __all__ = [
"cmd_containers_reconcile", "cmd_containers_reconcile",
"cmd_containers_prune", "cmd_containers_prune",
"cmd_containers_prune_builds", "cmd_containers_prune_builds",
"cmd_containers_prune_stopped",
"cmd_containers_gc_workspaces", "cmd_containers_gc_workspaces",
"cmd_emoji_sync", "cmd_emoji_sync",
"cmd_migrate_data", "cmd_migrate_data",
+27 -2
View File
@@ -1,5 +1,6 @@
# retoor <retoor@molodetz.nl> # retoor <retoor@molodetz.nl>
import asyncio
from devplacepy.cli._shared import _audit_cli from devplacepy.cli._shared import _audit_cli
@@ -18,7 +19,6 @@ def cmd_containers_list(args):
def cmd_containers_reconcile(args): def cmd_containers_reconcile(args):
import asyncio
from devplacepy.services.containers.service import ContainerService from devplacepy.services.containers.service import ContainerService
asyncio.run(ContainerService().run_once()) asyncio.run(ContainerService().run_once())
@@ -27,7 +27,6 @@ def cmd_containers_reconcile(args):
def cmd_containers_prune(args): def cmd_containers_prune(args):
import asyncio
from devplacepy.services.containers.runtime import get_backend from devplacepy.services.containers.runtime import get_backend
from devplacepy.services.containers.service import ContainerService from devplacepy.services.containers.service import ContainerService
@@ -66,6 +65,29 @@ def cmd_containers_prune_builds(args):
) )
def cmd_containers_prune_stopped(args):
from devplacepy.services.containers import api, store
from devplacepy.services.containers.service import ContainerService
async def run():
instances = store.all_instances()
marked = 0
for inst in instances:
if inst["status"] == store.ST_STOPPED and inst["desired_state"] == store.DESIRED_STOPPED:
api.mark_for_removal(inst, actor=("system", "prune-stopped"))
marked += 1
if marked:
await ContainerService().run_once()
return marked
marked = asyncio.run(run())
_audit_cli("cli.containers.prune_stopped", f"CLI removed {marked} stale stopped container(s)", metadata={"count": marked})
if marked:
print(f"Prune stopped: marked {marked} container(s) for removal and reconciled.")
else:
print("No stopped containers to prune.")
def cmd_containers_gc_workspaces(args): def cmd_containers_gc_workspaces(args):
import shutil import shutil
from pathlib import Path from pathlib import Path
@@ -102,6 +124,9 @@ def register_containers(subparsers):
"prune-builds", "prune-builds",
help="Remove legacy per-project images and clear the dockerfiles/builds tables", help="Remove legacy per-project images and clear the dockerfiles/builds tables",
).set_defaults(func=cmd_containers_prune_builds) ).set_defaults(func=cmd_containers_prune_builds)
containers_sub.add_parser(
"prune-stopped", help="Remove all stopped containers (completed purpose)"
).set_defaults(func=cmd_containers_prune_stopped)
containers_sub.add_parser( containers_sub.add_parser(
"gc-workspaces", help="Remove workspace dirs with no instances" "gc-workspaces", help="Remove workspace dirs with no instances"
).set_defaults(func=cmd_containers_gc_workspaces) ).set_defaults(func=cmd_containers_gc_workspaces)
-1
View File
@@ -68,7 +68,6 @@ INTERNAL_GATEWAY_URL = f"{INTERNAL_BASE_URL}/openai/v1/chat/completions"
INTERNAL_EMBED_URL = f"{INTERNAL_BASE_URL}/openai/v1/embeddings" INTERNAL_EMBED_URL = f"{INTERNAL_BASE_URL}/openai/v1/embeddings"
INTERNAL_MODEL = "molodetz" INTERNAL_MODEL = "molodetz"
INTERNAL_EMBED_MODEL = "molodetz~embed" INTERNAL_EMBED_MODEL = "molodetz~embed"
COMMENT_MAX_LENGTH = int(environ.get("DEVPLACE_COMMENT_MAX_LENGTH", "1000"))
DEFAULT_CORRECTION_PROMPT = "Leave literary as is, only do punctuation and casing" DEFAULT_CORRECTION_PROMPT = "Leave literary as is, only do punctuation and casing"
DEFAULT_MODIFIER_PROMPT = ( DEFAULT_MODIFIER_PROMPT = (
"Execute what is behind `@ai` (the prompt) and replace that part including `@ai`" "Execute what is behind `@ai` (the prompt) and replace that part including `@ai`"
+2 -3
View File
@@ -1,6 +1,5 @@
# retoor <retoor@molodetz.nl> # retoor <retoor@molodetz.nl>
from devplacepy.config import COMMENT_MAX_LENGTH
from .._shared import COMMENT_TARGETS, GIST_LANGUAGES, PROJECT_TYPES, endpoint, field from .._shared import COMMENT_TARGETS, GIST_LANGUAGES, PROJECT_TYPES, endpoint, field
from devplacepy.constants import TOPICS from devplacepy.constants import TOPICS
@@ -230,7 +229,7 @@ four ways to sign requests.
"textarea", "textarea",
True, True,
"Nice work.", "Nice work.",
f"Body, 3-{COMMENT_MAX_LENGTH} characters.", "Body, 3-1000 characters.",
), ),
field( field(
"target_uid", "target_uid",
@@ -291,7 +290,7 @@ four ways to sign requests.
"textarea", "textarea",
True, True,
"Edited body.", "Edited body.",
f"New body, 3-{COMMENT_MAX_LENGTH} characters.", "New body, 3-1000 characters.",
), ),
], ],
sample_response={ sample_response={
+1 -3
View File
@@ -1,7 +1,5 @@
# retoor <retoor@molodetz.nl> # retoor <retoor@molodetz.nl>
from devplacepy.config import COMMENT_MAX_LENGTH
ROLE_LABELS = {"public": "Public", "user": "Member"} ROLE_LABELS = {"public": "Public", "user": "Member"}
@@ -239,7 +237,7 @@ DEVRANT_GROUPS = {
encoding="form", encoding="form",
params=[ params=[
field("rant_id", "path", type="int", required=True, example="1", description="Rant id."), field("rant_id", "path", type="int", required=True, example="1", description="Rant id."),
field("comment", "body", type="textarea", required=True, example="Great rant!", description=f"Comment text, 1-{COMMENT_MAX_LENGTH} chars."), field("comment", "body", type="textarea", required=True, example="Great rant!", description="Comment text, 1-1000 chars."),
], ],
sample_response={"success": True}, sample_response={"success": True},
), ),
+3 -3
View File
@@ -6,7 +6,7 @@ from datetime import datetime
from typing import Literal, Optional from typing import Literal, Optional
from pydantic import BaseModel, Field, field_validator, model_validator from pydantic import BaseModel, Field, field_validator, model_validator
from devplacepy.constants import TOPICS, REACTION_EMOJI from devplacepy.constants import TOPICS, REACTION_EMOJI
from devplacepy.config import COMMENT_MAX_LENGTH, DEFAULT_CORRECTION_PROMPT, DEFAULT_MODIFIER_PROMPT from devplacepy.config import DEFAULT_CORRECTION_PROMPT, DEFAULT_MODIFIER_PROMPT
def normalize_european_date(value): def normalize_european_date(value):
@@ -157,7 +157,7 @@ class PostEditForm(BaseModel):
class CommentForm(BaseModel): class CommentForm(BaseModel):
content: str = Field(min_length=3, max_length=COMMENT_MAX_LENGTH) content: str = Field(min_length=3, max_length=1000)
target_uid: str = Field(default="", max_length=36) target_uid: str = Field(default="", max_length=36)
post_uid: str = Field(default="", max_length=36) post_uid: str = Field(default="", max_length=36)
target_type: Literal["post", "project", "news", "issue", "gist"] = "post" target_type: Literal["post", "project", "news", "issue", "gist"] = "post"
@@ -172,7 +172,7 @@ class CommentForm(BaseModel):
class CommentEditForm(BaseModel): class CommentEditForm(BaseModel):
content: str = Field(min_length=3, max_length=COMMENT_MAX_LENGTH) content: str = Field(min_length=3, max_length=1000)
class ProjectForm(BaseModel): class ProjectForm(BaseModel):
+1 -2
View File
@@ -69,8 +69,7 @@ async def edit_comment(request: Request, comment_id: str):
if not is_owner(comment, user): if not is_owner(comment, user):
return dr_error("You can only edit your own comments.", fail_reason="not_owner") return dr_error("You can only edit your own comments.", fail_reason="not_owner")
text = (params.get("comment") or "").strip() text = (params.get("comment") or "").strip()
from devplacepy.config import COMMENT_MAX_LENGTH if len(text) < 1 or len(text) > 1000:
if len(text) < 1 or len(text) > COMMENT_MAX_LENGTH:
return dr_error("Invalid comment length.", fail_reason="length") return dr_error("Invalid comment length.", fail_reason="length")
get_table("comments").update( get_table("comments").update(
{ {
+1 -2
View File
@@ -227,8 +227,7 @@ async def comment_rant(request: Request, rant_id: str):
text = (params.get("comment") or "").strip() text = (params.get("comment") or "").strip()
if len(text) < 1: if len(text) < 1:
return dr_error("Your comment is too short.") return dr_error("Your comment is too short.")
from devplacepy.config import COMMENT_MAX_LENGTH if len(text) > 1000:
if len(text) > COMMENT_MAX_LENGTH:
return dr_error("Your comment is too long.") return dr_error("Your comment is too long.")
create_comment_record(request, user, "post", post["uid"], text) create_comment_record(request, user, "post", post["uid"], text)
return dr_ok() return dr_ok()
+1 -1
View File
@@ -72,7 +72,7 @@ MENTION_POLL_SECONDS = int(os.environ.get("MENTION_POLL_SECONDS", "30"))
DM_POLL_SECONDS = int(os.environ.get("DM_POLL_SECONDS", "10")) DM_POLL_SECONDS = int(os.environ.get("DM_POLL_SECONDS", "10"))
BOT_USERNAME = os.environ.get("BOT_USERNAME", "") BOT_USERNAME = os.environ.get("BOT_USERNAME", "")
COMMENT_CHAR_LIMIT = int(os.environ.get("DEVPLACE_COMMENT_MAX_LENGTH", "1000")) COMMENT_CHAR_LIMIT = 1000
MESSAGE_CHAR_LIMIT = 2000 MESSAGE_CHAR_LIMIT = 2000
PART_SUFFIX_RESERVE = 12 PART_SUFFIX_RESERVE = 12
PART_DELIVERY_DELAY = 0.5 PART_DELIVERY_DELAY = 0.5
@@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
from devplacepy.config import COMMENT_MAX_LENGTH
from ..spec import Action from ..spec import Action
from ._shared import ATTACHMENTS, TARGET_TYPE, body, confirm, path from ._shared import ATTACHMENTS, TARGET_TYPE, body, confirm, path
@@ -29,7 +28,7 @@ COMMENTS_ACTIONS: tuple[Action, ...] = (
summary="Edit the body of one of your own comments", summary="Edit the body of one of your own comments",
params=( params=(
path("comment_uid", "Uid of the comment."), path("comment_uid", "Uid of the comment."),
body("content", f"New comment body, 3-{COMMENT_MAX_LENGTH} characters.", required=True), body("content", "New comment body, 3-1000 characters.", required=True),
), ),
), ),
Action( Action(
-2
View File
@@ -83,8 +83,6 @@ export class CommentManager {
textarea.className = "emoji-picker-target"; textarea.className = "emoji-picker-target";
textarea.value = text.dataset.raw || text.textContent; textarea.value = text.dataset.raw || text.textContent;
textarea.rows = 3; textarea.rows = 3;
const ref = document.querySelector("[data-comment-maxlength]");
textarea.maxLength = ref ? parseInt(ref.dataset.commentMaxlength, 10) : 1000;
form.appendChild(textarea); form.appendChild(textarea);
const actions = document.createElement("div"); const actions = document.createElement("div");
+2 -2
View File
@@ -1,9 +1,9 @@
{% if user %} {% if user %}
<form class="comment-form" method="POST" action="/comments/create" data-comment-maxlength="{{ comment_max_length }}"> <form class="comment-form" method="POST" action="/comments/create">
<input type="hidden" name="target_uid" value="{{ _comment_target_uid }}"> <input type="hidden" name="target_uid" value="{{ _comment_target_uid }}">
<input type="hidden" name="target_type" value="{{ _comment_target_type }}"> <input type="hidden" name="target_type" value="{{ _comment_target_type }}">
{% set _user = user %}{% set _size = 32 %}{% set _size_class = "sm" %}{% include "_avatar_link.html" %} {% set _user = user %}{% set _size = 32 %}{% set _size_class = "sm" %}{% include "_avatar_link.html" %}
<textarea name="content" placeholder="Your opinion goes here..." required aria-required="true" aria-label="Comment" maxlength="{{ comment_max_length }}" class="emoji-picker-target" data-mention></textarea> <textarea name="content" placeholder="Your opinion goes here..." required aria-required="true" aria-label="Comment" maxlength="1000" class="emoji-picker-target" data-mention></textarea>
<div class="comment-form-actions"> <div class="comment-form-actions">
<dp-upload multiple <dp-upload multiple
max-size="{{ max_upload_size_mb() }}" max-size="{{ max_upload_size_mb() }}"
@@ -107,7 +107,7 @@ instead of the gateway. Before the reconciler has recorded a gateway, the proxy
The service tile on `/admin/services` shows instance counts; per-instance aggregates (CPU/memory, p95 The service tile on `/admin/services` shows instance counts; per-instance aggregates (CPU/memory, p95
runtime) are computed on read over the ring buffer. The CLI exposes runtime) are computed on read over the ring buffer. The CLI exposes
`devplace containers list | reconcile | prune | prune-builds | gc-workspaces` (`prune-builds` is a `devplace containers list | reconcile | prune | prune-builds | prune-stopped | gc-workspaces` (`prune-builds` is a
one-time cleanup that removes legacy per-project images and the old dockerfiles/builds tables). Devii's one-time cleanup that removes legacy per-project images and the old dockerfiles/builds tables). Devii's
admin `container_*` tools cover the same instance operations in natural language. admin `container_*` tools cover the same instance operations in natural language.
</div> </div>
+1 -2
View File
@@ -6,7 +6,7 @@ import jinja2
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from markupsafe import Markup, escape from markupsafe import Markup, escape
from devplacepy.cache import TTLCache from devplacepy.cache import TTLCache
from devplacepy.config import COMMENT_MAX_LENGTH, STATIC_VERSION, TEMPLATES_DIR, TEMPLATE_AUTO_RELOAD, PRESENCE_TIMEOUT_SECONDS from devplacepy.config import STATIC_VERSION, TEMPLATES_DIR, TEMPLATE_AUTO_RELOAD, PRESENCE_TIMEOUT_SECONDS
from devplacepy.constants import TOPICS, REACTION_EMOJI from devplacepy.constants import TOPICS, REACTION_EMOJI
from devplacepy.database import get_int_setting, get_setting, get_table from devplacepy.database import get_int_setting, get_setting, get_table
from devplacepy.avatar import avatar_url, avatar_seed from devplacepy.avatar import avatar_url, avatar_seed
@@ -67,7 +67,6 @@ templates.env.globals["is_self"] = is_self
templates.env.globals["guest_disabled"] = guest_disabled templates.env.globals["guest_disabled"] = guest_disabled
templates.env.globals["is_online"] = presence.is_online templates.env.globals["is_online"] = presence.is_online
templates.env.globals["presence_timeout"] = PRESENCE_TIMEOUT_SECONDS templates.env.globals["presence_timeout"] = PRESENCE_TIMEOUT_SECONDS
templates.env.globals["comment_max_length"] = COMMENT_MAX_LENGTH
from devplacepy.docs_devrant import devrant_endpoints from devplacepy.docs_devrant import devrant_endpoints
+24 -28
View File
@@ -1,28 +1,24 @@
2026-07-19T08:11:05 INFO logging initialised at /workspace/repo/dpc.log 2026-07-19T09:32:07 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T08:11:05 DEBUG model=molodetz-pro fps=30 2026-07-19T09:32:07 DEBUG model=molodetz-pro fps=30
2026-07-19T08:11:05 INFO read task from file: /workspace/prompts/research-1.txt 2026-07-19T09:32:07 INFO read task from file: /workspace/prompts/research-1.txt
2026-07-19T08:11:05 INFO settings merged: model=<default> allow=0 deny=0 ask=0 2026-07-19T09:32:07 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T15:03:09 INFO logging initialised at /workspace/repo/dpc.log 2026-07-19T15:54:39 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T15:03:09 DEBUG model=molodetz-pro fps=30 2026-07-19T15:54:39 DEBUG model=molodetz-pro fps=30
2026-07-19T15:03:09 INFO read task from file: /workspace/prompts/research-2.txt 2026-07-19T15:54:39 INFO read task from file: /workspace/prompts/research-2.txt
2026-07-19T15:03:09 INFO settings merged: model=<default> allow=0 deny=0 ask=0 2026-07-19T15:54:39 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T15:40:16 INFO logging initialised at /workspace/repo/dpc.log 2026-07-19T17:25:31 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T15:40:16 DEBUG model=molodetz-pro fps=30 2026-07-19T17:25:31 DEBUG model=molodetz-pro fps=30
2026-07-19T15:40:16 INFO read task from file: /workspace/prompts/research-3.txt 2026-07-19T17:25:31 INFO read task from file: /workspace/prompts/research-3.txt
2026-07-19T15:40:16 INFO settings merged: model=<default> allow=0 deny=0 ask=0 2026-07-19T17:25:31 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T17:35:33 INFO logging initialised at /workspace/repo/dpc.log 2026-07-19T17:37:32 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T17:35:33 DEBUG model=molodetz-pro fps=30 2026-07-19T17:37:32 DEBUG model=molodetz-pro fps=30
2026-07-19T17:35:33 INFO read task from file: /workspace/prompts/research-4.txt 2026-07-19T17:37:32 INFO read task from file: /workspace/prompts/research-4.txt
2026-07-19T17:35:33 INFO settings merged: model=<default> allow=0 deny=0 ask=0 2026-07-19T17:37:32 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T18:11:47 INFO logging initialised at /workspace/repo/dpc.log 2026-07-19T18:22:18 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T18:11:47 DEBUG model=molodetz-pro fps=30 2026-07-19T18:22:18 DEBUG model=molodetz-pro fps=30
2026-07-19T18:11:47 INFO read task from file: /workspace/prompts/execution-1.txt 2026-07-19T18:22:18 INFO read task from file: /workspace/prompts/execution-1.txt
2026-07-19T18:11:47 INFO settings merged: model=<default> allow=0 deny=0 ask=0 2026-07-19T18:22:18 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T18:31:31 INFO logging initialised at /workspace/repo/dpc.log 2026-07-19T18:49:24 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T18:31:31 DEBUG model=molodetz-pro fps=30 2026-07-19T18:49:24 DEBUG model=molodetz-pro fps=30
2026-07-19T18:31:31 INFO read task from file: /workspace/prompts/execution-2.txt 2026-07-19T18:49:24 INFO read task from file: /workspace/prompts/execution-2.txt
2026-07-19T18:31:31 INFO settings merged: model=<default> allow=0 deny=0 ask=0 2026-07-19T18:49:24 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T18:58:51 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T18:58:51 DEBUG model=molodetz-pro fps=30
2026-07-19T18:58:51 INFO read task from file: /workspace/prompts/execution-3.txt
2026-07-19T18:58:51 INFO settings merged: model=<default> allow=0 deny=0 ask=0
-19
View File
@@ -345,22 +345,3 @@ def test_comment_links_multiple_attachments(app_server):
assert u1 in r.text and u2 in r.text, ( assert u1 in r.text and u2 in r.text, (
"both attachments must be linked and displayed on the comment" "both attachments must be linked and displayed on the comment"
) )
def test_create_comment_too_long_rejected(app_server):
from devplacepy.config import COMMENT_MAX_LENGTH
s, _ = _session_comments()
post_uid = _create_post_comments(s, f"cmt-long-{int(time.time() * 1000)}")
r = s.post(
f"{BASE_URL}/comments/create",
data={
"content": "x" * (COMMENT_MAX_LENGTH + 1),
"target_type": "post",
"post_uid": post_uid,
"target_uid": post_uid,
},
allow_redirects=False,
)
assert r.status_code in (400, 422), r.text[:300]
refresh_snapshot()
assert get_table("comments").find_one(target_uid=post_uid) is None
-16
View File
@@ -129,19 +129,3 @@ def test_edit_comment_too_short_rejected(app_server):
assert r.status_code in (400, 422) assert r.status_code in (400, 422)
refresh_snapshot() refresh_snapshot()
assert get_table("comments").find_one(uid=comment["uid"])["content"] == "Long enough body" assert get_table("comments").find_one(uid=comment["uid"])["content"] == "Long enough body"
def test_edit_comment_too_long_rejected(app_server):
from devplacepy.config import COMMENT_MAX_LENGTH
s, _ = _session_edit()
post_uid = _create_post_edit(s, f"edit-long-{int(time.time() * 1000)}")
comment = _create_comment_edit(s, post_uid, "Normal body length")
r = s.post(
f"{BASE_URL}/comments/edit/{comment['uid']}",
headers=JSON_edit,
data={"content": "x" * (COMMENT_MAX_LENGTH + 1)},
allow_redirects=False,
)
assert r.status_code in (400, 422), r.text[:300]
refresh_snapshot()
assert get_table("comments").find_one(uid=comment["uid"])["content"] == "Normal body length"