58f9ad72ed
Agent-Logs-Url: https://github.com/christianlouis/InboxConverge/sessions/8cb47b1c-b744-4212-8b6d-8132d6642d98 Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
193 lines
6.7 KiB
Python
193 lines
6.7 KiB
Python
"""
|
|
Unit tests for the OAuth / auth service (services/auth_service.py).
|
|
|
|
External HTTP calls are mocked via httpx. No real network or DB needed.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from app.services.auth_service import OAuthService
|
|
from app.models.database_models import User
|
|
|
|
# ── helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
def _make_user(id: int = 1, email: str = "user@example.com") -> MagicMock:
|
|
u = MagicMock(spec=User)
|
|
u.id = id
|
|
u.email = email
|
|
return u
|
|
|
|
|
|
def _mock_token_response(
|
|
access_token: str = "access123", refresh_token: str = "refresh456"
|
|
):
|
|
resp = MagicMock()
|
|
resp.status_code = 200
|
|
resp.json.return_value = {
|
|
"access_token": access_token,
|
|
"refresh_token": refresh_token,
|
|
"expires_in": 3600,
|
|
"scope": "openid email profile",
|
|
}
|
|
return resp
|
|
|
|
|
|
def _mock_user_info_response(
|
|
email: str = "user@google.com",
|
|
name: str = "Test User",
|
|
google_id: str = "g123",
|
|
verified: bool = True,
|
|
):
|
|
resp = MagicMock()
|
|
resp.status_code = 200
|
|
resp.json.return_value = {
|
|
"email": email,
|
|
"name": name,
|
|
"id": google_id,
|
|
"picture": "https://example.com/pic.jpg",
|
|
"verified_email": verified,
|
|
}
|
|
return resp
|
|
|
|
|
|
# ── OAuthService.get_google_user_info ────────────────────────────────────────
|
|
|
|
|
|
class TestGetGoogleUserInfo:
|
|
async def test_success(self):
|
|
svc = OAuthService()
|
|
token_resp = _mock_token_response()
|
|
user_info_resp = _mock_user_info_response()
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=False)
|
|
mock_client.post = AsyncMock(return_value=token_resp)
|
|
mock_client.get = AsyncMock(return_value=user_info_resp)
|
|
|
|
with patch(
|
|
"app.services.auth_service.httpx.AsyncClient", return_value=mock_client
|
|
):
|
|
result = await svc.get_google_user_info(
|
|
code="authcode", redirect_uri="http://localhost/callback"
|
|
)
|
|
|
|
assert result["email"] == "user@google.com"
|
|
assert result["google_id"] == "g123"
|
|
assert result["verified_email"] is True
|
|
assert result["access_token"] == "access123"
|
|
|
|
async def test_token_exchange_fails(self):
|
|
svc = OAuthService()
|
|
bad_resp = MagicMock()
|
|
bad_resp.status_code = 400
|
|
bad_resp.text = "bad_request"
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=False)
|
|
mock_client.post = AsyncMock(return_value=bad_resp)
|
|
|
|
with patch(
|
|
"app.services.auth_service.httpx.AsyncClient", return_value=mock_client
|
|
):
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await svc.get_google_user_info(
|
|
code="bad", redirect_uri="http://localhost"
|
|
)
|
|
assert exc_info.value.status_code == 400
|
|
|
|
async def test_no_access_token_in_response(self):
|
|
svc = OAuthService()
|
|
resp = MagicMock()
|
|
resp.status_code = 200
|
|
resp.json.return_value = {} # no access_token
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=False)
|
|
mock_client.post = AsyncMock(return_value=resp)
|
|
|
|
with patch(
|
|
"app.services.auth_service.httpx.AsyncClient", return_value=mock_client
|
|
):
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await svc.get_google_user_info(
|
|
code="c", redirect_uri="http://localhost"
|
|
)
|
|
assert exc_info.value.status_code == 400
|
|
|
|
async def test_user_info_fetch_fails(self):
|
|
svc = OAuthService()
|
|
token_resp = _mock_token_response()
|
|
bad_info = MagicMock()
|
|
bad_info.status_code = 500
|
|
bad_info.text = "server error"
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=False)
|
|
mock_client.post = AsyncMock(return_value=token_resp)
|
|
mock_client.get = AsyncMock(return_value=bad_info)
|
|
|
|
with patch(
|
|
"app.services.auth_service.httpx.AsyncClient", return_value=mock_client
|
|
):
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await svc.get_google_user_info(
|
|
code="c", redirect_uri="http://localhost"
|
|
)
|
|
assert exc_info.value.status_code == 400
|
|
|
|
async def test_unexpected_exception_becomes_500(self):
|
|
svc = OAuthService()
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
|
mock_client.__aexit__ = AsyncMock(return_value=False)
|
|
mock_client.post = AsyncMock(side_effect=RuntimeError("network down"))
|
|
|
|
with patch(
|
|
"app.services.auth_service.httpx.AsyncClient", return_value=mock_client
|
|
):
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
await svc.get_google_user_info(
|
|
code="c", redirect_uri="http://localhost"
|
|
)
|
|
assert exc_info.value.status_code == 500
|
|
|
|
|
|
# ── OAuthService.create_tokens_for_user ──────────────────────────────────────
|
|
|
|
|
|
class TestCreateTokensForUser:
|
|
def test_returns_all_fields(self):
|
|
user = _make_user(id=7)
|
|
result = OAuthService.create_tokens_for_user(user)
|
|
assert "access_token" in result
|
|
assert "refresh_token" in result
|
|
assert result["token_type"] == "bearer"
|
|
|
|
def test_access_token_is_string(self):
|
|
user = _make_user(id=3)
|
|
result = OAuthService.create_tokens_for_user(user)
|
|
assert isinstance(result["access_token"], str)
|
|
assert len(result["access_token"]) > 0
|
|
|
|
def test_refresh_token_is_string(self):
|
|
user = _make_user(id=5)
|
|
result = OAuthService.create_tokens_for_user(user)
|
|
assert isinstance(result["refresh_token"], str)
|
|
assert len(result["refresh_token"]) > 0
|
|
|
|
def test_different_users_get_different_tokens(self):
|
|
u1 = _make_user(id=1)
|
|
u2 = _make_user(id=2)
|
|
tokens1 = OAuthService.create_tokens_for_user(u1)
|
|
tokens2 = OAuthService.create_tokens_for_user(u2)
|
|
assert tokens1["access_token"] != tokens2["access_token"]
|