From 9cf4bc6979be5795a4e74027769308473d8f45a9 Mon Sep 17 00:00:00 2001 From: Ragini-Microsoft Date: Tue, 12 May 2026 12:35:38 +0530 Subject: [PATCH 1/4] add validation for empty request body and message content in chat handler --- src/backend/app.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/backend/app.py b/src/backend/app.py index 550b1e1a3..4685b7fd1 100644 --- a/src/backend/app.py +++ b/src/backend/app.py @@ -161,6 +161,14 @@ async def handle_chat(): """Unified chat endpoint - routes messages to appropriate handlers based on intent.""" data = await request.get_json() + if not data: + return jsonify({ + "action_type": "error", + "message": "Request body is required", + "data": {}, + "conversation_id": "" + }), 400 + # Extract request fields conversation_id = data.get("conversation_id") or str(uuid.uuid4()) user_id = data.get("user_id", "anonymous") @@ -168,6 +176,15 @@ async def handle_chat(): action = data.get("action") payload = data.get("payload", {}) + # Validate that message content is not empty when no action is specified + if not action and not message.strip(): + return jsonify({ + "action_type": "error", + "message": "Message content must not be empty", + "data": {}, + "conversation_id": conversation_id + }), 400 + selected_products = data.get("selected_products", []) brief_data = data.get("brief", {}) From 9107631a8ed0ac54ccaf51975cb41ffe4375a7e6 Mon Sep 17 00:00:00 2001 From: Ragini-Microsoft Date: Tue, 12 May 2026 12:53:04 +0530 Subject: [PATCH 2/4] add validation for missing, empty, and whitespace-only messages in chat endpoint --- src/tests/test_app.py | 48 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/tests/test_app.py b/src/tests/test_app.py index 57d9237d1..e94bd2fa3 100644 --- a/src/tests/test_app.py +++ b/src/tests/test_app.py @@ -63,7 +63,49 @@ async def test_health_check_api(client): @pytest.mark.asyncio async def test_chat_missing_message(client): - """Test chat endpoint with missing message still returns response (no validation).""" + """Test chat endpoint rejects missing/empty message with 400.""" + response = await client.post( + "/api/chat", + json={"conversation_id": "test-conv"} + ) + + assert response.status_code == 400 + data = await response.get_json() + assert data["action_type"] == "error" + assert "empty" in data["message"].lower() + + +@pytest.mark.asyncio +async def test_chat_empty_body(client): + """Test chat endpoint rejects empty request body with 400.""" + response = await client.post( + "/api/chat", + data="", + headers={"Content-Type": "application/json"} + ) + + assert response.status_code == 400 + data = await response.get_json() + assert data["action_type"] == "error" + assert "required" in data["message"].lower() + + +@pytest.mark.asyncio +async def test_chat_whitespace_message(client): + """Test chat endpoint rejects whitespace-only message with 400.""" + response = await client.post( + "/api/chat", + json={"conversation_id": "test-conv", "message": " "} + ) + + assert response.status_code == 400 + data = await response.get_json() + assert data["action_type"] == "error" + + +@pytest.mark.asyncio +async def test_chat_empty_message_with_action_allowed(client): + """Test chat endpoint allows empty message when action is specified.""" with patch("app.get_routing_service") as mock_routing, \ patch("app.get_cosmos_service") as mock_cosmos, \ patch("app.get_orchestrator") as mock_orch: @@ -88,10 +130,10 @@ async def test_chat_missing_message(client): response = await client.post( "/api/chat", - json={"conversation_id": "test-conv"} + json={"conversation_id": "test-conv", "action": "confirm_brief", "message": ""} ) - # API doesn't validate missing message - routes to handler with empty message + # Action-based requests bypass message validation assert response.status_code in [200, 500] From 9503de6fe70d24166afb5e84925866418dae3a7a Mon Sep 17 00:00:00 2001 From: Ragini-Microsoft Date: Tue, 12 May 2026 13:12:43 +0530 Subject: [PATCH 3/4] test file updated --- src/tests/test_app.py | 111 ++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 64 deletions(-) diff --git a/src/tests/test_app.py b/src/tests/test_app.py index e94bd2fa3..69cb4e4e7 100644 --- a/src/tests/test_app.py +++ b/src/tests/test_app.py @@ -85,9 +85,6 @@ async def test_chat_empty_body(client): ) assert response.status_code == 400 - data = await response.get_json() - assert data["action_type"] == "error" - assert "required" in data["message"].lower() @pytest.mark.asyncio @@ -103,6 +100,35 @@ async def test_chat_whitespace_message(client): assert data["action_type"] == "error" +@pytest.mark.asyncio +async def test_chat_null_message(client): + """Test chat endpoint handles null message without crashing.""" + response = await client.post( + "/api/chat", + json={"conversation_id": "test-conv", "message": None} + ) + + assert response.status_code == 400 + data = await response.get_json() + assert data["action_type"] == "error" + assert "empty" in data["message"].lower() + + +@pytest.mark.asyncio +async def test_chat_invalid_json_body(client): + """Test chat endpoint rejects invalid JSON body with 400.""" + response = await client.post( + "/api/chat", + data="not valid json", + headers={"Content-Type": "application/json"} + ) + + assert response.status_code == 400 + data = await response.get_json() + assert data["action_type"] == "error" + assert "required" in data["message"].lower() + + @pytest.mark.asyncio async def test_chat_empty_message_with_action_allowed(client): """Test chat endpoint allows empty message when action is specified.""" @@ -229,36 +255,15 @@ async def test_chat_cosmos_failure(client): @pytest.mark.asyncio async def test_parse_brief_missing_text(client): - """Test chat endpoint with missing message still processes (no validation).""" - with patch("app.get_routing_service") as mock_routing, \ - patch("app.get_cosmos_service") as mock_cosmos, \ - patch("app.get_orchestrator") as mock_orch: - - from services.routing_service import Intent, RoutingResult, ConversationState - mock_routing_service = MagicMock() - mock_routing_service.classify_intent = MagicMock(return_value=RoutingResult( - intent=Intent.PARSE_BRIEF, - confidence=0.5 - )) - mock_routing_service.derive_state_from_conversation = MagicMock(return_value=ConversationState()) - mock_routing.return_value = mock_routing_service - - mock_cosmos_service = AsyncMock() - mock_cosmos_service.get_conversation = AsyncMock(return_value=None) - mock_cosmos_service.add_message_to_conversation = AsyncMock() - mock_cosmos.return_value = mock_cosmos_service - - mock_orchestrator = AsyncMock() - mock_orchestrator.parse_brief = AsyncMock(return_value=(MagicMock(model_dump=lambda: {}), None, False)) - mock_orch.return_value = mock_orchestrator - - response = await client.post( - "/api/chat", - json={"conversation_id": "test-conv"} - ) + """Test chat endpoint rejects missing message with 400.""" + response = await client.post( + "/api/chat", + json={"conversation_id": "test-conv"} + ) - # API doesn't validate missing message - routes to handler with empty message - assert response.status_code in [200, 500] + assert response.status_code == 400 + data = await response.get_json() + assert data["action_type"] == "error" @pytest.mark.asyncio @@ -906,39 +911,17 @@ async def test_regenerate_content_success(client, sample_creative_brief_dict): @pytest.mark.asyncio async def test_regenerate_content_missing_modification_request(client, sample_creative_brief_dict): - """Test regeneration without message still routes (no validation).""" - with patch("app.get_routing_service") as mock_routing, \ - patch("app.get_cosmos_service") as mock_cosmos, \ - patch("app.get_orchestrator") as mock_orch: - - from services.routing_service import Intent, RoutingResult, ConversationState - mock_routing_service = MagicMock() - mock_routing_service.classify_intent = MagicMock(return_value=RoutingResult( - intent=Intent.PARSE_BRIEF, - confidence=0.5 - )) - mock_routing_service.derive_state_from_conversation = MagicMock(return_value=ConversationState()) - mock_routing.return_value = mock_routing_service - - mock_cosmos_service = AsyncMock() - mock_cosmos_service.get_conversation = AsyncMock(return_value=None) - mock_cosmos_service.add_message_to_conversation = AsyncMock() - mock_cosmos.return_value = mock_cosmos_service - - mock_orchestrator = AsyncMock() - mock_orchestrator.parse_brief = AsyncMock(return_value=(MagicMock(model_dump=lambda: {}), None, False)) - mock_orch.return_value = mock_orchestrator - - response = await client.post( - "/api/chat", - json={ - "conversation_id": "test-conv" - # Missing message - no validation in backend - } - ) + """Test regeneration rejects missing message with 400.""" + response = await client.post( + "/api/chat", + json={ + "conversation_id": "test-conv" + } + ) - # Backend doesn't validate missing message - assert response.status_code in [200, 500] + assert response.status_code == 400 + data = await response.get_json() + assert data["action_type"] == "error" @pytest.mark.asyncio From 17f4688648b83c6d19103aae2aedcab346aa22e7 Mon Sep 17 00:00:00 2001 From: Ragini-Microsoft Date: Tue, 12 May 2026 13:20:23 +0530 Subject: [PATCH 4/4] updated test file --- src/tests/test_app.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/tests/test_app.py b/src/tests/test_app.py index 69cb4e4e7..7882a72ae 100644 --- a/src/tests/test_app.py +++ b/src/tests/test_app.py @@ -100,35 +100,6 @@ async def test_chat_whitespace_message(client): assert data["action_type"] == "error" -@pytest.mark.asyncio -async def test_chat_null_message(client): - """Test chat endpoint handles null message without crashing.""" - response = await client.post( - "/api/chat", - json={"conversation_id": "test-conv", "message": None} - ) - - assert response.status_code == 400 - data = await response.get_json() - assert data["action_type"] == "error" - assert "empty" in data["message"].lower() - - -@pytest.mark.asyncio -async def test_chat_invalid_json_body(client): - """Test chat endpoint rejects invalid JSON body with 400.""" - response = await client.post( - "/api/chat", - data="not valid json", - headers={"Content-Type": "application/json"} - ) - - assert response.status_code == 400 - data = await response.get_json() - assert data["action_type"] == "error" - assert "required" in data["message"].lower() - - @pytest.mark.asyncio async def test_chat_empty_message_with_action_allowed(client): """Test chat endpoint allows empty message when action is specified."""