diff --git a/pyproject.toml b/pyproject.toml index 0008b09d..f1f7ed29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,22 +82,29 @@ addopts = [ "--self-contained-html", "--json-report", "--json-report-file=tests/reports/report.json", + "-v", # Verbose output ] testpaths = ["tests"] python_files = ["test_*.py", "*_test.py"] python_classes = ["Test*"] python_functions = ["test_*"] asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" markers = [ - "unit: Unit tests", - "integration: Integration tests", - "e2e: End-to-end tests", - "auth: Authentication tests", - "servers: Server management tests", - "search: Search and AI tests", + "unit: Unit tests (isolated components)", + "integration: Integration tests (component interaction)", + "e2e: End-to-end tests (complete workflows)", + "auth: Authentication and authorization tests", + "servers: Server management tests", + "search: Search and FAISS tests", "health: Health monitoring tests", "core: Core infrastructure tests", - "slow: Slow running tests", + "cli: CLI tools tests", + "scopes: Scopes manager tests", + "internal_api: Internal API tests", + "gateway: MCP Gateway server tests", + "slow: Slow running tests (>5s)", + "critical: Critical path tests (require 95% coverage)", ] # Coverage Configuration diff --git a/tests/reporting/test_reporting_system.py b/tests/reporting/test_reporting_system.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/auth/test_auth_routes.py b/tests/unit/auth/test_auth_routes.py index ea497226..a5c23145 100644 --- a/tests/unit/auth/test_auth_routes.py +++ b/tests/unit/auth/test_auth_routes.py @@ -15,7 +15,8 @@ oauth2_login_redirect, oauth2_callback, login_submit, - logout + logout_get, + logout_post ) @@ -275,9 +276,25 @@ async def test_login_submit_failure(self): assert "Invalid+username+or+password" in response.headers["location"] @pytest.mark.asyncio - async def test_logout(self, mock_settings): - """Test logout functionality.""" - response = await logout() + async def test_logout_get(self, mock_request, mock_settings): + """Test logout functionality via GET.""" + response = await logout_get(mock_request, session=None) + + assert isinstance(response, RedirectResponse) + assert response.status_code == 303 + assert response.headers["location"] == "/login" + + # Check that cookie deletion header is present + cookie_headers = [h for h in response.raw_headers if h[0] == b'set-cookie'] + assert len(cookie_headers) > 0 + cookie_value = cookie_headers[0][1].decode() + assert mock_settings.session_cookie_name in cookie_value + assert "expires=" in cookie_value.lower() # Cookie deletion sets expires in past + + @pytest.mark.asyncio + async def test_logout_post(self, mock_request, mock_settings): + """Test logout functionality via POST.""" + response = await logout_post(mock_request, session=None) assert isinstance(response, RedirectResponse) assert response.status_code == 303 diff --git a/tests/unit/services/test_access_control_service.py b/tests/unit/services/test_access_control_service.py deleted file mode 100644 index e67647f7..00000000 --- a/tests/unit/services/test_access_control_service.py +++ /dev/null @@ -1,160 +0,0 @@ -""" -Unit tests for access control service. -""" -import pytest -from unittest.mock import Mock, patch, mock_open -import yaml - -from registry.services.access_control_service import AccessControlService - - -@pytest.mark.unit -@pytest.mark.auth -class TestAccessControlService: - """Test suite for access control service.""" - - @pytest.fixture - def mock_scopes_config(self): - """Mock scopes configuration for testing.""" - return { - 'mcp-servers-unrestricted/read': [ - {'server': 'auth_server', 'methods': ['initialize', 'tools/list'], 'tools': ['validate_request']}, - {'server': 'currenttime', 'methods': ['initialize', 'tools/list'], 'tools': ['current_time_by_timezone']}, - {'server': 'mcpgw', 'methods': ['initialize', 'tools/list'], 'tools': ['intelligent_tool_finder']}, - {'server': 'fininfo', 'methods': ['initialize', 'tools/list'], 'tools': ['get_stock_aggregates']}, - ], - 'mcp-servers-unrestricted/execute': [ - {'server': 'auth_server', 'methods': ['tools/call'], 'tools': ['validate_request']}, - {'server': 'currenttime', 'methods': ['tools/call'], 'tools': ['current_time_by_timezone']}, - {'server': 'mcpgw', 'methods': ['tools/call'], 'tools': ['intelligent_tool_finder']}, - {'server': 'fininfo', 'methods': ['tools/call'], 'tools': ['get_stock_aggregates']}, - ], - 'mcp-servers-restricted/read': [ - {'server': 'auth_server', 'methods': ['initialize', 'tools/list'], 'tools': ['validate_request']}, - {'server': 'currenttime', 'methods': ['initialize', 'tools/list'], 'tools': ['current_time_by_timezone']}, - {'server': 'fininfo', 'methods': ['initialize', 'tools/list'], 'tools': ['get_stock_aggregates']}, - ], - 'mcp-servers-restricted/execute': [ - {'server': 'currenttime', 'methods': ['tools/call'], 'tools': ['current_time_by_timezone']}, - {'server': 'fininfo', 'methods': ['tools/call'], 'tools': ['get_stock_aggregates']}, - ] - } - - @pytest.fixture - def access_control_service_with_config(self, mock_scopes_config): - """Create access control service with mock configuration.""" - with patch('builtins.open', mock_open(read_data=yaml.dump(mock_scopes_config))): - with patch('pathlib.Path.exists', return_value=True): - service = AccessControlService() - return service - - def test_get_user_scopes_admin(self): - """Test scope mapping for admin users.""" - service = AccessControlService() - groups = ['mcp-admin'] - scopes = service.get_user_scopes(groups) - - expected_scopes = ['mcp-servers-unrestricted/read', 'mcp-servers-unrestricted/execute'] - assert scopes == expected_scopes - - def test_get_user_scopes_regular_user(self): - """Test scope mapping for regular users.""" - service = AccessControlService() - groups = ['mcp-user'] - scopes = service.get_user_scopes(groups) - - expected_scopes = ['mcp-servers-restricted/read'] - assert scopes == expected_scopes - - def test_get_user_scopes_server_specific(self): - """Test scope mapping for server-specific groups.""" - service = AccessControlService() - groups = ['mcp-server-currenttime'] - scopes = service.get_user_scopes(groups) - - expected_scopes = ['mcp-servers-restricted/execute'] - assert scopes == expected_scopes - - def test_get_user_scopes_multiple_groups(self): - """Test scope mapping for users with multiple groups.""" - service = AccessControlService() - groups = ['mcp-user', 'mcp-server-currenttime'] - scopes = service.get_user_scopes(groups) - - expected_scopes = ['mcp-servers-restricted/read', 'mcp-servers-restricted/execute'] - assert scopes == expected_scopes - - def test_get_accessible_servers_admin(self, access_control_service_with_config): - """Test accessible servers for admin users.""" - groups = ['mcp-admin'] - accessible = access_control_service_with_config.get_accessible_servers(groups) - - expected_servers = {'auth_server', 'currenttime', 'mcpgw', 'fininfo'} - assert accessible == expected_servers - - def test_get_accessible_servers_regular_user(self, access_control_service_with_config): - """Test accessible servers for regular users.""" - groups = ['mcp-user'] - accessible = access_control_service_with_config.get_accessible_servers(groups) - - expected_servers = {'auth_server', 'currenttime', 'fininfo'} - assert accessible == expected_servers - - def test_get_accessible_servers_no_groups(self, access_control_service_with_config): - """Test accessible servers for users with no groups.""" - groups = [] - accessible = access_control_service_with_config.get_accessible_servers(groups) - - assert accessible == set() - - def test_can_user_access_server_admin(self, access_control_service_with_config): - """Test server access for admin users.""" - groups = ['mcp-admin'] - - # Admin should have access to all servers - assert access_control_service_with_config.can_user_access_server('auth_server', groups) - assert access_control_service_with_config.can_user_access_server('currenttime', groups) - assert access_control_service_with_config.can_user_access_server('mcpgw', groups) - assert access_control_service_with_config.can_user_access_server('fininfo', groups) - - def test_can_user_access_server_regular_user(self, access_control_service_with_config): - """Test server access for regular users.""" - groups = ['mcp-user'] - - # Regular user should have access to restricted servers - assert access_control_service_with_config.can_user_access_server('auth_server', groups) - assert access_control_service_with_config.can_user_access_server('currenttime', groups) - assert access_control_service_with_config.can_user_access_server('fininfo', groups) - - # But not to unrestricted servers like mcpgw - assert not access_control_service_with_config.can_user_access_server('mcpgw', groups) - - def test_can_user_access_server_no_config(self): - """Test server access when no configuration is loaded.""" - with patch('pathlib.Path.exists', return_value=False): - service = AccessControlService() - - # Should allow access when no config is available (fail open) - assert service.can_user_access_server('any_server', ['any_group']) - - def test_can_user_access_server_unknown_server(self, access_control_service_with_config): - """Test access to unknown server.""" - groups = ['mcp-user'] - - # Unknown server should be denied access - assert not access_control_service_with_config.can_user_access_server('unknown_server', groups) - - def test_reload_config(self, mock_scopes_config): - """Test configuration reload functionality.""" - with patch('builtins.open', mock_open(read_data=yaml.dump(mock_scopes_config))): - with patch('pathlib.Path.exists', return_value=True): - service = AccessControlService() - - # Verify initial config - assert service._scopes_config is not None - - # Reload config - service.reload_config() - - # Verify config is still loaded - assert service._scopes_config is not None \ No newline at end of file