Skip to content

Commit 976fb66

Browse files
committed
test: add unit tests for image editing
Tests for: - ModelCapabilities editing fields - Storage service get_image method - Default edit model selection - KNOWN_CAPABILITIES editing info
1 parent 7a7b096 commit 976fb66

1 file changed

Lines changed: 169 additions & 0 deletions

File tree

tests/test_edit.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
from unittest.mock import AsyncMock, MagicMock, patch
2+
3+
import pytest
4+
from fastapi.testclient import TestClient
5+
6+
7+
def test_model_capabilities_editing_fields():
8+
"""Test that ModelCapabilities has editing fields."""
9+
from app.schemas.responses import ModelCapabilities
10+
11+
# Default values
12+
caps = ModelCapabilities()
13+
assert caps.supports_editing is False
14+
assert caps.editing_type is None
15+
16+
# With editing enabled
17+
caps = ModelCapabilities(supports_editing=True, editing_type="mask")
18+
assert caps.supports_editing is True
19+
assert caps.editing_type == "mask"
20+
21+
caps = ModelCapabilities(supports_editing=True, editing_type="prompt")
22+
assert caps.editing_type == "prompt"
23+
24+
25+
def test_known_capabilities_editing():
26+
"""Test that KNOWN_CAPABILITIES includes editing information."""
27+
from app.services.model_registry import ModelRegistry
28+
29+
caps = ModelRegistry.KNOWN_CAPABILITIES
30+
31+
# DALL-E 2 supports mask-based editing
32+
assert caps["dall-e-2"].supports_editing is True
33+
assert caps["dall-e-2"].editing_type == "mask"
34+
35+
# DALL-E 3 does NOT support editing
36+
assert caps["dall-e-3"].supports_editing is False
37+
38+
# GPT-Image-1 supports mask-based editing
39+
assert caps["gpt-image-1"].supports_editing is True
40+
assert caps["gpt-image-1"].editing_type == "mask"
41+
42+
# Gemini supports prompt-based editing
43+
assert caps["gemini-2.0-flash-preview-image-generation"].supports_editing is True
44+
assert caps["gemini-2.0-flash-preview-image-generation"].editing_type == "prompt"
45+
46+
47+
@pytest.mark.asyncio
48+
async def test_storage_get_image_local(temp_storage):
49+
"""Test getting image from local storage."""
50+
from app.services.storage_service import StorageService
51+
52+
with patch("app.services.storage_service.settings") as mock_settings:
53+
mock_settings.STORAGE_PATH = str(temp_storage)
54+
mock_settings.IMAGE_BASE_URL = "http://localhost:8000"
55+
56+
service = StorageService()
57+
58+
# Save an image first
59+
test_data = b"test image content"
60+
url = await service.save_image(test_data, "png")
61+
62+
# Retrieve the image
63+
retrieved = await service.get_image(url)
64+
assert retrieved == test_data
65+
66+
67+
@pytest.mark.asyncio
68+
async def test_storage_get_image_local_relative(temp_storage):
69+
"""Test getting image using relative URL."""
70+
from app.services.storage_service import StorageService
71+
72+
with patch("app.services.storage_service.settings") as mock_settings:
73+
mock_settings.STORAGE_PATH = str(temp_storage)
74+
mock_settings.IMAGE_BASE_URL = "http://localhost:8000"
75+
76+
service = StorageService()
77+
78+
# Save an image first
79+
test_data = b"test image content"
80+
url = await service.save_image(test_data, "png")
81+
82+
# Get filename and use relative URL
83+
filename = url.split("/")[-1]
84+
relative_url = f"/images/{filename}"
85+
86+
retrieved = await service.get_image(relative_url)
87+
assert retrieved == test_data
88+
89+
90+
@pytest.mark.asyncio
91+
async def test_storage_get_image_not_found(temp_storage):
92+
"""Test error when local image not found."""
93+
from app.services.storage_service import StorageService
94+
95+
with patch("app.services.storage_service.settings") as mock_settings:
96+
mock_settings.STORAGE_PATH = str(temp_storage)
97+
mock_settings.IMAGE_BASE_URL = "http://localhost:8000"
98+
99+
service = StorageService()
100+
101+
with pytest.raises(FileNotFoundError):
102+
await service.get_image("http://localhost:8000/images/nonexistent.png")
103+
104+
105+
@pytest.mark.asyncio
106+
async def test_storage_get_image_external():
107+
"""Test getting image from external URL."""
108+
from app.services.storage_service import StorageService
109+
110+
with patch("app.services.storage_service.settings") as mock_settings:
111+
mock_settings.STORAGE_PATH = "/tmp/test"
112+
mock_settings.IMAGE_BASE_URL = "http://localhost:8000"
113+
114+
service = StorageService()
115+
116+
# Mock httpx client
117+
mock_response = MagicMock()
118+
mock_response.content = b"external image data"
119+
mock_response.raise_for_status = MagicMock()
120+
121+
mock_client = MagicMock()
122+
mock_client.get = AsyncMock(return_value=mock_response)
123+
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
124+
mock_client.__aexit__ = AsyncMock(return_value=None)
125+
126+
with patch("app.services.storage_service.httpx.AsyncClient", return_value=mock_client):
127+
result = await service.get_image("https://example.com/image.png")
128+
129+
assert result == b"external image data"
130+
mock_client.get.assert_called_once_with("https://example.com/image.png")
131+
132+
133+
def test_get_default_edit_model():
134+
"""Test default edit model selection."""
135+
from app.api.routes.edit import _get_default_edit_model
136+
137+
with patch("app.api.routes.edit.settings") as mock_settings:
138+
mock_settings.DEFAULT_MODEL = None
139+
140+
with patch("app.api.routes.edit.model_registry") as mock_registry:
141+
# Create mock models with editing support
142+
mock_model = MagicMock()
143+
mock_model.id = "gpt-image-1"
144+
mock_model.provider = "openai"
145+
mock_model.capabilities.supports_editing = True
146+
147+
mock_registry.get_models.return_value = [mock_model]
148+
mock_registry.get_model.return_value = None
149+
150+
# Should return gpt-image-1 for litellm
151+
result = _get_default_edit_model("litellm")
152+
assert result == "gpt-image-1"
153+
154+
155+
def test_get_default_edit_model_fallback():
156+
"""Test fallback when no models in registry."""
157+
from app.api.routes.edit import _get_default_edit_model
158+
159+
with patch("app.api.routes.edit.settings") as mock_settings:
160+
mock_settings.DEFAULT_MODEL = None
161+
162+
with patch("app.api.routes.edit.model_registry") as mock_registry:
163+
mock_registry.get_models.return_value = []
164+
mock_registry.get_model.return_value = None
165+
166+
# Should return hardcoded defaults
167+
assert _get_default_edit_model("openai") == "gpt-image-1"
168+
assert _get_default_edit_model("litellm") == "gpt-image-1"
169+
assert _get_default_edit_model("gemini") == "gemini-2.0-flash-preview-image-generation"

0 commit comments

Comments
 (0)