Skip to content

Commit 5bd4a81

Browse files
committed
♻️ Translate error messages and fix test cases
1 parent 49d2f9e commit 5bd4a81

File tree

5 files changed

+237
-31
lines changed

5 files changed

+237
-31
lines changed

backend/services/user_management_service.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,21 @@ async def check_auth_service_health() -> bool:
8888
try:
8989
supabase_url = os.getenv("SUPABASE_URL")
9090
supabase_key = os.getenv("SUPABASE_KEY")
91-
91+
9292
health_url = f'{supabase_url}/auth/v1/health'
9393
headers = {'apikey': supabase_key}
94-
94+
9595
async with aiohttp.ClientSession() as session:
9696
async with session.get(health_url, headers=headers) as response:
9797
if not response.ok:
9898
return False
99-
99+
100100
data = await response.json()
101101
# Check if the service is available by checking if the response contains the name field and its value is "GoTrue"
102102
is_available = data and data.get("name") == "GoTrue"
103-
103+
104104
return is_available
105-
105+
106106
except aiohttp.ClientError as e:
107107
logging.error(f"Auth service connection failed: {str(e)}")
108108
return False
@@ -117,7 +117,8 @@ async def signup_user(email: EmailStr,
117117
invite_code: Optional[str] = None):
118118
"""User registration"""
119119
client = get_supabase_client()
120-
logging.info(f"Receive registration request: email={email}, is_admin={is_admin}")
120+
logging.info(
121+
f"Receive registration request: email={email}, is_admin={is_admin}")
121122
if is_admin:
122123
await verify_invite_code(invite_code)
123124

@@ -138,7 +139,8 @@ async def signup_user(email: EmailStr,
138139
# Create user tenant relationship
139140
insert_user_tenant(user_id=user_id, tenant_id=tenant_id)
140141

141-
logging.info(f"User {email} registered successfully, role: {user_role}, tenant: {tenant_id}")
142+
logging.info(
143+
f"User {email} registered successfully, role: {user_role}, tenant: {tenant_id}")
142144

143145
if is_admin:
144146
await generate_tts_stt_4_admin(tenant_id, user_id)
@@ -147,7 +149,8 @@ async def signup_user(email: EmailStr,
147149
else:
148150
logging.error(
149151
"Supabase registration request returned no user object")
150-
raise UserRegistrationException("Registration service is temporarily unavailable, please try again later")
152+
raise UserRegistrationException(
153+
"Registration service is temporarily unavailable, please try again later")
151154

152155

153156
async def parse_supabase_response(is_admin, response, user_role):
@@ -157,7 +160,7 @@ async def parse_supabase_response(is_admin, response, user_role):
157160
"email": response.user.email,
158161
"role": user_role
159162
}
160-
163+
161164
session_data = None
162165
if response.session:
163166
session_data = {
@@ -166,7 +169,7 @@ async def parse_supabase_response(is_admin, response, user_role):
166169
"expires_at": calculate_expires_at(response.session.access_token),
167170
"expires_in_seconds": get_jwt_expiry_seconds(response.session.access_token)
168171
}
169-
172+
170173
return {
171174
"user": user_data,
172175
"session": session_data,
@@ -206,7 +209,8 @@ async def generate_tts_stt_4_admin(tenant_id, user_id):
206209

207210

208211
async def verify_invite_code(invite_code):
209-
logging.info("detect admin registration request, start verifying invite code")
212+
logging.info(
213+
"detect admin registration request, start verifying invite code")
210214
logging.info(f"The INVITE_CODE obtained from consts.const: {INVITE_CODE}")
211215
if not INVITE_CODE:
212216
logging.error("please check the INVITE_CODE environment variable")
@@ -219,7 +223,8 @@ async def verify_invite_code(invite_code):
219223
if invite_code != INVITE_CODE:
220224
logging.warning(
221225
f"Admin invite code verification failed: user provided='{invite_code}', system configured='{INVITE_CODE}'")
222-
raise IncorrectInviteCodeException("Please enter the correct admin invite code")
226+
raise IncorrectInviteCodeException(
227+
"Please enter the correct admin invite code")
223228
logging.info("Admin invite code verification successful")
224229

225230

@@ -246,21 +251,21 @@ async def signin_user(email: EmailStr,
246251
f"User {email} logged in successfully, session validity is {expiry_seconds} seconds, role: {user_role}")
247252

248253
return {
249-
"message":f"Login successful, session validity is {expiry_seconds} seconds",
250-
"data":{
251-
"user": {
252-
"id": response.user.id,
253-
"email": response.user.email,
254-
"role": user_role
255-
},
256-
"session": {
257-
"access_token": response.session.access_token,
258-
"refresh_token": response.session.refresh_token,
259-
"expires_at": expires_at,
260-
"expires_in_seconds": expiry_seconds
261-
}
254+
"message": f"Login successful, session validity is {expiry_seconds} seconds",
255+
"data": {
256+
"user": {
257+
"id": response.user.id,
258+
"email": response.user.email,
259+
"role": user_role
260+
},
261+
"session": {
262+
"access_token": response.session.access_token,
263+
"refresh_token": response.session.refresh_token,
264+
"expires_at": expires_at,
265+
"expires_in_seconds": expiry_seconds
262266
}
263267
}
268+
}
264269

265270

266271
async def refresh_user_token(authorization, refresh_token: str):
@@ -283,10 +288,10 @@ async def get_session_by_authorization(authorization):
283288
if user.user_metadata and 'role' in user.user_metadata:
284289
user_role = user.user_metadata['role']
285290
return {"user": {
286-
"id": user.id,
287-
"email": user.email,
288-
"role": user_role
289-
}
290-
}
291+
"id": user.id,
292+
"email": user.email,
293+
"role": user_role
294+
}
295+
}
291296
else:
292297
raise ValueError("Session is invalid")

backend/utils/auth_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from database.user_tenant_db import get_user_tenant_by_user_id
1515

1616
# Get Supabase configuration
17-
SUPABASE_URL = os.getenv('SUPABASE_URL', 'http://118.31.249.152:8010')
17+
SUPABASE_URL = os.getenv('SUPABASE_URL')
1818
SUPABASE_KEY = os.getenv('SUPABASE_KEY', '')
1919
# Debug JWT expiration time (seconds), not set or 0 means not effective
2020
DEBUG_JWT_EXPIRE_SECONDS = int(os.getenv('DEBUG_JWT_EXPIRE_SECONDS', '0') or 0)

test/backend/app/test_file_management_app.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,79 @@ async def test_process_text_file(mock_files):
597597
assert "File test.txt content" in result
598598

599599

600+
async def test_process_text_file_http_error(mock_files):
601+
"""Test process_text_file when HTTP request fails with non-200 status code"""
602+
from backend.apps.file_management_app import process_text_file
603+
604+
with patch('backend.utils.config_utils.get_model_name_from_config', return_value="test-model"), \
605+
patch('backend.utils.attachment_utils.get_model_name_from_config', return_value="test-model"), \
606+
patch('backend.utils.attachment_utils.convert_long_text_to_text', return_value="Processed text content"), \
607+
patch('backend.apps.file_management_app.convert_long_text_to_text', return_value="Processed text content"):
608+
609+
with patch('httpx.AsyncClient') as mock_client:
610+
# Setup mock response for httpx client with error status
611+
mock_response = MagicMock()
612+
mock_response.status_code = 500
613+
mock_response.headers = {'content-type': 'application/json'}
614+
mock_response.json.return_value = {'detail': 'Internal server error'}
615+
mock_response.text = 'Internal server error'
616+
617+
mock_client_instance = MagicMock()
618+
mock_client_instance.post.return_value = asyncio.Future()
619+
mock_client_instance.post.return_value.set_result(mock_response)
620+
mock_client.return_value.__aenter__.return_value = mock_client_instance
621+
622+
# Test the function - should raise an exception
623+
with pytest.raises(Exception) as exc_info:
624+
await process_text_file(
625+
query="Test query",
626+
filename="test.txt",
627+
file_content=mock_files["mock_file_content"],
628+
tenant_id="tenant123",
629+
language="en"
630+
)
631+
632+
# Verify the exception message contains the expected error details
633+
assert "File processing failed (status code: 500)" in str(exc_info.value)
634+
assert "Internal server error" in str(exc_info.value)
635+
636+
637+
async def test_process_text_file_http_error_non_json(mock_files):
638+
"""Test process_text_file when HTTP request fails with non-JSON response"""
639+
from backend.apps.file_management_app import process_text_file
640+
641+
with patch('backend.utils.config_utils.get_model_name_from_config', return_value="test-model"), \
642+
patch('backend.utils.attachment_utils.get_model_name_from_config', return_value="test-model"), \
643+
patch('backend.utils.attachment_utils.convert_long_text_to_text', return_value="Processed text content"), \
644+
patch('backend.apps.file_management_app.convert_long_text_to_text', return_value="Processed text content"):
645+
646+
with patch('httpx.AsyncClient') as mock_client:
647+
# Setup mock response for httpx client with error status and non-JSON content
648+
mock_response = MagicMock()
649+
mock_response.status_code = 400
650+
mock_response.headers = {'content-type': 'text/html'}
651+
mock_response.text = 'Bad Request - HTML error page'
652+
653+
mock_client_instance = MagicMock()
654+
mock_client_instance.post.return_value = asyncio.Future()
655+
mock_client_instance.post.return_value.set_result(mock_response)
656+
mock_client.return_value.__aenter__.return_value = mock_client_instance
657+
658+
# Test the function - should raise an exception
659+
with pytest.raises(Exception) as exc_info:
660+
await process_text_file(
661+
query="Test query",
662+
filename="test.txt",
663+
file_content=mock_files["mock_file_content"],
664+
tenant_id="tenant123",
665+
language="en"
666+
)
667+
668+
# Verify the exception message contains the expected error details
669+
assert "File processing failed (status code: 400)" in str(exc_info.value)
670+
assert "Bad Request - HTML error page" in str(exc_info.value)
671+
672+
600673
def test_get_file_description(mock_files):
601674
# Import directly in the test to use the already established mocks
602675
from backend.apps.file_management_app import get_file_description

test/backend/app/test_model_managment_app.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,53 @@ def test_batch_create_models_exception(self, mock_get_user, mock_get_existing):
807807
self.assertEqual(data["code"], 500)
808808
self.assertIn("Failed to batch create models: Database connection error", data["message"])
809809

810+
@patch("test_model_managment_app.create_model_record")
811+
@patch("test_model_managment_app.prepare_model_dict")
812+
@patch("test_model_managment_app.get_model_by_display_name")
813+
@patch("test_model_managment_app.delete_model_record")
814+
@patch("test_model_managment_app.get_models_by_tenant_factory_type")
815+
@patch("test_model_managment_app.get_current_user_id")
816+
def test_batch_create_models_max_tokens_update(self, mock_get_user, mock_get_existing, mock_delete, mock_get_by_display, mock_prepare, mock_create):
817+
"""Test max_tokens update when model already exists with different max_tokens"""
818+
mock_get_user.return_value = (self.user_id, self.tenant_id)
819+
mock_get_existing.return_value = []
820+
821+
# Mock existing model with different max_tokens
822+
existing_model = {
823+
"model_id": "existing_model_id",
824+
"max_tokens": 1000,
825+
"display_name": "test_provider/existing_model"
826+
}
827+
828+
def get_by_display_name_side_effect(display_name, tenant_id):
829+
if display_name == "test_provider/existing_model":
830+
return existing_model
831+
return None
832+
mock_get_by_display.side_effect = get_by_display_name_side_effect
833+
834+
request_models = [
835+
{
836+
"id": "test_provider/existing_model",
837+
"max_tokens": 2000 # Different max_tokens
838+
}
839+
]
840+
841+
request_data = {
842+
"models": request_models,
843+
"provider": "test_provider",
844+
"type": "llm",
845+
"api_key": "test_key"
846+
}
847+
848+
response = client.post("/model/batch_create_models", json=request_data, headers=self.auth_header)
849+
850+
self.assertEqual(response.status_code, 200)
851+
data = response.json()
852+
self.assertEqual(data["code"], 200)
853+
854+
# Verify that get_model_by_display_name was called to check for existing model
855+
mock_get_by_display.assert_called_with("test_provider/existing_model", self.tenant_id)
856+
810857
@patch("test_model_managment_app.get_provider_models", new_callable=AsyncMock)
811858
@patch("test_model_managment_app.get_current_user_id")
812859
def test_create_provider_model_silicon_success(self, mock_get_user, mock_get_provider):
@@ -1794,6 +1841,43 @@ def test_delete_embedding_model(self, mock_delete, mock_get_by_display, mock_get
17941841
# Verify mock was called with correct model_id
17951842
mock_delete.assert_called_once_with("embedding_id", self.user_id, self.tenant_id)
17961843

1844+
@patch("test_model_managment_app.get_current_user_id")
1845+
@patch("test_model_managment_app.get_model_by_display_name")
1846+
@patch("test_model_managment_app.delete_model_record")
1847+
def test_delete_multi_embedding_model(self, mock_delete, mock_get_by_display, mock_get_user):
1848+
"""Test deletion of multi_embedding model with mutual deletion logic"""
1849+
mock_get_user.return_value = (self.user_id, self.tenant_id)
1850+
1851+
# Mock multi_embedding model deletion scenario
1852+
# First call: initial check returns multi_embedding model
1853+
# Second call: loop check for "embedding" type returns None
1854+
# Third call: loop check for "multi_embedding" type returns the model
1855+
mock_get_by_display.side_effect = [
1856+
{
1857+
"model_id": "multi_embedding_id",
1858+
"model_type": "multi_embedding",
1859+
"display_name": "Test Multi Embedding"
1860+
},
1861+
None, # No embedding model found
1862+
{
1863+
"model_id": "multi_embedding_id",
1864+
"model_type": "multi_embedding",
1865+
"display_name": "Test Multi Embedding"
1866+
}
1867+
]
1868+
1869+
# Send request
1870+
response = client.post("/model/delete", params={"display_name": "Test Multi Embedding"}, headers=self.auth_header)
1871+
1872+
# Assert response
1873+
self.assertEqual(response.status_code, 200)
1874+
data = response.json()
1875+
self.assertEqual(data["code"], 200)
1876+
self.assertIn("Successfully deleted model", data["message"])
1877+
1878+
# Verify mock was called with correct model_id
1879+
mock_delete.assert_called_once_with("multi_embedding_id", self.user_id, self.tenant_id)
1880+
17971881
@patch("test_model_managment_app.get_current_user_id")
17981882
@patch("test_model_managment_app.get_model_by_display_name")
17991883
def test_delete_model_not_found(self, mock_get_by_display, mock_get_user):

test/backend/services/test_data_process_service.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,50 @@ def test_get_all_tasks(self):
574574
"""
575575
asyncio.run(self.async_test_get_all_tasks())
576576

577+
@patch('backend.services.data_process_service.DataProcessService._get_celery_inspector')
578+
@patch('data_process.utils.get_task_info')
579+
@patch('data_process.utils.get_all_task_ids_from_redis')
580+
@pytest.mark.asyncio
581+
async def test_get_all_tasks_redis_error(self, mock_get_redis_task_ids, mock_get_task_info, mock_get_inspector):
582+
"""
583+
Test get_all_tasks when Redis query fails.
584+
585+
This test verifies that the service handles Redis errors gracefully
586+
and continues to process tasks from other sources.
587+
"""
588+
# Setup mocks
589+
mock_inspector = MagicMock()
590+
mock_inspector.active.return_value = {
591+
'worker1': [{'id': 'task1'}, {'id': 'task2'}]
592+
}
593+
mock_inspector.reserved.return_value = {
594+
'worker1': [{'id': 'task3'}]
595+
}
596+
mock_get_inspector.return_value = mock_inspector
597+
598+
# Mock Redis to raise an exception
599+
mock_get_redis_task_ids.side_effect = Exception("Redis connection failed")
600+
601+
# Setup task info mock
602+
async def mock_task_info(task_id):
603+
task_data = {
604+
'task1': {'id': 'task1', 'status': 'ACTIVE', 'index_name': 'index1', 'task_name': 'task_name1'},
605+
'task2': {'id': 'task2', 'status': 'ACTIVE', 'index_name': 'index2', 'task_name': 'task_name2'},
606+
'task3': {'id': 'task3', 'status': 'RESERVED', 'index_name': 'index3', 'task_name': 'task_name3'},
607+
}
608+
return task_data.get(task_id, {})
609+
610+
mock_get_task_info.side_effect = mock_task_info
611+
612+
# Get all tasks - should handle Redis error gracefully
613+
result = await self.service.get_all_tasks(filter=True)
614+
615+
# Verify result (should only include tasks from Celery, not Redis)
616+
self.assertEqual(len(result), 3)
617+
618+
# Verify that Redis was called and failed
619+
mock_get_redis_task_ids.assert_called_once()
620+
577621
@patch('backend.services.data_process_service.DataProcessService.get_all_tasks')
578622
@pytest.mark.asyncio
579623
async def async_test_get_index_tasks(self, mock_get_all_tasks):

0 commit comments

Comments
 (0)