Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/src/platform/api/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import time

from starlette.exceptions import HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
Expand Down Expand Up @@ -61,6 +62,8 @@ async def dispatch(self, request: Request, call_next) -> Response:
{"detail": str(exc)},
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
)
except HTTPException:
raise # Let Starlette handle route-level HTTP errors (e.g. 404)
except Exception:
logger.exception("Unhandled exception in PlatformMiddleware")
return JSONResponse(
Expand Down
6 changes: 5 additions & 1 deletion backend/src/platform/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,11 @@ async def init_environment(request: Request) -> JSONResponse:
logger.warning("Unauthorized template access in init_environment")
return unauthorized()
except ValueError as e:
logger.warning(f"Template resolution failed in init_environment: {e}")
logger.warning(
f"Template resolution failed in init_environment: {e} "
f"(service={body.templateService!r}, name={body.templateName!r}, "
f"testId={body.testId!r}, schema={body.templateSchema!r})"
)
return bad_request(str(e))

if not body.testId and not body.impersonateUserId and not body.impersonateEmail:
Expand Down
25 changes: 24 additions & 1 deletion backend/src/platform/isolationEngine/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,35 @@ def migrate_schema(self, template_schema: str, target_schema: str) -> None:
self._set_replica_identity(target_schema)

def _ensure_box_columns(self, schema: str) -> None:
"""Add columns that may be missing from older template snapshots."""
"""Add columns that may be missing from older template snapshots.

Only runs against schemas that actually contain Box tables. Each
ALTER TABLE is wrapped in a SAVEPOINT so that a single failure does
not poison the surrounding transaction (the same pattern used by
``_copy_custom_indexes``).
"""
_columns = [
# (table, column, SQL type, default)
("box_folders", "path", "VARCHAR(500)", "'/'"),
("box_files", "path", "VARCHAR(500)", "'/0/'"),
]
with self.session_manager.base_engine.begin() as conn:
# Quick check: skip entirely when the schema has no Box tables.
has_box = conn.execute(
text(
"SELECT EXISTS("
" SELECT 1 FROM information_schema.tables"
" WHERE table_schema = :schema"
" AND table_name = 'box_folders'"
")"
),
{"schema": schema},
).scalar()
if not has_box:
return

for table, col, sql_type, default in _columns:
nested = conn.begin_nested()
try:
conn.execute(
text(
Expand All @@ -71,7 +92,9 @@ def _ensure_box_columns(self, schema: str) -> None:
f"DEFAULT {default}"
)
)
nested.commit()
except Exception as exc:
nested.rollback()
logger.warning(
f"Could not ensure column {schema}.{table}.{col}: {exc}"
)
Expand Down
Loading