Skip to content

Commit 6f9db42

Browse files
authored
Merge pull request #112 from IBM/unit-testing-fix
Unit testing fix admin tests
2 parents 12df0b1 + 3cc2548 commit 6f9db42

File tree

3 files changed

+35
-38
lines changed

3 files changed

+35
-38
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
[![Docker Image](https://img.shields.io/badge/docker-ghcr.io%2Fibm%2Fmcp--context--forge-blue)](https://github.com/ibm/mcp-context-forge/pkgs/container/mcp-context-forge) 
1818

1919

20-
ContextForge MCP Gateway is a feature-rich gateway & proxy that federates MCP and REST services - unifying discovery, auth, rate-limiting, observability, virtual servers, multi-transport protocols, and an optional Admin UI into one clean endpoint for your AI clients. It runs as a fully compliant MCP server, deployable via PyPI or Docker, and scales to multi-cluster environments on Kubernetes with Redis-backed federation and caching.
20+
ContextForge MCP Gateway is a feature-rich gateway, proxy and MCP Registry that federates MCP and REST services - unifying discovery, auth, rate-limiting, observability, virtual servers, multi-transport protocols, and an optional Admin UI into one clean endpoint for your AI clients. It runs as a fully compliant MCP server, deployable via PyPI or Docker, and scales to multi-cluster environments on Kubernetes with Redis-backed federation and caching.
2121

2222
![MCP Gateway](https://ibm.github.io/mcp-context-forge/images/mcpgateway.gif)
2323
---
@@ -51,7 +51,7 @@ For a list of upcoming features, check out the [ContextForge MCP Gateway Roadmap
5151
</details>
5252

5353
<details>
54-
<summary><strong>🌐 Federation of Peer Gateways</strong></summary>
54+
<summary><strong>🌐 Federation of Peer Gateways (MCP Registry)</strong></summary>
5555

5656
* Auto-discovers or configures peer gateways (via mDNS or manual)
5757
* Performs health checks and merges remote registries transparently

mcpgateway/templates/admin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,7 @@ <h3 class="text-lg font-bold mb-4">Add New Prompt</h3>
12141214
<!-- Gateways Panel -->
12151215
<div id="gateways-panel" class="tab-panel hidden">
12161216
<div class="flex justify-between items-center mb-4">
1217-
<h2 class="text-2xl font-bold">Federated Gateways (MCP)</h2>
1217+
<h2 class="text-2xl font-bold">Federated Gateways (MCP Registry)</h2>
12181218
<p class="text-sm text-gray-600 mt-1">Gateways are where you register external MCP servers to federate their tools/resources/prompts into your environment.</p>
12191219
<div class="flex items-center">
12201220
<input

tests/unit/mcpgateway/test_admin.py

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ def mock_db():
7474
def mock_request():
7575
"""Create a mock FastAPI request."""
7676
request = MagicMock(spec=Request)
77+
78+
# FastAPI's Request always has a .scope dict; many admin helpers read "root_path".
79+
request.scope = {"root_path": ""}
80+
81+
# Pretend form() returns the full set of fields our admin helpers expect
7782
request.form = AsyncMock(
7883
return_value={
7984
"name": "test-name",
@@ -102,12 +107,20 @@ def mock_request():
102107
"activate": "true",
103108
}
104109
)
110+
111+
# Basic template rendering stub
112+
request.app = MagicMock() # ensure .app exists
113+
request.app.state = MagicMock() # ensure .app.state exists
105114
request.app.state.templates = MagicMock()
106-
request.app.state.templates.TemplateResponse.return_value = HTMLResponse(content="<html></html>")
115+
request.app.state.templates.TemplateResponse.return_value = HTMLResponse(
116+
content="<html></html>"
117+
)
118+
107119
request.query_params = {"include_inactive": "false"}
108120
return request
109121

110122

123+
111124
class TestAdminServerRoutes:
112125
"""Test admin routes for server management."""
113126

@@ -195,18 +208,15 @@ async def test_admin_toggle_server(self, mock_toggle_status, mock_request, mock_
195208
assert "/admin#catalog" in result.headers["location"]
196209

197210
@patch.object(ServerService, "delete_server")
198-
async def test_admin_delete_server(self, mock_delete_server, mock_db):
211+
async def test_admin_delete_server(self, mock_delete_server, mock_request, mock_db):
199212
"""Test deleting a server through admin UI."""
200-
# Execute
201-
result = await admin_delete_server(1, mock_db, "test-user")
213+
result = await admin_delete_server(1, mock_request, mock_db, "test-user")
202214

203-
# Assert
204215
mock_delete_server.assert_called_once_with(mock_db, 1)
205216
assert isinstance(result, RedirectResponse)
206217
assert result.status_code == 303
207218
assert "/admin#catalog" in result.headers["location"]
208219

209-
210220
class TestAdminToolRoutes:
211221
"""Test admin routes for tool management."""
212222

@@ -309,12 +319,10 @@ async def test_admin_toggle_tool(self, mock_toggle_status, mock_request, mock_db
309319
assert "/admin#tools" in result.headers["location"]
310320

311321
@patch.object(ToolService, "delete_tool")
312-
async def test_admin_delete_tool(self, mock_delete_tool, mock_db):
322+
async def test_admin_delete_tool(self, mock_delete_tool, mock_request, mock_db):
313323
"""Test deleting a tool through admin UI."""
314-
# Execute
315-
result = await admin_delete_tool(1, mock_db, "test-user")
324+
result = await admin_delete_tool(1, mock_request, mock_db, "test-user")
316325

317-
# Assert
318326
mock_delete_tool.assert_called_once_with(mock_db, 1)
319327
assert isinstance(result, RedirectResponse)
320328
assert result.status_code == 303
@@ -399,12 +407,10 @@ async def test_admin_toggle_resource(self, mock_toggle_status, mock_request, moc
399407
assert "/admin#resources" in result.headers["location"]
400408

401409
@patch.object(ResourceService, "delete_resource")
402-
async def test_admin_delete_resource(self, mock_delete_resource, mock_db):
410+
async def test_admin_delete_resource(self, mock_delete_resource, mock_request, mock_db):
403411
"""Test deleting a resource through admin UI."""
404-
# Execute
405-
result = await admin_delete_resource("/test/resource", mock_db, "test-user")
412+
result = await admin_delete_resource("/test/resource", mock_request, mock_db, "test-user")
406413

407-
# Assert
408414
mock_delete_resource.assert_called_once_with(mock_db, "/test/resource")
409415
assert isinstance(result, RedirectResponse)
410416
assert result.status_code == 303
@@ -503,12 +509,10 @@ async def test_admin_toggle_prompt(self, mock_toggle_status, mock_request, mock_
503509
assert "/admin#prompts" in result.headers["location"]
504510

505511
@patch.object(PromptService, "delete_prompt")
506-
async def test_admin_delete_prompt(self, mock_delete_prompt, mock_db):
512+
async def test_admin_delete_prompt(self, mock_delete_prompt, mock_request, mock_db):
507513
"""Test deleting a prompt through admin UI."""
508-
# Execute
509-
result = await admin_delete_prompt("test-prompt", mock_db, "test-user")
514+
result = await admin_delete_prompt("test-prompt", mock_request, mock_db, "test-user")
510515

511-
# Assert
512516
mock_delete_prompt.assert_called_once_with(mock_db, "test-prompt")
513517
assert isinstance(result, RedirectResponse)
514518
assert result.status_code == 303
@@ -589,46 +593,39 @@ async def test_admin_toggle_gateway(self, mock_toggle_status, mock_request, mock
589593
assert "/admin#gateways" in result.headers["location"]
590594

591595
@patch.object(GatewayService, "delete_gateway")
592-
async def test_admin_delete_gateway(self, mock_delete_gateway, mock_db):
596+
async def test_admin_delete_gateway(self, mock_delete_gateway, mock_request, mock_db):
593597
"""Test deleting a gateway through admin UI."""
594-
# Execute
595-
result = await admin_delete_gateway(1, mock_db, "test-user")
598+
result = await admin_delete_gateway(1, mock_request, mock_db, "test-user")
596599

597-
# Assert
598600
mock_delete_gateway.assert_called_once_with(mock_db, 1)
599601
assert isinstance(result, RedirectResponse)
600602
assert result.status_code == 303
601603
assert "/admin#gateways" in result.headers["location"]
602604

603-
604605
class TestAdminRootRoutes:
605606
"""Test admin routes for root management."""
606607

607-
@patch.object(RootService, "add_root")
608-
async def test_admin_add_root(self, mock_add_root, mock_request, mock_db):
608+
@patch("mcpgateway.admin.root_service.add_root", new_callable=AsyncMock)
609+
async def test_admin_add_root(self, mock_add_root, mock_request):
609610
"""Test adding a root through admin UI."""
610-
# Execute
611-
result = await admin_add_root(mock_request, mock_db, "test-user")
611+
result = await admin_add_root(mock_request, "test-user")
612612

613-
# Assert
614-
mock_add_root.assert_called_once_with("/test/resource", None)
613+
# expect ("uri", "name") → "test-name" comes from the form fixture
614+
mock_add_root.assert_called_once_with("/test/resource", "test-name")
615615
assert isinstance(result, RedirectResponse)
616616
assert result.status_code == 303
617617
assert "/admin#roots" in result.headers["location"]
618618

619-
@patch.object(RootService, "remove_root")
620-
async def test_admin_delete_root(self, mock_remove_root, mock_db):
619+
@patch("mcpgateway.admin.root_service.remove_root", new_callable=AsyncMock)
620+
async def test_admin_delete_root(self, mock_remove_root, mock_request):
621621
"""Test deleting a root through admin UI."""
622-
# Execute
623-
result = await admin_delete_root("/test/root", mock_db, "test-user")
622+
result = await admin_delete_root("/test/root", mock_request, "test-user")
624623

625-
# Assert
626624
mock_remove_root.assert_called_once_with("/test/root")
627625
assert isinstance(result, RedirectResponse)
628626
assert result.status_code == 303
629627
assert "/admin#roots" in result.headers["location"]
630628

631-
632629
class TestAdminMetricsRoutes:
633630
"""Test admin routes for metrics management."""
634631

0 commit comments

Comments
 (0)