Skip to content

Commit 4bac388

Browse files
authored
Merge pull request #555 from rakdutta/dev_363_js
Improve Error Messages #363 for add resources
2 parents e6597f4 + 40c123e commit 4bac388

File tree

7 files changed

+181
-147
lines changed

7 files changed

+181
-147
lines changed

mcpgateway/admin.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,7 +2638,7 @@ async def admin_add_resource(request: Request, db: Session = Depends(get_db), us
26382638
>>>
26392639
>>> async def test_admin_add_resource():
26402640
... response = await admin_add_resource(mock_request, mock_db, mock_user)
2641-
... return isinstance(response, RedirectResponse) and response.status_code == 303
2641+
... return isinstance(response, JSONResponse) and response.status_code == 200 and response.body.decode() == '{"message":"Add resource registered successfully!","success":true}'
26422642
>>>
26432643
>>> import asyncio; asyncio.run(test_admin_add_resource())
26442644
True
@@ -2656,9 +2656,10 @@ async def admin_add_resource(request: Request, db: Session = Depends(get_db), us
26562656
content=form["content"],
26572657
)
26582658
await resource_service.register_resource(db, resource)
2659-
2660-
root_path = request.scope.get("root_path", "")
2661-
return RedirectResponse(f"{root_path}/admin#resources", status_code=303)
2659+
return JSONResponse(
2660+
content={"message": "Add resource registered successfully!", "success": True},
2661+
status_code=200,
2662+
)
26622663
except Exception as ex:
26632664
if isinstance(ex, ValidationError):
26642665
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
26672668
error_message = ErrorFormatter.format_database_error(ex)
26682669
logger.error(f"IntegrityError in admin_add_resource: {error_message}")
26692670
return JSONResponse(status_code=409, content=error_message)
2671+
26702672
logger.error(f"Error in admin_add_resource: {ex}")
26712673
return JSONResponse(content={"message": str(ex), "success": False}, status_code=500)
26722674

mcpgateway/static/admin.js

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4183,48 +4183,70 @@ async function handleGatewayFormSubmit(e) {
41834183
}
41844184
}
41854185

