1078 lines
34 KiB
Python
1078 lines
34 KiB
Python
# =================== AIPass ====================
|
|
# Name: test_google_client.py
|
|
# Description: Tests for Google API client module
|
|
# Version: 1.0.0
|
|
# Created: 2026-03-24
|
|
# Modified: 2026-03-24
|
|
# =============================================
|
|
|
|
"""
|
|
Tests for google_client.py — Google API Client Module.
|
|
|
|
Tests:
|
|
- handle_command routing: help, introspection, validate, reauth, pass-through
|
|
- get_drive_service() delegates to get_google_service()
|
|
- get_google_service() standard and thread-safe paths
|
|
- get_google_service() error paths: libs missing, auth failure
|
|
- validate_google() success and failure
|
|
- authenticate_google() success and failure
|
|
- reauth_google() success and failure
|
|
- api_call_with_retry() delegation
|
|
- is_ssl_error() delegation
|
|
"""
|
|
|
|
from unittest.mock import patch, MagicMock, call
|
|
|
|
import pytest
|
|
|
|
from aipass.api.apps.modules.google_client import handle_command as _hc # noqa: F401 — seedgo test_coverage detection
|
|
|
|
|
|
# Base patch path for all mocks in this module
|
|
_MOD = "aipass.api.apps.modules.google_client"
|
|
|
|
|
|
# =============================================
|
|
# handle_command tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_returns_false_no_args(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, _auth, _console
|
|
):
|
|
"""handle_command returns False when args=[] and command != 'google'."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
result = google_client.handle_command("validate", [])
|
|
assert result is False
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_returns_false_non_google_provider(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, _auth, _console
|
|
):
|
|
"""handle_command returns False when provider is not 'google'."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
result = google_client.handle_command("validate", ["openrouter"])
|
|
assert result is False
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_routes_validate_google(
|
|
_warn, _err, _succ, _hdr, mock_json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""handle_command routes 'validate' with ['google'] to _cmd_validate."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = True
|
|
mock_auth.validate_credentials.return_value = True
|
|
|
|
result = google_client.handle_command("validate", ["google"])
|
|
|
|
assert result is True
|
|
mock_auth.validate_credentials.assert_called_once()
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_routes_reauth_google(
|
|
mock_warn, _err, _succ, _hdr, mock_json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""handle_command routes 'reauth' with ['google'] to _cmd_reauth."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = True
|
|
mock_auth.reauth.return_value = MagicMock()
|
|
|
|
result = google_client.handle_command("reauth", ["google"])
|
|
|
|
assert result is True
|
|
mock_auth.reauth.assert_called_once()
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_help_gate(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, _auth, mock_console
|
|
):
|
|
"""handle_command prints help when args=['google', '--help'] and returns True."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
result = google_client.handle_command("validate", ["google", "--help"])
|
|
|
|
assert result is True
|
|
# print_help calls console.print with the argparse output
|
|
mock_console.print.assert_called()
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_google_introspection(
|
|
_warn, _err, _succ, mock_hdr, _json, _retry, _factory, mock_auth, mock_console
|
|
):
|
|
"""handle_command prints introspection when command='google' and args=[]."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CREDS_PATH.exists.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = True
|
|
|
|
result = google_client.handle_command("google", [])
|
|
|
|
assert result is True
|
|
mock_hdr.assert_called_once_with("Google Client Module Introspection")
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_unknown_command_returns_false(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, _auth, _console
|
|
):
|
|
"""handle_command returns False for unknown commands with 'google' arg."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
result = google_client.handle_command("deploy", ["google"])
|
|
assert result is False
|
|
|
|
|
|
# =============================================
|
|
# get_drive_service tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_get_drive_service_delegates(
|
|
_warn, _err, _succ, _hdr, _json, _retry, mock_factory, mock_auth, _console
|
|
):
|
|
"""get_drive_service() delegates to get_google_service('drive', 'v3')."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_service = MagicMock()
|
|
mock_factory.build_service.return_value = mock_service
|
|
|
|
result = google_client.get_drive_service()
|
|
|
|
mock_factory.build_service.assert_called_once_with("drive", "v3", None)
|
|
assert result is mock_service
|
|
|
|
|
|
# =============================================
|
|
# get_google_service tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_get_google_service_standard(
|
|
_warn, _err, _succ, _hdr, _json, _retry, mock_factory, mock_auth, _console
|
|
):
|
|
"""get_google_service() returns service via build_service when is_available=True."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_service = MagicMock()
|
|
mock_factory.build_service.return_value = mock_service
|
|
|
|
result = google_client.get_google_service("calendar", "v3")
|
|
|
|
mock_factory.build_service.assert_called_once_with("calendar", "v3", None)
|
|
assert result is mock_service
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_get_google_service_thread_safe(
|
|
_warn, _err, _succ, _hdr, _json, _retry, mock_factory, mock_auth, _console
|
|
):
|
|
"""get_google_service(thread_safe=True) calls build_thread_safe_service."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_service = MagicMock()
|
|
mock_factory.build_thread_safe_service.return_value = mock_service
|
|
|
|
result = google_client.get_google_service("drive", "v3", thread_safe=True)
|
|
|
|
mock_factory.build_thread_safe_service.assert_called_once_with("drive", "v3", None)
|
|
assert result is mock_service
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_get_google_service_libs_not_available(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""get_google_service() raises RuntimeError when libraries are not installed."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = False
|
|
|
|
with pytest.raises(RuntimeError, match="not installed"):
|
|
google_client.get_google_service()
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_get_google_service_auth_failure(
|
|
_warn, _err, _succ, _hdr, _json, _retry, mock_factory, mock_auth, _console
|
|
):
|
|
"""get_google_service() raises RuntimeError when build_service returns None."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_factory.build_service.return_value = None
|
|
|
|
with pytest.raises(RuntimeError, match="Failed to authenticate"):
|
|
google_client.get_google_service("drive", "v3")
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_get_google_service_with_custom_scopes(
|
|
_warn, _err, _succ, _hdr, _json, _retry, mock_factory, mock_auth, _console
|
|
):
|
|
"""get_google_service() passes custom scopes through to build_service."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_service = MagicMock()
|
|
mock_factory.build_service.return_value = mock_service
|
|
scopes = ["https://www.googleapis.com/auth/drive.readonly"]
|
|
|
|
result = google_client.get_google_service("drive", "v3", scopes=scopes)
|
|
|
|
mock_factory.build_service.assert_called_once_with("drive", "v3", scopes)
|
|
assert result is mock_service
|
|
|
|
|
|
# =============================================
|
|
# validate_google tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_validate_google_true(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""validate_google() returns True when credentials are valid."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.validate_credentials.return_value = True
|
|
|
|
result = google_client.validate_google()
|
|
|
|
assert result is True
|
|
mock_auth.validate_credentials.assert_called_once_with(scopes=None)
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_validate_google_false(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""validate_google() returns False when no valid credentials exist."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.validate_credentials.return_value = False
|
|
|
|
result = google_client.validate_google()
|
|
|
|
assert result is False
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_validate_google_with_scopes(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""validate_google() passes scopes to validate_credentials."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.validate_credentials.return_value = True
|
|
scopes = ["https://www.googleapis.com/auth/calendar"]
|
|
|
|
result = google_client.validate_google(scopes=scopes)
|
|
|
|
assert result is True
|
|
mock_auth.validate_credentials.assert_called_once_with(scopes=scopes)
|
|
|
|
|
|
# =============================================
|
|
# authenticate_google tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_authenticate_google_success(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""authenticate_google() returns True when authenticate returns credentials."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.authenticate.return_value = MagicMock()
|
|
|
|
result = google_client.authenticate_google()
|
|
|
|
assert result is True
|
|
mock_auth.authenticate.assert_called_once_with(scopes=None)
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_authenticate_google_failure(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""authenticate_google() returns False when authenticate returns None."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.authenticate.return_value = None
|
|
|
|
result = google_client.authenticate_google()
|
|
|
|
assert result is False
|
|
|
|
|
|
# =============================================
|
|
# reauth_google tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_reauth_google_success(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""reauth_google() returns True when reauth returns credentials."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.reauth.return_value = MagicMock()
|
|
|
|
result = google_client.reauth_google()
|
|
|
|
assert result is True
|
|
mock_auth.reauth.assert_called_once_with(scopes=None)
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_reauth_google_failure(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""reauth_google() returns False when reauth returns None."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.reauth.return_value = None
|
|
|
|
result = google_client.reauth_google()
|
|
|
|
assert result is False
|
|
|
|
|
|
# =============================================
|
|
# api_call_with_retry tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_api_call_with_retry_delegates(
|
|
_warn, _err, _succ, _hdr, _json, mock_retry, _factory, _auth, _console
|
|
):
|
|
"""api_call_with_retry() delegates to google_retry.api_call_with_retry."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_callable = MagicMock()
|
|
mock_retry.api_call_with_retry.return_value = "result"
|
|
|
|
result = google_client.api_call_with_retry(mock_callable, retries=3)
|
|
|
|
mock_retry.api_call_with_retry.assert_called_once_with(mock_callable, retries=3)
|
|
assert result == "result"
|
|
|
|
|
|
# =============================================
|
|
# is_ssl_error tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_is_ssl_error_delegates(
|
|
_warn, _err, _succ, _hdr, _json, mock_retry, _factory, _auth, _console
|
|
):
|
|
"""is_ssl_error() delegates to google_retry.is_ssl_error."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
test_error = Exception("SSL handshake failed")
|
|
mock_retry.is_ssl_error.return_value = True
|
|
|
|
result = google_client.is_ssl_error(test_error)
|
|
|
|
mock_retry.is_ssl_error.assert_called_once_with(test_error)
|
|
assert result is True
|
|
|
|
|
|
# =============================================
|
|
# _cmd_validate CLI integration tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_cmd_validate_libs_not_available(
|
|
_warn, mock_err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""_cmd_validate shows error when Google libs are not installed."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = False
|
|
|
|
google_client._cmd_validate()
|
|
|
|
mock_err.assert_called_once()
|
|
assert "not installed" in mock_err.call_args[0][0]
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_cmd_validate_no_client_secret(
|
|
_warn, mock_err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""_cmd_validate shows error when client secret file is missing."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = False
|
|
|
|
google_client._cmd_validate()
|
|
|
|
mock_err.assert_called_once()
|
|
assert "Client secret not found" in mock_err.call_args[0][0]
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_cmd_validate_valid_creds(
|
|
_warn, _err, mock_succ, _hdr, mock_json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""_cmd_validate shows success when credentials are valid."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = True
|
|
mock_auth.validate_credentials.return_value = True
|
|
|
|
google_client._cmd_validate()
|
|
|
|
mock_succ.assert_called_once_with("Google credentials are valid")
|
|
mock_json.log_operation.assert_called_once_with(
|
|
"google_validate", {"status": "valid"}
|
|
)
|
|
_err.assert_not_called()
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_cmd_validate_invalid_creds(
|
|
mock_warn, _err, _succ, _hdr, mock_json, _retry, _factory, mock_auth, mock_console
|
|
):
|
|
"""_cmd_validate shows warning when credentials are invalid."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = True
|
|
mock_auth.validate_credentials.return_value = False
|
|
|
|
google_client._cmd_validate()
|
|
|
|
mock_warn.assert_called_once_with("No valid Google credentials found")
|
|
mock_json.log_operation.assert_called_once_with(
|
|
"google_validate", {"status": "invalid"}
|
|
)
|
|
_succ.assert_not_called()
|
|
|
|
|
|
# =============================================
|
|
# _cmd_reauth CLI integration tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_cmd_reauth_libs_not_available(
|
|
_warn, mock_err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""_cmd_reauth shows error when Google libs are not installed."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = False
|
|
|
|
google_client._cmd_reauth()
|
|
|
|
mock_err.assert_called_once()
|
|
assert "not installed" in mock_err.call_args[0][0]
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_cmd_reauth_success(
|
|
_warn, _err, mock_succ, _hdr, mock_json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""_cmd_reauth shows success when reauth returns credentials."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = True
|
|
mock_auth.reauth.return_value = MagicMock()
|
|
|
|
google_client._cmd_reauth()
|
|
|
|
mock_succ.assert_called_once_with("Google re-authentication successful")
|
|
mock_json.log_operation.assert_called_once_with(
|
|
"google_reauth", {"status": "success"}
|
|
)
|
|
_err.assert_not_called()
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_cmd_reauth_failure(
|
|
_warn, mock_err, _succ, _hdr, mock_json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""_cmd_reauth shows error when reauth returns None."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.return_value = True
|
|
mock_auth.CLIENT_SECRET_PATH.exists.return_value = True
|
|
mock_auth.reauth.return_value = None
|
|
|
|
google_client._cmd_reauth()
|
|
|
|
mock_err.assert_called_once_with("Google re-authentication failed")
|
|
mock_json.log_operation.assert_called_once_with(
|
|
"google_reauth", {"status": "failed"}
|
|
)
|
|
_succ.assert_not_called()
|
|
|
|
|
|
# =============================================
|
|
# handle_command — exception propagation
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_MOD}.console")
|
|
@patch(f"{_MOD}.google_auth")
|
|
@patch(f"{_MOD}.google_factory")
|
|
@patch(f"{_MOD}.google_retry")
|
|
@patch(f"{_MOD}.json_handler")
|
|
@patch(f"{_MOD}.header")
|
|
@patch(f"{_MOD}.success")
|
|
@patch(f"{_MOD}.error")
|
|
@patch(f"{_MOD}.warning")
|
|
def test_handle_command_propagates_exception(
|
|
_warn, _err, _succ, _hdr, _json, _retry, _factory, mock_auth, _console
|
|
):
|
|
"""handle_command re-raises exceptions from downstream handlers."""
|
|
from aipass.api.apps.modules import google_client
|
|
|
|
mock_auth.is_available.side_effect = RuntimeError("handler failed")
|
|
|
|
with pytest.raises(RuntimeError, match="handler failed"):
|
|
google_client.handle_command("validate", ["google"])
|
|
|
|
|
|
# =============================================
|
|
# auth.py — load_credentials tests
|
|
# =============================================
|
|
|
|
# Base patch path for auth module
|
|
_AUTH = "aipass.api.apps.handlers.google.auth"
|
|
|
|
|
|
@patch(f"{_AUTH}.json_handler")
|
|
@patch(f"{_AUTH}.Credentials")
|
|
@patch(f"{_AUTH}.CREDS_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_load_credentials_success(mock_creds_path, mock_creds_cls, mock_json):
|
|
"""load_credentials() returns Credentials when file exists and loads ok."""
|
|
from aipass.api.apps.handlers.google.auth import load_credentials
|
|
|
|
mock_creds_path.exists.return_value = True
|
|
mock_creds_obj = MagicMock()
|
|
mock_creds_cls.from_authorized_user_file.return_value = mock_creds_obj
|
|
|
|
result = load_credentials()
|
|
|
|
assert result is mock_creds_obj
|
|
mock_creds_cls.from_authorized_user_file.assert_called_once_with(
|
|
str(mock_creds_path),
|
|
["https://www.googleapis.com/auth/drive.file"],
|
|
)
|
|
mock_json.log_operation.assert_called_once_with(
|
|
"credentials_loaded", {"source": str(mock_creds_path)}
|
|
)
|
|
|
|
|
|
@patch(f"{_AUTH}.json_handler")
|
|
@patch(f"{_AUTH}.Credentials")
|
|
@patch(f"{_AUTH}.CREDS_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_load_credentials_custom_scopes(mock_creds_path, mock_creds_cls, mock_json):
|
|
"""load_credentials() uses custom scopes when provided."""
|
|
from aipass.api.apps.handlers.google.auth import load_credentials
|
|
|
|
mock_creds_path.exists.return_value = True
|
|
custom_scopes = ["https://www.googleapis.com/auth/calendar.readonly"]
|
|
mock_creds_cls.from_authorized_user_file.return_value = MagicMock()
|
|
|
|
load_credentials(scopes=custom_scopes)
|
|
|
|
mock_creds_cls.from_authorized_user_file.assert_called_once_with(
|
|
str(mock_creds_path),
|
|
custom_scopes,
|
|
)
|
|
|
|
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", False)
|
|
def test_load_credentials_libs_unavailable():
|
|
"""load_credentials() returns None when Google libs are not installed."""
|
|
from aipass.api.apps.handlers.google.auth import load_credentials
|
|
|
|
result = load_credentials()
|
|
|
|
assert result is None
|
|
|
|
|
|
@patch(f"{_AUTH}.CREDS_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_load_credentials_no_file(mock_creds_path):
|
|
"""load_credentials() returns None when creds file does not exist."""
|
|
from aipass.api.apps.handlers.google.auth import load_credentials
|
|
|
|
mock_creds_path.exists.return_value = False
|
|
|
|
result = load_credentials()
|
|
|
|
assert result is None
|
|
|
|
|
|
@patch(f"{_AUTH}.logger")
|
|
@patch(f"{_AUTH}.json_handler")
|
|
@patch(f"{_AUTH}.Credentials")
|
|
@patch(f"{_AUTH}.CREDS_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_load_credentials_exception(mock_creds_path, mock_creds_cls, mock_json, mock_logger):
|
|
"""load_credentials() returns None and logs error on exception."""
|
|
from aipass.api.apps.handlers.google.auth import load_credentials
|
|
|
|
mock_creds_path.exists.return_value = True
|
|
mock_creds_cls.from_authorized_user_file.side_effect = ValueError("corrupt file")
|
|
|
|
result = load_credentials()
|
|
|
|
assert result is None
|
|
mock_logger.error.assert_called_once()
|
|
mock_json.log_operation.assert_not_called()
|
|
|
|
|
|
# =============================================
|
|
# auth.py — refresh_credentials tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_AUTH}._save_credentials")
|
|
@patch(f"{_AUTH}.Request")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_refresh_credentials_success(mock_request_cls, mock_save):
|
|
"""refresh_credentials() returns True and saves when refresh succeeds."""
|
|
from aipass.api.apps.handlers.google.auth import refresh_credentials
|
|
|
|
mock_creds = MagicMock()
|
|
mock_creds.expired = True
|
|
mock_creds.refresh_token = "tok_refresh"
|
|
|
|
result = refresh_credentials(mock_creds)
|
|
|
|
assert result is True
|
|
mock_creds.refresh.assert_called_once_with(mock_request_cls())
|
|
mock_save.assert_called_once_with(mock_creds)
|
|
|
|
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", False)
|
|
def test_refresh_credentials_libs_unavailable():
|
|
"""refresh_credentials() returns False when Google libs are not installed."""
|
|
from aipass.api.apps.handlers.google.auth import refresh_credentials
|
|
|
|
result = refresh_credentials(MagicMock())
|
|
|
|
assert result is False
|
|
|
|
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_refresh_credentials_none_creds():
|
|
"""refresh_credentials() returns False when creds is None."""
|
|
from aipass.api.apps.handlers.google.auth import refresh_credentials
|
|
|
|
result = refresh_credentials(None)
|
|
|
|
assert result is False
|
|
|
|
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_refresh_credentials_not_expired():
|
|
"""refresh_credentials() returns False when creds are not expired."""
|
|
from aipass.api.apps.handlers.google.auth import refresh_credentials
|
|
|
|
mock_creds = MagicMock()
|
|
mock_creds.expired = False
|
|
mock_creds.refresh_token = "tok_refresh"
|
|
|
|
result = refresh_credentials(mock_creds)
|
|
|
|
assert result is False
|
|
|
|
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_refresh_credentials_no_refresh_token():
|
|
"""refresh_credentials() returns False when no refresh token exists."""
|
|
from aipass.api.apps.handlers.google.auth import refresh_credentials
|
|
|
|
mock_creds = MagicMock()
|
|
mock_creds.expired = True
|
|
mock_creds.refresh_token = None
|
|
|
|
result = refresh_credentials(mock_creds)
|
|
|
|
assert result is False
|
|
|
|
|
|
@patch(f"{_AUTH}.logger")
|
|
@patch(f"{_AUTH}._save_credentials")
|
|
@patch(f"{_AUTH}.Request")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_refresh_credentials_exception(mock_request_cls, mock_save, mock_logger):
|
|
"""refresh_credentials() returns False on refresh exception."""
|
|
from aipass.api.apps.handlers.google.auth import refresh_credentials
|
|
|
|
mock_creds = MagicMock()
|
|
mock_creds.expired = True
|
|
mock_creds.refresh_token = "tok_refresh"
|
|
mock_creds.refresh.side_effect = RuntimeError("network error")
|
|
|
|
result = refresh_credentials(mock_creds)
|
|
|
|
assert result is False
|
|
mock_save.assert_not_called()
|
|
mock_logger.error.assert_called_once()
|
|
|
|
|
|
# =============================================
|
|
# auth.py — run_oauth_flow tests
|
|
# =============================================
|
|
|
|
|
|
@patch(f"{_AUTH}._save_credentials")
|
|
@patch(f"{_AUTH}.InstalledAppFlow")
|
|
@patch(f"{_AUTH}.CLIENT_SECRET_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_run_oauth_flow_success(mock_secret_path, mock_flow_cls, mock_save):
|
|
"""run_oauth_flow() returns credentials after successful OAuth flow."""
|
|
from aipass.api.apps.handlers.google.auth import run_oauth_flow, DEFAULT_SCOPES
|
|
|
|
mock_secret_path.exists.return_value = True
|
|
mock_flow = MagicMock()
|
|
mock_creds = MagicMock()
|
|
mock_flow_cls.from_client_secrets_file.return_value = mock_flow
|
|
mock_flow.run_local_server.return_value = mock_creds
|
|
|
|
result = run_oauth_flow()
|
|
|
|
assert result is mock_creds
|
|
mock_flow_cls.from_client_secrets_file.assert_called_once_with(
|
|
str(mock_secret_path),
|
|
DEFAULT_SCOPES["drive"],
|
|
)
|
|
mock_flow.run_local_server.assert_called_once_with(port=0, open_browser=True)
|
|
mock_save.assert_called_once_with(mock_creds)
|
|
|
|
|
|
@patch(f"{_AUTH}._save_credentials")
|
|
@patch(f"{_AUTH}.InstalledAppFlow")
|
|
@patch(f"{_AUTH}.CLIENT_SECRET_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_run_oauth_flow_custom_params(mock_secret_path, mock_flow_cls, mock_save):
|
|
"""run_oauth_flow() passes custom scopes, port, and open_browser."""
|
|
from aipass.api.apps.handlers.google.auth import run_oauth_flow
|
|
|
|
mock_secret_path.exists.return_value = True
|
|
mock_flow = MagicMock()
|
|
mock_creds = MagicMock()
|
|
mock_flow_cls.from_client_secrets_file.return_value = mock_flow
|
|
mock_flow.run_local_server.return_value = mock_creds
|
|
custom_scopes = ["https://www.googleapis.com/auth/calendar"]
|
|
|
|
result = run_oauth_flow(scopes=custom_scopes, port=8085, open_browser=False)
|
|
|
|
assert result is mock_creds
|
|
mock_flow_cls.from_client_secrets_file.assert_called_once_with(
|
|
str(mock_secret_path),
|
|
custom_scopes,
|
|
)
|
|
mock_flow.run_local_server.assert_called_once_with(port=8085, open_browser=False)
|
|
|
|
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", False)
|
|
def test_run_oauth_flow_libs_unavailable():
|
|
"""run_oauth_flow() returns None when Google libs are not installed."""
|
|
from aipass.api.apps.handlers.google.auth import run_oauth_flow
|
|
|
|
result = run_oauth_flow()
|
|
|
|
assert result is None
|
|
|
|
|
|
@patch(f"{_AUTH}.CLIENT_SECRET_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_run_oauth_flow_no_client_secret(mock_secret_path):
|
|
"""run_oauth_flow() returns None when client secret file is missing."""
|
|
from aipass.api.apps.handlers.google.auth import run_oauth_flow
|
|
|
|
mock_secret_path.exists.return_value = False
|
|
|
|
result = run_oauth_flow()
|
|
|
|
assert result is None
|
|
|
|
|
|
@patch(f"{_AUTH}.logger")
|
|
@patch(f"{_AUTH}._save_credentials")
|
|
@patch(f"{_AUTH}.InstalledAppFlow")
|
|
@patch(f"{_AUTH}.CLIENT_SECRET_PATH")
|
|
@patch(f"{_AUTH}.GOOGLE_AUTH_AVAILABLE", True)
|
|
def test_run_oauth_flow_exception(mock_secret_path, mock_flow_cls, mock_save, mock_logger):
|
|
"""run_oauth_flow() returns None and logs error on exception."""
|
|
from aipass.api.apps.handlers.google.auth import run_oauth_flow
|
|
|
|
mock_secret_path.exists.return_value = True
|
|
mock_flow_cls.from_client_secrets_file.side_effect = OSError("bad secret file")
|
|
|
|
result = run_oauth_flow()
|
|
|
|
assert result is None
|
|
mock_save.assert_not_called()
|
|
mock_logger.error.assert_called_once()
|