mirror of
https://github.com/himanshudongre/smriti.git
synced 2026-08-28 05:14:59 +00:00
Fix smriti current crash on list-valued blocked_by
External-machine validation found GET /api/v5/current 500s on a real project: a structured task can carry `blocked_by` as a list of dependency labels, but CurrentTask.blocked_by is typed Optional[str], so constructing the model raised a Pydantic ValidationError. Add a field validator that normalizes `blocked_by` — string, list, or null — to a single display string. The API contract is unchanged (blocked_by stays a string), so the CLI and the Project Current State UI panel, which both read this endpoint, need no change. Adds a regression test.
This commit is contained in:
parent
93c8ef6a4e
commit
2bbb7d27bc
2 changed files with 59 additions and 1 deletions
|
|
@ -17,7 +17,7 @@ from datetime import datetime
|
|||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
|
@ -108,6 +108,25 @@ class CurrentTask(BaseModel):
|
|||
blocked_by: Optional[str] = None
|
||||
status: str = "open"
|
||||
|
||||
@field_validator("blocked_by", mode="before")
|
||||
@classmethod
|
||||
def _coerce_blocked_by(cls, value: object) -> Optional[str]:
|
||||
"""Normalize blocked_by from whatever real task payloads carry.
|
||||
|
||||
A structured task's `blocked_by` shows up as a string, a list of
|
||||
dependency labels (a task blocked by several others), or null.
|
||||
Coerce any of them to a single display string so the current-state
|
||||
surface never 500s on a list-valued blocked_by.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
return value.strip() or None
|
||||
if isinstance(value, (list, tuple)):
|
||||
labels = [str(item).strip() for item in value if str(item).strip()]
|
||||
return ", ".join(labels) or None
|
||||
return str(value).strip() or None
|
||||
|
||||
|
||||
class RecentActivityEntry(BaseModel):
|
||||
"""One recent checkpoint, newest-first. Spans all branches so the
|
||||
|
|
|
|||
|
|
@ -201,6 +201,45 @@ def test_current_open_tasks_accept_legacy_intent_type(client):
|
|||
assert cur["open_tasks_by_intent"]["implement"][0]["id"] == "middleware"
|
||||
|
||||
|
||||
def test_current_handles_list_valued_blocked_by(client):
|
||||
"""Real projects produce tasks whose `blocked_by` is a list of
|
||||
dependency labels. The current-state surface must normalize that to a
|
||||
display string, not 500 on it (regression: HTTP 500 from a Pydantic
|
||||
ValidationError when blocked_by was a list)."""
|
||||
repo_id = _create_repo(client, "List Blocked-By")
|
||||
session_id = _create_session(client, repo_id)
|
||||
_commit(
|
||||
client,
|
||||
repo_id,
|
||||
session_id,
|
||||
message="Tasks with varied blocked_by shapes",
|
||||
tasks=[
|
||||
{
|
||||
"text": "Wire the limiter into the gateway",
|
||||
"intent_hint": "implement",
|
||||
"id": "wire-gateway",
|
||||
"status": "open",
|
||||
"blocked_by": ["middleware", "load-test"],
|
||||
},
|
||||
{
|
||||
"text": "Single-dependency task",
|
||||
"intent_hint": "implement",
|
||||
"id": "single-dep",
|
||||
"status": "open",
|
||||
"blocked_by": "wire-gateway",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
# Pre-fix, a list-valued blocked_by raised a Pydantic ValidationError
|
||||
# and this request 500ed; _get_current asserts a 200.
|
||||
cur = _get_current(client, repo_id)
|
||||
|
||||
by_id = {t["id"]: t for t in cur["open_tasks_by_intent"]["implement"]}
|
||||
assert by_id["wire-gateway"]["blocked_by"] == "middleware, load-test"
|
||||
assert by_id["single-dep"]["blocked_by"] == "wire-gateway"
|
||||
|
||||
|
||||
def test_current_recent_milestones(client):
|
||||
"""Milestone notes surface in recent_milestones; plain notes do not."""
|
||||
repo_id = _create_repo(client, "Milestones")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue