"""Test Clinic API authentication methods in source_preparer.""" from unittest.mock import AsyncMock, MagicMock, patch import pytest from services.config import InvestibookSettings from services.source_preparer import prepare_issue_source as prepare_alert_source @pytest.fixture(autouse=True) def clear_clinic_env(): with patch.dict( "os.environ", { "SKILL_CLINIC_API_KEY": "", "CLINIC_API_KEY": "", "INVESTIBOOK_CLINIC_API_KEY": "", }, ): yield @pytest.fixture def mock_settings_api_key(): """Mock settings with API key authentication.""" settings = MagicMock(spec=InvestibookSettings) settings.aiops_api_url = "http://test-guardian" settings.s3_bucket = "test-bucket" settings.aws_region = "us-west-2" settings.clinic_endpoint = "https://test-clinic.com" settings.clinic_api_key = "test-api-key" settings.clinic_cookie = "" settings.clinic_csrf_token = "" settings.jira_base_url = "https://test-jira.com" settings.jira_username = "test@example.com" settings.jira_api_token = "test-jira-token" settings.openai_api_key = "test-openai-key" settings.openai_model = "gpt-4o" return settings @pytest.fixture def mock_settings_cookie(): """Mock settings with cookie authentication.""" settings = MagicMock(spec=InvestibookSettings) settings.aiops_api_url = "http://test-guardian" settings.s3_bucket = "test-bucket" settings.aws_region = "us-west-2" settings.clinic_endpoint = "https://test-clinic.com" settings.clinic_api_key = "" settings.clinic_cookie = "session=test123; _csrf=abc" settings.clinic_csrf_token = "test-csrf-token" settings.jira_base_url = "https://test-jira.com" settings.jira_username = "test@example.com" settings.jira_api_token = "test-jira-token" settings.openai_api_key = "test-openai-key" settings.openai_model = "gpt-4o" return settings @pytest.mark.asyncio async def test_clinic_auth_with_api_key(mock_settings_api_key, tmp_path): """Test that API key authentication is used when configured.""" captured_headers = {} # Create proper mock responses mock_jira_response = MagicMock() mock_jira_response.raise_for_status = MagicMock() mock_jira_response.json = MagicMock( return_value={ "key": "O11Y-12345", "fields": { "customfield_10160": '{"labels": {"tidb_cluster_id": "test-cluster", "o11y_tenant_id": "test-tenant"}}' }, } ) mock_clinic_response = MagicMock() mock_clinic_response.raise_for_status = MagicMock() mock_clinic_response.json = MagicMock( return_value={ "items": [ { "clusterID": "test-cluster", "tenantID": "test-tenant", "orgID": "test-org", "clusterVersion": "v7.5.0", } ] } ) mock_guardian_response = MagicMock() mock_guardian_response.raise_for_status = MagicMock() mock_guardian_response.json = MagicMock( return_value={ "items": [ { "tenant_id": "test-tenant", "cluster_id": "test-cluster", "analysis_uuid": "test-uuid", "status": "COMPLETED", "created_at": "2024-01-01T00:00:00Z", } ] } ) async def mock_get(url, **kwargs): if "/issue/" in url: return mock_jira_response elif "dashboard/clusters" in url: captured_headers.update(kwargs.get("headers", {})) return mock_clinic_response return mock_guardian_response with patch("services.source_preparer.settings", mock_settings_api_key): with patch("httpx.AsyncClient") as mock_client_class: mock_client = AsyncMock() mock_client.get = mock_get mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=None) mock_client_class.return_value = mock_client # Mock S3 and file operations mock_s3_client = AsyncMock() mock_s3_obj = {"Body": AsyncMock()} mock_s3_obj["Body"].__aenter__ = AsyncMock( return_value=AsyncMock(read=AsyncMock(return_value=b'{"test": "data"}')) ) mock_s3_obj["Body"].__aexit__ = AsyncMock(return_value=None) mock_s3_client.get_object = AsyncMock(return_value=mock_s3_obj) mock_s3_client.__aenter__ = AsyncMock(return_value=mock_s3_client) mock_s3_client.__aexit__ = AsyncMock(return_value=None) mock_session = MagicMock() mock_session.client = MagicMock(return_value=mock_s3_client) with patch( "services.source_preparer.aioboto3.Session", return_value=mock_session ): with patch("pathlib.Path.mkdir"): with patch("pathlib.Path.write_bytes"): with patch("pathlib.Path.write_text"): with patch("shutil.copytree"): with patch("pathlib.Path.rglob", return_value=[]): prepare_tasks = {"task1": {"status": "pending"}} workspaces_dir = tmp_path / "workspaces" templates_dir = tmp_path / "templates" await prepare_alert_source( task_id="task1", issue_id="O11Y-12345", workspace_id="ws1", prepare_tasks=prepare_tasks, workspaces_dir=workspaces_dir, templates_dir=templates_dir, ) # Verify API key was used in Authorization header assert "Authorization" in captured_headers, f"Headers captured: {captured_headers}" assert captured_headers["Authorization"] == "Bearer test-api-key" assert "Cookie" not in captured_headers assert "X-CSRF-Token" not in captured_headers @pytest.mark.asyncio async def test_clinic_auth_with_cookie(mock_settings_cookie, tmp_path): """Test that cookie authentication is used when API key is empty.""" captured_headers = {} # Create proper mock responses mock_jira_response = MagicMock() mock_jira_response.raise_for_status = MagicMock() mock_jira_response.json = MagicMock( return_value={ "key": "O11Y-12345", "fields": { "customfield_10160": '{"labels": {"tidb_cluster_id": "test-cluster", "o11y_tenant_id": "test-tenant"}}' }, } ) mock_clinic_response = MagicMock() mock_clinic_response.raise_for_status = MagicMock() mock_clinic_response.json = MagicMock( return_value={ "items": [ { "clusterID": "test-cluster", "tenantID": "test-tenant", "orgID": "test-org", "clusterVersion": "v7.5.0", } ] } ) mock_guardian_response = MagicMock() mock_guardian_response.raise_for_status = MagicMock() mock_guardian_response.json = MagicMock( return_value={ "items": [ { "tenant_id": "test-tenant", "cluster_id": "test-cluster", "analysis_uuid": "test-uuid", "status": "COMPLETED", "created_at": "2024-01-01T00:00:00Z", } ] } ) async def mock_get(url, **kwargs): if "/issue/" in url: return mock_jira_response elif "dashboard/clusters" in url: captured_headers.update(kwargs.get("headers", {})) return mock_clinic_response return mock_guardian_response with patch("services.source_preparer.settings", mock_settings_cookie): with patch("httpx.AsyncClient") as mock_client_class: mock_client = AsyncMock() mock_client.get = mock_get mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=None) mock_client_class.return_value = mock_client # Mock S3 and file operations mock_s3_client = AsyncMock() mock_s3_obj = {"Body": AsyncMock()} mock_s3_obj["Body"].__aenter__ = AsyncMock( return_value=AsyncMock(read=AsyncMock(return_value=b'{"test": "data"}')) ) mock_s3_obj["Body"].__aexit__ = AsyncMock(return_value=None) mock_s3_client.get_object = AsyncMock(return_value=mock_s3_obj) mock_s3_client.__aenter__ = AsyncMock(return_value=mock_s3_client) mock_s3_client.__aexit__ = AsyncMock(return_value=None) mock_session = MagicMock() mock_session.client = MagicMock(return_value=mock_s3_client) with patch( "services.source_preparer.aioboto3.Session", return_value=mock_session ): with patch("pathlib.Path.mkdir"): with patch("pathlib.Path.write_bytes"): with patch("pathlib.Path.write_text"): with patch("shutil.copytree"): with patch("pathlib.Path.rglob", return_value=[]): prepare_tasks = {"task1": {"status": "pending"}} workspaces_dir = tmp_path / "workspaces" templates_dir = tmp_path / "templates" await prepare_alert_source( task_id="task1", issue_id="O11Y-12345", workspace_id="ws1", prepare_tasks=prepare_tasks, workspaces_dir=workspaces_dir, templates_dir=templates_dir, ) # Verify cookie and CSRF token were used assert "Cookie" in captured_headers, f"Headers captured: {captured_headers}" assert captured_headers["Cookie"] == "session=test123; _csrf=abc" assert "X-CSRF-Token" in captured_headers assert captured_headers["X-CSRF-Token"] == "test-csrf-token" assert "Authorization" not in captured_headers @pytest.mark.asyncio async def test_clinic_auth_api_key_takes_precedence(tmp_path): """Test that API key takes precedence over cookie when both are provided.""" settings = MagicMock(spec=InvestibookSettings) settings.aiops_api_url = "http://test-guardian" settings.s3_bucket = "test-bucket" settings.aws_region = "us-west-2" settings.clinic_endpoint = "https://test-clinic.com" settings.clinic_api_key = "test-api-key" settings.clinic_cookie = "session=test123" settings.clinic_csrf_token = "test-csrf-token" settings.jira_base_url = "https://test-jira.com" settings.jira_username = "test@example.com" settings.jira_api_token = "test-jira-token" settings.openai_api_key = "test-openai-key" settings.openai_model = "gpt-4o" captured_headers = {} # Create proper mock responses mock_jira_response = MagicMock() mock_jira_response.raise_for_status = MagicMock() mock_jira_response.json = MagicMock( return_value={ "key": "O11Y-12345", "fields": { "customfield_10160": '{"labels": {"tidb_cluster_id": "test-cluster", "o11y_tenant_id": "test-tenant"}}' }, } ) mock_clinic_response = MagicMock() mock_clinic_response.raise_for_status = MagicMock() mock_clinic_response.json = MagicMock( return_value={ "items": [ { "clusterID": "test-cluster", "tenantID": "test-tenant", "orgID": "test-org", "clusterVersion": "v7.5.0", } ] } ) mock_guardian_response = MagicMock() mock_guardian_response.raise_for_status = MagicMock() mock_guardian_response.json = MagicMock( return_value={ "items": [ { "tenant_id": "test-tenant", "cluster_id": "test-cluster", "analysis_uuid": "test-uuid", "status": "COMPLETED", "created_at": "2024-01-01T00:00:00Z", } ] } ) async def mock_get(url, **kwargs): if "/issue/" in url: return mock_jira_response elif "dashboard/clusters" in url: captured_headers.update(kwargs.get("headers", {})) return mock_clinic_response return mock_guardian_response with patch("services.source_preparer.settings", settings): with patch("httpx.AsyncClient") as mock_client_class: mock_client = AsyncMock() mock_client.get = mock_get mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=None) mock_client_class.return_value = mock_client # Mock S3 and file operations mock_s3_client = AsyncMock() mock_s3_obj = {"Body": AsyncMock()} mock_s3_obj["Body"].__aenter__ = AsyncMock( return_value=AsyncMock(read=AsyncMock(return_value=b'{"test": "data"}')) ) mock_s3_obj["Body"].__aexit__ = AsyncMock(return_value=None) mock_s3_client.get_object = AsyncMock(return_value=mock_s3_obj) mock_s3_client.__aenter__ = AsyncMock(return_value=mock_s3_client) mock_s3_client.__aexit__ = AsyncMock(return_value=None) mock_session = MagicMock() mock_session.client = MagicMock(return_value=mock_s3_client) with patch( "services.source_preparer.aioboto3.Session", return_value=mock_session ): with patch("pathlib.Path.mkdir"): with patch("pathlib.Path.write_bytes"): with patch("pathlib.Path.write_text"): with patch("shutil.copytree"): with patch("pathlib.Path.rglob", return_value=[]): prepare_tasks = {"task1": {"status": "pending"}} workspaces_dir = tmp_path / "workspaces" templates_dir = tmp_path / "templates" await prepare_alert_source( task_id="task1", issue_id="O11Y-12345", workspace_id="ws1", prepare_tasks=prepare_tasks, workspaces_dir=workspaces_dir, templates_dir=templates_dir, ) # Verify API key was used (not cookie) assert "Authorization" in captured_headers, f"Headers captured: {captured_headers}" assert captured_headers["Authorization"] == "Bearer test-api-key" assert "Cookie" not in captured_headers assert "X-CSRF-Token" not in captured_headers