diff --git a/tests/test_upload_google_drive.py b/tests/test_upload_google_drive.py index 9cb4a8c1..6154b03e 100644 --- a/tests/test_upload_google_drive.py +++ b/tests/test_upload_google_drive.py @@ -65,6 +65,22 @@ class TestGetDriveServiceOAuth: with pytest.raises(RefreshError): get_drive_service_oauth() + @patch("app.tasks.upload_to_google_drive.OAuthCredentials") + @patch("app.tasks.upload_to_google_drive.settings") + def test_handles_generic_exception(self, mock_settings, mock_creds): + """Test handles generic exception during OAuth setup.""" + mock_settings.google_drive_client_id = "client_id" + mock_settings.google_drive_client_secret = "client_secret" + mock_settings.google_drive_refresh_token = "refresh_token" + + mock_credentials = Mock() + mock_credentials.refresh.side_effect = Exception("Network error") + mock_creds.return_value = mock_credentials + + result = get_drive_service_oauth() + + assert result is None + @pytest.mark.unit class TestGetGoogleDriveService: @@ -460,3 +476,98 @@ class TestUploadToGoogleDriveTask: call_args = mock_files.create.call_args file_metadata = call_args.kwargs["body"] assert file_metadata["parents"] == ["parent_folder_123"] + + @patch("app.tasks.upload_to_google_drive.os.path.splitext") + @patch("app.tasks.upload_to_google_drive.os.path.basename") + @patch("app.tasks.upload_to_google_drive.get_google_drive_service") + @patch("app.tasks.upload_to_google_drive.log_task_progress") + @patch("app.tasks.upload_to_google_drive.os.path.exists") + @patch("app.tasks.upload_to_google_drive.MediaFileUpload") + @patch("app.tasks.upload_to_google_drive.settings") + def test_skips_metadata_when_disabled( + self, mock_settings, mock_media, mock_exists, mock_log, mock_service, mock_basename, mock_splitext + ): + """Test skips metadata extraction when include_metadata is False.""" + mock_basename.return_value = "test.pdf" + mock_splitext.return_value = ("/tmp/test", ".pdf") + mock_exists.return_value = True + mock_settings.google_drive_folder_id = None + + mock_drive_service = Mock() + mock_files = Mock() + mock_create = Mock() + mock_execute = Mock( + return_value={ + "id": "file_123", + "name": "test.pdf", + "webViewLink": "https://drive.google.com/file/d/file_123", + } + ) + mock_create.execute = mock_execute + mock_files.create.return_value = mock_create + mock_drive_service.files.return_value = mock_files + mock_service.return_value = mock_drive_service + + mock_self = Mock() + mock_self.request.id = "test-task-id" + + result = upload_to_google_drive(mock_self, "/tmp/test.pdf", False) + + assert result["status"] == "Completed" + assert "metadata_included" not in result + + @patch("app.tasks.upload_to_google_drive.truncate_property_value") + @patch("app.tasks.upload_to_google_drive.os.path.splitext") + @patch("app.tasks.upload_to_google_drive.os.path.basename") + @patch("app.tasks.upload_to_google_drive.get_google_drive_service") + @patch("app.tasks.upload_to_google_drive.extract_metadata_from_file") + @patch("app.tasks.upload_to_google_drive.log_task_progress") + @patch("app.tasks.upload_to_google_drive.os.path.exists") + @patch("app.tasks.upload_to_google_drive.MediaFileUpload") + @patch("app.tasks.upload_to_google_drive.settings") + def test_handles_truncation_error_gracefully( + self, + mock_settings, + mock_media, + mock_exists, + mock_log, + mock_extract, + mock_service, + mock_basename, + mock_splitext, + mock_truncate, + ): + """Test handles truncation error gracefully and logs warning.""" + mock_basename.return_value = "test.pdf" + mock_splitext.return_value = ("/tmp/test", ".pdf") + mock_exists.return_value = True + mock_settings.google_drive_folder_id = None + + metadata = {"problematic_key": "value"} + mock_extract.return_value = metadata + mock_truncate.side_effect = Exception("Encoding error") + + mock_drive_service = Mock() + mock_files = Mock() + mock_create = Mock() + mock_execute = Mock( + return_value={ + "id": "file_123", + "name": "test.pdf", + "webViewLink": "https://drive.google.com/file/d/file_123", + } + ) + mock_create.execute = mock_execute + mock_files.create.return_value = mock_create + mock_drive_service.files.return_value = mock_files + mock_service.return_value = mock_drive_service + + mock_self = Mock() + mock_self.request.id = "test-task-id" + + result = upload_to_google_drive(mock_self, "/tmp/test.pdf", True) + + # Should complete successfully despite truncation error + assert result["status"] == "Completed" + # Should still include metadata flag + assert result["metadata_included"] is True diff --git a/tests/test_views_status.py b/tests/test_views_status.py index 956e7e84..7e70375b 100644 --- a/tests/test_views_status.py +++ b/tests/test_views_status.py @@ -236,31 +236,141 @@ class TestEnvDebug: 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") - def test_extracts_container_id(self, mock_file, mock_exists): - """Test extracts container ID from cgroup.""" + @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 = [] - # The container ID extraction logic is part of status_dashboard - # We test it indirectly through the function + mock_request = Mock() - @patch("app.views.status.os.path.exists") - def test_handles_missing_cgroup_file(self, mock_exists): - """Test handles missing cgroup file gracefully.""" + await status_dashboard(mock_request) - mock_exists.side_effect = [True, False] # Docker env exists, but cgroup doesn't - - # Should not raise exception + 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") - def test_includes_runtime_info_when_available(self, mock_settings): - """Test includes runtime info when available.""" + @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 - mock_settings.runtime_info = "Python 3.11.5 on Linux" + # 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 = [] - # Runtime info should be included in container_info + 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" @pytest.mark.integration