@@ -495,7 +495,7 @@ async def admin_edit_server(
495
495
>>> import asyncio
496
496
>>> from unittest.mock import AsyncMock, MagicMock
497
497
>>> from fastapi import Request
498
- >>> from fastapi.responses import RedirectResponse
498
+ >>> from fastapi.responses import JSONResponse
499
499
>>> from starlette.datastructures import FormData
500
500
>>>
501
501
>>> mock_db = MagicMock()
@@ -511,36 +511,67 @@ async def admin_edit_server(
511
511
>>>
512
512
>>> async def test_admin_edit_server_success():
513
513
... result = await admin_edit_server(server_id, mock_request_edit, mock_db, mock_user)
514
- ... return isinstance(result, RedirectResponse ) and result.status_code == 303 and "/admin#catalog" in result.headers["location"]
514
+ ... return isinstance(result, JSONResponse ) and result.status_code == 200 and result.body == b'{"message":"Server updated successfully!","success":true}'
515
515
>>>
516
516
>>> asyncio.run(test_admin_edit_server_success())
517
517
True
518
518
>>>
519
- >>> # Edge case: Edit server and include inactive checkbox
520
- >>> form_data_inactive = FormData([("name", "Inactive Server Edit"), ("is_inactive_checked", "true")])
521
- >>> mock_request_inactive = MagicMock(spec=Request, scope={"root_path": "/api"})
522
- >>> mock_request_inactive.form = AsyncMock(return_value=form_data_inactive)
523
- >>>
524
- >>> async def test_admin_edit_server_inactive_checked():
525
- ... result = await admin_edit_server(server_id, mock_request_inactive, mock_db, mock_user)
526
- ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/api/admin/?include_inactive=true#catalog" in result.headers["location"]
527
- >>>
528
- >>> asyncio.run(test_admin_edit_server_inactive_checked())
529
- True
530
- >>>
531
519
>>> # Error path: Simulate an exception during update
532
520
>>> form_data_error = FormData([("name", "Error Server")])
533
521
>>> mock_request_error = MagicMock(spec=Request, scope={"root_path": ""})
534
522
>>> mock_request_error.form = AsyncMock(return_value=form_data_error)
535
523
>>> server_service.update_server = AsyncMock(side_effect=Exception("Update failed"))
536
524
>>>
537
- >>> async def test_admin_edit_server_exception():
525
+ >>> # Restore original method
526
+ >>> server_service.update_server = original_update_server
527
+ >>> # 409 Conflict: ServerNameConflictError
528
+ >>> server_service.update_server = AsyncMock(side_effect=ServerNameConflictError("Name conflict"))
529
+ >>> async def test_admin_edit_server_conflict():
538
530
... result = await admin_edit_server(server_id, mock_request_error, mock_db, mock_user)
539
- ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/admin#catalog" in result.headers["location"]
540
- >>>
541
- >>> asyncio.run(test_admin_edit_server_exception())
531
+ ... return isinstance(result, JSONResponse) and result.status_code == 409 and b'Name conflict' in result.body
532
+ >>> asyncio.run(test_admin_edit_server_conflict())
533
+ True
534
+ >>> # 409 Conflict: IntegrityError
535
+ >>> from sqlalchemy.exc import IntegrityError
536
+ >>> server_service.update_server = AsyncMock(side_effect=IntegrityError("Integrity error", None, None))
537
+ >>> async def test_admin_edit_server_integrity():
538
+ ... result = await admin_edit_server(server_id, mock_request_error, mock_db, mock_user)
539
+ ... return isinstance(result, JSONResponse) and result.status_code == 409
540
+ >>> asyncio.run(test_admin_edit_server_integrity())
541
+ True
542
+ >>> # 422 Unprocessable Entity: ValidationError
543
+ >>> from pydantic import ValidationError, BaseModel
544
+ >>> from mcpgateway.schemas import ServerUpdate
545
+ >>> validation_error = ValidationError.from_exception_data("ServerUpdate validation error", [
546
+ ... {"loc": ("name",), "msg": "Field required", "type": "missing"}
547
+ ... ])
548
+ >>> server_service.update_server = AsyncMock(side_effect=validation_error)
549
+ >>> async def test_admin_edit_server_validation():
550
+ ... result = await admin_edit_server(server_id, mock_request_error, mock_db, mock_user)
551
+ ... return isinstance(result, JSONResponse) and result.status_code == 422
552
+ >>> asyncio.run(test_admin_edit_server_validation())
553
+ True
554
+ >>> # 400 Bad Request: ValueError
555
+ >>> server_service.update_server = AsyncMock(side_effect=ValueError("Bad value"))
556
+ >>> async def test_admin_edit_server_valueerror():
557
+ ... result = await admin_edit_server(server_id, mock_request_error, mock_db, mock_user)
558
+ ... return isinstance(result, JSONResponse) and result.status_code == 400 and b'Bad value' in result.body
559
+ >>> asyncio.run(test_admin_edit_server_valueerror())
560
+ True
561
+ >>> # 500 Internal Server Error: ServerError
562
+ >>> server_service.update_server = AsyncMock(side_effect=ServerError("Server error"))
563
+ >>> async def test_admin_edit_server_servererror():
564
+ ... result = await admin_edit_server(server_id, mock_request_error, mock_db, mock_user)
565
+ ... return isinstance(result, JSONResponse) and result.status_code == 500 and b'Server error' in result.body
566
+ >>> asyncio.run(test_admin_edit_server_servererror())
567
+ True
568
+ >>> # 500 Internal Server Error: RuntimeError
569
+ >>> server_service.update_server = AsyncMock(side_effect=RuntimeError("Runtime error"))
570
+ >>> async def test_admin_edit_server_runtimeerror():
571
+ ... result = await admin_edit_server(server_id, mock_request_error, mock_db, mock_user)
572
+ ... return isinstance(result, JSONResponse) and result.status_code == 500 and b'Runtime error' in result.body
573
+ >>> asyncio.run(test_admin_edit_server_runtimeerror())
542
574
True
543
- >>>
544
575
>>> # Restore original method
545
576
>>> server_service.update_server = original_update_server
546
577
"""
@@ -558,35 +589,23 @@ async def admin_edit_server(
558
589
await server_service .update_server (db , server_id , server )
559
590
560
591
return JSONResponse (
561
- content = {"message" : "Server update successfully!" , "success" : True },
592
+ content = {"message" : "Server updated successfully!" , "success" : True },
562
593
status_code = 200 ,
563
594
)
595
+ except (ValidationError , CoreValidationError ) as ex :
596
+ # Catch both Pydantic and pydantic_core validation errors
597
+ return JSONResponse (content = ErrorFormatter .format_validation_error (ex ), status_code = 422 )
598
+ except ServerNameConflictError as ex :
599
+ return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 409 )
600
+ except ServerError as ex :
601
+ return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 500 )
602
+ except ValueError as ex :
603
+ return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 400 )
604
+ except RuntimeError as ex :
605
+ return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 500 )
606
+ except IntegrityError as ex :
607
+ return JSONResponse (content = ErrorFormatter .format_database_error (ex ), status_code = 409 )
564
608
except Exception as ex :
565
- if isinstance (ex , ServerNameConflictError ):
566
- # Custom server name conflict error — 409 Conflict is appropriate
567
- return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 409 )
568
-
569
- if isinstance (ex , ServerError ):
570
- # Custom server logic error — 500 Internal Server Error makes sense
571
- return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 500 )
572
-
573
- if isinstance (ex , ValueError ):
574
- # Invalid input — 400 Bad Request is appropriate
575
- return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 400 )
576
-
577
- if isinstance (ex , RuntimeError ):
578
- # Unexpected error during runtime — 500 is suitable
579
- return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 500 )
580
-
581
- if isinstance (ex , ValidationError ):
582
- # Pydantic or input validation failure — 422 Unprocessable Entity is correct
583
- return JSONResponse (content = ErrorFormatter .format_validation_error (ex ), status_code = 422 )
584
-
585
- if isinstance (ex , IntegrityError ):
586
- # DB constraint violation — 409 Conflict is appropriate
587
- return JSONResponse (content = ErrorFormatter .format_database_error (ex ), status_code = 409 )
588
-
589
- # For any other unhandled error, default to 500
590
609
return JSONResponse (content = {"message" : str (ex ), "success" : False }, status_code = 500 )
591
610
592
611
@@ -2831,27 +2850,11 @@ async def admin_edit_resource(
2831
2850
>>>
2832
2851
>>> async def test_admin_edit_resource():
2833
2852
... response = await admin_edit_resource("test://resource1", mock_request, mock_db, mock_user)
2834
- ... return isinstance(response, RedirectResponse ) and response.status_code == 303
2853
+ ... return isinstance(response, JSONResponse ) and response.status_code == 200 and response.body == b'{"message":"Resource update successfully!","success":true}'
2835
2854
>>>
2836
2855
>>> import asyncio; asyncio.run(test_admin_edit_resource())
2837
2856
True
2838
2857
>>>
2839
- >>> # Test with inactive checkbox checked
2840
- >>> form_data_inactive = FormData([
2841
- ... ("name", "Updated Resource"),
2842
- ... ("description", "Updated description"),
2843
- ... ("mimeType", "text/plain"),
2844
- ... ("content", "Updated content"),
2845
- ... ("is_inactive_checked", "true")
2846
- ... ])
2847
- >>> mock_request.form = AsyncMock(return_value=form_data_inactive)
2848
- >>>
2849
- >>> async def test_admin_edit_resource_inactive():
2850
- ... response = await admin_edit_resource("test://resource1", mock_request, mock_db, mock_user)
2851
- ... return isinstance(response, RedirectResponse) and "include_inactive=true" in response.headers["location"]
2852
- >>>
2853
- >>> asyncio.run(test_admin_edit_resource_inactive())
2854
- True
2855
2858
>>> resource_service.update_resource = original_update_resource
2856
2859
"""
2857
2860
logger .debug (f"User { user } is editing resource URI { uri } " )
@@ -2865,7 +2868,7 @@ async def admin_edit_resource(
2865
2868
)
2866
2869
await resource_service .update_resource (db , uri , resource )
2867
2870
return JSONResponse (
2868
- content = {"message" : "Resource updated successfully!" , "success" : True },
2871
+ content = {"message" : "Resource update successfully!" , "success" : True },
2869
2872
status_code = 200 ,
2870
2873
)
2871
2874
except Exception as ex :
@@ -3254,7 +3257,7 @@ async def admin_edit_prompt(
3254
3257
user: Authenticated user.
3255
3258
3256
3259
Returns:
3257
- A redirect response to the admin dashboard .
3260
+ JSONResponse: A JSON response indicating success or failure of the server update operation .
3258
3261
3259
3262
Examples:
3260
3263
>>> import asyncio
0 commit comments