Repository: IBM/mcp-context-forge

Issue:
[Bug]: test_admin_tool_name_conflict creates record in actual db
### 🐞 Bug Summary
Test `test_admin_tool_name_conflict` creates a tool of the format `f"duplicate_tool_{uuid.uuid4().hex[:8]}"` in application database instead of mock database

---

### 🔁 Steps to Reproduce

1. Run `make test` on an existing application database
2. Run `make serve` and check tool list
3. Find tool with name starting with `duplicate_tool_`.

---

### 🤔 Expected Behavior
Tests should use mock db, not actual db

---



Discussion:

Patch:
diff --git a/mcpgateway/admin.py b/mcpgateway/admin.py
index c089c945..197de1fb 100644
--- a/mcpgateway/admin.py
+++ b/mcpgateway/admin.py
@@ -2638,7 +2638,7 @@ async def admin_add_resource(request: Request, db: Session = Depends(get_db), us
         >>>
         >>> async def test_admin_add_resource():
         ...     response = await admin_add_resource(mock_request, mock_db, mock_user)
-        ...     return isinstance(response, RedirectResponse) and response.status_code == 303
+        ...     return isinstance(response, JSONResponse) and response.status_code == 200 and response.body.decode() == '{"message":"Add resource registered successfully!","success":true}'
         >>>
         >>> import asyncio; asyncio.run(test_admin_add_resource())
         True
@@ -2656,9 +2656,10 @@ async def admin_add_resource(request: Request, db: Session = Depends(get_db), us
             content=form["content"],
         )
         await resource_service.register_resource(db, resource)
-
-        root_path = request.scope.get("root_path", "")
-        return RedirectResponse(f"{root_path}/admin#resources", status_code=303)
+        return JSONResponse(
+            content={"message": "Add resource registered successfully!", "success": True},
+            status_code=200,
+        )
     except Exception as ex:
         if isinstance(ex, ValidationError):
             logger.error(f"ValidationError in admin_add_resource: {ErrorFormatter.format_validation_error(ex)}")
@@ -2667,6 +2668,7 @@ async def admin_add_resource(request: Request, db: Session = Depends(get_db), us
             error_message = ErrorFormatter.format_database_error(ex)
             logger.error(f"IntegrityError in admin_add_resource: {error_message}")
             return JSONResponse(status_code=409, content=error_message)
+
         logger.error(f"Error in admin_add_resource: {ex}")
         return JSONResponse(content={"message": str(ex), "success": False}, status_code=500)
 
diff --git a/mcpgateway/static/admin.js b/mcpgateway/static/admin.js
index 5ee62a48..d12695da 100644
--- a/mcpgateway/static/admin.js
+++ b/mcpgateway/static/admin.js
@@ -4183,48 +4183,70 @@ async function handleGatewayFormSubmit(e) {
     }
 }
 
-function handleResourceFormSubmit(e) {
+async function handleResourceFormSubmit(e) {
     e.preventDefault();
     const form = e.target;
     const formData = new FormData(form);
+    const status = safeGetElement("status-resources");
+    const loading = safeGetElement("add-gateway-loading");
+    try {
+        // Validate inputs
+        const name = formData.get("name");
+        const uri = formData.get("uri");
+        const nameValidation = validateInputName(name, "resource");
+        const uriValidation = validateInputName(uri, "resource URI");
 
-    // Validate inputs
-    const name = formData.get("name");
-    const uri = formData.get("uri");
+        if (!nameValidation.valid) {
+            showErrorMessage(nameValidation.error);
+            return;
+        }
 
-    const nameValidation = validateInputName(name, "resource");
-    const uriValidation = validateInputName(uri, "resource URI");
+        if (!uriValidation.valid) {
+            showErrorMessage(uriValidation.error);
+            return;
+        }
 
-    if (!nameValidation.valid) {
-        showErrorMessage(nameValidation.error);
-        return;
-    }
+        if (loading) {
+            loading.style.display = "block";
+        }
+        if (status) {
+            status.textContent = "";
+            status.classList.remove("error-status");
+        }
+        
+        const isInactiveCheckedBool = isInactiveChecked("resources");
+        formData.append("is_inactive_checked", isInactiveCheckedBool);
 
-    if (!uriValidation.valid) {
-        showErrorMessage(uriValidation.error);
-        return;
-    }
+        const response = await fetchWithTimeout(
+                `${window.ROOT_PATH}/admin/resources`,
+                {
+                    method: "POST",
+                    body: formData,
+                },
+            );
 
-    fetchWithTimeout(`${window.ROOT_PATH}/admin/resources`, {
-        method: "POST",
-        body: formData,
-    })
-        .then((response) => {
-            if (!response.ok) {
-                const status = safeGetElement("status-resources");
-                if (status) {
-                    status.textContent = "Connection failed!";
-                    status.classList.add("error-status");
-                }
-                throw new Error("Network response was not ok");
-            } else {
-                location.reload();
+        const result = await response.json();
+        if (!result.success) {
+            throw new Error(result.message || "An error occurred");
+        } else {
+            const redirectUrl = isInactiveCheckedBool
+                ? `${window.ROOT_PATH}/admin?include_inactive=true#resources`
+                : `${window.ROOT_PATH}/admin#resources`;
+            window.location.href = redirectUrl;
             }
-        })
-        .catch((error) => {
+        } catch (error) {
             console.error("Error:", error);
-            showErrorMessage("Failed to create resource");
-        });
+            if (status) {
+                status.textContent = error.message || "An error occurred!";
+                status.classList.add("error-status");
+            }
+            showErrorMessage(error.message);
+        } finally {
+            // location.reload();
+            if (loading) {
+                loading.style.display = "none";
+            }
+        }		
 }
 
 async function handleToolFormSubmit(event) {
diff --git a/mcpgateway/templates/admin.html b/mcpgateway/templates/admin.html
index 9e185b8c..e93442d2 100644
--- a/mcpgateway/templates/admin.html
+++ b/mcpgateway/templates/admin.html
@@ -799,7 +799,9 @@ <h3 class="text-lg font-bold mb-4 dark:text-gray-200">Add New Resource</h3>
                 class="mt-1 block w-full rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:bg-gray-900 dark:placeholder-gray-300 dark:text-gray-300"
                 placeholder=""></textarea>
             </div>
-
+            <div id="add-resources-loading" style="display: none;">
+              <div class="spinner"></div>
+            </div>
             <div id="status-resources"></div>
           </div>
           <div class="mt-6">


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 0eb08fda..73d4cc5b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -10,12 +10,15 @@
 # Standard
 import asyncio
 import os
+import tempfile
 from unittest.mock import AsyncMock, patch
 
 # Third-Party
+from _pytest.monkeypatch import MonkeyPatch
 import pytest
 from sqlalchemy import create_engine
 from sqlalchemy.orm import sessionmaker
+from sqlalchemy.pool import StaticPool
 
 # First-Party
 from mcpgateway.config import Settings
@@ -107,3 +110,57 @@ def mock_websocket():
 #     import mcpgateway.translate as translate
 #     translate._run_stdio_to_sse = AsyncMock(return_value=None)
 #     translate._run_sse_to_stdio = AsyncMock(return_value=None)
+
+
+@pytest.fixture(scope="module")  # one DB per test module is usually fine
+def app_with_temp_db():
+    """Return a FastAPI app wired to a fresh SQLite database."""
+    mp = MonkeyPatch()
+
+    # 1) create temp SQLite file
+    fd, path = tempfile.mkstemp(suffix=".db")
+    url = f"sqlite:///{path}"
+
+    # 2) patch settings
+    # First-Party
+    from mcpgateway.config import settings
+
+    mp.setattr(settings, "database_url", url, raising=False)
+
+    # First-Party
+    import mcpgateway.db as db_mod
+
+    engine = create_engine(url, connect_args={"check_same_thread": False}, poolclass=StaticPool)
+    TestSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+    mp.setattr(db_mod, "engine", engine, raising=False)
+    mp.setattr(db_mod, "SessionLocal", TestSessionLocal, raising=False)
+
+    # 4) patch the already‑imported main module **without reloading**
+    # First-Party
+    import mcpgateway.main as main_mod
+
+    mp.setattr(main_mod, "SessionLocal", TestSessionLocal, raising=False)
+    # (patch engine too if your code references it)
+    mp.setattr(main_mod, "engine", engine, raising=False)
+
+    # 4) create schema
+    db_mod.Base.metadata.create_all(bind=engine)
+
+    # 5) reload main so routers, deps pick up new SessionLocal
+    # if "mcpgateway.main" in sys.modules:
+    #     import importlib
+
+    #     importlib.reload(sys.modules["mcpgateway.main"])
+    # else:
+    #     import mcpgateway.main  # noqa: F401
+
+    # First-Party
+    from mcpgateway.main import app
+
+    yield app
+
+    # 6) teardown
+    mp.undo()
+    engine.dispose()
+    os.close(fd)
+    os.unlink(path)
diff --git a/tests/e2e/test_admin_apis.py b/tests/e2e/test_admin_apis.py
index ed60e8e9..0bf87aab 100644
--- a/tests/e2e/test_admin_apis.py
+++ b/tests/e2e/test_admin_apis.py
@@ -35,8 +35,7 @@
 os.environ["MCPGATEWAY_UI_ENABLED"] = "true"
 
 # Standard
-import tempfile
-from typing import AsyncGenerator
+import logging
 from unittest.mock import patch
 from urllib.parse import quote
 import uuid
@@ -45,13 +44,18 @@
 from httpx import AsyncClient
 import pytest
 import pytest_asyncio
-from sqlalchemy import create_engine
-from sqlalchemy.orm import sessionmaker
-from sqlalchemy.pool import StaticPool
 
-# First-Party
-from mcpgateway.db import Base
-from mcpgateway.main import app, get_db
+# from mcpgateway.db import Base
+# from mcpgateway.main import app, get_db
+
+
+# Configure logging for debugging
+def setup_logging():
+    logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s")
+
+
+setup_logging()
+
 
 # pytest.skip("Temporarily disabling this suite", allow_module_level=True)
 
@@ -67,68 +71,23 @@
 # Fixtures
 # -------------------------
 @pytest_asyncio.fixture
-async def temp_db():
-    """
-    Create a temporary SQLite database for testing.
-
-    This fixture creates a fresh database for each test, ensuring complete
-    isolation between tests. The database is automatically cleaned up after
-    the test completes.
-    """
-    # Create temporary file for SQLite database
-    db_fd, db_path = tempfile.mkstemp(suffix=".db")
-
-    # Create engine with SQLite
-    engine = create_engine(
-        f"sqlite:///{db_path}",
-        connect_args={"check_same_thread": False},
-        poolclass=StaticPool,
-    )
-
-    # Create all tables
-    Base.metadata.create_all(bind=engine)
-
-    # Create session factory
-    TestSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
-
-    # Override the get_db dependency
-    def override_get_db():
-        db = TestSessionLocal()
-        try:
-            yield db
-        finally:
-            db.close()
-
-    app.dependency_overrides[get_db] = override_get_db
-
-    # Override authentication for all tests
+async def client(app_with_temp_db):
     # First-Party
     from mcpgateway.utils.verify_credentials import require_auth, require_basic_auth
 
-    def override_auth():
-        return TEST_USER
-
-    app.dependency_overrides[require_auth] = override_auth
-    app.dependency_overrides[require_basic_auth] = override_auth
-
-    yield engine
+    app_with_temp_db.dependency_overrides[require_auth] = lambda: TEST_USER
+    app_with_temp_db.dependency_overrides[require_basic_auth] = lambda: TEST_USER
 
-    # Cleanup
-    app.dependency_overrides.clear()
-    os.close(db_fd)
-    os.unlink(db_path)
-
-
-@pytest_asyncio.fixture
-async def client(temp_db) -> AsyncGenerator[AsyncClient, None]:
-    """Create an async test client with the test database."""
     # Third-Party
     from httpx import ASGITransport, AsyncClient
 
-    transport = ASGITransport(app=app)
+    transport = ASGITransport(app=app_with_temp_db)
     async with AsyncClient(transport=transport, base_url="http://test") as ac:
         yield ac
 
+    app_with_temp_db.dependency_overrides.pop(require_auth, None)
+    app_with_temp_db.dependency_overrides.pop(require_basic_auth, None)
+
 
 @pytest_asyncio.fixture
 async def mock_settings():
@@ -337,60 +296,47 @@ async def test_admin_tool_name_conflict(self, client: AsyncClient, mock_settings
 class TestAdminResourceAPIs:
     """Test admin resource management endpoints."""
 
-    async def test_admin_list_resources_empty(self, client: AsyncClient, mock_settings):
-        """Test GET /admin/resources returns empty list initially."""
-        response = await client.get("/admin/resources", headers=TEST_AUTH_HEADER)
-        assert response.status_code == 200
-        assert response.json() == []
-
-    async def test_admin_resource_lifecycle(self, client: AsyncClient, mock_settings):
-        """Test complete resource lifecycle through admin UI."""
-        # Create a resource via form submission
-        form_data = {
-            "uri": "admin/test/resource",
-            "name": "test_admin_resource",
-            "description": "Test resource via admin",
+    async def test_admin_add_resource(self, client: AsyncClient, mock_settings):
+        """Test adding a resource via the admin UI with new logic."""
+        # Define valid form data
+        valid_form_data = {
+            "uri": "test://resource1",
+            "name": "Test Resource",
+            "description": "A test resource",
             "mimeType": "text/plain",
-            "content": "Admin test content",
+            "content": "Sample content",
         }
 
-        # POST to /admin/resources should redirect
-        response = await client.post("/admin/resources", data=form_data, headers=TEST_AUTH_HEADER, follow_redirects=False)
-        assert response.status_code == 303
-
-        # List resources to verify creation
-        response = await client.get("/admin/resources", headers=TEST_AUTH_HEADER)
-        resources = response.json()
-        assert len(resources) == 1
-        resource = resources[0]
-        assert resource["name"] == "test_admin_resource"
-        resource_id = resource["id"]
-
-        # Get individual resource
-        encoded_uri = quote(form_data["uri"], safe="")
-        response = await client.get(f"/admin/resources/{encoded_uri}", headers=TEST_AUTH_HEADER)
+        # Test successful resource creation
+        response = await client.post("/admin/resources", data=valid_form_data, headers=TEST_AUTH_HEADER)
         assert response.status_code == 200
-        data = response.json()
-        assert "resource" in data
-        assert "content" in data
-
-        # Edit resource
-        edit_data = {
-            "name": "updated_admin_resource",
-            "description": "Updated description",
-            "mimeType": "text/markdown",
-            "content": "Updated admin content",
+        result = response.json()
+        assert result["success"] is True
+        assert "message" in result and "Add resource registered successfully!" in result["message"]
+
+        # Test missing required fields
+        invalid_form_data = {
+            "name": "Test Resource",
+            "description": "A test resource",
+            # Missing 'uri', 'mimeType', and 'content'
         }
-        response = await client.post(f"/admin/resources/{encoded_uri}/edit", data=edit_data, headers=TEST_AUTH_HEADER, follow_redirects=False)
-        assert response.status_code == 303
-
-        # Toggle resource status
-        response = await client.post(f"/admin/resources/{resource_id}/toggle", data={"activate": "false"}, headers=TEST_AUTH_HEADER, follow_redirects=False)
-        assert response.status_code == 303
+        response = await client.post("/admin/resources", data=invalid_form_data, headers=TEST_AUTH_HEADER)
+        assert response.status_code == 500
+
+        # Test ValidationError (422)
+        invalid_validation_data = {
+            "uri": "",
+            "name": "",
+            "description": "",
+            "mimeType": "",
+            "content": "",
+        }
+        response = await client.post("/admin/resources", data=invalid_validation_data, headers=TEST_AUTH_HEADER)
+        assert response.status_code == 422
 
-        # Delete resource
-        response = await client.post(f"/admin/resources/{encoded_uri}/delete", headers=TEST_AUTH_HEADER, follow_redirects=False)
-        assert response.status_code == 303
+        # Test duplicate URI
+        response = await client.post("/admin/resources", data=valid_form_data, headers=TEST_AUTH_HEADER)
+        assert response.status_code == 409
 
 
 # -------------------------
diff --git a/tests/security/test_rpc_api.py b/tests/security/test_rpc_api.py
index 115dbe77..371e9bbb 100644
--- a/tests/security/test_rpc_api.py
+++ b/tests/security/test_rpc_api.py
@@ -16,6 +16,8 @@
 import json
 import os
 import sys
+
+# Third-Party
 import pytest
 
 try:
@@ -25,6 +27,7 @@
     print("Please install requests: pip install requests")
     sys.exit(1)
 
+
 @pytest.mark.skip(reason="Disabled temporarily as this requires a live MCP Gateway instance")
 def test_rpc_vulnerability():
     """Test the RPC endpoint with malicious method names."""
diff --git a/tests/unit/mcpgateway/test_admin.py b/tests/unit/mcpgateway/test_admin.py
index a6efffad..8e0f2fa9 100644
--- a/tests/unit/mcpgateway/test_admin.py
+++ b/tests/unit/mcpgateway/test_admin.py
@@ -539,7 +539,9 @@ async def test_admin_add_resource_with_valid_mime_type(self, mock_register_resou
 
         result = await admin_add_resource(mock_request, mock_db, "test-user")
 
-        assert isinstance(result, RedirectResponse)
+        # Assert
+        mock_register_resource.assert_called_once()
+        assert result.status_code == 200
 
         # Verify template was passed
         call_args = mock_register_resource.call_args[0]
@@ -1282,7 +1284,7 @@ async def test_valid_characters_handling(self, mock_request, mock_db):
         with patch.object(ResourceService, "register_resource", new_callable=AsyncMock) as mock_register:
             result = await admin_add_resource(mock_request, mock_db, "test-user")
 
-            assert isinstance(result, RedirectResponse)
+            assert isinstance(result, JSONResponse)
 
             # Verify data was preserved
             call_args = mock_register.call_args[0]