fix: more test fixes and additions, fix loss on resume

This commit is contained in:
Alex committed 2026-08-22 12:57:58 +01:00
1 parent e96ff8658c
commit 29f661f3c7
24 files changed
+921 -85

No files matched your search

+41
View File
@@ -143,6 +143,47 @@ class TestAnswerResourcePost:
assert resp.status_code == 400
assert resp.get_json()["error"] == "Stream error"
def test_resume_already_in_progress_returns_409(
self, answer_client, mock_stream_processor,
):
"""A raced resume is a conflict the caller can retry, not a 500.
``ResumeInProgressError`` subclasses ``ValueError`` but reaches this
route through ``resume_from_tool_actions``, where the bare
``except Exception`` reported it as an opaque 500 with a full
traceback on the ERROR channel. ``/stream`` and
``/v1/chat/completions`` both answer 409 for the identical condition.
"""
from application.api.answer.services.continuation_service import (
ResumeInProgressError,
)
mock_stream_processor.resume_from_tool_actions.side_effect = (
ResumeInProgressError("Resume already in progress for this conversation.")
)
with patch(
"application.api.answer.routes.answer.AnswerResource.validate_request",
return_value=None,
):
resp = answer_client.post(
"/api/answer",
data=json.dumps({
"question": "",
"conversation_id": _CONV_ID,
"tool_actions": [{"call_id": "call_1", "decision": "approved"}],
}),
content_type="application/json",
)
assert resp.status_code == 409
body = resp.get_json()
assert body["code"] == "resume_in_progress"
assert "Resume already in progress" in body["error"]
# The generic handler's opaque text would tell the caller nothing
# about retryability.
assert body["error"] != "An error occurred processing your request"
def test_exception_returns_500(self, answer_client, mock_stream_processor):
with patch(
"application.api.answer.routes.answer.AnswerResource.validate_request",
+64
View File
@@ -1450,3 +1450,67 @@ class TestStreamingHeartbeatSeed:
)
resource.conversation_service.heartbeat_message.assert_not_called()
class TestCompleteStreamSupersededLookupIsGuarded:
"""A DB failure in the supersede lookup must not error a finished turn.
The ``NOT_FOUND`` branch used to only log. It now consults
``was_superseded`` to tell a replaced turn from an orphaned answer — over
its own connection, inside the streaming ``try``. Unguarded, a blip there
(or a deploy where the app runs ahead of migration 0030 and the
``superseded_messages`` table does not exist yet) escapes to the generic
handler: the client gets a terminal ``error`` after the whole answer, the
row is finalized a second time, a continuation's resume claim is released,
and the ``answer_persist_failed`` alert this branch exists to raise is
swallowed.
"""
def test_lookup_failure_falls_back_instead_of_erroring_the_stream(
self, pg_conn, flask_app, caplog,
):
import logging
from application.api.answer.routes.base import BaseAnswerResource
from application.api.answer.services.conversation_service import (
ConversationService,
)
from application.storage.db.repositories.conversations import (
MessageUpdateOutcome,
)
with flask_app.app_context():
resource = BaseAnswerResource()
mock_agent = MagicMock()
mock_agent.gen.return_value = iter(
[{"type": "answer", "answer": "the answer"}]
)
with _patch_db_session(pg_conn), patch.object(
ConversationService,
"finalize_message",
return_value=MessageUpdateOutcome.NOT_FOUND,
), patch.object(
ConversationService,
"was_superseded",
side_effect=RuntimeError("server closed the connection"),
), caplog.at_level(logging.ERROR):
stream = list(
resource.complete_stream(
question="does the guard hold?",
agent=mock_agent,
conversation_id=None,
user_api_key=None,
decoded_token={"sub": "u-superseded-guard"},
should_persist=True,
model_id="gpt-4",
)
)
# The answer was already streamed; the row is gone either way. What
# must not happen is retracting it with a terminal error frame.
assert [s for s in stream if '"type": "error"' in s] == []
# And the alert must still fire, on the orphaned-answer path.
assert any(
"answer_persist_failed" in r.getMessage() for r in caplog.records
)