Skip to content

Commit 70bc174

Browse files
author
jiangpeiling
committed
♻️ refactor image module.
1 parent 4b1f33b commit 70bc174

File tree

4 files changed

+372
-59
lines changed

4 files changed

+372
-59
lines changed

backend/apps/image_app.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import logging
22
from urllib.parse import unquote
33

4-
import aiohttp
54
from fastapi import APIRouter
65

7-
from consts.const import DATA_PROCESS_SERVICE
6+
from services.image_service import proxy_image_impl
87

98
# Create router
109
router = APIRouter()
@@ -28,23 +27,8 @@ async def proxy_image(url: str):
2827
try:
2928
# URL decode
3029
decoded_url = unquote(url)
31-
32-
# Create session to call the data processing service
33-
async with aiohttp.ClientSession() as session:
34-
# Call the data processing service to load the image
35-
data_process_url = f"{DATA_PROCESS_SERVICE}/tasks/load_image?url={decoded_url}"
36-
37-
async with session.get(data_process_url) as response:
38-
if response.status != 200:
39-
error_text = await response.text()
40-
logger.warning(
41-
f"Failed to fetch image from data process service: {error_text}")
42-
return {"success": False, "error": "Failed to fetch image or image format not supported"}
43-
44-
result = await response.json()
45-
return result
46-
30+
return await proxy_image_impl(decoded_url)
4731
except Exception as e:
4832
logger.error(
4933
f"Error occurred while proxying image: {str(e)}, URL: {url[:50]}...")
50-
return {"success": False, "error": str(e)}
34+
return {"success": False, "error": str(e)}

backend/services/image_service.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import logging
2+
from http import HTTPStatus
3+
4+
import aiohttp
5+
6+
from consts.const import DATA_PROCESS_SERVICE
7+
8+
logger = logging.getLogger("image_service")
9+
10+
async def proxy_image_impl(decoded_url: str):
11+
# Create session to call the data processing service
12+
async with aiohttp.ClientSession() as session:
13+
# Call the data processing service to load the image
14+
data_process_url = f"{DATA_PROCESS_SERVICE}/tasks/load_image?url={decoded_url}"
15+
16+
async with session.get(data_process_url) as response:
17+
if response.status != HTTPStatus.OK:
18+
error_text = await response.text()
19+
logger.error(
20+
f"Failed to fetch image from data process service: {error_text}")
21+
return {"success": False, "error": "Failed to fetch image or image format not supported"}
22+
23+
result = await response.json()
24+
return result

test/backend/app/test_image_app.py

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import json
21
import sys
32
import os
4-
from io import BytesIO
53
import pytest
64
from unittest.mock import patch, MagicMock, AsyncMock
75

@@ -36,74 +34,77 @@
3634
"error": "Failed to fetch image or image format not supported"
3735
}
3836

37+
3938
@pytest.mark.asyncio
4039
async def test_proxy_image_success(monkeypatch):
4140
"""Test successful image proxy request"""
4241
# Create mock response
4342
mock_response = AsyncMock()
4443
mock_response.status = 200
4544
mock_response.json = AsyncMock(return_value=success_response)
46-
45+
4746
# Create mock session
4847
mock_session = AsyncMock()
4948
mock_get = AsyncMock()
5049
mock_get.__aenter__.return_value = mock_response
5150
mock_session.get = MagicMock(return_value=mock_get)
52-
51+
5352
# Create mock session factory
5453
mock_client_session = AsyncMock()
5554
mock_client_session.__aenter__.return_value = mock_session
56-
57-
# Patch the ClientSession
58-
with patch('backend.apps.image_app.aiohttp.ClientSession') as mock_session_class:
55+
56+
# Patch the ClientSession in the correct module
57+
with patch('backend.services.image_service.aiohttp.ClientSession') as mock_session_class:
5958
mock_session_class.return_value = mock_client_session
60-
59+
6160
# Test with TestClient
6261
response = client.get(f"/image?url={encoded_test_url}")
63-
62+
6463
# Assertions
6564
assert response.status_code == 200
6665
assert response.json() == success_response
67-
66+
6867
# Verify correct URL was called
6968
mock_session.get.assert_called_once()
7069

70+
7171
@pytest.mark.asyncio
7272
async def test_proxy_image_remote_error(monkeypatch):
7373
"""Test image proxy when remote service returns error"""
7474
# Create mock response
7575
mock_response = AsyncMock()
7676
mock_response.status = 404
7777
mock_response.text = AsyncMock(return_value="Image not found")
78-
78+
7979
# Create mock session
8080
mock_session = AsyncMock()
8181
mock_get = AsyncMock()
8282
mock_get.__aenter__.return_value = mock_response
8383
mock_session.get = MagicMock(return_value=mock_get)
84-
84+
8585
# Create mock session factory
8686
mock_client_session = AsyncMock()
8787
mock_client_session.__aenter__.return_value = mock_session
88-
88+
8989
# Create expected error response
9090
expected_error_response = {
9191
"success": False,
9292
"error": "Failed to fetch image: Image not found"
9393
}
94-
95-
# Patch the ClientSession
96-
with patch('backend.apps.image_app.aiohttp.ClientSession') as mock_session_class:
94+
95+
# Patch the ClientSession in the correct module
96+
with patch('backend.services.image_service.aiohttp.ClientSession') as mock_session_class:
9797
mock_session_class.return_value = mock_client_session
98-
98+
9999
# Test with TestClient
100100
response = client.get(f"/image?url={encoded_test_url}")
101-
101+
102102
# Assertions
103103
assert response.status_code == 200
104104
assert response.json()["success"] is False
105105
assert "Failed to fetch image" in response.json()["error"]
106106

