From 047db8de409da5cf90636e97561739e36fdc2bea Mon Sep 17 00:00:00 2001 From: seniorswe Date: Fri, 3 Oct 2025 21:16:39 -0400 Subject: [PATCH] additional tests and fix --- backend-services/live-tests/conftest.py | 4 +++ backend-services/routes/gateway_routes.py | 30 +++++++++++++++---- backend-services/routes/memory_routes.py | 6 ++-- backend-services/services/endpoint_service.py | 6 ++++ 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/backend-services/live-tests/conftest.py b/backend-services/live-tests/conftest.py index 509428b..dd70dfb 100644 --- a/backend-services/live-tests/conftest.py +++ b/backend-services/live-tests/conftest.py @@ -1,8 +1,12 @@ import os +import sys import time import pytest import requests +# Ensure backend packages are importable when running from live-tests dir +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + from config import BASE_URL, ADMIN_EMAIL, ADMIN_PASSWORD, require_env, STRICT_HEALTH from client import LiveClient diff --git a/backend-services/routes/gateway_routes.py b/backend-services/routes/gateway_routes.py index 0b758a4..9a76c53 100644 --- a/backend-services/routes/gateway_routes.py +++ b/backend-services/routes/gateway_routes.py @@ -173,16 +173,36 @@ async def gateway(request: Request, path: str): parts = [p for p in (path or '').split('/') if p] api_public = False api_auth_required = True + resolved_api = None if len(parts) >= 2 and parts[1].startswith('v') and parts[1][1:].isdigit(): api_key = doorman_cache.get_cache('api_id_cache', f'/{parts[0]}/{parts[1]}') - api = await api_util.get_api(api_key, f'/{parts[0]}/{parts[1]}') - api_public = bool(api.get('api_public')) if api else False - api_auth_required = bool(api.get('api_auth_required')) if api and api.get('api_auth_required') is not None else True - if api: + resolved_api = await api_util.get_api(api_key, f'/{parts[0]}/{parts[1]}') + # Early endpoint existence check to return 404 before auth when missing + if resolved_api: try: - enforce_api_ip_policy(request, api) + # Enforce per-API IP policy first + enforce_api_ip_policy(request, resolved_api) except HTTPException as e: return process_response(ResponseModel(status_code=e.status_code, error_code=e.detail, error_message='IP restricted').dict(), 'rest') + # Build endpoint URI and verify it exists for this API/method + endpoint_uri = '/' + '/'.join(parts[2:]) if len(parts) > 2 else '/' + try: + endpoints = await api_util.get_api_endpoints(resolved_api.get('api_id')) + import re as _re + regex_pattern = _re.compile(r'\{[^/]+\}') + composite = request.method + endpoint_uri + if not any(_re.fullmatch(regex_pattern.sub(r'([^/]+)', ep), composite) for ep in (endpoints or [])): + return process_response(ResponseModel( + status_code=404, + response_headers={'request_id': request_id}, + error_code='GTW003', + error_message='Endpoint does not exist for the requested API' + ).dict(), 'rest') + except Exception: + # If endpoint introspection fails, fall through to service handler + pass + api_public = bool(resolved_api.get('api_public')) if resolved_api else False + api_auth_required = bool(resolved_api.get('api_auth_required')) if resolved_api and resolved_api.get('api_auth_required') is not None else True username = None if not api_public: if api_auth_required: diff --git a/backend-services/routes/memory_routes.py b/backend-services/routes/memory_routes.py index 7ed5fe3..ef44ad1 100644 --- a/backend-services/routes/memory_routes.py +++ b/backend-services/routes/memory_routes.py @@ -77,11 +77,12 @@ async def memory_dump(request: Request, body: Optional[DumpRequest] = None): if body and body.path: path = body.path dump_path = dump_memory_to_file(path) + # Wrap under 'response' key so clients/tests see a consistent envelope return process_response(ResponseModel( status_code=200, response_headers={'request_id': request_id}, message='Memory dump created successfully', - response={'path': dump_path} + response={'response': {'path': dump_path}} ).dict(), 'rest') except Exception as e: logger.critical(f'{request_id} | Unexpected error: {str(e)}', exc_info=True) @@ -150,11 +151,12 @@ async def memory_restore(request: Request, body: Optional[RestoreRequest] = None if body and body.path: path = body.path info = restore_memory_from_file(path) + # Wrap under 'response' key for consistency with dump return process_response(ResponseModel( status_code=200, response_headers={'request_id': request_id}, message='Memory restore completed', - response=info + response={'response': info} ).dict(), 'rest') except FileNotFoundError as e: return process_response(ResponseModel( diff --git a/backend-services/services/endpoint_service.py b/backend-services/services/endpoint_service.py index e5f744e..1c8044c 100644 --- a/backend-services/services/endpoint_service.py +++ b/backend-services/services/endpoint_service.py @@ -188,6 +188,12 @@ class EndpointService: error_message='Unable to delete endpoint' ).dict() doorman_cache.delete_cache('endpoint_cache', cache_key) + try: + api_id = endpoint.get('api_id') if isinstance(endpoint, dict) else None + if api_id: + doorman_cache.delete_cache('api_endpoint_cache', api_id) + except Exception: + pass logger.info(request_id + ' | Endpoint deletion successful') return ResponseModel( status_code=200,