Files
github-actions[bot] 724d7d870f style: apply ruff auto-fix
- Auto-formatted code with ruff format
- Applied ruff linting fixes with --fix

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-23 20:23:06 +00:00

405 lines
15 KiB
Python

"""Tests for app/tasks/check_credentials.py module."""
import json
import os
from unittest.mock import Mock, patch
import pytest
from app.tasks.check_credentials import (
MockRequest,
check_credentials,
get_failure_state,
save_failure_state,
sync_test_azure_connection,
sync_test_openai_connection,
unwrap_decorated_function,
)
@pytest.mark.unit
class TestMockRequest:
"""Tests for MockRequest class."""
def test_mock_request_has_session(self):
"""Test MockRequest has session attribute."""
req = MockRequest()
assert "user" in req.session
assert req.session["user"]["id"] == "credential_checker"
def test_mock_request_has_attributes(self):
"""Test MockRequest has required attributes."""
req = MockRequest()
assert req.app is None
assert isinstance(req.headers, dict)
assert isinstance(req.query_params, dict)
@pytest.mark.asyncio
async def test_mock_request_json(self):
"""Test MockRequest.json() returns empty dict."""
req = MockRequest()
result = await req.json()
assert result == {}
@pytest.mark.asyncio
async def test_mock_request_form(self):
"""Test MockRequest.form() returns empty dict."""
req = MockRequest()
result = await req.form()
assert result == {}
@pytest.mark.unit
class TestGetFailureState:
"""Tests for get_failure_state function."""
@patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state.json")
def test_returns_empty_dict_when_no_file(self):
"""Test returns empty dict when file doesn't exist."""
# Ensure file doesn't exist
if os.path.exists("/tmp/test_failure_state.json"):
os.remove("/tmp/test_failure_state.json")
result = get_failure_state()
assert result == {}
@patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state.json")
def test_reads_existing_state(self):
"""Test reads state from existing file."""
state = {"OpenAI": {"count": 2, "last_notified": 12345}}
with open("/tmp/test_failure_state.json", "w") as f:
json.dump(state, f)
result = get_failure_state()
assert result == state
# Clean up
os.remove("/tmp/test_failure_state.json")
@patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state_invalid.json")
def test_handles_invalid_json(self):
"""Test handles invalid JSON file."""
with open("/tmp/test_failure_state_invalid.json", "w") as f:
f.write("invalid json {")
result = get_failure_state()
assert result == {}
# Clean up
os.remove("/tmp/test_failure_state_invalid.json")
@pytest.mark.unit
class TestSaveFailureState:
"""Tests for save_failure_state function."""
@patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/tmp/test_failure_state_save.json")
def test_saves_state_to_file(self):
"""Test saves state to file."""
state = {"OpenAI": {"count": 1, "last_notified": 0}}
save_failure_state(state)
with open("/tmp/test_failure_state_save.json", "r") as f:
loaded = json.load(f)
assert loaded == state
# Clean up
os.remove("/tmp/test_failure_state_save.json")
@patch("app.tasks.check_credentials.FAILURE_STATE_FILE", "/invalid/path/test.json")
def test_handles_save_error(self):
"""Test handles save error gracefully."""
state = {"OpenAI": {"count": 1}}
# Should not raise exception
save_failure_state(state)
@pytest.mark.unit
class TestUnwrapDecoratedFunction:
"""Tests for unwrap_decorated_function."""
def test_returns_same_function_if_not_decorated(self):
"""Test returns same function if not decorated."""
def my_func():
return "hello"
result = unwrap_decorated_function(my_func)
assert result is my_func
def test_unwraps_decorated_function(self):
"""Test unwraps decorated function."""
def inner():
return "hello"
def wrapper():
return inner()
wrapper.__wrapped__ = inner
result = unwrap_decorated_function(wrapper)
assert result is inner
def test_unwraps_multiple_levels(self):
"""Test unwraps multiple levels of decoration."""
def original():
return "hello"
def middle():
return original()
middle.__wrapped__ = original
def outer():
return middle()
outer.__wrapped__ = middle
result = unwrap_decorated_function(outer)
assert result is original
@pytest.mark.unit
class TestSyncTestFunctions:
"""Tests for sync test wrapper functions."""
@patch("app.tasks.check_credentials.test_openai_connection")
@patch("app.tasks.check_credentials.unwrap_decorated_function")
@patch("app.tasks.check_credentials.asyncio.run")
def test_sync_test_openai_connection(self, mock_asyncio_run, mock_unwrap, mock_test_func):
"""Test sync wrapper for OpenAI connection test."""
mock_inner = Mock()
mock_inner.return_value = {"status": "success"}
mock_unwrap.return_value = mock_inner
# Mock as sync function
import inspect
with patch.object(inspect, "iscoroutinefunction", return_value=False):
result = sync_test_openai_connection()
mock_inner.assert_called_once()
@patch("app.tasks.check_credentials.test_azure_connection")
@patch("app.tasks.check_credentials.unwrap_decorated_function")
def test_sync_test_azure_connection(self, mock_unwrap, mock_test_func):
"""Test sync wrapper for Azure connection test."""
mock_inner = Mock()
mock_inner.return_value = {"status": "success"}
mock_unwrap.return_value = mock_inner
import inspect
with patch.object(inspect, "iscoroutinefunction", return_value=False):
result = sync_test_azure_connection()
mock_inner.assert_called_once()
@pytest.mark.unit
class TestCheckCredentialsTask:
"""Tests for check_credentials task."""
@patch("app.tasks.check_credentials.save_failure_state")
@patch("app.tasks.check_credentials.get_failure_state")
@patch("app.tasks.check_credentials.get_provider_status")
@patch("app.tasks.check_credentials.validate_storage_configs")
@patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
@patch("app.tasks.check_credentials.sync_test_azure_connection")
@patch("app.tasks.check_credentials.sync_test_dropbox_token")
@patch("app.tasks.check_credentials.sync_test_google_drive_token")
@patch("app.tasks.check_credentials.sync_test_onedrive_token")
def test_checks_all_configured_services(
self,
mock_onedrive,
mock_gdrive,
mock_dropbox,
mock_azure,
mock_ai_provider,
mock_storage_configs,
mock_provider_status,
mock_get_state,
mock_save_state,
):
"""Test checks all configured services."""
mock_get_state.return_value = {}
mock_provider_status.return_value = {
"AI Provider": {"configured": True},
"Azure AI": {"configured": True},
"Dropbox": {"configured": True},
"Google Drive": {"configured": True},
"OneDrive": {"configured": True},
}
mock_storage_configs.return_value = {"dropbox": [], "google_drive": [], "onedrive": []}
# All tests succeed
mock_ai_provider.return_value = {"status": "success"}
mock_azure.return_value = {"status": "success"}
mock_dropbox.return_value = {"status": "success"}
mock_gdrive.return_value = {"status": "success"}
mock_onedrive.return_value = {"status": "success"}
result = check_credentials()
assert result["checked"] == 5
assert result["failures"] == 0
@patch("app.tasks.check_credentials.save_failure_state")
@patch("app.tasks.check_credentials.get_failure_state")
@patch("app.tasks.check_credentials.get_provider_status")
@patch("app.tasks.check_credentials.validate_storage_configs")
@patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
def test_tracks_failures(
self, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
):
"""Test tracks credential failures."""
mock_get_state.return_value = {}
mock_provider_status.return_value = {
"AI Provider": {"configured": True},
"Azure AI": {"configured": False},
"Dropbox": {"configured": False},
"Google Drive": {"configured": False},
"OneDrive": {"configured": False},
}
mock_storage_configs.return_value = {}
mock_ai_provider.return_value = {"status": "error", "message": "Invalid API key"}
result = check_credentials()
assert result["checked"] == 1
assert result["failures"] == 1
@patch("app.tasks.check_credentials.save_failure_state")
@patch("app.tasks.check_credentials.get_failure_state")
@patch("app.tasks.check_credentials.get_provider_status")
@patch("app.tasks.check_credentials.validate_storage_configs")
def test_skips_unconfigured_services(
self, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
):
"""Test skips unconfigured services."""
mock_get_state.return_value = {}
mock_provider_status.return_value = {
"AI Provider": {"configured": False},
"Azure AI": {"configured": False},
"Dropbox": {"configured": False},
"Google Drive": {"configured": False},
"OneDrive": {"configured": False},
}
mock_storage_configs.return_value = {}
result = check_credentials()
assert result["checked"] == 0
assert result["unconfigured"] == 5
@patch("app.tasks.check_credentials.save_failure_state")
@patch("app.tasks.check_credentials.get_failure_state")
@patch("app.tasks.check_credentials.get_provider_status")
@patch("app.tasks.check_credentials.validate_storage_configs")
@patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
@patch("app.tasks.check_credentials.notify_credential_failure")
def test_sends_notifications_on_failure(
self, mock_notify, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
):
"""Test sends notifications on credential failure."""
mock_get_state.return_value = {}
mock_provider_status.return_value = {
"AI Provider": {"configured": True},
"Azure AI": {"configured": False},
"Dropbox": {"configured": False},
"Google Drive": {"configured": False},
"OneDrive": {"configured": False},
}
mock_storage_configs.return_value = {}
mock_ai_provider.return_value = {"status": "error", "message": "Invalid API key"}
check_credentials()
mock_notify.assert_called_once()
@patch("app.tasks.check_credentials.save_failure_state")
@patch("app.tasks.check_credentials.get_failure_state")
@patch("app.tasks.check_credentials.get_provider_status")
@patch("app.tasks.check_credentials.validate_storage_configs")
@patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
@patch("app.tasks.check_credentials.notify_credential_failure")
def test_suppresses_notifications_after_threshold(
self, mock_notify, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
):
"""Test suppresses notifications after failure threshold."""
# Existing state with 4 failures
mock_get_state.return_value = {"AI Provider": {"count": 4, "last_notified": 12345}}
mock_provider_status.return_value = {
"AI Provider": {"configured": True},
"Azure AI": {"configured": False},
"Dropbox": {"configured": False},
"Google Drive": {"configured": False},
"OneDrive": {"configured": False},
}
mock_storage_configs.return_value = {}
mock_ai_provider.return_value = {"status": "error", "message": "Invalid API key"}
check_credentials()
# Notification should be suppressed (already notified 3 times)
mock_notify.assert_not_called()
@patch("app.tasks.check_credentials.save_failure_state")
@patch("app.tasks.check_credentials.get_failure_state")
@patch("app.tasks.check_credentials.get_provider_status")
@patch("app.tasks.check_credentials.validate_storage_configs")
@patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
def test_tracks_recovery(
self, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
):
"""Test tracks service recovery."""
# Existing state with failures
mock_get_state.return_value = {"AI Provider": {"count": 2, "last_notified": 12345}}
mock_provider_status.return_value = {
"AI Provider": {"configured": True},
"Azure AI": {"configured": False},
"Dropbox": {"configured": False},
"Google Drive": {"configured": False},
"OneDrive": {"configured": False},
}
mock_storage_configs.return_value = {}
# Service is now valid
mock_ai_provider.return_value = {"status": "success"}
result = check_credentials()
assert result["failures"] == 0
@patch("app.tasks.check_credentials.save_failure_state")
@patch("app.tasks.check_credentials.get_failure_state")
@patch("app.tasks.check_credentials.get_provider_status")
@patch("app.tasks.check_credentials.validate_storage_configs")
@patch("app.tasks.check_credentials.sync_test_ai_provider_connection")
def test_handles_exception_during_check(
self, mock_ai_provider, mock_storage_configs, mock_provider_status, mock_get_state, mock_save_state
):
"""Test handles exception during credential check."""
mock_get_state.return_value = {}
mock_provider_status.return_value = {
"AI Provider": {"configured": True},
"Azure AI": {"configured": False},
"Dropbox": {"configured": False},
"Google Drive": {"configured": False},
"OneDrive": {"configured": False},
}
mock_storage_configs.return_value = {}
mock_ai_provider.side_effect = Exception("Network error")
result = check_credentials()
# Should still complete and record the error
assert result["failures"] == 1
assert "AI Provider" in result["results"]
assert result["results"]["AI Provider"]["status"] == "error"