Skip to content

Commit 2a2eeca

Browse files
Merge pull request #848 from microsoft/dev
chore: dev to main merge
2 parents 5a76906 + 49c45bb commit 2a2eeca

2 files changed

Lines changed: 77 additions & 64 deletions

File tree

src/backend/app.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,30 @@ async def handle_chat():
161161
"""Unified chat endpoint - routes messages to appropriate handlers based on intent."""
162162
data = await request.get_json()
163163

164+
if not data:
165+
return jsonify({
166+
"action_type": "error",
167+
"message": "Request body is required",
168+
"data": {},
169+
"conversation_id": ""
170+
}), 400
171+
164172
# Extract request fields
165173
conversation_id = data.get("conversation_id") or str(uuid.uuid4())
166174
user_id = data.get("user_id", "anonymous")
167175
message = data.get("message", "")
168176
action = data.get("action")
169177
payload = data.get("payload", {})
170178

179+
# Validate that message content is not empty when no action is specified
180+
if not action and not message.strip():
181+
return jsonify({
182+
"action_type": "error",
183+
"message": "Message content must not be empty",
184+
"data": {},
185+
"conversation_id": conversation_id
186+
}), 400
187+
171188
selected_products = data.get("selected_products", [])
172189
brief_data = data.get("brief", {})
173190

src/tests/test_app.py

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,46 @@ async def test_health_check_api(client):
6363

6464
@pytest.mark.asyncio
6565
async def test_chat_missing_message(client):
66-
"""Test chat endpoint with missing message still returns response (no validation)."""
66+
"""Test chat endpoint rejects missing/empty message with 400."""
67+
response = await client.post(
68+
"/api/chat",
69+
json={"conversation_id": "test-conv"}
70+
)
71+
72+
assert response.status_code == 400
73+
data = await response.get_json()
74+
assert data["action_type"] == "error"
75+
assert "empty" in data["message"].lower()
76+
77+
78+
@pytest.mark.asyncio
79+
async def test_chat_empty_body(client):
80+
"""Test chat endpoint rejects empty request body with 400."""
81+
response = await client.post(
82+
"/api/chat",
83+
data="",
84+
headers={"Content-Type": "application/json"}
85+
)
86+
87+
assert response.status_code == 400
88+
89+
90+
@pytest.mark.asyncio
91+
async def test_chat_whitespace_message(client):
92+
"""Test chat endpoint rejects whitespace-only message with 400."""
93+
response = await client.post(
94+
"/api/chat",
95+
json={"conversation_id": "test-conv", "message": " "}
96+
)
97+
98+
assert response.status_code == 400
99+
data = await response.get_json()
100+
assert data["action_type"] == "error"
101+
102+
103+
@pytest.mark.asyncio
104+
async def test_chat_empty_message_with_action_allowed(client):
105+
"""Test chat endpoint allows empty message when action is specified."""
67106
with patch("app.get_routing_service") as mock_routing, \
68107
patch("app.get_cosmos_service") as mock_cosmos, \
69108
patch("app.get_orchestrator") as mock_orch:
@@ -88,10 +127,10 @@ async def test_chat_missing_message(client):
88127

89128
response = await client.post(
90129
"/api/chat",
91-
json={"conversation_id": "test-conv"}
130+
json={"conversation_id": "test-conv", "action": "confirm_brief", "message": ""}
92131
)
93132

94-
# API doesn't validate missing message - routes to handler with empty message
133+
# Action-based requests bypass message validation
95134
assert response.status_code in [200, 500]
96135

97136

@@ -187,36 +226,15 @@ async def test_chat_cosmos_failure(client):
187226

188227
@pytest.mark.asyncio
189228
async def test_parse_brief_missing_text(client):
190-
"""Test chat endpoint with missing message still processes (no validation)."""
191-
with patch("app.get_routing_service") as mock_routing, \
192-
patch("app.get_cosmos_service") as mock_cosmos, \
193-
patch("app.get_orchestrator") as mock_orch:
194-
195-
from services.routing_service import Intent, RoutingResult, ConversationState
196-
mock_routing_service = MagicMock()
197-
mock_routing_service.classify_intent = MagicMock(return_value=RoutingResult(
198-
intent=Intent.PARSE_BRIEF,
199-
confidence=0.5
200-
))
201-
mock_routing_service.derive_state_from_conversation = MagicMock(return_value=ConversationState())
202-
mock_routing.return_value = mock_routing_service
203-
204-
mock_cosmos_service = AsyncMock()
205-
mock_cosmos_service.get_conversation = AsyncMock(return_value=None)
206-
mock_cosmos_service.add_message_to_conversation = AsyncMock()
207-
mock_cosmos.return_value = mock_cosmos_service
208-
209-
mock_orchestrator = AsyncMock()
210-
mock_orchestrator.parse_brief = AsyncMock(return_value=(MagicMock(model_dump=lambda: {}), None, False))
211-
mock_orch.return_value = mock_orchestrator
212-
213-
response = await client.post(
214-
"/api/chat",
215-
json={"conversation_id": "test-conv"}
216-
)
229+
"""Test chat endpoint rejects missing message with 400."""
230+
response = await client.post(
231+
"/api/chat",
232+
json={"conversation_id": "test-conv"}
233+
)
217234

218-
# API doesn't validate missing message - routes to handler with empty message
219-
assert response.status_code in [200, 500]
235+
assert response.status_code == 400
236+
data = await response.get_json()
237+
assert data["action_type"] == "error"
220238

221239

222240
@pytest.mark.asyncio
@@ -864,39 +882,17 @@ async def test_regenerate_content_success(client, sample_creative_brief_dict):
864882

865883
@pytest.mark.asyncio
866884
async def test_regenerate_content_missing_modification_request(client, sample_creative_brief_dict):
867-
"""Test regeneration without message still routes (no validation)."""
868-
with patch("app.get_routing_service") as mock_routing, \
869-
patch("app.get_cosmos_service") as mock_cosmos, \
870-
patch("app.get_orchestrator") as mock_orch:
871-
872-
from services.routing_service import Intent, RoutingResult, ConversationState
873-
mock_routing_service = MagicMock()
874-
mock_routing_service.classify_intent = MagicMock(return_value=RoutingResult(
875-
intent=Intent.PARSE_BRIEF,
876-
confidence=0.5
877-
))
878-
mock_routing_service.derive_state_from_conversation = MagicMock(return_value=ConversationState())
879-
mock_routing.return_value = mock_routing_service
880-
881-
mock_cosmos_service = AsyncMock()
882-
mock_cosmos_service.get_conversation = AsyncMock(return_value=None)
883-
mock_cosmos_service.add_message_to_conversation = AsyncMock()
884-
mock_cosmos.return_value = mock_cosmos_service
885-
886-
mock_orchestrator = AsyncMock()
887-
mock_orchestrator.parse_brief = AsyncMock(return_value=(MagicMock(model_dump=lambda: {}), None, False))
888-
mock_orch.return_value = mock_orchestrator
889-
890-
response = await client.post(
891-
"/api/chat",
892-
json={
893-
"conversation_id": "test-conv"
894-
# Missing message - no validation in backend
895-
}
896-
)
885+
"""Test regeneration rejects missing message with 400."""
886+
response = await client.post(
887+
"/api/chat",
888+
json={
889+
"conversation_id": "test-conv"
890+
}
891+
)
897892

898-
# Backend doesn't validate missing message
899-
assert response.status_code in [200, 500]
893+
assert response.status_code == 400
894+
data = await response.get_json()
895+
assert data["action_type"] == "error"
900896

901897

902898
@pytest.mark.asyncio

0 commit comments

Comments
 (0)