diff --git a/backend/app/api/endpoints/sessions.py b/backend/app/api/endpoints/sessions.py index 2054d651..86286b6c 100644 --- a/backend/app/api/endpoints/sessions.py +++ b/backend/app/api/endpoints/sessions.py @@ -10,7 +10,7 @@ from app.core.database import get_db from app.models.tree import Tree from app.models.session import Session from app.models.user import User -from app.schemas.session import SessionCreate, SessionUpdate, SessionResponse, SessionExport +from app.schemas.session import SessionCreate, SessionUpdate, SessionResponse, SessionExport, ScratchpadUpdate from app.api.deps import get_current_user router = APIRouter(prefix="/sessions", tags=["sessions"]) @@ -183,6 +183,35 @@ async def complete_session( return session +@router.patch("/{session_id}/scratchpad", response_model=SessionResponse) +async def update_scratchpad( + session_id: UUID, + data: ScratchpadUpdate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_user)] +): + """Update session scratchpad. Accepts updates on both active and completed sessions.""" + result = await db.execute(select(Session).where(Session.id == session_id)) + session = result.scalar_one_or_none() + + if not session: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Session not found" + ) + + if session.user_id != current_user.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You don't have access to this session" + ) + + session.scratchpad = data.scratchpad + await db.commit() + await db.refresh(session) + return session + + @router.post("/{session_id}/export") async def export_session( session_id: UUID, diff --git a/backend/tests/test_sessions.py b/backend/tests/test_sessions.py index 2e50a119..d7994de4 100644 --- a/backend/tests/test_sessions.py +++ b/backend/tests/test_sessions.py @@ -363,3 +363,101 @@ class TestSessions: assert response.status_code == 200 data = response.json() assert data["scratchpad"] == update_data["scratchpad"] + + @pytest.mark.asyncio + async def test_patch_scratchpad( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test the dedicated PATCH scratchpad 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"] + + # Patch scratchpad + response = await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": "- IP: 10.0.0.1\n- User: jsmith"}, + headers=auth_headers + ) + + assert response.status_code == 200 + data = response.json() + assert data["scratchpad"] == "- IP: 10.0.0.1\n- User: jsmith" + + @pytest.mark.asyncio + async def test_patch_scratchpad_not_found( + self, client: AsyncClient, auth_headers: dict + ): + """Test PATCH scratchpad with invalid session ID.""" + import uuid + fake_id = str(uuid.uuid4()) + + response = await client.patch( + f"/api/v1/sessions/{fake_id}/scratchpad", + json={"scratchpad": "test"}, + headers=auth_headers + ) + + assert response.status_code == 404 + + @pytest.mark.asyncio + async def test_patch_scratchpad_empty_string( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test PATCH scratchpad with empty string (clear scratchpad).""" + # Create session and set scratchpad + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + # Set scratchpad + await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": "some notes"}, + headers=auth_headers + ) + + # Clear scratchpad + response = await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": ""}, + headers=auth_headers + ) + + assert response.status_code == 200 + assert response.json()["scratchpad"] == "" + + @pytest.mark.asyncio + async def test_patch_scratchpad_completed_session( + self, client: AsyncClient, auth_headers: dict, test_tree: dict + ): + """Test that scratchpad can still be updated on completed sessions.""" + # Create and complete session + create_response = await client.post( + "/api/v1/sessions", + json={"tree_id": test_tree["id"]}, + headers=auth_headers + ) + session_id = create_response.json()["id"] + + await client.post( + f"/api/v1/sessions/{session_id}/complete", + headers=auth_headers + ) + + # Should still be able to update scratchpad on completed sessions + response = await client.patch( + f"/api/v1/sessions/{session_id}/scratchpad", + json={"scratchpad": "post-resolution notes"}, + headers=auth_headers + ) + + assert response.status_code == 200 + assert response.json()["scratchpad"] == "post-resolution notes"