|
5 | 5 | import pytest |
6 | 6 | from gooddata_eval.core.chat import sse_client as sse_mod |
7 | 7 | from gooddata_eval.core.chat.sse_client import ChatClient, ChatError, TransientChatError, parse_sse_lines |
| 8 | +from gooddata_eval.core.models import DatasetItem |
8 | 9 |
|
9 | 10 |
|
10 | 11 | def test_parse_sse_lines_collects_text_and_visualization(fixtures_dir): |
@@ -222,3 +223,120 @@ def test_float_env_uses_default_when_unset(monkeypatch): |
222 | 223 | def test_float_env_reads_override(monkeypatch): |
223 | 224 | monkeypatch.setenv("GD_TEST_FLOAT", "1.5") |
224 | 225 | assert sse_mod._float_env("GD_TEST_FLOAT", 5.0) == 1.5 |
| 226 | + |
| 227 | + |
| 228 | +def test_parse_sse_lines_captures_response_id_from_event_data(): |
| 229 | + """response_id is captured from the top-level event_data.""" |
| 230 | + lines = [ |
| 231 | + 'data: {"responseId": "resp-123", "item": {"role": "assistant", "content": {"type": "text", "text": "hi"}}}', |
| 232 | + ] |
| 233 | + result = parse_sse_lines(lines) |
| 234 | + assert result.response_id == "resp-123" |
| 235 | + |
| 236 | + |
| 237 | +def test_parse_sse_lines_captures_response_id_from_item(): |
| 238 | + """response_id is captured from item when not present at top level.""" |
| 239 | + lines = [ |
| 240 | + 'data: {"item": {"role": "assistant", "responseId": "resp-456", "content": {"type": "text", "text": "hi"}}}', |
| 241 | + ] |
| 242 | + result = parse_sse_lines(lines) |
| 243 | + assert result.response_id == "resp-456" |
| 244 | + |
| 245 | + |
| 246 | +def test_parse_sse_lines_first_response_id_wins(): |
| 247 | + """Only the first responseId encountered is kept.""" |
| 248 | + lines = [ |
| 249 | + 'data: {"responseId": "first", "item": {"role": "assistant", "content": {"type": "text", "text": "a"}}}', |
| 250 | + 'data: {"responseId": "second", "item": {"role": "assistant", "content": {"type": "text", "text": "b"}}}', |
| 251 | + ] |
| 252 | + result = parse_sse_lines(lines) |
| 253 | + assert result.response_id == "first" |
| 254 | + |
| 255 | + |
| 256 | +def test_ask_attaches_conversation_id_to_result(monkeypatch): |
| 257 | + """ChatClient.ask() sets conversation_id on the returned ChatResult.""" |
| 258 | + calls = [] |
| 259 | + |
| 260 | + def handler(request): |
| 261 | + if request.method == "POST" and request.url.path.endswith("/conversations"): |
| 262 | + return httpx.Response(200, json={"conversationId": "conv-abc"}) |
| 263 | + if request.method == "POST" and "messages" in str(request.url): |
| 264 | + return httpx.Response(200, content=_OK_SSE) |
| 265 | + if request.method == "DELETE": |
| 266 | + calls.append("delete") |
| 267 | + return httpx.Response(204) |
| 268 | + return httpx.Response(404) |
| 269 | + |
| 270 | + client = _client_with_handler(handler) |
| 271 | + item = DatasetItem(id="t1", dataset_name="d", test_kind="visualization", question="q", expected_output={}) |
| 272 | + result = client.ask(item) |
| 273 | + assert result.conversation_id == "conv-abc" |
| 274 | + assert "delete" in calls # conversation cleaned up on success |
| 275 | + |
| 276 | + |
| 277 | +def test_ask_preserve_failed_keeps_conversation_on_error(monkeypatch): |
| 278 | + """With preserve_failed=True, failed conversations are not deleted.""" |
| 279 | + calls = [] |
| 280 | + |
| 281 | + def handler(request): |
| 282 | + if request.method == "POST" and request.url.path.endswith("/conversations"): |
| 283 | + return httpx.Response(200, json={"conversationId": "conv-fail"}) |
| 284 | + if request.method == "POST" and "messages" in str(request.url): |
| 285 | + return httpx.Response(200, content=_NONRETRY_SSE) |
| 286 | + if request.method == "DELETE": |
| 287 | + calls.append("delete") |
| 288 | + return httpx.Response(204) |
| 289 | + return httpx.Response(404) |
| 290 | + |
| 291 | + monkeypatch.setattr(sse_mod.time, "sleep", lambda s: None) |
| 292 | + client = ChatClient(host="https://example.invalid", token="t", workspace_id="w", preserve_failed=True) |
| 293 | + client._client = httpx.Client(transport=httpx.MockTransport(handler)) |
| 294 | + |
| 295 | + item = DatasetItem(id="t1", dataset_name="d", test_kind="visualization", question="q", expected_output={}) |
| 296 | + with pytest.raises(ChatError): |
| 297 | + client.ask(item) |
| 298 | + assert "delete" not in calls # conversation preserved |
| 299 | + |
| 300 | + |
| 301 | +def test_ask_without_preserve_failed_deletes_on_error(monkeypatch): |
| 302 | + """Without preserve_failed, conversations are deleted even on error.""" |
| 303 | + calls = [] |
| 304 | + |
| 305 | + def handler(request): |
| 306 | + if request.method == "POST" and request.url.path.endswith("/conversations"): |
| 307 | + return httpx.Response(200, json={"conversationId": "conv-del"}) |
| 308 | + if request.method == "POST" and "messages" in str(request.url): |
| 309 | + return httpx.Response(200, content=_NONRETRY_SSE) |
| 310 | + if request.method == "DELETE": |
| 311 | + calls.append("delete") |
| 312 | + return httpx.Response(204) |
| 313 | + return httpx.Response(404) |
| 314 | + |
| 315 | + monkeypatch.setattr(sse_mod.time, "sleep", lambda s: None) |
| 316 | + client = _client_with_handler(handler) |
| 317 | + |
| 318 | + item = DatasetItem(id="t1", dataset_name="d", test_kind="visualization", question="q", expected_output={}) |
| 319 | + with pytest.raises(ChatError): |
| 320 | + client.ask(item) |
| 321 | + assert "delete" in calls # conversation deleted |
| 322 | + |
| 323 | + |
| 324 | +def test_ask_attaches_conversation_id_to_exception(monkeypatch): |
| 325 | + """On failure, conversation_id is attached to the raised exception.""" |
| 326 | + |
| 327 | + def handler(request): |
| 328 | + if request.method == "POST" and request.url.path.endswith("/conversations"): |
| 329 | + return httpx.Response(200, json={"conversationId": "conv-exc"}) |
| 330 | + if request.method == "POST" and "messages" in str(request.url): |
| 331 | + return httpx.Response(200, content=_NONRETRY_SSE) |
| 332 | + if request.method == "DELETE": |
| 333 | + return httpx.Response(204) |
| 334 | + return httpx.Response(404) |
| 335 | + |
| 336 | + monkeypatch.setattr(sse_mod.time, "sleep", lambda s: None) |
| 337 | + client = _client_with_handler(handler) |
| 338 | + |
| 339 | + item = DatasetItem(id="t1", dataset_name="d", test_kind="visualization", question="q", expected_output={}) |
| 340 | + with pytest.raises(ChatError) as ei: |
| 341 | + client.ask(item) |
| 342 | + assert ei.value.conversation_id == "conv-exc" |
0 commit comments