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).
This commit is contained in:
GareArc 2026-05-07 00:05:20 -07:00
parent 4bfc4af590
commit 6532b4d161
No known key found for this signature in database
2 changed files with 35 additions and 2 deletions

View File

@ -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,

View File

@ -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