phase: 83_chat_save_payload_limits
All verification criteria are confirmed green. Final report: **Phase 83 — final verification pass (all tasks already complete; verified, nothing to fix)** - Verified `app/schemas.py` carries every A1 cap (text/thinking 32 000, sources 20, suggestions 50×200, tools 50, SourceRef 120/1000/500, ToolCall 100/2000, messages ≤200) — boundary-only, `app/api/chats.py` untouched - Verified all test pins exist and pass: 32 unit boundary tests (both edges of every cap + round-trip), 4 oversized-422 integration pins (text/201-msgs/21-sources/PUT, each with nothing-stored/row-unchanged assertions), A3 SSE done-event pin at column maxima, dedicated E2E (anon 40 000-char POST → 422 + small save → 201) - `uv run pytest tests/unit/test_schemas.py -v` — 32 passed - `uv run pytest tests/integration/test_chats_api.py` — exit 0; A3 pin — passed - `uv run pytest tests/e2e/test_chat_save_payload_limits.py -v --no-cov` — 2 passed (isolation); `uv run pytest tests/e2e/test_chat_history.py -v --no-cov` — 5 passed - `uv run pytest` — exit 0 (~1 704 tests, 0 fail/skip); `uv run pytest --cov=app` — TOTAL 99%, `app/schemas.py` 100% (>90% ✓) - `uv run ruff check . && uv run pyright` — clean (0 errors) - `git diff --stat` — only `app/schemas.py`, 3 test files (+`test_chat_api.py` A3 pin, sanctioned by task 02), phase files; no `app/api/chats.py`/`alembic`/`frontend`/`pyproject`/`uv.lock` diff ✓ - All completion criteria met; commit + phase move left to the harness per pipeline rules (changes stay in working tree) - Deviation note: list caps use `max_length` instead of `max_items` — identical in pydantic 2.13 (`max_items` is a deprecated alias); both boundaries behaviorally pinned - Next pending phase: `84_docs_push_error_sanitization`
This commit is contained in:
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import ValidationError
|
||||
from sqlalchemy import delete, func, select, text
|
||||
|
||||
from app.api import chat as chat_api
|
||||
@@ -32,6 +33,7 @@ from app.rag import agent
|
||||
from app.rag.agent import AGENT_TOOLS
|
||||
from app.rag.importer import import_sources
|
||||
from app.rag.llm import EmbeddingError, LLMError, StreamPiece, ToolCallPiece
|
||||
from app.schemas import ChatDoneEvent, SourceRef
|
||||
from tests.conftest import ADMIN_PASSWORD
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -1406,3 +1408,34 @@ def test_history_rejects_more_than_100_entries(client, db) -> None:
|
||||
},
|
||||
)
|
||||
assert r.status_code == 422
|
||||
|
||||
|
||||
def test_done_event_serializes_column_maximum_source_refs() -> None:
|
||||
"""Phase 83 A3 pin: ``SourceRef`` is SHARED by the SSE ``done``
|
||||
event and the saved-chat surface — the boundary caps added there
|
||||
(``source`` ≤ 120, ``path`` ≤ 1000, ``title`` ≤ 500) mirror the
|
||||
``documents`` column lengths EXACTLY, so a server-built event from
|
||||
a full-length row (values at the column maxima) still constructs
|
||||
and serializes byte-identical: the SSE contract is provably
|
||||
unaffected. The one-over caps raise — only client-saved refs can
|
||||
ever trip a cap, never a server-built ref."""
|
||||
event = ChatDoneEvent(
|
||||
deflected=False,
|
||||
sources=[SourceRef(source="s" * 120, path="p" * 1000, title="t" * 500)],
|
||||
suggestions=[],
|
||||
)
|
||||
assert event.model_dump() == {
|
||||
"type": "done",
|
||||
"deflected": False,
|
||||
"sources": [{"source": "s" * 120, "path": "p" * 1000, "title": "t" * 500}],
|
||||
"suggestions": [],
|
||||
}
|
||||
# The caps sit exactly ON the column maxima: one over any of them
|
||||
# is rejected (a row could never hold such a value in the first
|
||||
# place — the string columns enforce the same lengths).
|
||||
with pytest.raises(ValidationError):
|
||||
SourceRef(source="s" * 121, path="p" * 1000, title="t" * 500)
|
||||
with pytest.raises(ValidationError):
|
||||
SourceRef(source="s" * 120, path="p" * 1001, title="t" * 500)
|
||||
with pytest.raises(ValidationError):
|
||||
SourceRef(source="s" * 120, path="p" * 1000, title="t" * 501)
|
||||
|
||||
Reference in New Issue
Block a user