open-swe/tests/test_dashboard_thread_api.py
Johannes du Plessis 5faf190954
fix: reject images for text-only models (#1439)
* fix: reject image uploads for text-only models

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

* fix: validate queued images against active model

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-06 13:09:31 -07:00

92 lines
3.3 KiB
Python

import base64
import pytest
from fastapi import HTTPException
from agent.dashboard import thread_api
from agent.dashboard.options import model_supports_images
_TEXT_ONLY_MODEL = "fireworks:accounts/fireworks/models/deepseek-v4-pro"
_VISION_MODEL = "openai:gpt-5.5"
def _image() -> thread_api.DashboardImageBody:
return thread_api.DashboardImageBody(
base64=base64.b64encode(b"image").decode("ascii"),
mimeType="image/png",
)
def test_model_supports_images_marks_text_only_fireworks_models() -> None:
assert not model_supports_images(_TEXT_ONLY_MODEL)
assert model_supports_images(_VISION_MODEL)
def test_user_message_content_rejects_images_for_text_only_model() -> None:
with pytest.raises(HTTPException) as exc_info:
thread_api._user_message_content("see attached", [_image()], model_id=_TEXT_ONLY_MODEL)
assert exc_info.value.status_code == 422
assert "does not support image input" in exc_info.value.detail
def test_user_message_content_allows_images_for_vision_model() -> None:
content = thread_api._user_message_content("see attached", [_image()], model_id=_VISION_MODEL)
assert isinstance(content, list)
assert content[-1] == {"type": "text", "text": "see attached"}
assert any(block.get("type") != "text" for block in content)
async def test_resolve_agent_model_choice_applies_profile_before_team_default(monkeypatch) -> None:
async def fake_team_default(role: str) -> tuple[str, str]:
assert role == "agent"
return _VISION_MODEL, "medium"
monkeypatch.setattr(thread_api, "get_team_default_model", fake_team_default)
model_id, effort = await thread_api._resolve_agent_model_choice(
{"default_model": _TEXT_ONLY_MODEL, "reasoning_effort": "high"},
None,
None,
)
assert (model_id, effort) == (_TEXT_ONLY_MODEL, "high")
async def test_resolve_agent_model_choice_applies_request_before_profile(monkeypatch) -> None:
async def fake_team_default(role: str) -> tuple[str, str]:
assert role == "agent"
return _VISION_MODEL, "medium"
monkeypatch.setattr(thread_api, "get_team_default_model", fake_team_default)
model_id, effort = await thread_api._resolve_agent_model_choice(
{"default_model": _TEXT_ONLY_MODEL, "reasoning_effort": "high"},
"anthropic:claude-opus-4-8",
"high",
)
assert (model_id, effort) == ("anthropic:claude-opus-4-8", "high")
async def test_create_dashboard_thread_rejects_images_for_resolved_text_only_model(
monkeypatch,
) -> None:
async def fake_profile(login: str) -> dict[str, str]:
assert login == "octocat"
return {"default_model": _TEXT_ONLY_MODEL, "reasoning_effort": "high"}
async def fake_team_default(role: str) -> tuple[str, str]:
assert role == "agent"
return _VISION_MODEL, "medium"
monkeypatch.setattr(thread_api, "get_profile", fake_profile)
monkeypatch.setattr(thread_api, "get_team_default_model", fake_team_default)
body = thread_api.ThreadCreateBody(prompt="see attached", images=[_image()])
with pytest.raises(HTTPException) as exc_info:
await thread_api.create_dashboard_thread("octocat", body)
assert exc_info.value.status_code == 422
assert "does not support image input" in exc_info.value.detail