107+
107108
@pytest.mark.asyncio
108109
async def test_proxy_image_exception(monkeypatch):
109110
"""Test image proxy when an exception occurs"""
@@ -112,60 +113,62 @@ async def test_proxy_image_exception(monkeypatch):
112113
mock_get = AsyncMock()
113114
mock_get.__aenter__.side_effect = Exception("Connection error")
114115
mock_session.get = MagicMock(return_value=mock_get)
115-
116+
116117
# Create mock session factory
117118
mock_client_session = AsyncMock()
118119
mock_client_session.__aenter__.return_value = mock_session
119-
120-
# Patch the ClientSession
121-
with patch('backend.apps.image_app.aiohttp.ClientSession') as mock_session_class:
120+
121+
# Patch the ClientSession in the correct module
122+
with patch('backend.services.image_service.aiohttp.ClientSession') as mock_session_class:
122123
mock_session_class.return_value = mock_client_session
123-
124+
124125
# Test with TestClient
125126
response = client.get(f"/image?url={encoded_test_url}")
126-
127+
127128
# Assertions
128129
assert response.status_code == 200
129130
assert response.json()["success"] is False
130131
assert response.json()["error"] == "Connection error"
131132

133+
132134
@pytest.mark.asyncio
133135
async def test_proxy_image_with_special_chars(monkeypatch):
134136
"""Test image proxy with URL containing special characters"""
135137
special_url = "https://example.com/image with spaces.jpg"
136138
encoded_special_url = "https%3A%2F%2Fexample.com%2Fimage%20with%20spaces.jpg"
137-
139+
138140
# Create mock response
139141
mock_response = AsyncMock()
140142
mock_response.status = 200
141143
mock_response.json = AsyncMock(return_value=success_response)
142-
144+
143145
# Create mock session
144146
mock_session = AsyncMock()
145147
mock_get = AsyncMock()
146148
mock_get.__aenter__.return_value = mock_response
147149
mock_session.get = MagicMock(return_value=mock_get)
148-
150+
149151
# Create mock session factory
150152
mock_client_session = AsyncMock()
151153
mock_client_session.__aenter__.return_value = mock_session
152-
153-
# Patch the ClientSession
154-
with patch('backend.apps.image_app.aiohttp.ClientSession') as mock_session_class:
154+
155+
# Patch the ClientSession in the correct module
156+
with patch('backend.services.image_service.aiohttp.ClientSession') as mock_session_class:
155157
mock_session_class.return_value = mock_client_session
156-
158+
157159
# Test with TestClient
158160
response = client.get(f"/image?url={encoded_special_url}")
159-
161+
160162
# Assertions
161163
assert response.status_code == 200
162164
assert response.json() == success_response
163-
165+
164166
# Verify URL was correctly passed
165167
mock_session.get.assert_called_once()
166168
called_args = mock_session.get.call_args[0][0]
167169
assert special_url in called_args or encoded_special_url in called_args
168170

171+
169172
@pytest.mark.asyncio
170173
async def test_proxy_image_logging(monkeypatch):
171174
"""Test error handling when an exception occurs"""
@@ -174,23 +177,23 @@ async def test_proxy_image_logging(monkeypatch):
174177
mock_get = AsyncMock()
175178
mock_get.__aenter__.side_effect = Exception("Logging test error")
176179
mock_session.get = MagicMock(return_value=mock_get)
177-
180+
178181
# Create mock session factory
179182
mock_client_session = AsyncMock()
180183
mock_client_session.__aenter__.return_value = mock_session
181-
182-
# Patch the ClientSession
183-
with patch('backend.apps.image_app.aiohttp.ClientSession') as mock_session_class:
184+
185+
# Patch the ClientSession in the correct module
186+
with patch('backend.services.image_service.aiohttp.ClientSession') as mock_session_class:
184187
mock_session_class.return_value = mock_client_session
185-
188+
186189
# Test with TestClient
187190
response = client.get(f"/image?url={encoded_test_url}")
188-
191+
189192
# Focus on verifying the error handling in the response
190193
assert response.status_code == 200 # API should still return 200 status
191194
response_data = response.json()
192195
assert response_data["success"] is False
193196
assert "Logging test error" in response_data["error"]
194-
197+
195198
# Verify the mock was called with the expected URL
196199
mock_session.get.assert_called_once()

0 commit comments

Comments
 (0)