"""Tests for app/api/settings.py module.""" from unittest.mock import MagicMock, patch import pytest from fastapi import HTTPException from app.api.settings import require_admin @pytest.mark.unit class TestRequireAdmin: """Tests for require_admin dependency.""" def test_raises_403_when_no_user(self): """Test that 403 is raised when no user in session.""" mock_request = MagicMock() mock_request.session = {} with pytest.raises(HTTPException) as exc_info: require_admin(mock_request) assert exc_info.value.status_code == 403 def test_raises_403_when_not_admin(self): """Test that 403 is raised for non-admin user.""" mock_request = MagicMock() mock_request.session = {"user": {"id": "1", "is_admin": False}} with pytest.raises(HTTPException) as exc_info: require_admin(mock_request) assert exc_info.value.status_code == 403 def test_returns_user_when_admin(self): """Test that admin user is returned.""" mock_request = MagicMock() user = {"id": "admin", "is_admin": True} mock_request.session = {"user": user} result = require_admin(mock_request) assert result == user def test_raises_403_with_correct_detail_message(self): """Test that 403 includes correct detail message.""" mock_request = MagicMock() mock_request.session = {} with pytest.raises(HTTPException) as exc_info: require_admin(mock_request) assert exc_info.value.detail == "Admin access required" @pytest.mark.integration class TestSettingsEndpoints: """Integration tests for settings API endpoints.""" def test_get_settings_requires_admin(self, client): """Test GET /settings requires admin access.""" response = client.get("/api/settings/") assert response.status_code in [302, 401, 403] def test_get_single_setting_requires_admin(self, client): """Test GET /settings/{key} requires admin access.""" response = client.get("/api/settings/workdir") assert response.status_code in [302, 401, 403] def test_update_setting_requires_admin(self, client): """Test POST /settings/{key} requires admin access.""" response = client.post("/api/settings/test_key", json={"key": "test_key", "value": "test_value"}) assert response.status_code in [302, 401, 403] def test_delete_setting_requires_admin(self, client): """Test DELETE /settings/{key} requires admin access.""" response = client.delete("/api/settings/test_key") assert response.status_code in [302, 401, 403] def test_bulk_update_requires_admin(self, client): """Test POST /settings/bulk-update requires admin access.""" response = client.post("/api/settings/bulk-update", json=[{"key": "test_key", "value": "test_value"}]) assert response.status_code in [302, 401, 403] @pytest.mark.integration class TestSettingsEndpointsWithAuth: """Integration tests for settings endpoints with authentication.""" @patch("app.api.settings.get_all_settings_from_db") @patch("app.api.settings.get_settings_by_category") @patch("app.api.settings.get_setting_metadata") def test_get_all_settings_success(self, mock_metadata, mock_categories, mock_db_settings, client, db_session): """Test GET /settings returns all settings.""" from app.api.settings import require_admin from app.main import app as fastapi_app # Mock the settings data mock_db_settings.return_value = {"test_key": "test_value"} mock_categories.return_value = {"General": ["workdir", "debug"]} mock_metadata.return_value = {"type": "str", "description": "Test setting"} # Override the require_admin dependency to return a mock admin user def override_require_admin(): return {"id": "admin", "is_admin": True} fastapi_app.dependency_overrides[require_admin] = override_require_admin try: # Make the request with the mocked admin dependency response = client.get("/api/settings/") assert response.status_code == 200 data = response.json() assert "settings" in data assert "categories" in data assert "db_settings" in data finally: # Clean up the override fastapi_app.dependency_overrides.pop(require_admin, None) @patch("app.api.settings.get_setting_metadata") def test_get_single_setting_returns_metadata(self, mock_metadata, client): """Test GET /settings/{key} returns setting with metadata.""" mock_metadata.return_value = {"type": "str", "description": "Working directory"} # Without admin auth, should be 403 response = client.get("/api/settings/workdir") assert response.status_code in [302, 401, 403] @patch("app.api.settings.validate_setting_value") @patch("app.api.settings.save_setting_to_db") @patch("app.api.settings.get_setting_metadata") def test_update_setting_validates_value(self, mock_metadata, mock_save, mock_validate, client): """Test POST /settings/{key} validates setting value.""" mock_validate.return_value = (False, "Invalid value") mock_metadata.return_value = {"restart_required": False} # Without admin auth, should be 403 response = client.post("/api/settings/test_key", json={"key": "test_key", "value": "invalid"}) assert response.status_code in [302, 401, 403] @patch("app.api.settings.delete_setting_from_db") def test_delete_setting_handles_not_found(self, mock_delete, client): """Test DELETE /settings/{key} handles not found.""" mock_delete.return_value = False # Without admin auth, should be 403 response = client.delete("/api/settings/nonexistent_key") assert response.status_code in [302, 401, 403] @patch("app.api.settings.validate_setting_value") @patch("app.api.settings.save_setting_to_db") @patch("app.api.settings.get_setting_metadata") def test_bulk_update_processes_multiple_settings(self, mock_metadata, mock_save, mock_validate, client): """Test POST /settings/bulk-update processes multiple settings.""" mock_validate.return_value = (True, None) mock_save.return_value = True mock_metadata.return_value = {"restart_required": False} updates = [{"key": "setting1", "value": "value1"}, {"key": "setting2", "value": "value2"}] # Without admin auth, should be 403 response = client.post("/api/settings/bulk-update", json=updates) assert response.status_code in [302, 401, 403] @pytest.mark.unit class TestSettingModels: """Tests for Pydantic models.""" def test_setting_update_model(self): """Test SettingUpdate model.""" from app.api.settings import SettingUpdate update = SettingUpdate(key="test_key", value="test_value") assert update.key == "test_key" assert update.value == "test_value" def test_setting_update_model_with_none_value(self): """Test SettingUpdate model with None value.""" from app.api.settings import SettingUpdate update = SettingUpdate(key="test_key", value=None) assert update.key == "test_key" assert update.value is None def test_setting_response_model(self): """Test SettingResponse model.""" from app.api.settings import SettingResponse response = SettingResponse( key="test_key", value="test_value", metadata={"type": "str", "description": "Test setting"} ) assert response.key == "test_key" assert response.value == "test_value" assert response.metadata["type"] == "str" def test_settings_list_response_model(self): """Test SettingsListResponse model.""" from app.api.settings import SettingsListResponse response = SettingsListResponse( settings={"test_key": {"value": "test_value", "metadata": {}}}, categories={"General": ["test_key"]}, db_settings={"test_key": "test_value"}, ) assert "test_key" in response.settings assert "General" in response.categories assert "test_key" in response.db_settings