Skip to content

Commit 02efb2f

Browse files
ryanmcmillanDelega Bot
andauthored
fix: reject blank task content (#18)
Co-authored-by: Delega Bot <hello@delega.dev>
1 parent 4bf4cf2 commit 02efb2f

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

backend/schemas.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ def _normalize_recurring_type(v: Optional[str]) -> Optional[str]:
2020
return _RECURRING_TYPE_ALIASES.get(v, v)
2121

2222

23+
def _reject_blank_text(v: Optional[str], field_name: str) -> Optional[str]:
24+
if v is None:
25+
return v
26+
if not isinstance(v, str):
27+
return v
28+
if not v.strip():
29+
raise ValueError(f"{field_name} must not be blank")
30+
return v
31+
32+
2333
# ============ Project Schemas ============
2434

2535
class ProjectBase(BaseModel):
@@ -112,6 +122,11 @@ class TaskBase(BaseModel):
112122
reminder_time: Optional[datetime] = None
113123
context: Optional[dict] = None # Persistent context blob for agent state
114124

125+
@field_validator("content")
126+
@classmethod
127+
def validate_content(cls, v):
128+
return _reject_blank_text(v, "content")
129+
115130

116131
class TaskCreate(TaskBase):
117132
assigned_to_agent_id: Optional[int] = None # Assign to agent at creation
@@ -141,6 +156,11 @@ class TaskUpdate(BaseModel):
141156
assigned_to_agent_id: Optional[int] = None # Assign/reassign to agent
142157
context: Optional[dict] = None # Update context blob
143158

159+
@field_validator("content")
160+
@classmethod
161+
def validate_content(cls, v):
162+
return _reject_blank_text(v, "content")
163+
144164
@field_validator("recurring_type", mode="before")
145165
@classmethod
146166
def normalize_recurring(cls, v):

backend/tests/test_permissions.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,3 +535,29 @@ def test_post_complete_and_uncomplete_keep_status_in_sync(self, fresh_db, client
535535
assert task.status == "open"
536536
assert task.completed_by_agent_id is None
537537
db.close()
538+
539+
540+
class TestTaskContentValidation:
541+
"""Task content must contain non-whitespace text."""
542+
543+
@pytest.mark.parametrize("content", ["", " ", "\n\t"])
544+
def test_create_task_rejects_blank_content(self, fresh_db, client, content):
545+
_, agent_key = make_agent(fresh_db, "worker")
546+
547+
response = client.post("/api/tasks", json={"content": content}, headers=auth(agent_key))
548+
549+
assert response.status_code == 422
550+
assert "content" in response.text.lower()
551+
552+
@pytest.mark.parametrize("content", ["", " ", "\n\t"])
553+
def test_update_task_rejects_blank_content(self, fresh_db, client, content):
554+
_, agent_key = make_agent(fresh_db, "worker")
555+
556+
created = client.post("/api/tasks", json={"content": "valid"}, headers=auth(agent_key))
557+
assert created.status_code == 200
558+
task_id = created.json()["id"]
559+
560+
response = client.put(f"/api/tasks/{task_id}", json={"content": content}, headers=auth(agent_key))
561+
562+
assert response.status_code == 422
563+
assert "content" in response.text.lower()

0 commit comments

Comments
 (0)