"""Tests for app/views/status.py module.""" from unittest.mock import MagicMock, Mock, mock_open, patch import pytest @pytest.mark.unit class TestRequireAdmin: """Unit tests for the _require_admin helper in views/status.py.""" def test_returns_none_when_no_user_in_session(self): """_require_admin returns None when session has no user.""" from app.views.status import _require_admin mock_request = MagicMock() mock_request.session = {} result = _require_admin(mock_request) assert result is None def test_returns_none_for_non_admin_user(self): """_require_admin returns None when user is not an admin.""" from app.views.status import _require_admin mock_request = MagicMock() mock_request.session = {"user": {"email": "user@example.com", "is_admin": False}} result = _require_admin(mock_request) assert result is None def test_logs_warning_for_non_admin(self): """_require_admin logs a warning when a non-admin attempts access.""" from app.views.status import _require_admin mock_request = MagicMock() mock_request.session = {"user": {"email": "user@example.com", "is_admin": False}} with patch("app.views.status.logger") as mock_logger: _require_admin(mock_request) mock_logger.warning.assert_called_once() def test_logs_warning_when_no_user(self): """_require_admin logs a warning when there is no user in the session.""" from app.views.status import _require_admin mock_request = MagicMock() mock_request.session = {} with patch("app.views.status.logger") as mock_logger: _require_admin(mock_request) mock_logger.warning.assert_called_once() def test_returns_user_for_admin(self): """_require_admin returns the user dict when the user is an admin.""" from app.views.status import _require_admin admin_user = {"email": "admin@example.com", "is_admin": True} mock_request = MagicMock() mock_request.session = {"user": admin_user} result = _require_admin(mock_request) assert result == admin_user @pytest.mark.integration class TestStatusViews: """Tests for status view routes.""" def test_status_dashboard(self, client): """Test status dashboard page returns 200 (auth disabled in tests).""" response = client.get("/status") assert response.status_code == 200 @pytest.mark.unit class TestStatusDashboard: """Tests for status_dashboard function.""" @pytest.mark.asyncio async def test_redirects_non_admin_to_home(self): """status_dashboard redirects to '/' when user is not an admin.""" from fastapi.responses import RedirectResponse from app.views.status import status_dashboard mock_request = MagicMock() mock_request.session = {"user": {"email": "user@example.com", "is_admin": False}} result = await status_dashboard(mock_request) assert isinstance(result, RedirectResponse) assert result.status_code == 302 assert result.headers["location"] == "/" @pytest.mark.asyncio async def test_redirects_when_no_user_in_session(self): """status_dashboard redirects to '/' when no user is in the session.""" from fastapi.responses import RedirectResponse from app.views.status import status_dashboard mock_request = MagicMock() mock_request.session = {} result = await status_dashboard(mock_request) assert isinstance(result, RedirectResponse) assert result.status_code == 302 @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @pytest.mark.asyncio async def test_returns_template_for_admin_user(self, mock_exists, mock_settings, mock_templates, mock_providers): """status_dashboard renders the template for an authenticated admin user.""" from app.views.status import status_dashboard mock_exists.return_value = False mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.debug = False mock_settings.git_sha = "abc123" mock_settings.notification_urls = [] mock_request = MagicMock() mock_request.session = {"user": {"email": "admin@example.com", "is_admin": True}} await status_dashboard(mock_request) mock_templates.TemplateResponse.assert_called_once() call_args = mock_templates.TemplateResponse.call_args assert call_args[0][0] == "status_dashboard.html" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @pytest.mark.asyncio async def test_status_dashboard_returns_template(self, mock_exists, mock_settings, mock_templates, mock_providers): """Test status dashboard returns template response.""" from app.views.status import status_dashboard mock_exists.return_value = False mock_providers.return_value = { "AI Provider": {"configured": True, "status": "success"}, "Azure AI": {"configured": False}, } mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.debug = False mock_settings.git_sha = "abc123" mock_settings.runtime_info = "Python 3.11" mock_settings.notification_urls = [] mock_request = Mock() result = await status_dashboard(mock_request) mock_templates.TemplateResponse.assert_called_once() call_args = mock_templates.TemplateResponse.call_args assert call_args[0][0] == "status_dashboard.html" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @patch("builtins.open", new_callable=mock_open, read_data="12:docker:/container_id") @pytest.mark.asyncio async def test_detects_docker_environment( self, mock_file, mock_exists, mock_settings, mock_templates, mock_providers ): """Test detects Docker environment.""" from app.views.status import status_dashboard mock_exists.return_value = True mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = "abc123" mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] assert context["container_info"]["is_docker"] is True @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @pytest.mark.asyncio async def test_handles_non_docker_environment(self, mock_exists, mock_settings, mock_templates, mock_providers): """Test handles non-Docker environment.""" from app.views.status import status_dashboard mock_exists.return_value = False mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = "abc123" mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] assert context["container_info"]["is_docker"] is False @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @pytest.mark.asyncio async def test_includes_git_sha_in_context(self, mock_settings, mock_templates, mock_providers): """Test includes git SHA in context.""" from app.views.status import status_dashboard mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = "abc1234567890" mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] assert "git_sha" in context["container_info"] @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @pytest.mark.asyncio async def test_includes_notification_urls(self, mock_settings, mock_templates, mock_providers): """Test includes notification URLs in context.""" from app.views.status import status_dashboard mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = "abc123" mock_settings.notification_urls = ["https://webhook.example.com/notify"] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] assert context["settings"]["notification_urls"] == ["https://webhook.example.com/notify"] @pytest.mark.unit class TestContainerInfoDetection: """Tests for container information detection logic.""" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @patch("builtins.open", side_effect=IOError("Permission denied")) @pytest.mark.asyncio async def test_handles_cgroup_read_error( self, mock_file, mock_exists, mock_settings, mock_templates, mock_providers ): """Test handles cgroup file read errors.""" from app.views.status import status_dashboard mock_exists.return_value = True # Docker env exists mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = "abc123" mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] # Should handle error gracefully and set id to Unknown assert context["container_info"]["is_docker"] is True assert context["container_info"]["id"] == "Unknown" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @patch("builtins.open", new_callable=mock_open, read_data="12:cpuset:/system.slice\n13:memory:/user.slice") @pytest.mark.asyncio async def test_handles_cgroup_without_docker( self, mock_file, mock_exists, mock_settings, mock_templates, mock_providers ): """Test handles cgroup without docker in path.""" from app.views.status import status_dashboard mock_exists.return_value = True # Docker env exists mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = "abc123" mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] # When docker not found in cgroup, id is not set in the code # The loop completes without setting id, so it won't be in container_info assert context["container_info"]["is_docker"] is True @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @patch("builtins.open", new_callable=mock_open, read_data="12:docker:/abc123456789") @pytest.mark.asyncio async def test_handles_unknown_git_sha_string( self, mock_file, mock_exists, mock_settings, mock_templates, mock_providers ): """Test handles 'unknown' git_sha string value.""" from app.views.status import status_dashboard mock_exists.return_value = True mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = "unknown" mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] # Should handle "unknown" string and set to Unknown assert context["container_info"]["git_sha"] == "Unknown" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @pytest.mark.asyncio async def test_handles_complete_exception_in_container_info( self, mock_exists, mock_settings, mock_templates, mock_providers ): """Test handles complete exception in container info extraction.""" from app.views.status import status_dashboard # Simulate exception when checking Docker env mock_exists.side_effect = Exception("Unexpected error") mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] # Should have fallback container_info assert context["container_info"]["is_docker"] is False assert context["container_info"]["id"] == "Unknown" assert context["container_info"]["git_sha"] == "Unknown" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.settings") @patch("app.views.status.os.path.exists") @pytest.mark.asyncio async def test_handles_null_git_sha(self, mock_exists, mock_settings, mock_templates, mock_providers): """Test handles None/null git_sha value.""" from app.views.status import status_dashboard mock_exists.return_value = False # Not in Docker mock_providers.return_value = {} mock_settings.version = "1.0.0" mock_settings.build_date = "2024-01-01" mock_settings.git_sha = None # None value mock_settings.notification_urls = [] mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] # Should handle None and set to Unknown assert context["container_info"]["git_sha"] == "Unknown" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.os.path.exists") @patch("builtins.open", new_callable=mock_open, read_data="12:docker:/abc123456789") @pytest.mark.asyncio async def test_handles_git_sha_exception_in_docker(self, mock_file, mock_exists, mock_templates, mock_providers): """Test handles exception when reading git_sha in Docker environment (lines 53-54).""" import types from app.views.status import status_dashboard mock_exists.return_value = True mock_providers.return_value = {} # SimpleNamespace without git_sha causes AttributeError on access → triggers except block fake_settings = types.SimpleNamespace(version="1.0.0") with patch("app.views.status.settings", fake_settings): mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] assert context["container_info"]["is_docker"] is True assert context["container_info"]["git_sha"] == "Unknown" @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.os.path.exists") @patch("builtins.open", new_callable=mock_open, read_data="12:docker:/abc123456789") @pytest.mark.asyncio async def test_handles_runtime_info_exception_in_docker( self, mock_file, mock_exists, mock_templates, mock_providers ): """Test handles exception when reading runtime_info in Docker environment (lines 59-60).""" import types from app.views.status import status_dashboard mock_exists.return_value = True mock_providers.return_value = {} # SimpleNamespace has git_sha but not runtime_info → triggers except block at line 59-60 fake_settings = types.SimpleNamespace(version="1.0.0", git_sha="abc1234567890") with patch("app.views.status.settings", fake_settings): mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] assert context["container_info"]["is_docker"] is True # runtime_info should not be in container_info when it raises an exception assert "runtime_info" not in context["container_info"] @patch("app.views.status.get_provider_status") @patch("app.views.status.templates") @patch("app.views.status.os.path.exists") @pytest.mark.asyncio async def test_handles_git_sha_exception_in_non_docker(self, mock_exists, mock_templates, mock_providers): """Test handles exception when reading git_sha in non-Docker environment (lines 68-69).""" import types from app.views.status import status_dashboard mock_exists.return_value = False # Not in Docker mock_providers.return_value = {} # SimpleNamespace without git_sha causes AttributeError on access → triggers except block fake_settings = types.SimpleNamespace(version="1.0.0") with patch("app.views.status.settings", fake_settings): mock_request = Mock() await status_dashboard(mock_request) call_args = mock_templates.TemplateResponse.call_args context = call_args[0][1] assert context["container_info"]["is_docker"] is False assert context["container_info"]["git_sha"] == "Unknown" @pytest.mark.integration class TestStatusEndpointsRequireAuth: """Tests for status endpoint authentication.""" def test_status_dashboard_requires_login(self, client): """Test status dashboard requires authentication.""" # Should return 200 or redirect to login response = client.get("/status", follow_redirects=False) assert response.status_code in [200, 302, 401]