From 6532b4d1616950cc1c1055815db3c94eff5bf2c8 Mon Sep 17 00:00:00 2001 From: GareArc Date: Thu, 7 May 2026 00:05:20 -0700 Subject: [PATCH] fix(openapi): tighten _DISPATCH return type + cover helper edge cases - _DISPATCH value type: tuple[Any, dict[str, Any] | None] (was Any, Any). Helpers always return one of (stream_obj, None) or (None, dict); tightened sig lets mypy flag wrong shapes when Task 3's route handler unpacks the result. - Comment _run_completion's auto_generate_name + query mutations (legacy parity; non-obvious without grep-archaeology). - Unit cover _unpack_blocking (mapping / tuple / non-mapping branches) + AppRunRequest.conversation_id field validator (strip-to-None, invalid-uuid raise, valid-uuid passthrough). --- api/controllers/openapi/app_run.py | 3 +- .../openapi/test_app_run_dispatch.py | 34 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/api/controllers/openapi/app_run.py b/api/controllers/openapi/app_run.py index 0a1b08ae60..6d9144c70b 100644 --- a/api/controllers/openapi/app_run.py +++ b/api/controllers/openapi/app_run.py @@ -139,6 +139,7 @@ def _run_chat(app: App, caller: Any, payload: AppRunRequest, streaming: bool): def _run_completion(app: App, caller: Any, payload: AppRunRequest, streaming: bool): args = payload.model_dump(exclude_none=True) + # Completion mode disables auto-naming + tolerates absent query (legacy parity). args["auto_generate_name"] = False args.setdefault("query", "") try: @@ -191,7 +192,7 @@ def _run_workflow(app: App, caller: Any, payload: AppRunRequest, streaming: bool return None, WorkflowRunResponse.model_validate(body).model_dump(mode="json") -_DISPATCH: dict[AppMode, Callable[[App, Any, AppRunRequest, bool], tuple[Any, Any]]] = { +_DISPATCH: dict[AppMode, Callable[[App, Any, AppRunRequest, bool], tuple[Any, dict[str, Any] | None]]] = { AppMode.CHAT: _run_chat, AppMode.AGENT_CHAT: _run_chat, AppMode.ADVANCED_CHAT: _run_chat, diff --git a/api/tests/unit_tests/controllers/openapi/test_app_run_dispatch.py b/api/tests/unit_tests/controllers/openapi/test_app_run_dispatch.py index 27e402e4c9..92ddf89573 100644 --- a/api/tests/unit_tests/controllers/openapi/test_app_run_dispatch.py +++ b/api/tests/unit_tests/controllers/openapi/test_app_run_dispatch.py @@ -1,11 +1,12 @@ import pytest -from werkzeug.exceptions import UnprocessableEntity +from werkzeug.exceptions import InternalServerError, UnprocessableEntity from controllers.openapi.app_run import ( _DISPATCH, AppRunRequest, _enforce_chat_constraint, _enforce_workflow_constraint, + _unpack_blocking, ) from models.model import AppMode @@ -23,3 +24,34 @@ def test_chat_constraint_requires_query(): def test_workflow_constraint_rejects_query(): with pytest.raises(UnprocessableEntity, match="query_not_supported_for_workflow"): _enforce_workflow_constraint(AppRunRequest(inputs={}, query="hi")) + + +def test_unpack_blocking_passes_through_mapping(): + assert _unpack_blocking({"a": 1}) == {"a": 1} + + +def test_unpack_blocking_unwraps_tuple(): + assert _unpack_blocking(({"a": 1}, 200)) == {"a": 1} + + +def test_unpack_blocking_rejects_non_mapping(): + with pytest.raises(InternalServerError): + _unpack_blocking("not a mapping") + + +def test_app_run_request_strips_blank_conversation_id(): + payload = AppRunRequest(inputs={}, conversation_id=" ") + assert payload.conversation_id is None + + +def test_app_run_request_rejects_invalid_uuid_conversation_id(): + from pydantic import ValidationError + with pytest.raises(ValidationError, match="conversation_id must be a valid UUID"): + AppRunRequest(inputs={}, conversation_id="not-a-uuid") + + +def test_app_run_request_accepts_valid_uuid_conversation_id(): + import uuid as _uuid + cid = str(_uuid.uuid4()) + payload = AppRunRequest(inputs={}, conversation_id=cid) + assert payload.conversation_id == cid