from __future__ import annotations from unittest.mock import MagicMock import pytest from app.features.auth import repo from app.features.auth.google import GoogleIdentity from app.features.auth.service import AuthError, AuthService from app.features.user.models import User def _identity( *, subject: str = "google-sub-1", email: str = "person@example.com", name: str = "Google Person", ) -> GoogleIdentity: return GoogleIdentity( subject=subject, email=email, email_verified=True, name=name, picture="https://example.com/avatar.jpg", ) @pytest.mark.asyncio async def test_google_login_creates_user( auth_session_factory, monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr( "app.features.auth.service.verify_google_id_token", lambda _token: _identity(), ) async with auth_session_factory() as db: svc = AuthService(db=db, sms=MagicMock()) result = await svc.login_with_google("id-token", language="en") assert result["access_token"] assert result["refresh_token"] assert result["is_new_user"] is True user = await repo.get_user_by_openid("google:google-sub-1", db) assert user is not None assert user.phone == "google:google-sub-1" assert user.email == "person@example.com" assert user.nickname == "Google Person" assert user.language_preference == "en" @pytest.mark.asyncio async def test_google_login_links_existing_verified_email_user( auth_session_factory, make_test_user, monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr( "app.features.auth.service.verify_google_id_token", lambda _token: _identity(email="linked@example.com"), ) async with auth_session_factory() as db: existing: User = make_test_user(nickname="Existing") existing.email = "linked@example.com" db.add(existing) await db.commit() svc = AuthService(db=db, sms=MagicMock()) result = await svc.login_with_google("id-token", language="en") assert result["is_new_user"] is False await db.refresh(existing) assert existing.openid == "google:google-sub-1" assert existing.nickname == "Existing" assert existing.language_preference != "en" @pytest.mark.asyncio async def test_google_login_rejects_email_bound_to_other_openid( auth_session_factory, make_test_user, monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setattr( "app.features.auth.service.verify_google_id_token", lambda _token: _identity(email="taken@example.com"), ) async with auth_session_factory() as db: existing: User = make_test_user(nickname="Existing") existing.email = "taken@example.com" existing.openid = "google:another-sub" db.add(existing) await db.commit() svc = AuthService(db=db, sms=MagicMock()) with pytest.raises(AuthError) as exc_info: await svc.login_with_google("id-token") assert exc_info.value.code == "EMAIL_EXISTS"