From 7d0000827bee0c428a40b3a95f69b7e3b98d3846 Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Wed, 4 Feb 2026 02:47:22 -0500 Subject: [PATCH] feat: add scratchpad field to session model and schemas Co-Authored-By: Claude Opus 4.5 --- backend/app/models/session.py | 6 ++++- backend/app/schemas/session.py | 12 ++++++++- backend/tests/test_sessions.py | 48 ++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/backend/app/models/session.py b/backend/app/models/session.py index 4d222519..bec2cdd6 100644 --- a/backend/app/models/session.py +++ b/backend/app/models/session.py @@ -1,7 +1,8 @@ import uuid from datetime import datetime, timezone from typing import Optional, Any -from sqlalchemy import String, DateTime, ForeignKey, Boolean +from sqlalchemy import String, DateTime, ForeignKey, Boolean, Text +import sqlalchemy as sa from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID, JSONB from app.core.database import Base @@ -44,6 +45,9 @@ class Session(Base): ticket_number: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) client_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) exported: Mapped[bool] = mapped_column(Boolean, default=False) + scratchpad: Mapped[Optional[str]] = mapped_column( + Text, nullable=True, server_default=sa.text("''") + ) # Relationships tree: Mapped["Tree"] = relationship("Tree", back_populates="sessions") diff --git a/backend/app/schemas/session.py b/backend/app/schemas/session.py index ab0033cf..b4ab6377 100644 --- a/backend/app/schemas/session.py +++ b/backend/app/schemas/session.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Optional, Any from uuid import UUID -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, validator class DecisionRecord(BaseModel): @@ -27,6 +27,7 @@ class SessionUpdate(BaseModel): custom_steps: Optional[list[dict[str, Any]]] = None ticket_number: Optional[str] = Field(None, max_length=100) client_name: Optional[str] = Field(None, max_length=255) + scratchpad: Optional[str] = None class SessionResponse(BaseModel): @@ -42,6 +43,11 @@ class SessionResponse(BaseModel): ticket_number: Optional[str] = None client_name: Optional[str] = None exported: bool + scratchpad: str = "" + + @validator('scratchpad', pre=True, always=True) + def normalize_scratchpad(cls, v): + return v or "" class Config: from_attributes = True @@ -51,3 +57,7 @@ class SessionExport(BaseModel): format: str = Field(default="markdown", pattern="^(text|markdown|html)$") include_timestamps: bool = True include_tree_info: bool = True + + +class ScratchpadUpdate(BaseModel): + scratchpad: str diff --git a/backend/tests/test_sessions.py b/backend/tests/test_sessions.py index b8263caa..2e50a119 100644 --- a/backend/tests/test_sessions.py +++ b/backend/tests/test_sessions.py @@ -315,3 +315,51 @@ class TestSessions: data = response.json() assert len(data) >= 1 assert all(s["completed_at"] is None for s in data) + + @pytest.mark.asyncio + async def test_create_session_has_scratchpad( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test that new sessions include scratchpad field.""" + session_data = { + "tree_id": test_tree["id"], + } + + response = await client.post( + "/api/v1/sessions", + json=session_data, + headers=auth_headers + ) + + assert response.status_code == 201 + data = response.json() + assert "scratchpad" in data + assert data["scratchpad"] == "" + + @pytest.mark.asyncio + async def test_update_scratchpad_via_put( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test updating scratchpad through the existing PUT endpoint.""" + # Create session + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + # Update scratchpad via PUT + update_data = { + "scratchpad": "- Server IP: 192.168.1.50\n- Error: 0x80070005" + } + + response = await client.put( + f"/api/v1/sessions/{session_id}", + json=update_data, + headers=auth_headers + ) + + assert response.status_code == 200 + data = response.json() + assert data["scratchpad"] == update_data["scratchpad"]