diff --git a/autobot-backend/api/advanced_control.py b/autobot-backend/api/advanced_control.py index a93c68960..c8bea110c 100644 --- a/autobot-backend/api/advanced_control.py +++ b/autobot-backend/api/advanced_control.py @@ -16,6 +16,7 @@ from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from constants.threshold_constants import TimingConstants +from constants.error_constants import ERR_SESSION_NOT_FOUND from desktop_streaming_manager import desktop_streaming from enhanced_memory_manager_async import TaskPriority from takeover_manager import TakeoverTrigger, takeover_manager @@ -127,7 +128,7 @@ async def terminate_streaming_session( logger.info("Desktop streaming session terminated: %s", session_id) return {"success": True, "session_id": session_id} else: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) @with_error_handling( @@ -358,7 +359,7 @@ async def complete_takeover_session( if success: return {"success": True, "session_id": session_id, "status": "completed"} else: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) @with_error_handling( diff --git a/autobot-backend/api/agent_terminal.py b/autobot-backend/api/agent_terminal.py index 0d50efb71..39fc52794 100644 --- a/autobot-backend/api/agent_terminal.py +++ b/autobot-backend/api/agent_terminal.py @@ -229,6 +229,7 @@ from typing import Dict, Optional from fastapi import APIRouter, Depends, HTTPException +from constants.error_constants import ERR_SESSION_NOT_FOUND from pydantic import BaseModel, Field from auth_middleware import get_current_user @@ -492,7 +493,7 @@ async def get_agent_terminal_session( session_info = await service.get_session_info(session_id) if not session_info: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) return { "status": "success", @@ -519,7 +520,7 @@ async def delete_agent_terminal_session( success = await service.close_session(session_id) if not success: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) return { "status": "deleted", diff --git a/autobot-backend/api/api_endpoint_migrations_test.py b/autobot-backend/api/api_endpoint_migrations_test.py index a67507ea0..8c40f1d70 100644 --- a/autobot-backend/api/api_endpoint_migrations_test.py +++ b/autobot-backend/api/api_endpoint_migrations_test.py @@ -3910,7 +3910,7 @@ def test_scan_for_unimported_files_preserves_httpexception(self): # Should preserve directory validation assert "if not scan_path.exists():" in source - assert "Directory not found" in source + assert ERR_DIRECTORY_NOT_FOUND in source assert "status_code=404" in source def test_scan_for_unimported_files_preserves_import_tracker(self): @@ -4520,7 +4520,7 @@ def test_list_files_preserves_httpexceptions(self): 'status_code=403, detail="Insufficient permissions for file operations"' in source ) - assert 'status_code=404, detail="Directory not found"' in source + assert 'status_code=404, detail=ERR_DIRECTORY_NOT_FOUND' in source assert 'status_code=400, detail="Path is not a directory"' in source def test_list_files_preserves_business_logic(self): @@ -4722,7 +4722,7 @@ def test_download_file_preserves_httpexceptions(self): source = inspect.getsource(download_file) assert "status_code=403" in source - assert 'status_code=404, detail="File not found"' in source + assert 'status_code=404, detail=ERR_FILE_NOT_FOUND' in source assert 'status_code=400, detail="Path is not a file"' in source def test_download_file_preserves_business_logic(self): @@ -4771,7 +4771,7 @@ def test_view_file_preserves_httpexceptions(self): source = inspect.getsource(view_file) assert "status_code=403" in source - assert 'status_code=404, detail="File not found"' in source + assert 'status_code=404, detail=ERR_FILE_NOT_FOUND' in source assert 'status_code=400, detail="Path is not a file"' in source def test_view_file_preserves_business_logic(self): @@ -4850,7 +4850,7 @@ def test_rename_file_preserves_httpexceptions(self): source = inspect.getsource(rename_file_or_directory) assert "status_code=403" in source assert 'status_code=400, detail="Invalid file/directory name"' in source - assert 'status_code=404, detail="File or directory not found"' in source + assert 'status_code=404, detail=ERR_FILE_OR_DIR_NOT_FOUND' in source assert "status_code=409" in source def test_rename_file_preserves_business_logic(self): @@ -4899,7 +4899,7 @@ def test_preview_file_preserves_httpexceptions(self): source = inspect.getsource(preview_file) assert "status_code=403" in source - assert 'status_code=404, detail="File not found"' in source + assert 'status_code=404, detail=ERR_FILE_NOT_FOUND' in source assert 'status_code=400, detail="Path is not a file"' in source def test_preview_file_preserves_business_logic(self): @@ -4977,7 +4977,7 @@ def test_delete_file_preserves_httpexceptions(self): source = inspect.getsource(delete_file) assert "status_code=403" in source - assert 'status_code=404, detail="File or directory not found"' in source + assert 'status_code=404, detail=ERR_FILE_OR_DIR_NOT_FOUND' in source def test_delete_file_preserves_business_logic(self): import inspect @@ -5105,7 +5105,7 @@ def test_get_directory_tree_preserves_httpexceptions(self): source = inspect.getsource(get_directory_tree) assert "status_code=403" in source - assert 'status_code=404, detail="Directory not found"' in source + assert 'status_code=404, detail=ERR_DIRECTORY_NOT_FOUND' in source assert 'status_code=400, detail="Path is not a directory"' in source def test_get_directory_tree_preserves_business_logic(self): @@ -5269,7 +5269,7 @@ def test_get_workflow_details_preserves_httpexception(self): from api.workflow import get_workflow_details source = inspect.getsource(get_workflow_details) - assert 'status_code=404, detail="Workflow not found"' in source + assert 'status_code=404, detail=ERR_WORKFLOW_NOT_FOUND' in source def test_get_workflow_details_preserves_business_logic(self): import inspect @@ -5343,7 +5343,7 @@ def test_get_workflow_status_preserves_httpexception(self): from api.workflow import get_workflow_status source = inspect.getsource(get_workflow_status) - assert 'status_code=404, detail="Workflow not found"' in source + assert 'status_code=404, detail=ERR_WORKFLOW_NOT_FOUND' in source def test_get_workflow_status_preserves_business_logic(self): import inspect @@ -5389,7 +5389,7 @@ def test_approve_workflow_step_preserves_httpexceptions(self): from api.workflow import approve_workflow_step source = inspect.getsource(approve_workflow_step) - assert 'status_code=404, detail="Workflow not found"' in source + assert 'status_code=404, detail=ERR_WORKFLOW_NOT_FOUND' in source assert 'status_code=404, detail="No pending approval' in source def test_approve_workflow_step_preserves_business_logic(self): @@ -8403,7 +8403,7 @@ def test_get_terminal_session_business_logic_preserved(self): "config = session_manager.session_configs.get(session_id)", source ) self.assertIn( - 'raise HTTPException(status_code=404, detail="Session not found")', source + 'raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND)', source ) self.assertIn("is_active = session_manager.has_connection(session_id)", source) self.assertIn("stats = {}", source) @@ -8468,7 +8468,7 @@ def test_delete_terminal_session_business_logic_preserved(self): "config = session_manager.session_configs.get(session_id)", source ) self.assertIn( - 'raise HTTPException(status_code=404, detail="Session not found")', source + 'raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND)', source ) self.assertIn("if session_manager.has_connection(session_id):", source) self.assertIn("await session_manager.close_connection(session_id)", source) @@ -8793,7 +8793,7 @@ def test_get_terminal_command_history_business_logic_preserved(self): ) self.assertIn("if not config:", source) self.assertIn( - 'raise HTTPException(status_code=404, detail="Session not found")', source + 'raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND)', source ) self.assertIn("is_active = session_manager.has_connection(session_id)", source) self.assertIn("if not is_active:", source) @@ -8882,7 +8882,7 @@ def test_get_session_audit_log_business_logic_preserved(self): ) self.assertIn("if not config:", source) self.assertIn( - 'raise HTTPException(status_code=404, detail="Session not found")', source + 'raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND)', source ) self.assertIn('"session_id": session_id', source) self.assertIn('config.get("enable_logging", False)', source) @@ -10631,7 +10631,7 @@ def test_batch65_httpexception_preserved(self): f"{func.__name__} should preserve HTTPException raises", ) self.assertIn( - '"Session not found"', + 'ERR_SESSION_NOT_FOUND', source, f"{func.__name__} should check for session existence", ) @@ -10731,7 +10731,7 @@ def test_navigate_session_httpexception_preserved(self): source = inspect.getsource(navigate_session) self.assertIn("HTTPException", source) - self.assertIn('"Session not found"', source) + self.assertIn('ERR_SESSION_NOT_FOUND', source) # Should check session existence self.assertIn("if not session:", source) @@ -10767,7 +10767,7 @@ def test_get_browser_info_httpexception_preserved(self): source = inspect.getsource(get_browser_info) self.assertIn("HTTPException", source) - self.assertIn('"Session not found"', source) + self.assertIn('ERR_SESSION_NOT_FOUND', source) # Should check session existence (after special chat-browser handling) self.assertIn("if not session:", source) @@ -10859,7 +10859,7 @@ def test_batch66_httpexception_preservation(self): f"{func.__name__} should preserve HTTPException raises", ) self.assertIn( - '"Session not found"', + 'ERR_SESSION_NOT_FOUND', source, f"{func.__name__} should check for session existence", ) @@ -10961,7 +10961,7 @@ def test_get_agent_terminal_session_httpexception_preserved(self): source = inspect.getsource(get_agent_terminal_session) self.assertIn("HTTPException", source) - self.assertIn('"Session not found"', source) + self.assertIn('ERR_SESSION_NOT_FOUND', source) self.assertIn("status_code=404", source) # Should check session existence self.assertIn("if not session_info:", source) @@ -11093,7 +11093,7 @@ def test_delete_agent_terminal_session_httpexception_preserved(self): source = inspect.getsource(delete_agent_terminal_session) self.assertIn("HTTPException", source) - self.assertIn('"Session not found"', source) + self.assertIn('ERR_SESSION_NOT_FOUND', source) self.assertIn("status_code=404", source) # Should check session close success self.assertIn("if not success:", source) @@ -11237,7 +11237,7 @@ def test_batch68_httpexception_preservation(self): source, "delete_agent_terminal_session should preserve HTTPException raises", ) - self.assertIn('"Session not found"', source) + self.assertIn('ERR_SESSION_NOT_FOUND', source) def test_batch68_business_logic_preservation(self): """Test batch 68 preserves all business logic""" @@ -16538,7 +16538,7 @@ def test_batch_94_terminate_streaming_session_preserves_http_exception(self): # Should preserve 404 HTTPException for business logic self.assertIn("HTTPException", source) self.assertIn("status_code=404", source) - self.assertIn("Session not found", source) + self.assertIn(ERR_SESSION_NOT_FOUND, source) def test_batch_94_list_streaming_sessions_has_decorator(self): """Verify list_streaming_sessions has @with_error_handling decorator""" @@ -16811,7 +16811,7 @@ def test_batch_95_complete_takeover_session_preserves_http_exception(self): # Should preserve 404 HTTPException for business logic self.assertIn("HTTPException", source) self.assertIn("status_code=404", source) - self.assertIn("Session not found", source) + self.assertIn(ERR_SESSION_NOT_FOUND, source) # Should NOT have outer try-catch wrapper try_count = source.count("try:") self.assertEqual( @@ -17283,7 +17283,7 @@ def test_batch_97_get_workflow_details_preserves_404(self): # Should preserve 404 HTTPException self.assertIn("HTTPException", source) self.assertIn("status_code=404", source) - self.assertIn("Workflow not found", source) + self.assertIn(ERR_WORKFLOW_NOT_FOUND, source) # Should have NO try-catch blocks (Mixed Pattern with direct checks) try_count = source.count("try:") self.assertEqual( diff --git a/autobot-backend/api/auth.py b/autobot-backend/api/auth.py index cacfd2a1a..188782fd4 100644 --- a/autobot-backend/api/auth.py +++ b/autobot-backend/api/auth.py @@ -18,6 +18,7 @@ from auth_middleware import auth_middleware from autobot_shared.error_boundaries import ErrorCategory, with_error_handling +from constants.error_constants import ERR_INVALID_CREDENTIALS, ERR_INVALID_TOKEN from user_management.database import db_session_context from user_management.services.user_service import UserService @@ -184,7 +185,7 @@ async def _authenticate_and_build_user_data( ip_address=ip_address, ) if not user: - raise HTTPException(status_code=401, detail="Invalid username or password") + raise HTTPException(status_code=401, detail=ERR_INVALID_CREDENTIALS) user_data = { "username": user.username, "user_id": str(user.id), @@ -554,7 +555,7 @@ def _decode_refresh_token(token: str) -> Dict: options={"verify_exp": False}, ) except pyjwt.InvalidTokenError as exc: - raise HTTPException(status_code=401, detail="Invalid token") from exc + raise HTTPException(status_code=401, detail=ERR_INVALID_TOKEN) from exc exp = payload.get("exp") if exp: diff --git a/autobot-backend/api/files.py b/autobot-backend/api/files.py index c8703f6f8..3a3ab4cb9 100644 --- a/autobot-backend/api/files.py +++ b/autobot-backend/api/files.py @@ -27,6 +27,12 @@ from auth_middleware import auth_middleware from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from autobot_shared.security.path_validator import validate_relative_path +from constants.error_constants import ( + ERR_DIRECTORY_NOT_FOUND, + ERR_FILE_NOT_FOUND, + ERR_FILE_OR_DIR_NOT_FOUND, + ERR_PATH_NOT_FOUND, +) from security_layer import SecurityLayer from utils.io_executor import run_in_file_executor from utils.path_validation import is_invalid_name @@ -415,7 +421,7 @@ async def list_files(request: Request, path: str = ""): # Issue #358/#718: Use dedicated executor for non-blocking file I/O if not await run_in_file_executor(target_path.exists): - raise HTTPException(status_code=404, detail="Directory not found") + raise HTTPException(status_code=404, detail=ERR_DIRECTORY_NOT_FOUND) if not await run_in_file_executor(target_path.is_dir): raise HTTPException(status_code=400, detail="Path is not a directory") @@ -757,7 +763,7 @@ async def download_file(request: Request, path: str): # Issue #358/#718: Use dedicated executor for non-blocking file I/O if not await run_in_file_executor(target_file.exists): - raise HTTPException(status_code=404, detail="File not found") + raise HTTPException(status_code=404, detail=ERR_FILE_NOT_FOUND) if not await run_in_file_executor(target_file.is_file): raise HTTPException(status_code=400, detail="Path is not a file") @@ -814,7 +820,7 @@ async def view_file(request: Request, path: str): # Issue #358/#718: Use dedicated executor for non-blocking file I/O if not await run_in_file_executor(target_file.exists): - raise HTTPException(status_code=404, detail="File not found") + raise HTTPException(status_code=404, detail=ERR_FILE_NOT_FOUND) if not await run_in_file_executor(target_file.is_file): raise HTTPException(status_code=400, detail="Path is not a file") @@ -898,7 +904,7 @@ async def _validate_rename_paths(source_path: Path, new_name: str) -> Path: Issue #620. """ if not await run_in_file_executor(source_path.exists): - raise HTTPException(status_code=404, detail="File or directory not found") + raise HTTPException(status_code=404, detail=ERR_FILE_OR_DIR_NOT_FOUND) target_path = source_path.parent / new_name @@ -1024,7 +1030,7 @@ async def preview_file(request: Request, path: str): # Issue #358/#718: Use dedicated executor for non-blocking file I/O if not await run_in_file_executor(target_file.exists): - raise HTTPException(status_code=404, detail="File not found") + raise HTTPException(status_code=404, detail=ERR_FILE_NOT_FOUND) if not await run_in_file_executor(target_file.is_file): raise HTTPException(status_code=400, detail="Path is not a file") @@ -1076,7 +1082,7 @@ async def delete_file(request: Request, path: str): target_path = validate_and_resolve_path(path) if not await run_in_file_executor(target_path.exists): - raise HTTPException(status_code=404, detail="File or directory not found") + raise HTTPException(status_code=404, detail=ERR_FILE_OR_DIR_NOT_FOUND) security_layer = get_security_layer(request) is_file = await run_in_file_executor(target_path.is_file) @@ -1191,7 +1197,7 @@ async def get_directory_tree(request: Request, path: str = ""): # Issue #358/#718: Use dedicated executor for non-blocking file I/O if not await run_in_file_executor(target_path.exists): - raise HTTPException(status_code=404, detail="Directory not found") + raise HTTPException(status_code=404, detail=ERR_DIRECTORY_NOT_FOUND) if not await run_in_file_executor(target_path.is_dir): raise HTTPException(status_code=400, detail="Path is not a directory") @@ -1346,7 +1352,7 @@ async def admin_list_directory(path: str = "/home/autobot") -> dict: # noqa: ss """ target = _validate_admin_path(path) if not target.exists(): - raise HTTPException(status_code=404, detail="Path not found") + raise HTTPException(status_code=404, detail=ERR_PATH_NOT_FOUND) if not target.is_dir(): raise HTTPException(status_code=400, detail="Path is not a directory") try: @@ -1366,7 +1372,7 @@ async def admin_read_file(path: str) -> dict: """ target = _validate_admin_path(path) if not target.exists(): - raise HTTPException(status_code=404, detail="File not found") + raise HTTPException(status_code=404, detail=ERR_FILE_NOT_FOUND) if not target.is_file(): raise HTTPException(status_code=400, detail="Path is not a file") if target.stat().st_size > _ADMIN_MAX_READ_BYTES: diff --git a/autobot-backend/api/knowledge_connectors.py b/autobot-backend/api/knowledge_connectors.py index 719443897..a849f579c 100644 --- a/autobot-backend/api/knowledge_connectors.py +++ b/autobot-backend/api/knowledge_connectors.py @@ -32,6 +32,7 @@ from auth_middleware import check_admin_permission from autobot_shared.redis_client import get_redis_client +from constants.error_constants import ERR_CONNECTOR_NOT_FOUND from knowledge.connectors.models import ConnectorConfig from knowledge.connectors.registry import ConnectorRegistry from knowledge.connectors.scheduler import get_connector_scheduler @@ -312,7 +313,7 @@ async def get_connector(connector_id: str): """Return config and status for a single connector.""" cfg = await _load_connector(connector_id) if cfg is None: - raise HTTPException(status_code=404, detail="Connector not found") + raise HTTPException(status_code=404, detail=ERR_CONNECTOR_NOT_FOUND) status = await _get_status_for_config(cfg) return {"config": _cfg_to_dict(cfg), "status": status} @@ -322,7 +323,7 @@ async def update_connector(connector_id: str, request: UpdateConnectorRequest): """Update mutable fields of an existing connector.""" cfg = await _load_connector(connector_id) if cfg is None: - raise HTTPException(status_code=404, detail="Connector not found") + raise HTTPException(status_code=404, detail=ERR_CONNECTOR_NOT_FOUND) _apply_updates(cfg, request) await _save_connector(cfg) @@ -341,7 +342,7 @@ async def delete_connector(connector_id: str): """Remove a connector, stop its schedule, and delete its Redis keys.""" cfg = await _load_connector(connector_id) if cfg is None: - raise HTTPException(status_code=404, detail="Connector not found") + raise HTTPException(status_code=404, detail=ERR_CONNECTOR_NOT_FOUND) scheduler = get_connector_scheduler() await scheduler.stop(connector_id) @@ -355,7 +356,7 @@ async def test_connector_connection(connector_id: str): """Run a connection test against the connector's target.""" cfg = await _load_connector(connector_id) if cfg is None: - raise HTTPException(status_code=404, detail="Connector not found") + raise HTTPException(status_code=404, detail=ERR_CONNECTOR_NOT_FOUND) instance = _load_or_create_instance(cfg) try: healthy = await instance.test_connection() @@ -374,7 +375,7 @@ async def trigger_sync( """Trigger a manual sync for a connector (runs in background).""" cfg = await _load_connector(connector_id) if cfg is None: - raise HTTPException(status_code=404, detail="Connector not found") + raise HTTPException(status_code=404, detail=ERR_CONNECTOR_NOT_FOUND) background_tasks.add_task(_run_sync_background, connector_id, incremental) logger.info( "Triggered sync for connector %s (incremental=%s)", connector_id, incremental @@ -391,7 +392,7 @@ async def get_sync_history(connector_id: str, limit: int = 20): """Return recent sync results for a connector.""" cfg = await _load_connector(connector_id) if cfg is None: - raise HTTPException(status_code=404, detail="Connector not found") + raise HTTPException(status_code=404, detail=ERR_CONNECTOR_NOT_FOUND) if limit > _MAX_HISTORY: limit = _MAX_HISTORY diff --git a/autobot-backend/api/research_browser.py b/autobot-backend/api/research_browser.py index c98121da6..7afc9a377 100644 --- a/autobot-backend/api/research_browser.py +++ b/autobot-backend/api/research_browser.py @@ -21,6 +21,7 @@ from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from config import ConfigManager from constants.network_constants import NetworkConstants +from constants.error_constants import ERR_SESSION_NOT_FOUND logger = logging.getLogger(__name__) @@ -123,7 +124,7 @@ async def handle_session_action(request: SessionAction): _require_browser() session = research_browser_manager.get_session(request.session_id) if not session: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) result = {"success": True, "session_id": request.session_id} @@ -173,7 +174,7 @@ async def get_session_status(session_id: str): _require_browser() session = research_browser_manager.get_session(session_id) if not session: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) return JSONResponse( status_code=200, @@ -202,7 +203,7 @@ async def download_mhtml(session_id: str, filename: str): _require_browser() session = research_browser_manager.get_session(session_id) if not session: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) # Find the MHTML file mhtml_path = None @@ -299,7 +300,7 @@ async def navigate_session(session_id: str, request: NavigationRequest): _require_browser() session = research_browser_manager.get_session(session_id) if not session: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) result = await session.navigate_to(request.url) @@ -319,7 +320,7 @@ async def get_browser_info(session_id: str): session = _get_or_create_browser_session(session_id) if not session: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) docker_browser_info = await _get_docker_browser_info(session) diff --git a/autobot-backend/api/scheduler.py b/autobot-backend/api/scheduler.py index 5321a741b..59cdfeeb3 100644 --- a/autobot-backend/api/scheduler.py +++ b/autobot-backend/api/scheduler.py @@ -19,6 +19,7 @@ from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling from constants.threshold_constants import RetryConfig +from constants.error_constants import ERR_TEMPLATE_NOT_FOUND, ERR_WORKFLOW_NOT_FOUND from type_defs.common import Metadata from workflow_scheduler import WorkflowPriority from workflow_scheduler import WorkflowScheduleRequest as InternalScheduleRequest @@ -146,7 +147,7 @@ async def get_workflow_details(workflow_id: str): """Get detailed information about a specific scheduled workflow (Issue #372)""" workflow = workflow_scheduler.get_workflow(workflow_id) if not workflow: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) # Use model method to reduce feature envy (Issue #372) return { @@ -437,7 +438,7 @@ async def schedule_template_workflow( template = workflow_template_manager.get_template(template_id) if not template: - raise HTTPException(status_code=404, detail="Template not found") + raise HTTPException(status_code=404, detail=ERR_TEMPLATE_NOT_FOUND) internal_request = _build_template_schedule_request( template_id, diff --git a/autobot-backend/api/security_assessment.py b/autobot-backend/api/security_assessment.py index fd685e22d..fbcc5c5b2 100644 --- a/autobot-backend/api/security_assessment.py +++ b/autobot-backend/api/security_assessment.py @@ -18,6 +18,7 @@ from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling +from constants.error_constants import ERR_ASSESSMENT_NOT_FOUND from services.security_tool_parsers import parse_tool_output from services.security_workflow_manager import ( PHASE_DESCRIPTIONS, @@ -243,7 +244,7 @@ async def get_assessment( assessment = await manager.get_assessment(assessment_id) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, @@ -283,7 +284,7 @@ async def get_assessment_summary( summary = await manager.get_assessment_summary(assessment_id) if not summary: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, @@ -323,7 +324,7 @@ async def delete_assessment( deleted = await manager.delete_assessment(assessment_id) if not deleted: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, @@ -363,7 +364,7 @@ async def get_current_phase( assessment = await manager.get_assessment(assessment_id) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) current_phase = assessment.phase.value phase_info = PHASE_DESCRIPTIONS.get(current_phase, {}) @@ -472,7 +473,7 @@ async def add_host( ) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, @@ -523,7 +524,7 @@ async def add_port( ) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, @@ -576,7 +577,7 @@ async def add_vulnerability( ) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, @@ -628,7 +629,7 @@ async def add_finding( ) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, @@ -672,7 +673,7 @@ async def get_findings( assessment = await manager.get_assessment(assessment_id) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) findings = assessment.findings @@ -819,7 +820,7 @@ async def parse_and_store_tool_output( # Verify assessment exists assessment = await manager.get_assessment(assessment_id) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) # Parse the output parsed = parse_tool_output(request.output, request.tool) @@ -886,7 +887,7 @@ async def set_error_state( assessment = await manager.set_error(assessment_id, error_message) if not assessment: - raise HTTPException(status_code=404, detail="Assessment not found") + raise HTTPException(status_code=404, detail=ERR_ASSESSMENT_NOT_FOUND) return JSONResponse( status_code=200, diff --git a/autobot-backend/api/templates.py b/autobot-backend/api/templates.py index 33ce45c5c..fbe742262 100644 --- a/autobot-backend/api/templates.py +++ b/autobot-backend/api/templates.py @@ -15,6 +15,7 @@ from typing import Dict, Optional from fastapi import APIRouter, Depends, HTTPException, Query +from constants.error_constants import ERR_TEMPLATE_NOT_FOUND from pydantic import BaseModel from auth_middleware import check_admin_permission @@ -303,7 +304,7 @@ async def get_template_details(template_id: str): try: template = workflow_template_manager.get_template(template_id) if not template: - raise HTTPException(status_code=404, detail="Template not found") + raise HTTPException(status_code=404, detail=ERR_TEMPLATE_NOT_FOUND) # Issue #372: Use model method to reduce feature envy return { @@ -331,7 +332,7 @@ async def preview_template_workflow( try: template = workflow_template_manager.get_template(template_id) if not template: - raise HTTPException(status_code=404, detail="Template not found") + raise HTTPException(status_code=404, detail=ERR_TEMPLATE_NOT_FOUND) # Parse variables if provided template_variables = {} @@ -423,7 +424,7 @@ async def create_workflow_from_template( # Validate template exists template = workflow_template_manager.get_template(template_id) if not template: - raise HTTPException(status_code=404, detail="Template not found") + raise HTTPException(status_code=404, detail=ERR_TEMPLATE_NOT_FOUND) # Validate variables if provided if request.variables: diff --git a/autobot-backend/api/terminal.py b/autobot-backend/api/terminal.py index 009afe9de..416e60c8b 100644 --- a/autobot-backend/api/terminal.py +++ b/autobot-backend/api/terminal.py @@ -134,6 +134,7 @@ ) from auth_middleware import check_admin_permission, get_current_user from autobot_shared.error_boundaries import ErrorCategory, with_error_handling +from constants.error_constants import ERR_SESSION_NOT_FOUND from services.simple_pty import simple_pty_manager # Import terminal secrets service for SSH key integration (Issue #211) @@ -282,7 +283,7 @@ async def get_terminal_session( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) is_active = session_manager.has_connection(session_id) @@ -316,7 +317,7 @@ async def delete_terminal_session( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) # Close WebSocket connection if active if session_manager.has_connection(session_id): @@ -367,7 +368,7 @@ async def setup_ssh_keys( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) try: terminal_secrets = get_terminal_secrets_service() @@ -401,7 +402,7 @@ async def list_session_ssh_keys( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) terminal_secrets = get_terminal_secrets_service() keys = terminal_secrets.get_session_keys(session_id) @@ -433,7 +434,7 @@ async def add_key_to_ssh_agent( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) terminal_secrets = get_terminal_secrets_service() success = await terminal_secrets.add_key_to_agent( @@ -474,7 +475,7 @@ async def get_ssh_key_path( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) terminal_secrets = get_terminal_secrets_service() key_path = terminal_secrets.get_key_path(session_id, key_name) @@ -617,7 +618,7 @@ async def get_terminal_command_history( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) # Check if session has active connection is_active = session_manager.has_connection(session_id) @@ -656,7 +657,7 @@ async def get_session_audit_log( """ config = session_manager.session_configs.get(session_id) if not config: - raise HTTPException(status_code=404, detail="Session not found") + raise HTTPException(status_code=404, detail=ERR_SESSION_NOT_FOUND) # In a real implementation, you'd check user permissions here # For now, return basic audit information diff --git a/autobot-backend/api/workflow.py b/autobot-backend/api/workflow.py index 2502f0a75..b47f9fced 100644 --- a/autobot-backend/api/workflow.py +++ b/autobot-backend/api/workflow.py @@ -19,6 +19,7 @@ from api.workflow_state import get_workflow_state_machine from auth_middleware import check_admin_permission from autobot_shared.error_boundaries import ErrorCategory, with_error_handling +from constants.error_constants import ERR_WORKFLOW_NOT_FOUND from event_manager import event_manager from metrics.system_monitor import system_monitor from metrics.workflow_metrics import workflow_metrics @@ -483,7 +484,7 @@ async def get_workflow_details( Issue #744: Requires admin authentication.""" async with _workflows_lock: if workflow_id not in active_workflows: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) # Create a copy to avoid race conditions workflow = dict(active_workflows[workflow_id]) @@ -504,7 +505,7 @@ async def get_workflow_status( Issue #744: Requires admin authentication.""" if workflow_id not in active_workflows: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) workflow = active_workflows[workflow_id] current_step = workflow.get("current_step", 0) @@ -594,7 +595,7 @@ async def approve_workflow_step( Issue #744: Requires admin authentication.""" async with _workflows_lock: if workflow_id not in active_workflows: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) approval_key = f"{workflow_id}_{approval.step_id}" @@ -1052,7 +1053,7 @@ async def cancel_workflow( Issue #744: Requires admin authentication.""" async with _workflows_lock: if workflow_id not in active_workflows: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) workflow = active_workflows[workflow_id] workflow["status"] = "cancelled" @@ -1088,7 +1089,7 @@ async def get_pending_approvals( Issue #744: Requires admin authentication.""" if workflow_id not in active_workflows: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) workflow = active_workflows[workflow_id] pending_steps = [] diff --git a/autobot-backend/constants/__init__.py b/autobot-backend/constants/__init__.py index 1ecd5a885..6ef0c13ab 100644 --- a/autobot-backend/constants/__init__.py +++ b/autobot-backend/constants/__init__.py @@ -8,6 +8,22 @@ Centralized constants to eliminate hardcoded values throughout the codebase. """ +from .error_constants import ( # Issue #3530: Centralized error message strings + ERR_ASSESSMENT_NOT_FOUND, + ERR_CONNECTOR_NOT_FOUND, + ERR_DIRECTORY_NOT_FOUND, + ERR_FILE_NOT_FOUND, + ERR_FILE_OR_DIR_NOT_FOUND, + ERR_INVALID_CREDENTIALS, + ERR_INVALID_TOKEN, + ERR_JOB_NOT_FOUND, + ERR_NOT_FOUND, + ERR_PATH_NOT_FOUND, + ERR_SESSION_NOT_FOUND, + ERR_TEMPLATE_NOT_FOUND, + ERR_WORKFLOW_NOT_FOUND, + ERR_FAILED_TO, +) from .network_constants import ( # Legacy compatibility exports BACKEND_URL, FRONTEND_URL, @@ -105,4 +121,19 @@ "QueryDefaults", "CategoryDefaults", "ProtocolDefaults", + # Issue #3530: Error message string constants + "ERR_NOT_FOUND", + "ERR_ASSESSMENT_NOT_FOUND", + "ERR_SESSION_NOT_FOUND", + "ERR_FILE_NOT_FOUND", + "ERR_DIRECTORY_NOT_FOUND", + "ERR_FILE_OR_DIR_NOT_FOUND", + "ERR_PATH_NOT_FOUND", + "ERR_CONNECTOR_NOT_FOUND", + "ERR_JOB_NOT_FOUND", + "ERR_TEMPLATE_NOT_FOUND", + "ERR_WORKFLOW_NOT_FOUND", + "ERR_INVALID_CREDENTIALS", + "ERR_INVALID_TOKEN", + "ERR_FAILED_TO", ] diff --git a/autobot-backend/constants/error_constants.py b/autobot-backend/constants/error_constants.py new file mode 100644 index 000000000..987c8e592 --- /dev/null +++ b/autobot-backend/constants/error_constants.py @@ -0,0 +1,26 @@ +# AutoBot - AI-Powered Automation Platform +# Copyright (c) 2025 mrveiss +# Author: mrveiss +"""Shared error message string constants for HTTP responses and logging.""" + +# Generic resource errors (use as: ERR_NOT_FOUND.format(resource='Workflow')}") +ERR_NOT_FOUND = "{resource} not found" + +# Specific resource errors (pre-formatted for common cases) +ERR_ASSESSMENT_NOT_FOUND = "Assessment not found" +ERR_SESSION_NOT_FOUND = "Session not found" +ERR_FILE_NOT_FOUND = "File not found" +ERR_DIRECTORY_NOT_FOUND = "Directory not found" +ERR_FILE_OR_DIR_NOT_FOUND = "File or directory not found" +ERR_PATH_NOT_FOUND = "Path not found" +ERR_CONNECTOR_NOT_FOUND = "Connector not found" +ERR_JOB_NOT_FOUND = "Job not found" +ERR_TEMPLATE_NOT_FOUND = "Template not found" +ERR_WORKFLOW_NOT_FOUND = "Workflow not found" + +# Auth errors +ERR_INVALID_CREDENTIALS = "Invalid username or password" +ERR_INVALID_TOKEN = "Invalid token" + +# Operation errors — use as f-string prefix at call site +ERR_FAILED_TO = "Failed to {operation}" diff --git a/autobot-backend/services/workflow_automation/routes.py b/autobot-backend/services/workflow_automation/routes.py index 8cf03a5f4..c4d96d896 100644 --- a/autobot-backend/services/workflow_automation/routes.py +++ b/autobot-backend/services/workflow_automation/routes.py @@ -13,6 +13,7 @@ from dataclasses import asdict from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect +from constants.error_constants import ERR_WORKFLOW_NOT_FOUND from auth_middleware import get_current_user from autobot_shared.error_boundaries import ErrorCategory, with_error_handling @@ -124,7 +125,7 @@ async def start_workflow( "message": f"Workflow {workflow_id} started successfully", } else: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) except Exception as e: logger.error("Failed to start workflow: %s", e) @@ -178,7 +179,7 @@ async def get_workflow_status( if status: return {"success": True, "workflow": status} else: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) except Exception as e: logger.error("Failed to get workflow status: %s", e) @@ -341,7 +342,7 @@ async def present_plan( "plan": plan_approval.to_presentation_dict(), } else: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) except Exception as e: logger.error("Failed to present plan: %s", e) @@ -355,7 +356,7 @@ def _validate_approval_request(workflow_id: str) -> None: Raises HTTPException(404) if either check fails. """ if not get_workflow_manager().get_workflow_status(workflow_id): - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) if not get_workflow_manager().get_pending_approval(workflow_id): raise HTTPException( status_code=404, detail="No pending approval for this workflow" @@ -484,7 +485,7 @@ def _find_workflow(workflow_id: str, current_user: dict = None): if wf is None: wf = mgr.completed_workflows.get(workflow_id) if wf is None: - raise HTTPException(status_code=404, detail="Workflow not found") + raise HTTPException(status_code=404, detail=ERR_WORKFLOW_NOT_FOUND) if current_user and hasattr(wf, "owner_id") and wf.owner_id: user_id = current_user.get("user_id", current_user.get("sub")) if user_id and wf.owner_id != user_id: