diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eadcbcb4..7c191ec4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -584,7 +584,7 @@ This release focuses on **Advanced OAuth Integration, Plugin Ecosystem, MCP Regi - `GET /grpc` - List all gRPC services with team filtering - `GET /grpc/{id}` - Get service details - `PUT /grpc/{id}` - Update service configuration - - `POST /grpc/{id}/toggle` - Enable/disable service + - `POST /grpc/{id}/state` - Enable/disable service - `POST /grpc/{id}/delete` - Delete service - `POST /grpc/{id}/reflect` - Re-trigger service discovery - `GET /grpc/{id}/methods` - List discovered methods diff --git a/README.md b/README.md index a0d8dab20..bb342e1c3 100644 --- a/README.md +++ b/README.md @@ -2264,9 +2264,9 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ # Toggle active status curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/tools/1/toggle?activate=false + http://localhost:4444/tools/1/state?activate=false curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/tools/1/toggle?activate=true + http://localhost:4444/tools/1/state?activate=true # Delete tool curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools/1 @@ -2326,7 +2326,7 @@ curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ # Toggle agent status curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/a2a/agent-id/toggle?activate=false + http://localhost:4444/a2a/agent-id/state?activate=false # Delete agent curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ @@ -2376,7 +2376,7 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ # Toggle active status curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/gateways/1/toggle?activate=false + http://localhost:4444/gateways/1/state?activate=false # Delete gateway curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/gateways/1 @@ -2462,7 +2462,7 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ # Toggle active curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/prompts/5/toggle?activate=false + http://localhost:4444/prompts/5/state?activate=false # Delete prompt curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/prompts/greet @@ -2520,7 +2520,7 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ # Toggle active curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/servers/UUID_OF_SERVER_1/toggle?activate=false + http://localhost:4444/servers/UUID_OF_SERVER_1/state?activate=false ``` diff --git a/docs/docs/architecture/plugins/gateway-hooks.md b/docs/docs/architecture/plugins/gateway-hooks.md index f2c79e830..65b057105 100644 --- a/docs/docs/architecture/plugins/gateway-hooks.md +++ b/docs/docs/architecture/plugins/gateway-hooks.md @@ -884,7 +884,7 @@ async def server_post_delete(self, payload: ServerPostOperationPayload, | Attribute | Type | Description | |-----------|------|-------------| | **Hook Name** | `server_pre_status_change` | Hook identifier for configuration | -| **Execution Point** | Before server status toggle | When MCP server is about to be activated or deactivated | +| **Execution Point** | Before server status change | When MCP server is about to be activated or deactivated | | **Purpose** | Access control, dependency validation, impact assessment | Validate status change permissions and assess operational impact | **Payload Attributes (`ServerPreOperationPayload`)** - Same structure as other pre-hooks: diff --git a/docs/docs/design/images/code2flow.svg b/docs/docs/design/images/code2flow.svg index 512306397..07859fe10 100644 --- a/docs/docs/design/images/code2flow.svg +++ b/docs/docs/design/images/code2flow.svg @@ -1306,7 +1306,7 @@ node_7cd26d76 -419: toggle_agent_status() +419: set_a2a_agent_state() @@ -3093,7 +3093,7 @@ node_1b63b77c -8588: admin_toggle_a2a_agent() +8588: admin_set_a2a_agent_state() @@ -3105,7 +3105,7 @@ node_5de30b09 -1596: admin_toggle_gateway() +1596: admin_set_gateway_state() @@ -3117,7 +3117,7 @@ node_7a5e44f1 -6872: admin_toggle_prompt() +6872: admin_set_prompt_state() @@ -3129,7 +3129,7 @@ node_bc807ceb -6378: admin_toggle_resource() +6378: admin_set_resource_state() @@ -3141,7 +3141,7 @@ node_ddba35fe -1080: admin_toggle_server() +1080: admin_set_server_state() @@ -3153,7 +3153,7 @@ node_880f2772 -5257: admin_toggle_tool() +5257: admin_set_tool_state() @@ -6813,7 +6813,7 @@ node_d8cf5c91 -1291: toggle_gateway_status() +1291: set_gateway_state() @@ -10222,7 +10222,7 @@ node_713e7ad0 -1916: toggle_a2a_agent_status() +1916: set_a2a_agent_state() @@ -10234,7 +10234,7 @@ node_8d8912a8 -3044: toggle_gateway_status() +3044: set_gateway_state() @@ -10246,13 +10246,13 @@ node_64a5110c -2658: toggle_prompt_status() +2658: set_prompt_state() node_bb7af980 -773: toggle_prompt_status() +773: set_prompt_state() @@ -10264,13 +10264,13 @@ node_5e76168a -2338: toggle_resource_status() +2338: set_resource_state() node_b0cb6099 -717: toggle_resource_status() +717: set_resource_state() @@ -10282,13 +10282,13 @@ node_f1ffc7ad -1484: toggle_server_status() +1484: set_server_state() node_2aaac624 -833: toggle_server_status() +833: set_server_state() @@ -10300,13 +10300,13 @@ node_c67b693a -2277: toggle_tool_status() +2277: set_tool_state() node_0ddbe8ce -707: toggle_tool_status() +707: set_tool_state() diff --git a/docs/docs/development/developer-onboarding.md b/docs/docs/development/developer-onboarding.md index b28eeab03..63f2ff918 100644 --- a/docs/docs/development/developer-onboarding.md +++ b/docs/docs/development/developer-onboarding.md @@ -173,7 +173,7 @@ - Resources - Prompts - Gateways - - [ ] Toggle active/inactive switches + - [ ] Set active/inactive states - [ ] JWT stored in `HttpOnly` cookie, no errors in DevTools Console ???+ check "Metrics" diff --git a/docs/docs/index.md b/docs/docs/index.md index 43b68af99..684d8002e 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -1714,11 +1714,11 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -d '{ "description":"Updated desc" }' \ http://localhost:4444/tools/1 -# Toggle active status +# Set active status curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/tools/1/toggle?activate=false + http://localhost:4444/tools/1/state?activate=false curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/tools/1/toggle?activate=true + http://localhost:4444/tools/1/state?activate=true # Delete tool curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools/1 @@ -1776,9 +1776,9 @@ curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ }' \ http://localhost:4444/a2a/agent-name/invoke -# Toggle agent status +# Set agent state curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/a2a/agent-id/toggle?activate=false + http://localhost:4444/a2a/agent-id/state?activate=false # Delete agent curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ @@ -1826,9 +1826,9 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -d '{"description":"New description"}' \ http://localhost:4444/gateways/1 -# Toggle active status +# Set active status curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/gateways/1/toggle?activate=false + http://localhost:4444/gateways/1/state?activate=false # Delete gateway curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/gateways/1 @@ -1912,9 +1912,9 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -d '{"template":"Hi, {{ user }}!"}' \ http://localhost:4444/prompts/greet -# Toggle active +# Set active curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/prompts/5/toggle?activate=false + http://localhost:4444/prompts/5/state?activate=false # Delete prompt curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/prompts/greet @@ -1970,9 +1970,9 @@ curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -d '{"description":"Updated"}' \ http://localhost:4444/servers/UUID_OF_SERVER_1 -# Toggle active +# Set active curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ - http://localhost:4444/servers/UUID_OF_SERVER_1/toggle?activate=false + http://localhost:4444/servers/UUID_OF_SERVER_1/state?activate=false ``` diff --git a/docs/docs/manage/api-usage.md b/docs/docs/manage/api-usage.md index 54e9f2498..e08dd883a 100644 --- a/docs/docs/manage/api-usage.md +++ b/docs/docs/manage/api-usage.md @@ -162,9 +162,9 @@ curl -s -X PUT -H "Authorization: Bearer $TOKEN" \ ### Enable/Disable Gateway ```bash -# Toggle gateway enabled status +# Set gateway state (enable/disable) curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - $BASE_URL/gateways/$GATEWAY_ID/toggle?activate=false | jq '.' + $BASE_URL/gateways/$GATEWAY_ID/state?activate=false | jq '.' ``` ### Delete Gateway @@ -284,9 +284,9 @@ curl -s -X PUT -H "Authorization: Bearer $TOKEN" \ ### Enable/Disable Tool ```bash -# Toggle tool enabled status +# Set tool state (enable/disable) curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - $BASE_URL/tools/$TOOL_ID/toggle?activate=false | jq '.' + $BASE_URL/tools/$TOOL_ID/state?activate=false | jq '.' ``` ### Delete Tool @@ -407,9 +407,9 @@ curl -s -X PUT -H "Authorization: Bearer $TOKEN" \ ### Enable/Disable Server ```bash -# Toggle server enabled status +# Set server state (enable/disable) curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - $BASE_URL/servers/$SERVER_ID/toggle?activate=false | jq '.' + $BASE_URL/servers/$SERVER_ID/state?activate=false | jq '.' ``` ### Delete Server @@ -499,9 +499,9 @@ curl -s -X PUT -H "Authorization: Bearer $TOKEN" \ ### Enable/Disable Resource ```bash -# Toggle resource enabled status +# Set resource state (enable/disable) curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - $BASE_URL/resources/$RESOURCE_ID/toggle?activate=false | jq '.' + $BASE_URL/resources/$RESOURCE_ID/state?activate=false | jq '.' ``` ### Delete Resource @@ -581,9 +581,9 @@ curl -s -X PUT -H "Authorization: Bearer $TOKEN" \ ### Enable/Disable Prompt ```bash -# Toggle prompt enabled status +# Set prompt state (enable/disable) curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - $BASE_URL/prompts/$PROMPT_ID/toggle?activate=false | jq '.' + $BASE_URL/prompts/$PROMPT_ID/state?activate=false | jq '.' ``` ### Delete Prompt @@ -1038,7 +1038,7 @@ TOOLS=$(curl -s -H "Authorization: Bearer $TOKEN" $BASE_URL/tools | \ for TOOL_ID in $TOOLS; do echo "Enabling tool: $TOOL_ID" curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - $BASE_URL/tools/$TOOL_ID/toggle > /dev/null + $BASE_URL/tools/$TOOL_ID/state > /dev/null done echo "Done!" diff --git a/docs/docs/overview/ui.md b/docs/docs/overview/ui.md index 724a3bd2e..32531e750 100644 --- a/docs/docs/overview/ui.md +++ b/docs/docs/overview/ui.md @@ -39,7 +39,7 @@ It provides tabbed access to: | Bulk import tools | Use API endpoint `/admin/tools/import` (see [Bulk Import](../manage/bulk-import.md)) | | View prompt output | Go to Prompts β click View | | **View entity metadata** | Click "View" on any entity β scroll to "Metadata" section | -| Toggle server activity | Use the "Activate/Deactivate" buttons in Servers tab | +| Set server activity | Use the "Activate/Deactivate" buttons in Servers tab | | Delete a resource | Navigate to Resources β click Delete (after confirming) | All actions are reflected in the live API via `/tools`, `/prompts`, etc. diff --git a/docs/docs/tutorials/openwebui-tutorial.md b/docs/docs/tutorials/openwebui-tutorial.md index 4202e2b47..66af3c117 100644 --- a/docs/docs/tutorials/openwebui-tutorial.md +++ b/docs/docs/tutorials/openwebui-tutorial.md @@ -352,7 +352,7 @@ docker logs -f openwebui 1. Navigate to **Workspace** β **Models** 2. Edit each model (granite, llama, mistral) 3. Scroll to **Tools** section -4. Toggle on the MCP tools you want available +4. Enable the MCP tools you want available 5. Click **Save** ### 6.4 Add MCP Servers to Gateway diff --git a/docs/docs/using/grpc-services.md b/docs/docs/using/grpc-services.md index af6d91cbf..4eb8c9c30 100644 --- a/docs/docs/using/grpc-services.md +++ b/docs/docs/using/grpc-services.md @@ -236,7 +236,7 @@ Click **View Methods** to see all discovered gRPC methods: - Output message type - Streaming flags (client/server streaming) -### Toggle Service +### Service State Use **Activate/Deactivate** to enable/disable a service: - Disabled services are not available for tool invocation @@ -304,10 +304,10 @@ Content-Type: application/json } ``` -### Toggle Service +### Service State ```bash -POST /grpc/{service_id}/toggle +POST /grpc/{service_id}/state ``` ### Delete Service diff --git a/mcpgateway/admin.py b/mcpgateway/admin.py index bff61268b..932be8e82 100644 --- a/mcpgateway/admin.py +++ b/mcpgateway/admin.py @@ -1337,23 +1337,23 @@ async def admin_edit_server( return JSONResponse(content={"message": str(ex), "success": False}, status_code=500) -@admin_router.post("/servers/{server_id}/toggle") -async def admin_toggle_server( +@admin_router.post("/servers/{server_id}/state") +async def admin_set_server_state( server_id: str, request: Request, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> Response: """ - Toggle a server's active status via the admin UI. + Set a server's active state via the admin UI. This endpoint processes a form request to activate or deactivate a server. It expects a form field 'activate' with value "true" to activate the server or "false" to deactivate it. The endpoint handles exceptions gracefully and - logs any errors that might occur during the status toggle operation. + logs any errors that might occur during the state change operation. Args: - server_id (str): The ID of the server whose status to toggle. + server_id (str): The ID of the server whose state to set. request (Request): FastAPI request containing form data with the 'activate' field. db (Session): Database session dependency. user (str): Authenticated user dependency. @@ -1371,20 +1371,20 @@ async def admin_toggle_server( >>> >>> mock_db = MagicMock() >>> mock_user = {"email": "test_user", "db": mock_db} - >>> server_id = "server-to-toggle" + >>> server_id = "server-to-set" >>> >>> # Happy path: Activate server >>> form_data_activate = FormData([("activate", "true"), ("is_inactive_checked", "false")]) >>> mock_request_activate = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_activate.form = AsyncMock(return_value=form_data_activate) - >>> original_toggle_server_status = server_service.toggle_server_status - >>> server_service.toggle_server_status = AsyncMock() + >>> original_set_server_state = server_service.set_server_state + >>> server_service.set_server_state = AsyncMock() >>> - >>> async def test_admin_toggle_server_activate(): - ... result = await admin_toggle_server(server_id, mock_request_activate, mock_db, mock_user) + >>> async def test_admin_set_server_state_activate(): + ... result = await admin_set_server_state(server_id, mock_request_activate, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/admin#catalog" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_server_activate()) + >>> asyncio.run(test_admin_set_server_state_activate()) True >>> >>> # Happy path: Deactivate server @@ -1392,33 +1392,33 @@ async def admin_toggle_server( >>> mock_request_deactivate = MagicMock(spec=Request, scope={"root_path": "/api"}) >>> mock_request_deactivate.form = AsyncMock(return_value=form_data_deactivate) >>> - >>> async def test_admin_toggle_server_deactivate(): - ... result = await admin_toggle_server(server_id, mock_request_deactivate, mock_db, mock_user) + >>> async def test_admin_set_server_state_deactivate(): + ... result = await admin_set_server_state(server_id, mock_request_deactivate, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/api/admin#catalog" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_server_deactivate()) + >>> asyncio.run(test_admin_set_server_state_deactivate()) True >>> - >>> # Edge case: Toggle with inactive checkbox checked + >>> # Edge case: Set state with inactive checkbox checked >>> form_data_inactive = FormData([("activate", "true"), ("is_inactive_checked", "true")]) >>> mock_request_inactive = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_inactive.form = AsyncMock(return_value=form_data_inactive) >>> - >>> async def test_admin_toggle_server_inactive_checked(): - ... result = await admin_toggle_server(server_id, mock_request_inactive, mock_db, mock_user) + >>> async def test_admin_set_server_state_inactive_checked(): + ... result = await admin_set_server_state(server_id, mock_request_inactive, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/admin/?include_inactive=true#catalog" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_server_inactive_checked()) + >>> asyncio.run(test_admin_set_server_state_inactive_checked()) True >>> - >>> # Error path: Simulate an exception during toggle + >>> # Error path: Simulate an exception during state change >>> form_data_error = FormData([("activate", "true")]) >>> mock_request_error = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_error.form = AsyncMock(return_value=form_data_error) - >>> server_service.toggle_server_status = AsyncMock(side_effect=Exception("Toggle failed")) + >>> server_service.set_server_state = AsyncMock(side_effect=Exception("Set failed")) >>> - >>> async def test_admin_toggle_server_exception(): - ... result = await admin_toggle_server(server_id, mock_request_error, mock_db, mock_user) + >>> async def test_admin_set_server_state_exception(): + ... result = await admin_set_server_state(server_id, mock_request_error, mock_db, mock_user) ... location_header = result.headers["location"] ... return ( ... isinstance(result, RedirectResponse) @@ -1428,20 +1428,20 @@ async def admin_toggle_server( ... and location_header.endswith("#catalog") # Ensure the fragment is correct ... ) >>> - >>> asyncio.run(test_admin_toggle_server_exception()) + >>> asyncio.run(test_admin_set_server_state_exception()) True >>> >>> # Restore original method - >>> server_service.toggle_server_status = original_toggle_server_status + >>> server_service.set_server_state = original_set_server_state """ form = await request.form() error_message = None user_email = get_user_email(user) - LOGGER.debug(f"User {user_email} is toggling server ID {server_id} with activate: {form.get('activate')}") + LOGGER.debug(f"User {user_email} is setting server ID {server_id} state with activate: {form.get('activate')}") activate = str(form.get("activate", "true")).lower() == "true" is_inactive_checked = str(form.get("is_inactive_checked", "false")) try: - await server_service.toggle_server_status(db, server_id, activate, user_email=user_email) + await server_service.set_server_state(db, server_id, activate, user_email=user_email) except PermissionError as e: LOGGER.warning(f"Permission denied for user {user_email} toggling servers {server_id}: {e}") error_message = str(e) @@ -1917,22 +1917,22 @@ async def admin_list_gateway_ids( return {"gateway_ids": ids} -@admin_router.post("/gateways/{gateway_id}/toggle") -async def admin_toggle_gateway( +@admin_router.post("/gateways/{gateway_id}/state") +async def admin_set_gateway_state( gateway_id: str, request: Request, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> RedirectResponse: """ - Toggle the active status of a gateway via the admin UI. + Set the active state of a gateway via the admin UI. - This endpoint allows an admin to toggle the active status of a gateway. + This endpoint allows an admin to set the active state of a gateway. It expects a form field 'activate' with a value of "true" or "false" to - determine the new status of the gateway. + determine the new state of the gateway. Args: - gateway_id (str): The ID of the gateway to toggle. + gateway_id (str): The ID of the gateway whose state to set. request (Request): The FastAPI request object containing form data. db (Session): The database session dependency. user (str): The authenticated user dependency. @@ -1950,20 +1950,20 @@ async def admin_toggle_gateway( >>> >>> mock_db = MagicMock() >>> mock_user = {"email": "test_user", "db": mock_db} - >>> gateway_id = "gateway-to-toggle" + >>> gateway_id = "gateway-to-set" >>> >>> # Happy path: Activate gateway >>> form_data_activate = FormData([("activate", "true"), ("is_inactive_checked", "false")]) >>> mock_request_activate = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_activate.form = AsyncMock(return_value=form_data_activate) - >>> original_toggle_gateway_status = gateway_service.toggle_gateway_status - >>> gateway_service.toggle_gateway_status = AsyncMock() + >>> original_set_gateway_state = gateway_service.set_gateway_state + >>> gateway_service.set_gateway_state = AsyncMock() >>> - >>> async def test_admin_toggle_gateway_activate(): - ... result = await admin_toggle_gateway(gateway_id, mock_request_activate, mock_db, mock_user) + >>> async def test_admin_set_gateway_state_activate(): + ... result = await admin_set_gateway_state(gateway_id, mock_request_activate, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/admin#gateways" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_gateway_activate()) + >>> asyncio.run(test_admin_set_gateway_state_activate()) True >>> >>> # Happy path: Deactivate gateway @@ -1971,21 +1971,21 @@ async def admin_toggle_gateway( >>> mock_request_deactivate = MagicMock(spec=Request, scope={"root_path": "/api"}) >>> mock_request_deactivate.form = AsyncMock(return_value=form_data_deactivate) >>> - >>> async def test_admin_toggle_gateway_deactivate(): - ... result = await admin_toggle_gateway(gateway_id, mock_request_deactivate, mock_db, mock_user) + >>> async def test_admin_set_gateway_state_deactivate(): + ... result = await admin_set_gateway_state(gateway_id, mock_request_deactivate, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/api/admin#gateways" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_gateway_deactivate()) + >>> asyncio.run(test_admin_set_gateway_state_deactivate()) True >>> - >>> # Error path: Simulate an exception during toggle + >>> # Error path: Simulate an exception during state change >>> form_data_error = FormData([("activate", "true")]) >>> mock_request_error = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_error.form = AsyncMock(return_value=form_data_error) - >>> gateway_service.toggle_gateway_status = AsyncMock(side_effect=Exception("Toggle failed")) + >>> gateway_service.set_gateway_state = AsyncMock(side_effect=Exception("Set failed")) >>> - >>> async def test_admin_toggle_gateway_exception(): - ... result = await admin_toggle_gateway(gateway_id, mock_request_error, mock_db, mock_user) + >>> async def test_admin_set_gateway_state_exception(): + ... result = await admin_set_gateway_state(gateway_id, mock_request_error, mock_db, mock_user) ... location_header = result.headers["location"] ... return ( ... isinstance(result, RedirectResponse) @@ -1995,26 +1995,26 @@ async def admin_toggle_gateway( ... and location_header.endswith("#gateways") # Ensure the fragment is correct ... ) >>> - >>> asyncio.run(test_admin_toggle_gateway_exception()) + >>> asyncio.run(test_admin_set_gateway_state_exception()) True >>> # Restore original method - >>> gateway_service.toggle_gateway_status = original_toggle_gateway_status + >>> gateway_service.set_gateway_state = original_set_gateway_state """ error_message = None user_email = get_user_email(user) - LOGGER.debug(f"User {user_email} is toggling gateway ID {gateway_id}") + LOGGER.debug(f"User {user_email} is setting gateway ID {gateway_id} state") form = await request.form() activate = str(form.get("activate", "true")).lower() == "true" is_inactive_checked = str(form.get("is_inactive_checked", "false")) try: - await gateway_service.toggle_gateway_status(db, gateway_id, activate, user_email=user_email) + await gateway_service.set_gateway_state(db, gateway_id, activate, user_email=user_email) except PermissionError as e: - LOGGER.warning(f"Permission denied for user {user_email} toggling gateway {gateway_id}: {e}") + LOGGER.warning(f"Permission denied for user {user_email} setting gateway {gateway_id} state: {e}") error_message = str(e) except Exception as e: - LOGGER.error(f"Error toggling gateway status: {e}") - error_message = "Failed to toggle gateway status. Please try again." + LOGGER.error(f"Error setting gateway state: {e}") + error_message = "Failed to set gateway state. Please try again." root_path = request.scope.get("root_path", "") @@ -5567,18 +5567,28 @@ async def admin_resources_partial_html( resources_db = list(db.scalars(query).all()) - # Convert to schemas using ResourceService + # Convert DB rows to ResourceRead using ResourceService public API (async) + local_resource_service = ResourceService() resources_data = [] for r in resources_db: try: - resources_data.append(local_resource_service._convert_resource_to_read(r)) # pylint: disable=protected-access + # Use the public async getter which resolves team name and converts to schema + try: + resource_schema = await local_resource_service.get_resource_by_id(db, getattr(r, "id", None), include_inactive=include_inactive) + except Exception as inner_exc: + LOGGER.warning( + "Failed to load resource id=%s via ResourceService.get_resource_by_id: %s", + getattr(r, "id", ""), + inner_exc, + ) + continue + resources_data.append(resource_schema) except Exception as e: LOGGER.warning(f"Failed to convert resource {getattr(r, 'id', '')} to schema: {e}") continue data = jsonable_encoder(resources_data) - # Build pagination metadata pagination = PaginationMeta( page=page, @@ -6578,23 +6588,23 @@ async def admin_delete_tool(tool_id: str, request: Request, db: Session = Depend return RedirectResponse(f"{root_path}/admin#tools", status_code=303) -@admin_router.post("/tools/{tool_id}/toggle") -async def admin_toggle_tool( +@admin_router.post("/tools/{tool_id}/state") +async def admin_set_tool_state( tool_id: str, request: Request, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> RedirectResponse: """ - Toggle a tool's active status via the admin UI. + Set a tool's active state via the admin UI. This endpoint processes a form request to activate or deactivate a tool. It expects a form field 'activate' with value "true" to activate the tool or "false" to deactivate it. The endpoint handles exceptions gracefully and - logs any errors that might occur during the status toggle operation. + logs any errors that might occur during the state change operation. Args: - tool_id (str): The ID of the tool whose status to toggle. + tool_id (str): The ID of the tool whose state to set. request (Request): FastAPI request containing form data with the 'activate' field. db (Session): Database session dependency. user (str): Authenticated user dependency. @@ -6612,20 +6622,20 @@ async def admin_toggle_tool( >>> >>> mock_db = MagicMock() >>> mock_user = {"email": "test_user", "db": mock_db} - >>> tool_id = "tool-to-toggle" + >>> tool_id = "tool-to-set" >>> >>> # Happy path: Activate tool >>> form_data_activate = FormData([("activate", "true"), ("is_inactive_checked", "false")]) >>> mock_request_activate = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_activate.form = AsyncMock(return_value=form_data_activate) - >>> original_toggle_tool_status = tool_service.toggle_tool_status - >>> tool_service.toggle_tool_status = AsyncMock() + >>> original_set_tool_state = tool_service.set_tool_state + >>> tool_service.set_tool_state = AsyncMock() >>> - >>> async def test_admin_toggle_tool_activate(): - ... result = await admin_toggle_tool(tool_id, mock_request_activate, mock_db, mock_user) + >>> async def test_admin_set_tool_state_activate(): + ... result = await admin_set_tool_state(tool_id, mock_request_activate, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/admin#tools" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_tool_activate()) + >>> asyncio.run(test_admin_set_tool_state_activate()) True >>> >>> # Happy path: Deactivate tool @@ -6633,33 +6643,33 @@ async def admin_toggle_tool( >>> mock_request_deactivate = MagicMock(spec=Request, scope={"root_path": "/api"}) >>> mock_request_deactivate.form = AsyncMock(return_value=form_data_deactivate) >>> - >>> async def test_admin_toggle_tool_deactivate(): - ... result = await admin_toggle_tool(tool_id, mock_request_deactivate, mock_db, mock_user) + >>> async def test_admin_set_tool_state_deactivate(): + ... result = await admin_set_tool_state(tool_id, mock_request_deactivate, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/api/admin#tools" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_tool_deactivate()) + >>> asyncio.run(test_admin_set_tool_state_deactivate()) True >>> - >>> # Edge case: Toggle with inactive checkbox checked + >>> # Edge case: Set state with inactive checkbox checked >>> form_data_inactive = FormData([("activate", "true"), ("is_inactive_checked", "true")]) >>> mock_request_inactive = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_inactive.form = AsyncMock(return_value=form_data_inactive) >>> - >>> async def test_admin_toggle_tool_inactive_checked(): - ... result = await admin_toggle_tool(tool_id, mock_request_inactive, mock_db, mock_user) + >>> async def test_admin_set_tool_state_inactive_checked(): + ... result = await admin_set_tool_state(tool_id, mock_request_inactive, mock_db, mock_user) ... return isinstance(result, RedirectResponse) and result.status_code == 303 and "/admin/?include_inactive=true#tools" in result.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_tool_inactive_checked()) + >>> asyncio.run(test_admin_set_tool_state_inactive_checked()) True >>> - >>> # Error path: Simulate an exception during toggle + >>> # Error path: Simulate an exception during state change >>> form_data_error = FormData([("activate", "true")]) >>> mock_request_error = MagicMock(spec=Request, scope={"root_path": ""}) >>> mock_request_error.form = AsyncMock(return_value=form_data_error) - >>> tool_service.toggle_tool_status = AsyncMock(side_effect=Exception("Toggle failed")) + >>> tool_service.set_tool_state = AsyncMock(side_effect=Exception("Set failed")) >>> - >>> async def test_admin_toggle_tool_exception(): - ... result = await admin_toggle_tool(tool_id, mock_request_error, mock_db, mock_user) + >>> async def test_admin_set_tool_state_exception(): + ... result = await admin_set_tool_state(tool_id, mock_request_error, mock_db, mock_user) ... location_header = result.headers["location"] ... return ( ... isinstance(result, RedirectResponse) @@ -6669,26 +6679,26 @@ async def admin_toggle_tool( ... and location_header.endswith("#tools") # Ensure fragment is correct ... ) >>> - >>> asyncio.run(test_admin_toggle_tool_exception()) + >>> asyncio.run(test_admin_set_tool_state_exception()) True >>> >>> # Restore original method - >>> tool_service.toggle_tool_status = original_toggle_tool_status + >>> tool_service.set_tool_state = original_set_tool_state """ error_message = None user_email = get_user_email(user) - LOGGER.debug(f"User {user_email} is toggling tool ID {tool_id}") + LOGGER.debug(f"User {user_email} is setting tool ID {tool_id} state") form = await request.form() activate = str(form.get("activate", "true")).lower() == "true" is_inactive_checked = str(form.get("is_inactive_checked", "false")) try: - await tool_service.toggle_tool_status(db, tool_id, activate, reachable=activate, user_email=user_email) + await tool_service.set_tool_state(db, tool_id, activate, reachable=activate, user_email=user_email) except PermissionError as e: - LOGGER.warning(f"Permission denied for user {user_email} toggling tools {tool_id}: {e}") + LOGGER.warning(f"Permission denied for user {user_email} setting tool {tool_id} state: {e}") error_message = str(e) except Exception as e: - LOGGER.error(f"Error toggling tool status: {e}") - error_message = "Failed to toggle tool status. Please try again." + LOGGER.error(f"Error setting tool state: {e}") + error_message = "Failed to set tool state. Please try again." root_path = request.scope.get("root_path", "") @@ -7946,23 +7956,23 @@ async def admin_delete_resource(resource_id: str, request: Request, db: Session return RedirectResponse(f"{root_path}/admin#resources", status_code=303) -@admin_router.post("/resources/{resource_id}/toggle") -async def admin_toggle_resource( +@admin_router.post("/resources/{resource_id}/state") +async def admin_set_resource_state( resource_id: int, request: Request, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> RedirectResponse: """ - Toggle a resource's active status via the admin UI. + Set a resource's active state via the admin UI. This endpoint processes a form request to activate or deactivate a resource. It expects a form field 'activate' with value "true" to activate the resource or "false" to deactivate it. The endpoint handles exceptions gracefully and - logs any errors that might occur during the status toggle operation. + logs any errors that might occur during the state change operation. Args: - resource_id (int): The ID of the resource whose status to toggle. + resource_id (int): The ID of the resource whose state to set. request (Request): FastAPI request containing form data with the 'activate' field. db (Session): Database session dependency. user (str): Authenticated user dependency. @@ -7988,14 +7998,14 @@ async def admin_toggle_resource( >>> mock_request.form = AsyncMock(return_value=form_data) >>> mock_request.scope = {"root_path": ""} >>> - >>> original_toggle_resource_status = resource_service.toggle_resource_status - >>> resource_service.toggle_resource_status = AsyncMock() + >>> original_set_resource_state = resource_service.set_resource_state + >>> resource_service.set_resource_state = AsyncMock() >>> - >>> async def test_admin_toggle_resource(): - ... response = await admin_toggle_resource(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_resource_state(): + ... response = await admin_set_resource_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and response.status_code == 303 >>> - >>> asyncio.run(test_admin_toggle_resource()) + >>> asyncio.run(test_admin_set_resource_state()) True >>> >>> # Test with activate=false @@ -8005,11 +8015,11 @@ async def admin_toggle_resource( ... ]) >>> mock_request.form = AsyncMock(return_value=form_data_deactivate) >>> - >>> async def test_admin_toggle_resource_deactivate(): - ... response = await admin_toggle_resource(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_resource_state_deactivate(): + ... response = await admin_set_resource_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and response.status_code == 303 >>> - >>> asyncio.run(test_admin_toggle_resource_deactivate()) + >>> asyncio.run(test_admin_set_resource_state_deactivate()) True >>> >>> # Test with inactive checkbox checked @@ -8019,43 +8029,43 @@ async def admin_toggle_resource( ... ]) >>> mock_request.form = AsyncMock(return_value=form_data_inactive) >>> - >>> async def test_admin_toggle_resource_inactive(): - ... response = await admin_toggle_resource(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_resource_state_inactive(): + ... response = await admin_set_resource_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and "include_inactive=true" in response.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_resource_inactive()) + >>> asyncio.run(test_admin_set_resource_state_inactive()) True >>> >>> # Test exception handling - >>> resource_service.toggle_resource_status = AsyncMock(side_effect=Exception("Test error")) + >>> resource_service.set_resource_state = AsyncMock(side_effect=Exception("Test error")) >>> form_data_error = FormData([ ... ("activate", "true"), ... ("is_inactive_checked", "false") ... ]) >>> mock_request.form = AsyncMock(return_value=form_data_error) >>> - >>> async def test_admin_toggle_resource_exception(): - ... response = await admin_toggle_resource(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_resource_state_exception(): + ... response = await admin_set_resource_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and response.status_code == 303 >>> - >>> asyncio.run(test_admin_toggle_resource_exception()) + >>> asyncio.run(test_admin_set_resource_state_exception()) True - >>> resource_service.toggle_resource_status = original_toggle_resource_status + >>> resource_service.set_resource_state = original_set_resource_state """ user_email = get_user_email(user) - LOGGER.debug(f"User {user_email} is toggling resource ID {resource_id}") + LOGGER.debug(f"User {user_email} is setting resource ID {resource_id} state") form = await request.form() error_message = None activate = str(form.get("activate", "true")).lower() == "true" is_inactive_checked = str(form.get("is_inactive_checked", "false")) try: - await resource_service.toggle_resource_status(db, resource_id, activate, user_email=user_email) + await resource_service.set_resource_state(db, resource_id, activate, user_email=user_email) except PermissionError as e: - LOGGER.warning(f"Permission denied for user {user_email} toggling resource status {resource_id}: {e}") + LOGGER.warning(f"Permission denied for user {user_email} setting resource state {resource_id}: {e}") error_message = str(e) except Exception as e: - LOGGER.error(f"Error toggling resource status: {e}") - error_message = "Failed to toggle resource status. Please try again." + LOGGER.error(f"Error setting resource state: {e}") + error_message = "Failed to set resource state. Please try again." root_path = request.scope.get("root_path", "") @@ -8501,23 +8511,23 @@ async def admin_delete_prompt(prompt_id: str, request: Request, db: Session = De return RedirectResponse(f"{root_path}/admin#prompts", status_code=303) -@admin_router.post("/prompts/{prompt_id}/toggle") -async def admin_toggle_prompt( +@admin_router.post("/prompts/{prompt_id}/state") +async def admin_set_prompt_state( prompt_id: int, request: Request, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> RedirectResponse: """ - Toggle a prompt's active status via the admin UI. + Set a prompt's active state via the admin UI. This endpoint processes a form request to activate or deactivate a prompt. It expects a form field 'activate' with value "true" to activate the prompt or "false" to deactivate it. The endpoint handles exceptions gracefully and - logs any errors that might occur during the status toggle operation. + logs any errors that might occur during the state change operation. Args: - prompt_id (int): The ID of the prompt whose status to toggle. + prompt_id (int): The ID of the prompt whose state to set. request (Request): FastAPI request containing form data with the 'activate' field. db (Session): Database session dependency. user (str): Authenticated user dependency. @@ -8543,14 +8553,14 @@ async def admin_toggle_prompt( >>> mock_request.form = AsyncMock(return_value=form_data) >>> mock_request.scope = {"root_path": ""} >>> - >>> original_toggle_prompt_status = prompt_service.toggle_prompt_status - >>> prompt_service.toggle_prompt_status = AsyncMock() + >>> original_set_prompt_state = prompt_service.set_prompt_state + >>> prompt_service.set_prompt_state = AsyncMock() >>> - >>> async def test_admin_toggle_prompt(): - ... response = await admin_toggle_prompt(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_prompt_state(): + ... response = await admin_set_prompt_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and response.status_code == 303 >>> - >>> asyncio.run(test_admin_toggle_prompt()) + >>> asyncio.run(test_admin_set_prompt_state()) True >>> >>> # Test with activate=false @@ -8560,11 +8570,11 @@ async def admin_toggle_prompt( ... ]) >>> mock_request.form = AsyncMock(return_value=form_data_deactivate) >>> - >>> async def test_admin_toggle_prompt_deactivate(): - ... response = await admin_toggle_prompt(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_prompt_state_deactivate(): + ... response = await admin_set_prompt_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and response.status_code == 303 >>> - >>> asyncio.run(test_admin_toggle_prompt_deactivate()) + >>> asyncio.run(test_admin_set_prompt_state_deactivate()) True >>> >>> # Test with inactive checkbox checked @@ -8574,43 +8584,43 @@ async def admin_toggle_prompt( ... ]) >>> mock_request.form = AsyncMock(return_value=form_data_inactive) >>> - >>> async def test_admin_toggle_prompt_inactive(): - ... response = await admin_toggle_prompt(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_prompt_state_inactive(): + ... response = await admin_set_prompt_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and "include_inactive=true" in response.headers["location"] >>> - >>> asyncio.run(test_admin_toggle_prompt_inactive()) + >>> asyncio.run(test_admin_set_prompt_state_inactive()) True >>> >>> # Test exception handling - >>> prompt_service.toggle_prompt_status = AsyncMock(side_effect=Exception("Test error")) + >>> prompt_service.set_prompt_state = AsyncMock(side_effect=Exception("Test error")) >>> form_data_error = FormData([ ... ("activate", "true"), ... ("is_inactive_checked", "false") ... ]) >>> mock_request.form = AsyncMock(return_value=form_data_error) >>> - >>> async def test_admin_toggle_prompt_exception(): - ... response = await admin_toggle_prompt(1, mock_request, mock_db, mock_user) + >>> async def test_admin_set_prompt_state_exception(): + ... response = await admin_set_prompt_state(1, mock_request, mock_db, mock_user) ... return isinstance(response, RedirectResponse) and response.status_code == 303 >>> - >>> asyncio.run(test_admin_toggle_prompt_exception()) + >>> asyncio.run(test_admin_set_prompt_state_exception()) True - >>> prompt_service.toggle_prompt_status = original_toggle_prompt_status + >>> prompt_service.set_prompt_state = original_set_prompt_state """ user_email = get_user_email(user) - LOGGER.debug(f"User {user_email} is toggling prompt ID {prompt_id}") + LOGGER.debug(f"User {user_email} is setting prompt ID {prompt_id} state") error_message = None form = await request.form() activate: bool = str(form.get("activate", "true")).lower() == "true" is_inactive_checked: str = str(form.get("is_inactive_checked", "false")) try: - await prompt_service.toggle_prompt_status(db, prompt_id, activate, user_email=user_email) + await prompt_service.set_prompt_state(db, prompt_id, activate, user_email=user_email) except PermissionError as e: - LOGGER.warning(f"Permission denied for user {user_email} toggling prompt {prompt_id}: {e}") + LOGGER.warning(f"Permission denied for user {user_email} setting prompt state {prompt_id}: {e}") error_message = str(e) except Exception as e: - LOGGER.error(f"Error toggling prompt status: {e}") - error_message = "Failed to toggle prompt status. Please try again." + LOGGER.error(f"Error setting prompt state: {e}") + error_message = "Failed to set prompt state. Please try again." root_path = request.scope.get("root_path", "") @@ -10862,8 +10872,8 @@ async def admin_edit_a2a_agent( return JSONResponse({"message": str(e), "success": False}, status_code=500) -@admin_router.post("/a2a/{agent_id}/toggle") -async def admin_toggle_a2a_agent( +@admin_router.post("/a2a/{agent_id}/state") +async def admin_set_a2a_agent_state( agent_id: str, request: Request, db: Session = Depends(get_db), @@ -10895,21 +10905,21 @@ async def admin_toggle_a2a_agent( user_email = get_user_email(user) - await a2a_service.toggle_agent_status(db, agent_id, activate, user_email=user_email) + await a2a_service.set_a2a_agent_state(db, agent_id, activate, user_email=user_email) root_path = request.scope.get("root_path", "") return RedirectResponse(f"{root_path}/admin#a2a-agents", status_code=303) except PermissionError as e: - LOGGER.warning(f"Permission denied for user {user_email} toggling A2A agent status{agent_id}: {e}") + LOGGER.warning(f"Permission denied for user {user_email} setting A2A agent state {agent_id}: {e}") error_message = str(e) except A2AAgentNotFoundError as e: - LOGGER.error(f"A2A agent toggle failed - not found: {e}") + LOGGER.error(f"A2A agent set state failed - not found: {e}") root_path = request.scope.get("root_path", "") error_message = "A2A agent not found." except Exception as e: - LOGGER.error(f"Error toggling A2A agent: {e}") + LOGGER.error(f"Error setting A2A agent state: {e}") root_path = request.scope.get("root_path", "") - error_message = "Failed to toggle status of A2A agent. Please try again." + error_message = "Failed to set status of A2A agent. Please try again." root_path = request.scope.get("root_path", "") @@ -11155,13 +11165,13 @@ async def admin_update_grpc_service( raise HTTPException(status_code=500, detail=str(e)) -@admin_router.post("/grpc/{service_id}/toggle") -async def admin_toggle_grpc_service( +@admin_router.post("/grpc/{service_id}/state") +async def admin_set_grpc_service_state( service_id: str, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), # pylint: disable=unused-argument ): - """Toggle a gRPC service's enabled status. + """Set a gRPC service's enabled status (via admin UI). Args: service_id: Service ID @@ -11179,7 +11189,7 @@ async def admin_toggle_grpc_service( try: service = await grpc_service_mgr.get_service(db, service_id) - result = await grpc_service_mgr.toggle_service(db, service_id, not service.enabled) + result = await grpc_service_mgr.set_grpc_service_state(db, service_id, not service.enabled) return JSONResponse(content=jsonable_encoder(result)) except GrpcServiceNotFoundError as e: raise HTTPException(status_code=404, detail=str(e)) diff --git a/mcpgateway/main.py b/mcpgateway/main.py index 7e2bd787f..e984371e3 100644 --- a/mcpgateway/main.py +++ b/mcpgateway/main.py @@ -1815,33 +1815,37 @@ async def update_server( raise HTTPException(status_code=409, detail=ErrorFormatter.format_database_error(e)) -@server_router.post("/{server_id}/toggle", response_model=ServerRead) +@server_router.post("/{server_id}/state", response_model=ServerRead) @require_permission("servers.update") -async def toggle_server_status( +async def set_server_state( server_id: str, activate: bool = True, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> ServerRead: """ - Toggles the status of a server (activate or deactivate). + Set the state of a server (activate or deactivate). + + This endpoint updates the server's active/inactive state. Prefer the + language "set ... state" over "toggle" to avoid ambiguity; the + `activate` boolean explicitly controls the resulting state. Args: - server_id (str): The ID of the server to toggle. - activate (bool): Whether to activate or deactivate the server. + server_id (str): The ID of the server to modify. + activate (bool): Whether to activate (True) or deactivate (False) the server. db (Session): The database session used to interact with the data store. user (str): The authenticated user making the request. Returns: - ServerRead: The server object after the status change. + ServerRead: The server object after the state change. Raises: HTTPException: If the server is not found or there is an error. """ try: user_email = user.get("email") if isinstance(user, dict) else str(user) - logger.debug(f"User {user} is toggling server with ID {server_id} to {'active' if activate else 'inactive'}") - return await server_service.toggle_server_status(db, server_id, activate, user_email=user_email) + logger.debug(f"User {user} is setting server {server_id} to {'active' if activate else 'inactive'}") + return await server_service.set_server_state(db, server_id, activate, user_email=user_email) except PermissionError as e: raise HTTPException(status_code=403, detail=str(e)) except ServerNotFoundError as e: @@ -2290,35 +2294,39 @@ async def update_a2a_agent( raise HTTPException(status_code=409, detail=ErrorFormatter.format_database_error(e)) -@a2a_router.post("/{agent_id}/toggle", response_model=A2AAgentRead) +@a2a_router.post("/{agent_id}/state", response_model=A2AAgentRead) @require_permission("a2a.update") -async def toggle_a2a_agent_status( +async def set_a2a_agent_state( agent_id: str, activate: bool = True, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> A2AAgentRead: """ - Toggles the status of an A2A agent (activate or deactivate). + Set the state of an A2A agent (activate or deactivate). + + This endpoint updates the agent's active/inactive state. Use "set ... state" + wording rather than "toggle"; the `activate` boolean explicitly controls the + resulting state. Args: - agent_id (str): The ID of the agent to toggle. - activate (bool): Whether to activate or deactivate the agent. + agent_id (str): The ID of the agent to modify. + activate (bool): Whether to activate (True) or deactivate (False) the agent. db (Session): The database session used to interact with the data store. user (str): The authenticated user making the request. Returns: - A2AAgentRead: The agent object after the status change. + A2AAgentRead: The agent object after the state change. Raises: HTTPException: If the agent is not found or there is an error. """ try: user_email = user.get("email") if isinstance(user, dict) else str(user) - logger.debug(f"User {user} is toggling A2A agent with ID {agent_id} to {'active' if activate else 'inactive'}") + logger.debug(f"User {user} is setting A2A agent with ID {agent_id} to {'active' if activate else 'inactive'}") if a2a_service is None: raise HTTPException(status_code=503, detail="A2A service not available") - return await a2a_service.toggle_agent_status(db, agent_id, activate, user_email=user_email) + return await a2a_service.set_a2a_agent_state(db, agent_id, activate, user_email=user_email) except PermissionError as e: raise HTTPException(status_code=403, detail=str(e)) except A2AAgentNotFoundError as e: @@ -2689,9 +2697,9 @@ async def delete_tool(tool_id: str, db: Session = Depends(get_db), user=Depends( raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) -@tool_router.post("/{tool_id}/toggle") +@tool_router.post("/{tool_id}/state") @require_permission("tools.update") -async def toggle_tool_status( +async def set_tool_state( tool_id: str, activate: bool = True, db: Session = Depends(get_db), @@ -2701,7 +2709,7 @@ async def toggle_tool_status( Activates or deactivates a tool. Args: - tool_id (str): The ID of the tool to toggle. + tool_id (str): The ID of the tool whose state to set. activate (bool): Whether to activate (`True`) or deactivate (`False`) the tool. db (Session): The database session dependency. user (str): The authenticated user making the request. @@ -2710,12 +2718,12 @@ async def toggle_tool_status( Dict[str, Any]: The status, message, and updated tool data. Raises: - HTTPException: If an error occurs during status toggling. + HTTPException: If an error occurs during the state change. """ try: - logger.debug(f"User {user} is toggling tool with ID {tool_id} to {'active' if activate else 'inactive'}") + logger.debug(f"User {user} is setting tool with ID {tool_id} to {'active' if activate else 'inactive'}") user_email = user.get("email") if isinstance(user, dict) else str(user) - tool = await tool_service.toggle_tool_status(db, tool_id, activate, reachable=activate, user_email=user_email) + tool = await tool_service.set_tool_state(db, tool_id, activate, reachable=activate, user_email=user_email) return { "status": "success", "message": f"Tool {tool_id} {'activated' if activate else 'deactivated'}", @@ -2753,9 +2761,9 @@ async def list_resource_templates( return ListResourceTemplatesResult(_meta={}, resource_templates=resource_templates, next_cursor=None) # No pagination for now -@resource_router.post("/{resource_id}/toggle") +@resource_router.post("/{resource_id}/state") @require_permission("resources.update") -async def toggle_resource_status( +async def set_resource_state( resource_id: int, activate: bool = True, db: Session = Depends(get_db), @@ -2776,10 +2784,10 @@ async def toggle_resource_status( Raises: HTTPException: If toggling fails. """ - logger.debug(f"User {user} is toggling resource with ID {resource_id} to {'active' if activate else 'inactive'}") + logger.debug(f"User {user} is setting resource with ID {resource_id} to {'active' if activate else 'inactive'}") try: user_email = user.get("email") if isinstance(user, dict) else str(user) - resource = await resource_service.toggle_resource_status(db, resource_id, activate, user_email=user_email) + resource = await resource_service.set_resource_state(db, resource_id, activate, user_email=user_email) return { "status": "success", "message": f"Resource {resource_id} {'activated' if activate else 'deactivated'}", @@ -3101,19 +3109,19 @@ async def subscribe_resource(resource_id: str, user=Depends(get_current_user_wit ############### # Prompt APIs # ############### -@prompt_router.post("/{prompt_id}/toggle") +@prompt_router.post("/{prompt_id}/state") @require_permission("prompts.update") -async def toggle_prompt_status( +async def set_prompt_state( prompt_id: int, activate: bool = True, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> Dict[str, Any]: """ - Toggle the activation status of a prompt. + Set the activation state of a prompt. Args: - prompt_id: ID of the prompt to toggle. + prompt_id: ID of the prompt whose state to set. activate: True to activate, False to deactivate. db: Database session. user: Authenticated user. @@ -3122,12 +3130,12 @@ async def toggle_prompt_status( Status message and updated prompt details. Raises: - HTTPException: If the toggle fails (e.g., prompt not found or database error); emitted with *400 Bad Request* status and an error message. + HTTPException: If the state change fails (e.g., prompt not found or database error); emitted with *400 Bad Request* status and an error message. """ - logger.debug(f"User: {user} requested toggle for prompt {prompt_id}, activate={activate}") + logger.debug(f"User: {user} requested set state for prompt {prompt_id}, activate={activate}") try: user_email = user.get("email") if isinstance(user, dict) else str(user) - prompt = await prompt_service.toggle_prompt_status(db, prompt_id, activate, user_email=user_email) + prompt = await prompt_service.set_prompt_state(db, prompt_id, activate, user_email=user_email) return { "status": "success", "message": f"Prompt {prompt_id} {'activated' if activate else 'deactivated'}", @@ -3460,19 +3468,19 @@ async def delete_prompt(prompt_id: str, db: Session = Depends(get_db), user=Depe ################ # Gateway APIs # ################ -@gateway_router.post("/{gateway_id}/toggle") +@gateway_router.post("/{gateway_id}/state") @require_permission("gateways.update") -async def toggle_gateway_status( +async def set_gateway_state( gateway_id: str, activate: bool = True, db: Session = Depends(get_db), user=Depends(get_current_user_with_permissions), ) -> Dict[str, Any]: """ - Toggle the activation status of a gateway. + Set the activation state of a gateway. Args: - gateway_id (str): String ID of the gateway to toggle. + gateway_id (str): String ID of the gateway whose state to set. activate (bool): ``True`` to activate, ``False`` to deactivate. db (Session): Active SQLAlchemy session. user (str): Authenticated username. @@ -3481,12 +3489,12 @@ async def toggle_gateway_status( Dict[str, Any]: A dict containing the operation status, a message, and the updated gateway object. Raises: - HTTPException: Returned with **400 Bad Request** if the toggle operation fails (e.g., the gateway does not exist or the database raises an unexpected error). + HTTPException: Returned with **400 Bad Request** if the state change fails (e.g., the gateway does not exist or the database raises an unexpected error). """ - logger.debug(f"User '{user}' requested toggle for gateway {gateway_id}, activate={activate}") + logger.debug(f"User '{user}' requested set state for gateway {gateway_id}, activate={activate}") try: user_email = user.get("email") if isinstance(user, dict) else str(user) - gateway = await gateway_service.toggle_gateway_status( + gateway = await gateway_service.set_gateway_state( db, gateway_id, activate, diff --git a/mcpgateway/services/a2a_service.py b/mcpgateway/services/a2a_service.py index 33f3468d0..b697f448a 100644 --- a/mcpgateway/services/a2a_service.py +++ b/mcpgateway/services/a2a_service.py @@ -119,7 +119,7 @@ def __init__(self, name: str, is_active: bool = True, agent_id: Optional[str] = class A2AAgentService: """Service for managing A2A agents in the gateway. - Provides methods to create, list, retrieve, update, toggle status, and delete agent records. + Provides methods to create, list, retrieve, update, set state, and delete agent records. Also supports interactions with A2A-compatible agents. """ @@ -675,8 +675,8 @@ async def update_agent( db.rollback() raise A2AAgentError(f"Failed to update A2A agent: {str(e)}") - async def toggle_agent_status(self, db: Session, agent_id: str, activate: bool, reachable: Optional[bool] = None, user_email: Optional[str] = None) -> A2AAgentRead: - """Toggle the activation status of an A2A agent. + async def set_a2a_agent_state(self, db: Session, agent_id: str, activate: bool, reachable: Optional[bool] = None, user_email: Optional[str] = None) -> A2AAgentRead: + """Set the activation state of an A2A agent. Args: db: Database session. @@ -718,6 +718,9 @@ async def toggle_agent_status(self, db: Session, agent_id: str, activate: bool, return self._db_to_schema(db=db, db_agent=agent) + # Backwards-compatible alias + toggle_agent_status = set_a2a_agent_state + async def delete_agent(self, db: Session, agent_id: str, user_email: Optional[str] = None) -> None: """Delete an A2A agent. diff --git a/mcpgateway/services/gateway_service.py b/mcpgateway/services/gateway_service.py index 722b27799..c64b0c775 100644 --- a/mcpgateway/services/gateway_service.py +++ b/mcpgateway/services/gateway_service.py @@ -1546,9 +1546,9 @@ async def get_gateway(self, db: Session, gateway_id: str, include_inactive: bool raise GatewayNotFoundError(f"Gateway not found: {gateway_id}") - async def toggle_gateway_status(self, db: Session, gateway_id: str, activate: bool, reachable: bool = True, only_update_reachable: bool = False, user_email: Optional[str] = None) -> GatewayRead: + async def set_gateway_state(self, db: Session, gateway_id: str, activate: bool, reachable: bool = True, only_update_reachable: bool = False, user_email: Optional[str] = None) -> GatewayRead: """ - Toggle the activation status of a gateway. + Set the activation state of a gateway. Args: db: Database session @@ -1671,10 +1671,10 @@ async def toggle_gateway_status(self, db: Session, gateway_id: str, activate: bo if only_update_reachable: for tool in tools: - await self.tool_service.toggle_tool_status(db, tool.id, tool.enabled, reachable) + await self.tool_service.set_tool_state(db, tool.id, tool.enabled, reachable) else: for tool in tools: - await self.tool_service.toggle_tool_status(db, tool.id, activate, reachable) + await self.tool_service.set_tool_state(db, tool.id, activate, reachable) # Notify subscribers if activate: @@ -1691,7 +1691,10 @@ async def toggle_gateway_status(self, db: Session, gateway_id: str, activate: bo raise e except Exception as e: db.rollback() - raise GatewayError(f"Failed to toggle gateway status: {str(e)}") + raise GatewayError(f"Failed to set gateway state: {str(e)}") + + # Backwards-compatible alias + toggle_gateway_status = set_gateway_state async def _notify_gateway_updated(self, gateway: DbGateway) -> None: """ @@ -2073,7 +2076,7 @@ async def _handle_gateway_failure(self, gateway: DbGateway) -> None: if count >= GW_FAILURE_THRESHOLD: logger.error(f"Gateway {gateway.name} failed {GW_FAILURE_THRESHOLD} times. Deactivating...") with cast(Any, SessionLocal)() as db: - await self.toggle_gateway_status(db, gateway.id, activate=True, reachable=False, only_update_reachable=True) + await self.set_gateway_state(db, gateway.id, activate=True, reachable=False, only_update_reachable=True) self._gateway_failure_counts[gateway.id] = 0 # Reset after deactivation async def check_health_of_gateways(self, db: Session, gateways: List[DbGateway], user_email: Optional[str] = None) -> bool: @@ -2272,7 +2275,7 @@ def get_httpx_client_factory( # Reactivate gateway if it was previously inactive and health check passed now if gateway.enabled and not gateway.reachable: logger.info(f"Reactivating gateway: {gateway.name}, as it is healthy now") - await self.toggle_gateway_status(db, gateway.id, activate=True, reachable=True, only_update_reachable=True) + await self.set_gateway_state(db, gateway.id, activate=True, reachable=True, only_update_reachable=True) # Mark successful check gateway.last_seen = datetime.now(timezone.utc) diff --git a/mcpgateway/services/grpc_service.py b/mcpgateway/services/grpc_service.py index 221a5beac..4f042fc59 100644 --- a/mcpgateway/services/grpc_service.py +++ b/mcpgateway/services/grpc_service.py @@ -278,13 +278,13 @@ async def update_service( return GrpcServiceRead.model_validate(service) - async def toggle_service( + async def set_grpc_service_state( self, db: Session, service_id: str, activate: bool, ) -> GrpcServiceRead: - """Toggle a gRPC service's enabled status. + """Set a gRPC service's enabled state. Args: db: Database session @@ -313,6 +313,9 @@ async def toggle_service( return GrpcServiceRead.model_validate(service) + # Backwards-compatible alias + toggle_service = set_grpc_service_state + async def delete_service( self, db: Session, diff --git a/mcpgateway/services/prompt_service.py b/mcpgateway/services/prompt_service.py index 456ed2461..f549fca37 100644 --- a/mcpgateway/services/prompt_service.py +++ b/mcpgateway/services/prompt_service.py @@ -1014,9 +1014,9 @@ async def update_prompt( db.rollback() raise PromptError(f"Failed to update prompt: {str(e)}") - async def toggle_prompt_status(self, db: Session, prompt_id: int, activate: bool, user_email: Optional[str] = None) -> PromptRead: + async def set_prompt_state(self, db: Session, prompt_id: int, activate: bool, user_email: Optional[str] = None) -> PromptRead: """ - Toggle the activation status of a prompt. + Set the activation state of a prompt. Args: db: Database session @@ -1046,7 +1046,7 @@ async def toggle_prompt_status(self, db: Session, prompt_id: int, activate: bool >>> service._convert_db_prompt = MagicMock(return_value={}) >>> import asyncio >>> try: - ... asyncio.run(service.toggle_prompt_status(db, 1, True)) + ... asyncio.run(service.set_prompt_state(db, 1, True)) ... except Exception: ... pass """ @@ -1079,7 +1079,10 @@ async def toggle_prompt_status(self, db: Session, prompt_id: int, activate: bool raise e except Exception as e: db.rollback() - raise PromptError(f"Failed to toggle prompt status: {str(e)}") + raise PromptError(f"Failed to set prompt state: {str(e)}") + + # Backwards-compatible alias + toggle_prompt_status = set_prompt_state # Get prompt details for admin ui async def get_prompt_details(self, db: Session, prompt_id: Union[int, str], include_inactive: bool = False) -> Dict[str, Any]: # pylint: disable=unused-argument diff --git a/mcpgateway/services/resource_service.py b/mcpgateway/services/resource_service.py index 5bc52b450..a42d6edb1 100644 --- a/mcpgateway/services/resource_service.py +++ b/mcpgateway/services/resource_service.py @@ -940,9 +940,9 @@ async def read_resource( except Exception as e: logger.warning(f"Failed to end observability span for resource reading: {e}") - async def toggle_resource_status(self, db: Session, resource_id: int, activate: bool, user_email: Optional[str] = None) -> ResourceRead: + async def set_resource_state(self, db: Session, resource_id: int, activate: bool, user_email: Optional[str] = None) -> ResourceRead: """ - Toggle the activation status of a resource. + Set the activation state of a resource. Args: db: Database session @@ -973,7 +973,7 @@ async def toggle_resource_status(self, db: Session, resource_id: int, activate: >>> service._convert_resource_to_read = MagicMock(return_value='resource_read') >>> ResourceRead.model_validate = MagicMock(return_value='resource_read') >>> import asyncio - >>> asyncio.run(service.toggle_resource_status(db, 1, True)) + >>> asyncio.run(service.set_resource_state(db, 1, True)) 'resource_read' """ try: @@ -1010,7 +1010,10 @@ async def toggle_resource_status(self, db: Session, resource_id: int, activate: raise e except Exception as e: db.rollback() - raise ResourceError(f"Failed to toggle resource status: {str(e)}") + raise ResourceError(f"Failed to set resource state: {str(e)}") + + # Backwards-compatible alias + toggle_resource_status = set_resource_state async def subscribe_resource(self, db: Session, subscription: ResourceSubscription) -> None: """ @@ -1360,6 +1363,8 @@ async def get_resource_by_id(self, db: Session, resource_id: int, include_inacti raise ResourceNotFoundError(f"Resource not found: {resource_id}") + # Ensure team name is resolved before conversion (consistent with ToolService.get_tool) + resource.team = self._get_team_name(db, getattr(resource, "team_id", None)) return self._convert_resource_to_read(resource) async def _notify_resource_activated(self, resource: DbResource) -> None: diff --git a/mcpgateway/services/server_service.py b/mcpgateway/services/server_service.py index f93c21df3..59d035425 100644 --- a/mcpgateway/services/server_service.py +++ b/mcpgateway/services/server_service.py @@ -103,7 +103,7 @@ def __init__(self, name: str, is_active: bool = True, server_id: Optional[str] = class ServerService: """Service for managing MCP Servers in the catalog. - Provides methods to create, list, retrieve, update, toggle status, and delete server records. + Provides methods to create, list, retrieve, update, set state, and delete server records. Also supports event notifications for changes in server data. """ @@ -879,8 +879,8 @@ async def update_server( db.rollback() raise ServerError(f"Failed to update server: {str(e)}") - async def toggle_server_status(self, db: Session, server_id: str, activate: bool, user_email: Optional[str] = None) -> ServerRead: - """Toggle the activation status of a server. + async def set_server_state(self, db: Session, server_id: str, activate: bool, user_email: Optional[str] = None) -> ServerRead: + """Set the activation state of a server. Args: db: Database session. @@ -911,7 +911,7 @@ async def toggle_server_status(self, db: Session, server_id: str, activate: bool >>> service._convert_server_to_read = MagicMock(return_value='server_read') >>> ServerRead.model_validate = MagicMock(return_value='server_read') >>> import asyncio - >>> asyncio.run(service.toggle_server_status(db, 'server_id', True)) + >>> asyncio.run(service.set_server_state(db, 'server_id', True)) 'server_read' """ try: @@ -957,7 +957,10 @@ async def toggle_server_status(self, db: Session, server_id: str, activate: bool raise e except Exception as e: db.rollback() - raise ServerError(f"Failed to toggle server status: {str(e)}") + raise ServerError(f"Failed to set server state: {str(e)}") + + # Backwards-compatible alias + toggle_server_status = set_server_state async def delete_server(self, db: Session, server_id: str, user_email: Optional[str] = None) -> None: """Permanently delete a server. diff --git a/mcpgateway/services/tool_service.py b/mcpgateway/services/tool_service.py index 1baaf6e25..1f211675c 100644 --- a/mcpgateway/services/tool_service.py +++ b/mcpgateway/services/tool_service.py @@ -1000,9 +1000,9 @@ async def delete_tool(self, db: Session, tool_id: str, user_email: Optional[str] db.rollback() raise ToolError(f"Failed to delete tool: {str(e)}") - async def toggle_tool_status(self, db: Session, tool_id: str, activate: bool, reachable: bool, user_email: Optional[str] = None) -> ToolRead: + async def set_tool_state(self, db: Session, tool_id: str, activate: bool, reachable: bool, user_email: Optional[str] = None) -> ToolRead: """ - Toggle the activation status of a tool. + Set the activation state of a tool. Args: db (Session): The SQLAlchemy database session. @@ -1034,7 +1034,7 @@ async def toggle_tool_status(self, db: Session, tool_id: str, activate: bool, re >>> service._convert_tool_to_read = MagicMock(return_value='tool_read') >>> ToolRead.model_validate = MagicMock(return_value='tool_read') >>> import asyncio - >>> asyncio.run(service.toggle_tool_status(db, 'tool_id', True, True)) + >>> asyncio.run(service.set_tool_state(db, 'tool_id', True, True)) 'tool_read' """ try: @@ -1074,7 +1074,10 @@ async def toggle_tool_status(self, db: Session, tool_id: str, activate: bool, re raise e except Exception as e: db.rollback() - raise ToolError(f"Failed to toggle tool status: {str(e)}") + raise ToolError(f"Failed to set tool state: {str(e)}") + + # Backwards-compatible alias + toggle_tool_status = set_tool_state async def invoke_tool(self, db: Session, name: str, arguments: Dict[str, Any], request_headers: Optional[Dict[str, str]] = None, app_user_email: Optional[str] = None) -> ToolResult: """ @@ -1620,9 +1623,9 @@ async def update_tool( db.rollback() logger.error(f"Tool name conflict during update: {tnce}") raise tnce - except Exception as ex: + except Exception as e: db.rollback() - raise ToolError(f"Failed to update tool: {str(ex)}") + raise ToolError(f"Failed to update tool: {str(e)}") async def _notify_tool_updated(self, tool: DbTool) -> None: """ diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html index 3fe94e430..a27833dff 100644 --- a/mcpgateway/templates/admin.html +++ b/mcpgateway/templates/admin.html @@ -1962,7 +1962,7 @@ {% if server.isActive %} @@ -1978,7 +1978,7 @@ {% else %} @@ -4173,7 +4173,7 @@ {% if gateway.enabled %} @@ -4189,7 +4189,7 @@ {% else %} @@ -5438,7 +5438,7 @@ {% if agent.enabled %} - + {% else %} - + - + const isActiveFlag = item.isActive || item.enabled || item.is_active; const activeButton = isActiveFlag ? - ` + ` Deactivate ` : - ` + ` Activate `; diff --git a/mcpgateway/templates/prompts_partial.html b/mcpgateway/templates/prompts_partial.html index ff3b2914e..b813794fc 100644 --- a/mcpgateway/templates/prompts_partial.html +++ b/mcpgateway/templates/prompts_partial.html @@ -41,7 +41,7 @@ - + {% if prompt.is_active %}Deactivate{% else %}Activate{% endif %} diff --git a/mcpgateway/templates/resources_partial.html b/mcpgateway/templates/resources_partial.html index 5d013f0c1..a72c37e27 100644 --- a/mcpgateway/templates/resources_partial.html +++ b/mcpgateway/templates/resources_partial.html @@ -34,12 +34,12 @@ Edit {% if resource.isActive %} - + Deactivate {% else %} - + Activate diff --git a/mcpgateway/templates/tools_partial.html b/mcpgateway/templates/tools_partial.html index 3436696a3..baeb58457 100644 --- a/mcpgateway/templates/tools_partial.html +++ b/mcpgateway/templates/tools_partial.html @@ -222,7 +222,7 @@ {% if tool.enabled %} @@ -238,7 +238,7 @@ {% else %} diff --git a/mcpgateway/templates/tools_with_pagination.html b/mcpgateway/templates/tools_with_pagination.html index f7a628374..41cc7ffa2 100644 --- a/mcpgateway/templates/tools_with_pagination.html +++ b/mcpgateway/templates/tools_with_pagination.html @@ -128,14 +128,14 @@ {% if tool.enabled %} - + Deactivate {% else %} - + Activate @@ -285,14 +285,14 @@ {% if tool.enabled %} - + Deactivate {% else %} - + Activate diff --git a/tests/e2e/test_admin_apis.py b/tests/e2e/test_admin_apis.py index 9e78cb698..7a4f8da05 100644 --- a/tests/e2e/test_admin_apis.py +++ b/tests/e2e/test_admin_apis.py @@ -249,8 +249,8 @@ async def test_admin_server_lifecycle(self, client: AsyncClient, mock_settings): response = await client.post(f"/admin/servers/{server_id}/edit", data=edit_data, headers=TEST_AUTH_HEADER, follow_redirects=False) assert response.status_code == 200 - # Toggle server status - response = await client.post(f"/admin/servers/{server_id}/toggle", data={"activate": "false"}, headers=TEST_AUTH_HEADER, follow_redirects=False) + # Set server state + response = await client.post(f"/admin/servers/{server_id}/state", data={"activate": "false"}, headers=TEST_AUTH_HEADER, follow_redirects=False) assert response.status_code == 303 # Delete server @@ -324,7 +324,7 @@ async def test_admin_list_tools_empty(self, client: AsyncClient, mock_settings): # assert response.status_code == 303 # # Toggle tool status - # response = await client.post(f"/admin/tools/{tool_id}/toggle", data={"activate": "false"}, headers=TEST_AUTH_HEADER, follow_redirects=False) + # response = await client.post(f"/admin/tools/{tool_id}/state", data={"activate": "false"}, headers=TEST_AUTH_HEADER, follow_redirects=False) # assert response.status_code == 303 # # Delete tool @@ -520,7 +520,7 @@ async def test_admin_prompt_lifecycle(self, client: AsyncClient, mock_settings): assert response.status_code == 200 # Toggle prompt status - response = await client.post(f"/admin/prompts/{prompt_id}/toggle", data={"activate": "false"}, headers=TEST_AUTH_HEADER, follow_redirects=False) + response = await client.post(f"/admin/prompts/{prompt_id}/state", data={"activate": "false"}, headers=TEST_AUTH_HEADER, follow_redirects=False) assert response.status_code == 303 # Delete prompt (use updated name) @@ -699,7 +699,7 @@ class TestAdminIncludeInactive: # "is_inactive_checked": "true", # } - # response = await client.post(f"/admin/servers/{server_id}/toggle", data=form_data, headers=TEST_AUTH_HEADER, follow_redirects=False) + # response = await client.post(f"/admin/servers/{server_id}/state", data=form_data, headers=TEST_AUTH_HEADER, follow_redirects=False) # assert response.status_code == 303 # assert "include_inactive=true" in response.headers["location"] diff --git a/tests/e2e/test_main_apis.py b/tests/e2e/test_main_apis.py index e94ddaf88..2718642bd 100644 --- a/tests/e2e/test_main_apis.py +++ b/tests/e2e/test_main_apis.py @@ -583,8 +583,8 @@ async def test_update_server(self, client: AsyncClient, mock_auth): assert result["description"] == update_data["description"] assert result["icon"] == update_data["icon"] - async def test_toggle_server_status(self, client: AsyncClient, mock_auth): - """Test POST /servers/{server_id}/toggle.""" + async def test_set_server_state(self, client: AsyncClient, mock_auth): + """Test POST /servers/{server_id}/state.""" # Create a server server_data = {"server": {"name": "toggle_test_server"}, "team_id": None, "visibility": "private"} @@ -592,7 +592,7 @@ async def test_toggle_server_status(self, client: AsyncClient, mock_auth): server_id = create_response.json()["id"] # Deactivate the server - response = await client.post(f"/servers/{server_id}/toggle?activate=false", headers=TEST_AUTH_HEADER) + response = await client.post(f"/servers/{server_id}/state?activate=false", headers=TEST_AUTH_HEADER) assert response.status_code == 200 result = response.json() @@ -603,7 +603,7 @@ async def test_toggle_server_status(self, client: AsyncClient, mock_auth): assert result.get("isActive") is False or result.get("is_active") is False # Reactivate the server - response = await client.post(f"/servers/{server_id}/toggle?activate=true", headers=TEST_AUTH_HEADER) + response = await client.post(f"/servers/{server_id}/state?activate=true", headers=TEST_AUTH_HEADER) assert response.status_code == 200 result = response.json() @@ -819,7 +819,7 @@ async def test_update_tool(self, client: AsyncClient, mock_auth): assert result["headers"] == update_data["headers"] async def test_toggle_tool_status(self, client: AsyncClient, mock_auth): - """Test POST /tools/{tool_id}/toggle.""" + """Test POST /tools/{tool_id}/state.""" # Create a tool tool_data = {"tool": {"name": "test_toggle_tool"}, "team_id": None, "visibility": "private"} @@ -827,7 +827,7 @@ async def test_toggle_tool_status(self, client: AsyncClient, mock_auth): tool_id = create_response.json()["id"] # Deactivate the tool - response = await client.post(f"/tools/{tool_id}/toggle?activate=false", headers=TEST_AUTH_HEADER) + response = await client.post(f"/tools/{tool_id}/state?activate=false", headers=TEST_AUTH_HEADER) assert response.status_code == 200 result = response.json() @@ -1042,15 +1042,15 @@ async def test_update_resource(self, client: AsyncClient, mock_auth): assert result["description"] == update_data["description"] async def test_toggle_resource_status(self, client: AsyncClient, mock_auth): - """Test POST /resources/{resource_id}/toggle.""" + """Test POST /resources/{resource_id}/state.""" # Create a resource - resource_data = {"resource": {"uri": "test/toggle", "name": "toggle_test", "content": "Test"}, "team_id": None, "visibility": "private"} + resource_data = {"resource": {"uri": "test/state", "name": "toggle_test", "content": "Test"}, "team_id": None, "visibility": "private"} create_response = await client.post("/resources", json=resource_data, headers=TEST_AUTH_HEADER) resource_id = create_response.json()["id"] # Toggle resource status - response = await client.post(f"/resources/{resource_id}/toggle?activate=false", headers=TEST_AUTH_HEADER) + response = await client.post(f"/resources/{resource_id}/state?activate=false", headers=TEST_AUTH_HEADER) assert response.status_code == 200 assert response.json()["status"] == "success" @@ -1256,7 +1256,7 @@ async def test_get_prompt_no_args(self, client: AsyncClient, mock_auth): assert "messages" in result async def test_toggle_prompt_status(self, client: AsyncClient, mock_auth): - """Test POST /prompts/{prompt_id}/toggle.""" + """Test POST /prompts/{prompt_id}/state.""" # Create a prompt prompt_data = {"prompt": {"name": "toggle_prompt", "template": "Test prompt", "arguments": []}, "team_id": None, "visibility": "private"} @@ -1264,7 +1264,7 @@ async def test_toggle_prompt_status(self, client: AsyncClient, mock_auth): prompt_id = create_response.json()["id"] # Toggle prompt status - response = await client.post(f"/prompts/{prompt_id}/toggle?activate=false", headers=TEST_AUTH_HEADER) + response = await client.post(f"/prompts/{prompt_id}/state?activate=false", headers=TEST_AUTH_HEADER) assert response.status_code == 200 assert response.json()["status"] == "success" @@ -1408,7 +1408,7 @@ async def test_register_gateway(self, client: AsyncClient, mock_auth): """Test POST /gateways - would require mocking external connections.""" async def test_toggle_gateway_status(self, client: AsyncClient, mock_auth): - """Test POST /gateways/{gateway_id}/toggle.""" + """Test POST /gateways/{gateway_id}/state.""" # Mock a gateway for testing # In real tests, you'd need to register a gateway first # This is skipped as it requires external connectivity diff --git a/tests/integration/test_resource_plugin_integration.py b/tests/integration/test_resource_plugin_integration.py index ae9013503..292548b3c 100644 --- a/tests/integration/test_resource_plugin_integration.py +++ b/tests/integration/test_resource_plugin_integration.py @@ -352,7 +352,7 @@ async def test_inactive_resource_handling(self, test_db, resource_service_with_m created = await service.register_resource(test_db, resource) # Deactivate the resource - await service.toggle_resource_status(test_db, created.id, activate=False) + await service.set_resource_state(test_db, created.id, activate=False) # Try to read inactive resource diff --git a/tests/unit/mcpgateway/services/test_a2a_service.py b/tests/unit/mcpgateway/services/test_a2a_service.py index 1db5cc251..a4b6b2f2d 100644 --- a/tests/unit/mcpgateway/services/test_a2a_service.py +++ b/tests/unit/mcpgateway/services/test_a2a_service.py @@ -234,8 +234,8 @@ async def test_update_agent_not_found(self, service, mock_db): with pytest.raises(A2AAgentNotFoundError): await service.update_agent(mock_db, "non-existent-id", update_data) - async def test_toggle_agent_status_success(self, service, mock_db, sample_db_agent): - """Test successful agent status toggle.""" + async def test_set_a2a_agent_state_success(self, service, mock_db, sample_db_agent): + """Test successful agent state update.""" # Mock database query mock_db.execute.return_value.scalar_one_or_none.return_value = sample_db_agent mock_db.commit = MagicMock() @@ -243,7 +243,7 @@ async def test_toggle_agent_status_success(self, service, mock_db, sample_db_age service._db_to_schema = MagicMock(return_value=MagicMock()) # Execute - result = await service.toggle_agent_status(mock_db, sample_db_agent.id, False) + result = await service.set_a2a_agent_state(mock_db, sample_db_agent.id, False) # Verify assert sample_db_agent.enabled == False diff --git a/tests/unit/mcpgateway/services/test_gateway_service.py b/tests/unit/mcpgateway/services/test_gateway_service.py index 0469bafcb..397a338ed 100644 --- a/tests/unit/mcpgateway/services/test_gateway_service.py +++ b/tests/unit/mcpgateway/services/test_gateway_service.py @@ -1037,17 +1037,17 @@ async def test_update_gateway_without_auth_type_attr(self, gateway_service, test test_db.commit.assert_called_once() # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - # TOGGLE ACTIVE / INACTIVE + # SET ACTIVE / INACTIVE # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ @pytest.mark.asyncio - async def test_toggle_gateway_status(self, gateway_service, mock_gateway, test_db): - """Deactivating an active gateway triggers tool-status toggle + event.""" + async def test_set_gateway_state(self, gateway_service, mock_gateway, test_db): + """Deactivating an active gateway triggers tool-state updates + event.""" test_db.get = Mock(return_value=mock_gateway) test_db.commit = Mock() test_db.refresh = Mock() - # Return one tool so toggle_tool_status gets called + # Return one tool so set_tool_state gets called query_proxy = MagicMock() filter_proxy = MagicMock() filter_proxy.all.return_value = [MagicMock(id=101)] @@ -1060,7 +1060,7 @@ async def test_toggle_gateway_status(self, gateway_service, mock_gateway, test_d gateway_service._initialize_gateway = AsyncMock(return_value=({"prompts": {}}, [], [], [])) tool_service_stub = MagicMock() - tool_service_stub.toggle_tool_status = AsyncMock() + tool_service_stub.set_tool_state = AsyncMock() gateway_service.tool_service = tool_service_stub # Patch model_validate to return a mock with .masked() @@ -1068,22 +1068,22 @@ async def test_toggle_gateway_status(self, gateway_service, mock_gateway, test_d mock_gateway_read.masked.return_value = mock_gateway_read with patch("mcpgateway.services.gateway_service.GatewayRead.model_validate", return_value=mock_gateway_read): - result = await gateway_service.toggle_gateway_status(test_db, 1, activate=False) + result = await gateway_service.set_gateway_state(test_db, 1, activate=False) assert mock_gateway.enabled is False gateway_service._notify_gateway_deactivated.assert_called_once() - assert tool_service_stub.toggle_tool_status.called + assert tool_service_stub.set_tool_state.called assert result == mock_gateway_read @pytest.mark.asyncio - async def test_toggle_gateway_status_activate(self, gateway_service, mock_gateway, test_db): + async def test_set_gateway_state_activate(self, gateway_service, mock_gateway, test_db): """Test activating an inactive gateway.""" mock_gateway.enabled = False test_db.get = Mock(return_value=mock_gateway) test_db.commit = Mock() test_db.refresh = Mock() - # Return one tool so toggle_tool_status gets called + # Return one tool so set_tool_state gets called query_proxy = MagicMock() filter_proxy = MagicMock() filter_proxy.all.return_value = [MagicMock(id=101)] @@ -1096,7 +1096,7 @@ async def test_toggle_gateway_status_activate(self, gateway_service, mock_gatewa gateway_service._initialize_gateway = AsyncMock(return_value=({"prompts": {}}, [], [], [])) tool_service_stub = MagicMock() - tool_service_stub.toggle_tool_status = AsyncMock() + tool_service_stub.set_tool_state = AsyncMock() gateway_service.tool_service = tool_service_stub # Patch model_validate to return a mock with .masked() @@ -1104,32 +1104,32 @@ async def test_toggle_gateway_status_activate(self, gateway_service, mock_gatewa mock_gateway_read.masked.return_value = mock_gateway_read with patch("mcpgateway.services.gateway_service.GatewayRead.model_validate", return_value=mock_gateway_read): - result = await gateway_service.toggle_gateway_status(test_db, 1, activate=True) + result = await gateway_service.set_gateway_state(test_db, 1, activate=True) assert mock_gateway.enabled is True gateway_service._notify_gateway_activated.assert_called_once() - assert tool_service_stub.toggle_tool_status.called + assert tool_service_stub.set_tool_state.called assert result == mock_gateway_read @pytest.mark.asyncio - async def test_toggle_gateway_status_not_found(self, gateway_service, test_db): - """Test toggling status of non-existent gateway.""" + async def test_set_gateway_state_not_found(self, gateway_service, test_db): + """Test setting status of non-existent gateway.""" test_db.get = Mock(return_value=None) with pytest.raises(GatewayError) as exc_info: - await gateway_service.toggle_gateway_status(test_db, 999, activate=True) + await gateway_service.set_gateway_state(test_db, 999, activate=True) assert "Gateway not found: 999" in str(exc_info.value) @pytest.mark.asyncio - async def test_toggle_gateway_status_with_tools_error(self, gateway_service, mock_gateway, test_db): - """Test toggling gateway status when tool toggle fails.""" + async def test_set_gateway_state_with_tools_error(self, gateway_service, mock_gateway, test_db): + """Test setting gateway status when tool state update fails.""" test_db.get = Mock(return_value=mock_gateway) test_db.commit = Mock() test_db.refresh = Mock() test_db.rollback = Mock() - # Return one tool so toggle_tool_status gets called + # Return one tool so set_tool_state gets called query_proxy = MagicMock() filter_proxy = MagicMock() filter_proxy.all.return_value = [MagicMock(id=101)] @@ -1140,17 +1140,17 @@ async def test_toggle_gateway_status_with_tools_error(self, gateway_service, moc gateway_service._notify_gateway_deactivated = AsyncMock() gateway_service._initialize_gateway = AsyncMock(return_value=({"prompts": {}}, [], [], [])) - # Make tool toggle fail + # Make tool set_state fail tool_service_stub = MagicMock() - tool_service_stub.toggle_tool_status = AsyncMock(side_effect=Exception("Tool toggle failed")) + tool_service_stub.set_tool_state = AsyncMock(side_effect=Exception("Tool set_state failed")) gateway_service.tool_service = tool_service_stub # The toggle_gateway_status method will catch the exception and raise GatewayError with pytest.raises(GatewayError) as exc_info: - await gateway_service.toggle_gateway_status(test_db, 1, activate=False) + await gateway_service.set_gateway_state(test_db, 1, activate=False) - assert "Failed to toggle gateway status" in str(exc_info.value) - assert "Tool toggle failed" in str(exc_info.value) + assert "Failed to set gateway state" in str(exc_info.value) + assert "Tool set_state failed" in str(exc_info.value) test_db.rollback.assert_called_once() # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ diff --git a/tests/unit/mcpgateway/services/test_grpc_service.py b/tests/unit/mcpgateway/services/test_grpc_service.py index bc029ebee..4ee32b4f5 100644 --- a/tests/unit/mcpgateway/services/test_grpc_service.py +++ b/tests/unit/mcpgateway/services/test_grpc_service.py @@ -247,7 +247,7 @@ async def test_toggle_service(self, service, mock_db, sample_db_service): mock_db.commit = MagicMock() mock_db.refresh = MagicMock() - result = await service.toggle_service(mock_db, sample_db_service.id, activate=False) + result = await service.set_grpc_service_state(mock_db, sample_db_service.id, activate=False) assert result.enabled is False mock_db.commit.assert_called() diff --git a/tests/unit/mcpgateway/services/test_prompt_service.py b/tests/unit/mcpgateway/services/test_prompt_service.py index a69650fc1..4fe565a49 100644 --- a/tests/unit/mcpgateway/services/test_prompt_service.py +++ b/tests/unit/mcpgateway/services/test_prompt_service.py @@ -371,45 +371,47 @@ async def test_toggle_prompt_status(self, prompt_service, test_db): test_db.refresh = Mock() prompt_service._notify_prompt_deactivated = AsyncMock() - res = await prompt_service.toggle_prompt_status(test_db, 1, activate=False) + res = await prompt_service.set_prompt_state(test_db, 1, activate=False) assert p.is_active is False prompt_service._notify_prompt_deactivated.assert_called_once() assert res["is_active"] is False @pytest.mark.asyncio - async def test_toggle_prompt_status_not_found(self, prompt_service, test_db): + async def test_set_prompt_state(self, prompt_service, test_db): + # Ensure the mock prompt has a real id and primitive attributes + p = MagicMock(spec=DbPrompt) + p.id = 1 + p.team_id = 1 + p.name = "hello" + p.is_active = True + test_db.get = Mock(return_value=p) + test_db.commit = Mock() + test_db.refresh = Mock() + prompt_service._notify_prompt_deactivated = AsyncMock() + + res = await prompt_service.set_prompt_state(test_db, 1, activate=False) + + assert p.is_active is False + prompt_service._notify_prompt_deactivated.assert_called_once() + assert res["is_active"] is False + # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + # delete_prompt + @pytest.mark.asyncio + async def test_set_prompt_state_not_found(self, prompt_service, test_db): test_db.get = Mock(return_value=None) with pytest.raises(PromptError) as exc_info: - await prompt_service.toggle_prompt_status(test_db, 999, activate=True) + await prompt_service.set_prompt_state(test_db, 999, activate=True) assert "Prompt not found" in str(exc_info.value) @pytest.mark.asyncio - async def test_toggle_prompt_status_exception(self, prompt_service, test_db): + async def test_set_prompt_state_exception(self, prompt_service, test_db): p = _build_db_prompt(is_active=True) test_db.get = Mock(return_value=p) test_db.commit = Mock(side_effect=Exception("fail")) with pytest.raises(PromptError) as exc_info: - await prompt_service.toggle_prompt_status(test_db, 1, activate=False) - assert "Failed to toggle prompt status" in str(exc_info.value) - - # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - # delete_prompt - # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - - - @pytest.mark.asyncio - async def test_delete_prompt_success(self, prompt_service, test_db): - p = _build_db_prompt() - test_db.get = Mock(return_value=p) - test_db.delete = Mock() - test_db.commit = Mock() - prompt_service._notify_prompt_deleted = AsyncMock() - - await prompt_service.delete_prompt(test_db, 1) - - test_db.delete.assert_called_once_with(p) - prompt_service._notify_prompt_deleted.assert_called_once() + await prompt_service.set_prompt_state(test_db, 1, activate=False) + assert "Failed to set prompt state" in str(exc_info.value) @pytest.mark.asyncio diff --git a/tests/unit/mcpgateway/services/test_prompt_service_extended.py b/tests/unit/mcpgateway/services/test_prompt_service_extended.py index 3cddb5350..274486cb8 100644 --- a/tests/unit/mcpgateway/services/test_prompt_service_extended.py +++ b/tests/unit/mcpgateway/services/test_prompt_service_extended.py @@ -179,23 +179,22 @@ async def test_update_prompt_template_validation_error(self): assert callable(method) @pytest.mark.asyncio - async def test_toggle_prompt_status_not_found(self): - """Test toggle_prompt_status method exists.""" + async def test_set_prompt_state_exists(self): + """Test set_prompt_state method exists.""" service = PromptService() # Test method exists - assert hasattr(service, 'toggle_prompt_status') - assert callable(getattr(service, 'toggle_prompt_status')) + assert hasattr(service, 'set_prompt_state') + assert callable(getattr(service, 'set_prompt_state')) @pytest.mark.asyncio - async def test_toggle_prompt_status_no_change_needed(self): - """Test toggle_prompt_status method is async.""" + async def test_set_prompt_state_is_async(self): + """Test set_prompt_state method is async.""" service = PromptService() # Test method is async - # Standard import asyncio - assert asyncio.iscoroutinefunction(service.toggle_prompt_status) + assert asyncio.iscoroutinefunction(service.set_prompt_state) @pytest.mark.asyncio async def test_delete_prompt_not_found(self): diff --git a/tests/unit/mcpgateway/services/test_resource_service.py b/tests/unit/mcpgateway/services/test_resource_service.py index 506763996..553bba755 100644 --- a/tests/unit/mcpgateway/services/test_resource_service.py +++ b/tests/unit/mcpgateway/services/test_resource_service.py @@ -49,6 +49,15 @@ def resource_service(monkeypatch): def mock_db(): """Create a mock database session.""" db = MagicMock() + # Provide sensible defaults so tests that don't explicitly configure + # execute/scalars/scalar_one_or_none do not get MagicMock objects + db.execute.return_value = MagicMock() + db.execute.return_value.scalar_one_or_none.return_value = None + db.execute.return_value.scalars.return_value = MagicMock() + db.execute.return_value.scalars.return_value.all.return_value = [] + db.query.return_value = MagicMock() + db.query.return_value.filter.return_value = MagicMock() + db.query.return_value.filter.return_value.first.return_value = None return db @@ -524,7 +533,7 @@ class TestResourceManagement: """Test resource management operations.""" @pytest.mark.asyncio - async def test_toggle_resource_status_activate(self, resource_service, mock_db, mock_inactive_resource): + async def test_set_resource_state_activate(self, resource_service, mock_db, mock_inactive_resource): """Test activating an inactive resource.""" mock_db.get.return_value = mock_inactive_resource @@ -552,13 +561,13 @@ async def test_toggle_resource_status_activate(self, resource_service, mock_db, }, ) - result = await resource_service.toggle_resource_status(mock_db, 2, activate=True) + result = await resource_service.set_resource_state(mock_db, 2, activate=True) assert mock_inactive_resource.is_active is True mock_db.commit.assert_called_once() @pytest.mark.asyncio - async def test_toggle_resource_status_deactivate(self, resource_service, mock_db, mock_resource): + async def test_set_resource_state_deactivate(self, resource_service, mock_db, mock_resource): """Test deactivating an active resource.""" mock_db.get.return_value = mock_resource @@ -586,25 +595,25 @@ async def test_toggle_resource_status_deactivate(self, resource_service, mock_db }, ) - result = await resource_service.toggle_resource_status(mock_db, 1, activate=False) + result = await resource_service.set_resource_state(mock_db, 1, activate=False) assert mock_resource.is_active is False mock_db.commit.assert_called_once() @pytest.mark.asyncio - async def test_toggle_resource_status_not_found(self, resource_service, mock_db): - """Test toggling status of non-existent resource.""" + async def test_set_resource_state_not_found(self, resource_service, mock_db): + """Test setting status of non-existent resource.""" mock_db.get.return_value = None with pytest.raises(ResourceError) as exc_info: # ResourceError, not ResourceNotFoundError - await resource_service.toggle_resource_status(mock_db, 999, activate=True) + await resource_service.set_resource_state(mock_db, 999, activate=True) # The actual error message will vary, just check it mentions the resource assert "999" in str(exc_info.value) @pytest.mark.asyncio - async def test_toggle_resource_status_no_change(self, resource_service, mock_db, mock_resource): - """Test toggling status when no change needed.""" + async def test_set_resource_state_no_change(self, resource_service, mock_db, mock_resource): + """Test setting status when no change needed.""" mock_db.get.return_value = mock_resource mock_resource.is_active = True @@ -633,7 +642,7 @@ async def test_toggle_resource_status_no_change(self, resource_service, mock_db, ) # Try to activate already active resource - result = await resource_service.toggle_resource_status(mock_db, 1, activate=True) + result = await resource_service.set_resource_state(mock_db, 1, activate=True) # Should not commit or notify mock_db.commit.assert_not_called() @@ -760,6 +769,31 @@ async def test_get_resource_by_id_success(self, resource_service, mock_db, mock_ mock_scalar = MagicMock() mock_scalar.scalar_one_or_none.return_value = mock_resource mock_db.execute.return_value = mock_scalar + # Patch conversion to return a concrete ResourceRead to avoid Pydantic + # validation issues when converting MagicMock DB objects. + with patch.object(resource_service, "_convert_resource_to_read") as mock_convert: + mock_convert.return_value = ResourceRead( + id=mock_resource.id, + uri=mock_resource.uri, + name=mock_resource.name, + description=mock_resource.description, + mime_type=mock_resource.mime_type, + size=mock_resource.size, + is_active=mock_resource.is_active, + created_at=mock_resource.created_at, + updated_at=mock_resource.updated_at, + uri_template=mock_resource.uri_template, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) result = await resource_service.get_resource_by_id(mock_db, "1") @@ -797,6 +831,31 @@ async def test_get_resource_by_uri_include_inactive(self, resource_service, mock mock_scalar = MagicMock() mock_scalar.scalar_one_or_none.return_value = mock_inactive_resource mock_db.execute.return_value = mock_scalar + # Patch conversion to return a concrete ResourceRead to avoid Pydantic + # validation issues when converting MagicMock DB objects. + with patch.object(resource_service, "_convert_resource_to_read") as mock_convert: + mock_convert.return_value = ResourceRead( + id=mock_inactive_resource.id, + uri=mock_inactive_resource.uri, + name=mock_inactive_resource.name, + description=mock_inactive_resource.description, + mime_type=mock_inactive_resource.mime_type, + size=mock_inactive_resource.size, + is_active=mock_inactive_resource.is_active, + created_at=mock_inactive_resource.created_at, + updated_at=mock_inactive_resource.updated_at, + uri_template=mock_inactive_resource.uri_template, + metrics={ + "total_executions": 0, + "successful_executions": 0, + "failed_executions": 0, + "failure_rate": 0.0, + "min_response_time": None, + "max_response_time": None, + "avg_response_time": None, + "last_execution_time": None, + }, + ) result = await resource_service.get_resource_by_id(mock_db, "1", include_inactive=True) @@ -1407,13 +1466,13 @@ async def test_register_resource_generic_error(self, resource_service, mock_db, mock_db.rollback.assert_called_once() @pytest.mark.asyncio - async def test_toggle_resource_status_error(self, resource_service, mock_db, mock_resource): - """Test toggle status with error.""" + async def test_set_resource_state_error(self, resource_service, mock_db, mock_resource): + """Test setting resource status with error.""" mock_db.get.return_value = mock_resource mock_db.commit.side_effect = Exception("Database error") with pytest.raises(ResourceError): - await resource_service.toggle_resource_status(mock_db, 1, activate=False) + await resource_service.set_resource_state(mock_db, 1, activate=False) mock_db.rollback.assert_called_once() diff --git a/tests/unit/mcpgateway/services/test_server_service.py b/tests/unit/mcpgateway/services/test_server_service.py index 119b85e74..ed6d1dc7d 100644 --- a/tests/unit/mcpgateway/services/test_server_service.py +++ b/tests/unit/mcpgateway/services/test_server_service.py @@ -704,7 +704,7 @@ async def test_update_server_name_conflict(self, server_service, mock_server, te # -------------------------- toggle --------------------------------- # @pytest.mark.asyncio - async def test_toggle_server_status(self, server_service, mock_server, test_db): + async def test_set_server_state(self, server_service, mock_server, test_db): mock_server.team_id = 1 test_db.get = Mock(return_value=mock_server) test_db.commit = Mock() @@ -737,7 +737,7 @@ async def test_toggle_server_status(self, server_service, mock_server, test_db): ) ) - result = await server_service.toggle_server_status(test_db, 1, activate=False) + result = await server_service.set_server_state(test_db, 1, activate=False) test_db.get.assert_called_once_with(DbServer, 1) test_db.commit.assert_called_once() diff --git a/tests/unit/mcpgateway/services/test_tool_service.py b/tests/unit/mcpgateway/services/test_tool_service.py index b7bd82773..96f3965d6 100644 --- a/tests/unit/mcpgateway/services/test_tool_service.py +++ b/tests/unit/mcpgateway/services/test_tool_service.py @@ -723,8 +723,8 @@ async def test_delete_tool_not_found(self, tool_service, test_db): assert "Tool not found: 999" in str(exc_info.value) @pytest.mark.asyncio - async def test_toggle_tool_status(self, tool_service, mock_tool, test_db): - """Test toggling tool active status.""" + async def test_set_tool_state(self, tool_service, mock_tool, test_db): + """Test setting tool active status.""" # Mock DB get to return tool test_db.get = Mock(return_value=mock_tool) test_db.commit = Mock() @@ -771,7 +771,7 @@ async def test_toggle_tool_status(self, tool_service, mock_tool, test_db): tool_service._convert_tool_to_read = Mock(return_value=tool_read) # Deactivate the tool (it's active by default) - result = await tool_service.toggle_tool_status(test_db, 1, activate=False, reachable=True) + result = await tool_service.set_tool_state(test_db, 1, activate=False, reachable=True) # Verify DB operations test_db.get.assert_called_once_with(DbTool, 1) @@ -789,15 +789,15 @@ async def test_toggle_tool_status(self, tool_service, mock_tool, test_db): assert result == tool_read @pytest.mark.asyncio - async def test_toggle_tool_status_not_found(self, tool_service, test_db): - """Test toggling tool active status.""" + async def test_set_tool_state_not_found(self, tool_service, test_db): + """Test setting tool active status when tool not found.""" # Mock DB get to return tool test_db.get = Mock(return_value=None) test_db.commit = Mock() test_db.refresh = Mock() with pytest.raises(ToolError) as exc: - await tool_service.toggle_tool_status(test_db, "1", activate=False, reachable=True) + await tool_service.set_tool_state(test_db, "1", activate=False, reachable=True) assert "Tool not found: 1" in str(exc.value) @@ -805,8 +805,8 @@ async def test_toggle_tool_status_not_found(self, tool_service, test_db): test_db.get.assert_called_once_with(DbTool, "1") @pytest.mark.asyncio - async def test_toggle_tool_status_activate_tool(self, tool_service, test_db, mock_tool, monkeypatch): - """Test toggling tool active status.""" + async def test_set_tool_state_activate_tool(self, tool_service, test_db, mock_tool, monkeypatch): + """Test setting tool active status (activate).""" # Mock DB get to return tool mock_tool.enabled = False test_db.get = Mock(return_value=mock_tool) @@ -815,7 +815,7 @@ async def test_toggle_tool_status_activate_tool(self, tool_service, test_db, moc tool_service._notify_tool_activated = AsyncMock() - result = await tool_service.toggle_tool_status(test_db, "1", activate=True, reachable=True) + result = await tool_service.set_tool_state(test_db, "1", activate=True, reachable=True) # Verify DB operations test_db.get.assert_called_once_with(DbTool, "1") @@ -906,8 +906,8 @@ async def test_publish_event_with_real_queue(self, tool_service): assert q.empty() @pytest.mark.asyncio - async def test_toggle_tool_status_no_change(self, tool_service, mock_tool, test_db): - """Test toggling tool active status.""" + async def test_set_tool_state_no_change(self, tool_service, mock_tool, test_db): + """Test setting tool active status with no-op when already in desired state.""" # Mock DB get to return tool test_db.get = Mock(return_value=mock_tool) test_db.commit = Mock() @@ -954,7 +954,7 @@ async def test_toggle_tool_status_no_change(self, tool_service, mock_tool, test_ tool_service._convert_tool_to_read = Mock(return_value=tool_read) # Deactivate the tool (it's active by default) - result = await tool_service.toggle_tool_status(test_db, 1, activate=True, reachable=True) + result = await tool_service.set_tool_state(test_db, 1, activate=True, reachable=True) # Verify DB operations test_db.get.assert_called_once_with(DbTool, 1) diff --git a/tests/unit/mcpgateway/test_admin.py b/tests/unit/mcpgateway/test_admin.py index 7e05bdad2..dea60ac98 100644 --- a/tests/unit/mcpgateway/test_admin.py +++ b/tests/unit/mcpgateway/test_admin.py @@ -67,12 +67,12 @@ admin_stream_logs, admin_test_a2a_agent, admin_test_gateway, - admin_toggle_a2a_agent, - admin_toggle_gateway, - admin_toggle_prompt, - admin_toggle_resource, - admin_toggle_server, - admin_toggle_tool, + admin_set_a2a_agent_state, + admin_set_gateway_state, + admin_set_prompt_state, + admin_set_resource_state, + admin_set_server_state, + admin_set_tool_state, admin_ui, get_aggregated_metrics, get_global_passthrough_headers, @@ -327,13 +327,13 @@ async def test_admin_edit_server_with_root_path(self, mock_update_server, mock_r assert isinstance(result, JSONResponse) assert result.status_code in (200, 409, 422, 500) - @patch.object(ServerService, "toggle_server_status") - async def test_admin_toggle_server_with_exception(self, mock_toggle_status, mock_request, mock_db): - """Test toggling server status with exception handling.""" - mock_toggle_status.side_effect = Exception("Toggle operation failed") + @patch.object(ServerService, "set_server_state") + async def test_admin_set_server_state_with_exception(self, mock_set_status, mock_request, mock_db): + """Test setting server state with exception handling.""" + mock_set_status.side_effect = Exception("Toggle operation failed") # Should still return redirect - result = await admin_toggle_server("server-1", mock_request, mock_db, "test-user") + result = await admin_set_server_state("server-1", mock_request, mock_db, "test-user") assert isinstance(result, RedirectResponse) assert result.status_code == 303 @@ -529,31 +529,31 @@ async def test_admin_edit_tool_with_empty_optional_fields(self, mock_update_tool assert tool_update.headers == {} assert tool_update.input_schema == {} - @patch.object(ToolService, "toggle_tool_status") - async def test_admin_toggle_tool_various_activate_values(self, mock_toggle_status, mock_request, mock_db): - """Test toggling tool with various activate values.""" + @patch.object(ToolService, "set_tool_state") + async def test_admin_set_tool_state_various_activate_values(self, mock_set_status, mock_request, mock_db): + """Test setting tool state with various activate values.""" tool_id = "tool-1" # Test with "false" form_data = FakeForm({"activate": "false"}) mock_request.form = AsyncMock(return_value=form_data) - await admin_toggle_tool(tool_id, mock_request, mock_db, "test-user") - mock_toggle_status.assert_called_with(mock_db, tool_id, False, reachable=False, user_email="test-user") + await admin_set_tool_state(tool_id, mock_request, mock_db, "test-user") + mock_set_status.assert_called_with(mock_db, tool_id, False, reachable=False, user_email="test-user") # Test with "FALSE" form_data = FakeForm({"activate": "FALSE"}) mock_request.form = AsyncMock(return_value=form_data) - await admin_toggle_tool(tool_id, mock_request, mock_db, "test-user") - mock_toggle_status.assert_called_with(mock_db, tool_id, False, reachable=False, user_email="test-user") + await admin_set_tool_state(tool_id, mock_request, mock_db, "test-user") + mock_set_status.assert_called_with(mock_db, tool_id, False, reachable=False, user_email="test-user") # Test with missing activate field (defaults to true) form_data = FakeForm({}) mock_request.form = AsyncMock(return_value=form_data) - await admin_toggle_tool(tool_id, mock_request, mock_db, "test-user") - mock_toggle_status.assert_called_with(mock_db, tool_id, True, reachable=True, user_email="test-user") + await admin_set_tool_state(tool_id, mock_request, mock_db, "test-user") + mock_set_status.assert_called_with(mock_db, tool_id, True, reachable=True, user_email="test-user") class TestAdminBulkImportRoutes: @@ -873,16 +873,16 @@ async def test_admin_edit_resource_special_uri_characters(self, mock_update_reso mock_update_resource.assert_called_once() assert mock_update_resource.call_args[0][1] == uri - @patch.object(ResourceService, "toggle_resource_status") - async def test_admin_toggle_resource_numeric_id(self, mock_toggle_status, mock_request, mock_db): - """Test toggling resource with numeric ID.""" + @patch.object(ResourceService, "set_resource_state") + async def test_admin_set_resource_state_numeric_id(self, mock_set_status, mock_request, mock_db): + """Test setting resource state with numeric ID.""" # Test with integer ID - await admin_toggle_resource(123, mock_request, mock_db, "test-user") - mock_toggle_status.assert_called_with(mock_db, 123, True, user_email="test-user") + await admin_set_resource_state(123, mock_request, mock_db, "test-user") + mock_set_status.assert_called_with(mock_db, 123, True, user_email="test-user") # Test with string number - await admin_toggle_resource("456", mock_request, mock_db, "test-user") - mock_toggle_status.assert_called_with(mock_db, "456", True, user_email="test-user") + await admin_set_resource_state("456", mock_request, mock_db, "test-user") + mock_set_status.assert_called_with(mock_db, "456", True, user_email="test-user") class TestAdminPromptRoutes: @@ -1029,16 +1029,16 @@ async def test_admin_edit_prompt_name_change(self, mock_update_prompt, mock_requ mock_update_prompt.assert_called_once() assert mock_update_prompt.call_args[0][1] == "old-prompt-name" - @patch.object(PromptService, "toggle_prompt_status") - async def test_admin_toggle_prompt_edge_cases(self, mock_toggle_status, mock_request, mock_db): - """Test toggling prompt with edge cases.""" + @patch.object(PromptService, "set_prompt_state") + async def test_admin_set_prompt_state_edge_cases(self, mock_set_status, mock_request, mock_db): + """Test setting prompt state with edge cases.""" # Test with string ID that looks like number - await admin_toggle_prompt("123", mock_request, mock_db, "test-user") - mock_toggle_status.assert_called_with(mock_db, "123", True, user_email="test-user") + await admin_set_prompt_state("123", mock_request, mock_db, "test-user") + mock_set_status.assert_called_with(mock_db, "123", True, user_email="test-user") # Test with negative number - await admin_toggle_prompt(-1, mock_request, mock_db, "test-user") - mock_toggle_status.assert_called_with(mock_db, -1, True, user_email="test-user") + await admin_set_prompt_state(-1, mock_request, mock_db, "test-user") + mock_set_status.assert_called_with(mock_db, -1, True, user_email="test-user") class TestAdminGatewayRoutes: @@ -1182,9 +1182,9 @@ async def test_admin_edit_gateway_url_validation(self, mock_update_gateway, mock assert result.status_code in (400, 422) assert body["success"] is False - @patch.object(GatewayService, "toggle_gateway_status") - async def test_admin_toggle_gateway_concurrent_calls(self, mock_toggle_status, mock_request, mock_db): - """Test toggling gateway with simulated concurrent calls.""" + @patch.object(GatewayService, "set_gateway_state") + async def test_admin_set_gateway_state_concurrent_calls(self, mock_set_status, mock_request, mock_db): + """Test setting gateway state with simulated concurrent calls.""" # Simulate race condition call_count = 0 @@ -1195,14 +1195,14 @@ def side_effect(*args, **kwargs): raise Exception("Gateway is being modified by another process") return None - mock_toggle_status.side_effect = side_effect + mock_set_status.side_effect = side_effect # First call should fail - result1 = await admin_toggle_gateway("gateway-1", mock_request, mock_db, "test-user") + result1 = await admin_set_gateway_state("gateway-1", mock_request, mock_db, "test-user") assert isinstance(result1, RedirectResponse) # Second call should succeed - result2 = await admin_toggle_gateway("gateway-1", mock_request, mock_db, "test-user") + result2 = await admin_set_gateway_state("gateway-1", mock_request, mock_db, "test-user") assert isinstance(result2, RedirectResponse) @@ -1863,21 +1863,21 @@ async def test_admin_add_a2a_agent_name_conflict_error(self, mock_register_agent assert data["success"] is False assert "agent name already exists" in data["message"].lower() - @patch.object(A2AAgentService, "toggle_agent_status") - async def test_admin_toggle_a2a_agent_success(self, mock_toggle_status, mock_request, mock_db): - """Test toggling A2A agent status.""" + @patch.object(A2AAgentService, "set_a2a_agent_state") + async def test_admin_set_a2a_agent_state_success(self, mock_set_status, mock_request, mock_db): + """Test setting A2A agent state.""" # First-Party form_data = FakeForm({"activate": "true"}) mock_request.form = AsyncMock(return_value=form_data) mock_request.scope = {"root_path": ""} - result = await admin_toggle_a2a_agent("agent-1", mock_request, mock_db, "test-user") + result = await admin_set_a2a_agent_state("agent-1", mock_request, mock_db, "test-user") assert isinstance(result, RedirectResponse) assert result.status_code == 303 assert "#a2a-agents" in result.headers["location"] - mock_toggle_status.assert_called_with(mock_db, "agent-1", True, user_email="test-user") + mock_set_status.assert_called_with(mock_db, "agent-1", True, user_email="test-user") @patch.object(A2AAgentService, "delete_agent") async def test_admin_delete_a2a_agent_success(self, mock_delete_agent, mock_request, mock_db): @@ -2591,8 +2591,8 @@ async def test_boolean_field_parsing(self, form_field, value, mock_request, mock mock_request.form = AsyncMock(return_value=form_data) # Test with toggle operations which use boolean parsing - with patch.object(ServerService, "toggle_server_status", new_callable=AsyncMock) as mock_toggle: - await admin_toggle_server("server-1", mock_request, mock_db, "test-user") + with patch.object(ServerService, "set_server_state", new_callable=AsyncMock) as mock_toggle: + await admin_set_server_state("server-1", mock_request, mock_db, "test-user") # Check how the value was parsed if form_field == "activate": diff --git a/tests/unit/mcpgateway/test_main.py b/tests/unit/mcpgateway/test_main.py index eab1fae2f..82cceb95c 100644 --- a/tests/unit/mcpgateway/test_main.py +++ b/tests/unit/mcpgateway/test_main.py @@ -501,15 +501,15 @@ def test_update_server_endpoint(self, mock_update, test_client, auth_headers): assert response.status_code == 200 mock_update.assert_called_once() - @patch("mcpgateway.main.server_service.toggle_server_status") - def test_toggle_server_status(self, mock_toggle, test_client, auth_headers): - """Test toggling server active/inactive status.""" + @patch("mcpgateway.main.server_service.set_server_state") + def test_set_server_state(self, mock_set, test_client, auth_headers): + """Test setting server active/inactive status.""" updated_server = MOCK_SERVER_READ.copy() updated_server["is_active"] = False - mock_toggle.return_value = ServerRead(**updated_server) - response = test_client.post("/servers/1/toggle?activate=false", headers=auth_headers) + mock_set.return_value = ServerRead(**updated_server) + response = test_client.post("/servers/1/state?activate=false", headers=auth_headers) assert response.status_code == 200 - mock_toggle.assert_called_once() + mock_set.assert_called_once() @patch("mcpgateway.main.server_service.delete_server") def test_delete_server_endpoint(self, mock_delete, test_client, auth_headers): @@ -620,13 +620,13 @@ def test_update_tool_endpoint(self, mock_update, test_client, auth_headers): assert response.status_code == 200 mock_update.assert_called_once() - @patch("mcpgateway.main.tool_service.toggle_tool_status") - def test_toggle_tool_status(self, mock_toggle, test_client, auth_headers): - """Test toggling tool active/inactive status.""" + @patch("mcpgateway.main.tool_service.set_tool_state") + def test_set_tool_state(self, mock_set, test_client, auth_headers): + """Test setting tool active/inactive status.""" mock_tool = MagicMock() mock_tool.model_dump.return_value = {"id": 1, "name": "test", "is_active": False} - mock_toggle.return_value = mock_tool - response = test_client.post("/tools/1/toggle?activate=false", headers=auth_headers) + mock_set.return_value = mock_tool + response = test_client.post("/tools/1/state?activate=false", headers=auth_headers) assert response.status_code == 200 assert response.json()["status"] == "success" @@ -736,13 +736,13 @@ def test_list_resource_templates(self, mock_list, test_client, auth_headers): assert response.status_code == 200 mock_list.assert_called_once() - @patch("mcpgateway.main.resource_service.toggle_resource_status") - def test_toggle_resource_status(self, mock_toggle, test_client, auth_headers): - """Test toggling resource active/inactive status.""" + @patch("mcpgateway.main.resource_service.set_resource_state") + def test_set_resource_state(self, mock_set, test_client, auth_headers): + """Test setting resource active/inactive status.""" mock_resource = MagicMock() mock_resource.model_dump.return_value = {"id": 1, "is_active": False} - mock_toggle.return_value = mock_resource - response = test_client.post("/resources/1/toggle?activate=false", headers=auth_headers) + mock_set.return_value = mock_resource + response = test_client.post("/resources/1/state?activate=false", headers=auth_headers) assert response.status_code == 200 assert response.json()["status"] == "success" @@ -816,16 +816,16 @@ def test_delete_prompt_endpoint(self, mock_delete, test_client, auth_headers): assert response.json()["status"] == "success" mock_delete.assert_called_once() - @patch("mcpgateway.main.prompt_service.toggle_prompt_status") - def test_toggle_prompt_status(self, mock_toggle, test_client, auth_headers): - """Test toggling prompt active/inactive status.""" + @patch("mcpgateway.main.prompt_service.set_prompt_state") + def test_set_prompt_state(self, mock_set, test_client, auth_headers): + """Test setting prompt active/inactive status.""" mock_prompt = MagicMock() mock_prompt.model_dump.return_value = {"id": 1, "is_active": False} - mock_toggle.return_value = mock_prompt - response = test_client.post("/prompts/1/toggle?activate=false", headers=auth_headers) + mock_set.return_value = mock_prompt + response = test_client.post("/prompts/1/state?activate=false", headers=auth_headers) assert response.status_code == 200 assert response.json()["status"] == "success" - mock_toggle.assert_called_once() + mock_set.assert_called_once() """Tests for prompt template management: creation, rendering, arguments, etc.""" @@ -893,13 +893,13 @@ def test_delete_prompt_endpoint(self, mock_delete, test_client, auth_headers): assert response.status_code == 200 assert response.json()["status"] == "success" - @patch("mcpgateway.main.prompt_service.toggle_prompt_status") - def test_toggle_prompt_status(self, mock_toggle, test_client, auth_headers): - """Test toggling prompt active/inactive status.""" + @patch("mcpgateway.main.prompt_service.set_prompt_state") + def test_set_prompt_state_duplicate(self, mock_set, test_client, auth_headers): + """Test setting prompt active/inactive status (duplicate entry).""" mock_prompt = MagicMock() mock_prompt.model_dump.return_value = {"id": 1, "is_active": False} - mock_toggle.return_value = mock_prompt - response = test_client.post("/prompts/1/toggle?activate=false", headers=auth_headers) + mock_set.return_value = mock_prompt + response = test_client.post("/prompts/1/state?activate=false", headers=auth_headers) assert response.status_code == 200 assert response.json()["status"] == "success" @@ -970,16 +970,16 @@ def test_delete_gateway_endpoint_with_resources(self, mock_invalidate_cache, moc mock_delete.assert_called_once() mock_invalidate_cache.assert_called_once() - @patch("mcpgateway.main.gateway_service.toggle_gateway_status") - def test_toggle_gateway_status(self, mock_toggle, test_client, auth_headers): - """Test toggling gateway active/inactive status.""" + @patch("mcpgateway.main.gateway_service.set_gateway_state") + def test_set_gateway_state(self, mock_set, test_client, auth_headers): + """Test setting gateway active/inactive status.""" mock_gateway = MagicMock() mock_gateway.model_dump.return_value = {"id": "1", "is_active": False} - mock_toggle.return_value = mock_gateway - response = test_client.post("/gateways/1/toggle?activate=false", headers=auth_headers) + mock_set.return_value = mock_gateway + response = test_client.post("/gateways/1/state?activate=false", headers=auth_headers) assert response.status_code == 200 assert response.json()["status"] == "success" - mock_toggle.assert_called_once() + mock_set.assert_called_once() """Tests for gateway federation: registration, discovery, forwarding, etc.""" @@ -1030,13 +1030,13 @@ def test_delete_gateway_endpoint(self, mock_get, mock_delete, test_client, auth_ assert response.status_code == 200 assert response.json()["status"] == "success" - @patch("mcpgateway.main.gateway_service.toggle_gateway_status") + @patch("mcpgateway.main.gateway_service.set_gateway_state") def test_toggle_gateway_status(self, mock_toggle, test_client, auth_headers): """Test toggling gateway active/inactive status.""" mock_gateway = MagicMock() mock_gateway.model_dump.return_value = {"id": "1", "is_active": False} mock_toggle.return_value = mock_gateway - response = test_client.post("/gateways/1/toggle?activate=false", headers=auth_headers) + response = test_client.post("/gateways/1/state?activate=false", headers=auth_headers) assert response.status_code == 200 assert response.json()["status"] == "success" @@ -1389,7 +1389,7 @@ def test_reset_invalid_entity_metrics(self, test_client, auth_headers): # """Test toggling A2A agent status.""" # mock_toggle.return_value = {"id": "test-id", "enabled": False} # -# response = test_client.post("/a2a/test-id/toggle?activate=false", headers=auth_headers) +# response = test_client.post("/a2a/test-id/state?activate=false", headers=auth_headers) # assert response.status_code == 200 # mock_toggle.assert_called_once() # diff --git a/tests/unit/mcpgateway/test_main_extended.py b/tests/unit/mcpgateway/test_main_extended.py index 5dfa2d5c7..91bc93eb5 100644 --- a/tests/unit/mcpgateway/test_main_extended.py +++ b/tests/unit/mcpgateway/test_main_extended.py @@ -277,8 +277,8 @@ def test_sse_endpoint_edge_cases(self, test_client, auth_headers): assert response.status_code in [404, 500, 503] def test_server_toggle_edge_cases(self, test_client, auth_headers): - """Test server toggle endpoint edge cases.""" - with patch("mcpgateway.main.server_service.toggle_server_status") as mock_toggle: + """Test server state endpoint edge cases.""" + with patch("mcpgateway.main.server_service.set_server_state") as mock_toggle: # Create a proper ServerRead model response # First-Party from mcpgateway.schemas import ServerRead @@ -309,13 +309,13 @@ def test_server_toggle_edge_cases(self, test_client, auth_headers): mock_toggle.return_value = ServerRead(**mock_server_data) # Test activate=true - response = test_client.post("/servers/1/toggle?activate=true", headers=auth_headers) + response = test_client.post("/servers/1/state?activate=true", headers=auth_headers) assert response.status_code == 200 # Test activate=false mock_server_data["is_active"] = False mock_toggle.return_value = ServerRead(**mock_server_data) - response = test_client.post("/servers/1/toggle?activate=false", headers=auth_headers) + response = test_client.post("/servers/1/state?activate=false", headers=auth_headers) assert response.status_code == 200