4186-
function handleResourceFormSubmit(e) {
4186+
async function handleResourceFormSubmit(e) {
41874187
e.preventDefault();
41884188
const form = e.target;
41894189
const formData = new FormData(form);
4190+
const status = safeGetElement("status-resources");
4191+
const loading = safeGetElement("add-gateway-loading");
4192+
try {
4193+
// Validate inputs
4194+
const name = formData.get("name");
4195+
const uri = formData.get("uri");
4196+
const nameValidation = validateInputName(name, "resource");
4197+
const uriValidation = validateInputName(uri, "resource URI");
41904198

4191-
// Validate inputs
4192-
const name = formData.get("name");
4193-
const uri = formData.get("uri");
4199+
if (!nameValidation.valid) {
4200+
showErrorMessage(nameValidation.error);
4201+
return;
4202+
}
41944203

4195-
const nameValidation = validateInputName(name, "resource");
4196-
const uriValidation = validateInputName(uri, "resource URI");
4204+
if (!uriValidation.valid) {
4205+
showErrorMessage(uriValidation.error);
4206+
return;
4207+
}
41974208

4198-
if (!nameValidation.valid) {
4199-
showErrorMessage(nameValidation.error);
4200-
return;
4201-
}
4209+
if (loading) {
4210+
loading.style.display = "block";
4211+
}
4212+
if (status) {
4213+
status.textContent = "";
4214+
status.classList.remove("error-status");
4215+
}
4216+
4217+
const isInactiveCheckedBool = isInactiveChecked("resources");
4218+
formData.append("is_inactive_checked", isInactiveCheckedBool);
42024219

4203-
if (!uriValidation.valid) {
4204-
showErrorMessage(uriValidation.error);
4205-
return;
4206-
}
4220+
const response = await fetchWithTimeout(
4221+
`${window.ROOT_PATH}/admin/resources`,
4222+
{
4223+
method: "POST",
4224+
body: formData,
4225+
},
4226+
);
42074227

4208-
fetchWithTimeout(`${window.ROOT_PATH}/admin/resources`, {
4209-
method: "POST",
4210-
body: formData,
4211-
})
4212-
.then((response) => {
4213-
if (!response.ok) {
4214-
const status = safeGetElement("status-resources");
4215-
if (status) {
4216-
status.textContent = "Connection failed!";
4217-
status.classList.add("error-status");
4218-
}
4219-
throw new Error("Network response was not ok");
4220-
} else {
4221-
location.reload();
4228+
const result = await response.json();
4229+
if (!result.success) {
4230+
throw new Error(result.message || "An error occurred");
4231+
} else {
4232+
const redirectUrl = isInactiveCheckedBool
4233+
? `${window.ROOT_PATH}/admin?include_inactive=true#resources`
4234+
: `${window.ROOT_PATH}/admin#resources`;
4235+
window.location.href = redirectUrl;
42224236
}
4223-
})
4224-
.catch((error) => {
4237+
} catch (error) {
42254238
console.error("Error:", error);
4226-
showErrorMessage("Failed to create resource");
4227-
});
4239+
if (status) {
4240+
status.textContent = error.message || "An error occurred!";
4241+
status.classList.add("error-status");
4242+
}
4243+
showErrorMessage(error.message);
4244+
} finally {
4245+
// location.reload();
4246+
if (loading) {
4247+
loading.style.display = "none";
4248+
}
4249+
}
42284250
}
42294251

42304252
async function handleToolFormSubmit(event) {

mcpgateway/templates/admin.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,9 @@ <h3 class="text-lg font-bold mb-4 dark:text-gray-200">Add New Resource</h3>
799799
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"
800800
placeholder=""></textarea>
801801
</div>
802-
802+
<div id="add-resources-loading" style="display: none;">
803+
<div class="spinner"></div>
804+
</div>
803805
<div id="status-resources"></div>
804806
</div>
805807
<div class="mt-6">

tests/conftest.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
# Standard
1111
import asyncio
1212
import os
13+
import tempfile
1314
from unittest.mock import AsyncMock, patch
1415

1516
# Third-Party
17+
from _pytest.monkeypatch import MonkeyPatch
1618
import pytest
1719
from sqlalchemy import create_engine
1820
from sqlalchemy.orm import sessionmaker
21+
from sqlalchemy.pool import StaticPool
1922

2023
# First-Party
2124
from mcpgateway.config import Settings
@@ -107,3 +110,57 @@ def mock_websocket():
107110
# import mcpgateway.translate as translate
108111
# translate._run_stdio_to_sse = AsyncMock(return_value=None)
109112
# translate._run_sse_to_stdio = AsyncMock(return_value=None)
113+
114+
115+
@pytest.fixture(scope="module") # one DB per test module is usually fine
116+
def app_with_temp_db():
117+
"""Return a FastAPI app wired to a fresh SQLite database."""
118+
mp = MonkeyPatch()
119+
120+
# 1) create temp SQLite file
121+
fd, path = tempfile.mkstemp(suffix=".db")
122+
url = f"sqlite:///{path}"
123+
124+
# 2) patch settings
125+
# First-Party
126+
from mcpgateway.config import settings
127+
128+
mp.setattr(settings, "database_url", url, raising=False)
129+
130+
# First-Party
131+
import mcpgateway.db as db_mod
132+
133+
engine = create_engine(url, connect_args={"check_same_thread": False}, poolclass=StaticPool)
134+
TestSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
135+
mp.setattr(db_mod, "engine", engine, raising=False)
136+
mp.setattr(db_mod, "SessionLocal", TestSessionLocal, raising=False)
137+
138+
# 4) patch the already‑imported main module **without reloading**
139+
# First-Party
140+
import mcpgateway.main as main_mod
141+
142+
mp.setattr(main_mod, "SessionLocal", TestSessionLocal, raising=False)
143+
# (patch engine too if your code references it)
144+
mp.setattr(main_mod, "engine", engine, raising=False)
145+
146+
# 4) create schema
147+
db_mod.Base.metadata.create_all(bind=engine)
148+
149+
# 5) reload main so routers, deps pick up new SessionLocal
150+
# if "mcpgateway.main" in sys.modules:
151+
# import importlib
152+
153+
# importlib.reload(sys.modules["mcpgateway.main"])
154+
# else:
155+
# import mcpgateway.main # noqa: F401
156+
157+
# First-Party
158+
from mcpgateway.main import app
159+
160+
yield app
161+
162+
# 6) teardown
163+
mp.undo()
164+
engine.dispose()
165+
os.close(fd)
166+
os.unlink(path)

0 commit comments

Comments
 (0)