""" Coverage-targeted tests for app/api/onedrive.py Focuses on uncovered lines: 98-99, 121-143, 160-161, 170-171, 324-326, 400-402, 436-438. """ from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch import pytest from fastapi.testclient import TestClient @pytest.mark.unit class TestTestTokenRefreshFailed: """Cover lines 98-99: token refresh returns non-200.""" @patch("httpx.AsyncClient.post", new_callable=AsyncMock) def test_test_token_refresh_returns_non_200(self, mock_post, client: TestClient): """Test token refresh returning a failure status hits the error branch.""" from app.config import settings mock_resp = MagicMock() mock_resp.status_code = 400 mock_resp.text = "invalid_grant" mock_post.return_value = mock_resp with ( patch.object(settings, "onedrive_refresh_token", "tok"), patch.object(settings, "onedrive_client_id", "cid"), patch.object(settings, "onedrive_client_secret", "sec"), ): response = client.get("/api/onedrive/test-token") assert response.status_code == 200 data = response.json() assert data["status"] == "error" assert data["needs_reauth"] is True @pytest.mark.unit class TestTestTokenRotation: """Cover lines 121-143, 160-161: token rotation with .env and DB persist.""" @patch("httpx.AsyncClient.get", new_callable=AsyncMock) @patch("httpx.AsyncClient.post", new_callable=AsyncMock) def test_token_rotation_env_file_exists(self, mock_post, mock_get, client: TestClient, tmp_path): """When a new refresh token is received and .env file exists, it should be updated.""" from app.config import settings # Create a temporary .env file env_file = tmp_path / ".env" env_file.write_text("ONEDRIVE_REFRESH_TOKEN=old_token\nOTHER=value\n") # Mock token refresh returning a new refresh token mock_post_resp = MagicMock() mock_post_resp.status_code = 200 mock_post_resp.json.return_value = { "access_token": "new_access", "refresh_token": "brand_new_token", "expires_in": 3600, } mock_post.return_value = mock_post_resp # Mock user info mock_get_resp = MagicMock() mock_get_resp.status_code = 200 mock_get_resp.json.return_value = { "displayName": "Test User", "userPrincipalName": "test@example.com", } mock_get.return_value = mock_get_resp with ( patch.object(settings, "onedrive_refresh_token", "old_token"), patch.object(settings, "onedrive_client_id", "cid"), patch.object(settings, "onedrive_client_secret", "sec"), patch("app.utils.env_utils.os.path.join", return_value=str(env_file)), patch("app.utils.env_utils.os.path.exists", return_value=True), patch("app.database.SessionLocal") as mock_session_local, patch("app.api.onedrive.save_setting_to_db"), patch("app.api.onedrive.notify_settings_updated"), ): mock_db = MagicMock() mock_session_local.return_value = mock_db response = client.get("/api/onedrive/test-token") assert response.status_code == 200 data = response.json() assert data["status"] == "success" @patch("httpx.AsyncClient.get", new_callable=AsyncMock) @patch("httpx.AsyncClient.post", new_callable=AsyncMock) def test_token_rotation_env_not_existing(self, mock_post, mock_get, client: TestClient): """Token rotation when .env doesn't exist still succeeds.""" from app.config import settings mock_post_resp = MagicMock() mock_post_resp.status_code = 200 mock_post_resp.json.return_value = { "access_token": "new_access", "refresh_token": "brand_new_token", "expires_in": 3600, } mock_post.return_value = mock_post_resp mock_get_resp = MagicMock() mock_get_resp.status_code = 200 mock_get_resp.json.return_value = { "displayName": "Test User", "userPrincipalName": "test@example.com", } mock_get.return_value = mock_get_resp with ( patch.object(settings, "onedrive_refresh_token", "old_token"), patch.object(settings, "onedrive_client_id", "cid"), patch.object(settings, "onedrive_client_secret", "sec"), patch("app.utils.env_utils.os.path.exists", return_value=False), patch("app.database.SessionLocal") as mock_session_local, patch("app.api.onedrive.save_setting_to_db"), patch("app.api.onedrive.notify_settings_updated"), ): mock_db = MagicMock() mock_session_local.return_value = mock_db response = client.get("/api/onedrive/test-token") assert response.status_code == 200 assert response.json()["status"] == "success" @patch("httpx.AsyncClient.get", new_callable=AsyncMock) @patch("httpx.AsyncClient.post", new_callable=AsyncMock) def test_token_rotation_env_write_failure(self, mock_post, mock_get, client: TestClient): """Token rotation when .env write fails (lines 142-143) still continues.""" from app.config import settings mock_post_resp = MagicMock() mock_post_resp.status_code = 200 mock_post_resp.json.return_value = { "access_token": "new_access", "refresh_token": "brand_new_token", "expires_in": 3600, } mock_post.return_value = mock_post_resp mock_get_resp = MagicMock() mock_get_resp.status_code = 200 mock_get_resp.json.return_value = { "displayName": "Test User", "userPrincipalName": "test@example.com", } mock_get.return_value = mock_get_resp with ( patch.object(settings, "onedrive_refresh_token", "old_token"), patch.object(settings, "onedrive_client_id", "cid"), patch.object(settings, "onedrive_client_secret", "sec"), patch("app.utils.env_utils.os.path.exists", return_value=True), patch("builtins.open", side_effect=PermissionError("Permission denied")), patch("app.database.SessionLocal") as mock_session_local, patch("app.api.onedrive.save_setting_to_db"), patch("app.api.onedrive.notify_settings_updated"), ): mock_db = MagicMock() mock_session_local.return_value = mock_db response = client.get("/api/onedrive/test-token") assert response.status_code == 200 assert response.json()["status"] == "success" @patch("httpx.AsyncClient.get", new_callable=AsyncMock) @patch("httpx.AsyncClient.post", new_callable=AsyncMock) def test_token_rotation_db_persist_failure(self, mock_post, mock_get, client: TestClient): """Token rotation when DB persist fails (lines 160-161) still continues.""" from app.config import settings mock_post_resp = MagicMock() mock_post_resp.status_code = 200 mock_post_resp.json.return_value = { "access_token": "new_access", "refresh_token": "brand_new_token", "expires_in": 3600, } mock_post.return_value = mock_post_resp mock_get_resp = MagicMock() mock_get_resp.status_code = 200 mock_get_resp.json.return_value = { "displayName": "Test User", "userPrincipalName": "test@example.com", } mock_get.return_value = mock_get_resp with ( patch.object(settings, "onedrive_refresh_token", "old_token"), patch.object(settings, "onedrive_client_id", "cid"), patch.object(settings, "onedrive_client_secret", "sec"), patch("app.utils.env_utils.os.path.exists", return_value=False), patch("app.database.SessionLocal", side_effect=Exception("DB error")), ): response = client.get("/api/onedrive/test-token") assert response.status_code == 200 assert response.json()["status"] == "success" @pytest.mark.unit class TestTestTokenUserInfoFailed: """Cover lines 170-171: user info request fails.""" @patch("httpx.AsyncClient.get", new_callable=AsyncMock) @patch("httpx.AsyncClient.post", new_callable=AsyncMock) def test_user_info_returns_non_200(self, mock_post, mock_get, client: TestClient): """Test when user info request fails after successful token refresh.""" from app.config import settings mock_post_resp = MagicMock() mock_post_resp.status_code = 200 mock_post_resp.json.return_value = { "access_token": "tok", "expires_in": 3600, } mock_post.return_value = mock_post_resp mock_get_resp = MagicMock() mock_get_resp.status_code = 401 mock_get_resp.text = "Unauthorized" mock_get.return_value = mock_get_resp with ( patch.object(settings, "onedrive_refresh_token", "tok"), patch.object(settings, "onedrive_client_id", "cid"), patch.object(settings, "onedrive_client_secret", "sec"), ): response = client.get("/api/onedrive/test-token") assert response.status_code == 200 data = response.json() assert data["status"] == "error" assert "401" in data["message"] @pytest.mark.unit class TestTokenRotationEnvAppendLine: """Cover the branch at line 134 where token line is not found in .env and must be appended.""" @patch("httpx.AsyncClient.get", new_callable=AsyncMock) @patch("httpx.AsyncClient.post", new_callable=AsyncMock) def test_token_rotation_appends_to_env(self, mock_post, mock_get, client: TestClient, tmp_path): """When .env exists but doesn't have ONEDRIVE_REFRESH_TOKEN, it should append.""" from app.config import settings env_file = tmp_path / ".env" env_file.write_text("OTHER_KEY=value\n") mock_post_resp = MagicMock() mock_post_resp.status_code = 200 mock_post_resp.json.return_value = { "access_token": "new_access", "refresh_token": "brand_new_token", "expires_in": 3600, } mock_post.return_value = mock_post_resp mock_get_resp = MagicMock() mock_get_resp.status_code = 200 mock_get_resp.json.return_value = { "displayName": "Test User", "userPrincipalName": "test@example.com", } mock_get.return_value = mock_get_resp with ( patch.object(settings, "onedrive_refresh_token", "old_token"), patch.object(settings, "onedrive_client_id", "cid"), patch.object(settings, "onedrive_client_secret", "sec"), patch("app.utils.env_utils.os.path.join", return_value=str(env_file)), patch("app.utils.env_utils.os.path.exists", return_value=True), patch("app.database.SessionLocal") as mock_sl, patch("app.api.onedrive.save_setting_to_db"), patch("app.api.onedrive.notify_settings_updated"), ): mock_sl.return_value = MagicMock() response = client.get("/api/onedrive/test-token") assert response.status_code == 200 assert response.json()["status"] == "success" @pytest.mark.unit class TestSaveSettingsException: """Cover lines 324-326: save_onedrive_settings outer exception handler.""" def test_save_settings_outer_exception(self, client: TestClient): """Trigger the outer exception handler in save_onedrive_settings.""" with patch("app.api.onedrive.notify_settings_updated", side_effect=Exception("Unexpected boom")): response = client.post( "/api/onedrive/save-settings", data={ "refresh_token": "test_token", "client_id": "cid", "client_secret": "sec", }, ) assert response.status_code == 500 assert "Failed to save OneDrive settings" in response.json()["detail"] @pytest.mark.unit class TestUpdateSettingsException: """Cover lines 400-402: update_onedrive_settings outer exception handler.""" def test_update_settings_outer_exception(self, client: TestClient): """Trigger the outer exception handler in update_onedrive_settings.""" with patch("app.api.onedrive.save_setting_to_db", side_effect=Exception("DB crash")): response = client.post( "/api/onedrive/update-settings", data={ "refresh_token": "test_token", }, ) assert response.status_code == 500 assert "Failed to update OneDrive settings" in response.json()["detail"] @pytest.mark.unit class TestGetFullConfigException: """Cover lines 436-438: get_onedrive_full_config exception handler.""" def test_get_full_config_exception(self, client: TestClient): """Trigger the exception handler in get_onedrive_full_config.""" mock_settings = MagicMock() type(mock_settings).onedrive_client_id = PropertyMock(side_effect=Exception("boom")) with patch("app.api.onedrive.settings", mock_settings): response = client.get("/api/onedrive/get-full-config") assert response.status_code == 200 data = response.json() assert data["status"] == "error"