- Add UserRepository and LinkedAccountRepository protocols to protocols.py - Add UserEntry and LinkedAccountEntry DTOs for service layer decoupling - Implement PostgresUserRepository and PostgresLinkedAccountRepository - Refactor UserService to use constructor-injected repositories - Add get_user_service factory and UserServiceDep to API deps - Update auth.py and users.py endpoints to use UserServiceDep - Rewrite tests to use FastAPI dependency overrides (no monkey patching) This follows the established repository pattern used by DeckService and CollectionService, enabling future offline fork support. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
689 lines
23 KiB
Python
689 lines
23 KiB
Python
"""Tests for UserService.
|
|
|
|
Tests the user service CRUD operations and OAuth-based user creation.
|
|
Uses real Postgres via the db_session fixture from conftest.
|
|
|
|
The UserService now uses dependency injection with repositories injected
|
|
via constructor, so we create a fresh service instance per test.
|
|
"""
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from uuid import UUID
|
|
|
|
import pytest
|
|
|
|
from app.db.models import User
|
|
from app.db.models.oauth_account import OAuthLinkedAccount
|
|
from app.repositories.postgres.linked_account import PostgresLinkedAccountRepository
|
|
from app.repositories.postgres.user import PostgresUserRepository
|
|
from app.schemas.user import OAuthUserInfo, UserUpdate
|
|
from app.services.user_service import AccountLinkingError, UserService
|
|
|
|
# Import db_session fixture from db conftest
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
@pytest.fixture
|
|
def user_service(db_session):
|
|
"""Create UserService with real PostgreSQL repositories.
|
|
|
|
This fixture provides a properly constructed UserService for each test,
|
|
following the dependency injection pattern used in production.
|
|
"""
|
|
user_repo = PostgresUserRepository(db_session)
|
|
linked_repo = PostgresLinkedAccountRepository(db_session)
|
|
return UserService(user_repo, linked_repo)
|
|
|
|
|
|
class TestGetById:
|
|
"""Tests for get_by_id method."""
|
|
|
|
async def test_returns_user_when_found(self, db_session, user_service):
|
|
"""Test that get_by_id returns user when it exists.
|
|
|
|
Creates a user and verifies it can be retrieved by ID.
|
|
"""
|
|
# Create user directly
|
|
user = User(
|
|
email="test@example.com",
|
|
display_name="Test User",
|
|
oauth_provider="google",
|
|
oauth_id="123456",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
# Retrieve by ID
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
result = await user_service.get_by_id(user_id)
|
|
|
|
assert result is not None
|
|
assert result.email == "test@example.com"
|
|
|
|
async def test_returns_none_when_not_found(self, user_service):
|
|
"""Test that get_by_id returns None for nonexistent users."""
|
|
from uuid import uuid4
|
|
|
|
result = await user_service.get_by_id(uuid4())
|
|
assert result is None
|
|
|
|
|
|
class TestGetByEmail:
|
|
"""Tests for get_by_email method."""
|
|
|
|
async def test_returns_user_when_found(self, db_session, user_service):
|
|
"""Test that get_by_email returns user when it exists."""
|
|
user = User(
|
|
email="findme@example.com",
|
|
display_name="Find Me",
|
|
oauth_provider="discord",
|
|
oauth_id="discord123",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
result = await user_service.get_by_email("findme@example.com")
|
|
|
|
assert result is not None
|
|
assert result.display_name == "Find Me"
|
|
|
|
async def test_returns_none_when_not_found(self, user_service):
|
|
"""Test that get_by_email returns None for nonexistent emails."""
|
|
result = await user_service.get_by_email("nobody@example.com")
|
|
assert result is None
|
|
|
|
|
|
class TestGetByOAuth:
|
|
"""Tests for get_by_oauth method."""
|
|
|
|
async def test_returns_user_when_found(self, db_session, user_service):
|
|
"""Test that get_by_oauth returns user for matching provider+id."""
|
|
user = User(
|
|
email="oauth@example.com",
|
|
display_name="OAuth User",
|
|
oauth_provider="google",
|
|
oauth_id="google-unique-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
result = await user_service.get_by_oauth("google", "google-unique-id")
|
|
|
|
assert result is not None
|
|
assert result.email == "oauth@example.com"
|
|
|
|
async def test_returns_none_for_wrong_provider(self, db_session, user_service):
|
|
"""Test that get_by_oauth returns None if provider doesn't match."""
|
|
user = User(
|
|
email="oauth2@example.com",
|
|
display_name="OAuth User 2",
|
|
oauth_provider="google",
|
|
oauth_id="google-id-2",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
# Same ID, different provider
|
|
result = await user_service.get_by_oauth("discord", "google-id-2")
|
|
assert result is None
|
|
|
|
async def test_returns_none_when_not_found(self, user_service):
|
|
"""Test that get_by_oauth returns None for nonexistent OAuth."""
|
|
result = await user_service.get_by_oauth("google", "nonexistent")
|
|
assert result is None
|
|
|
|
|
|
class TestCreate:
|
|
"""Tests for create method."""
|
|
|
|
async def test_creates_user_with_all_fields(self, user_service):
|
|
"""Test that create properly persists all user fields."""
|
|
result = await user_service.create(
|
|
email="new@example.com",
|
|
display_name="New User",
|
|
oauth_provider="discord",
|
|
oauth_id="discord-new-id",
|
|
avatar_url="https://example.com/avatar.jpg",
|
|
)
|
|
|
|
assert result.id is not None
|
|
assert result.email == "new@example.com"
|
|
assert result.display_name == "New User"
|
|
assert result.avatar_url == "https://example.com/avatar.jpg"
|
|
assert result.oauth_provider == "discord"
|
|
assert result.oauth_id == "discord-new-id"
|
|
assert result.is_premium is False
|
|
assert result.premium_until is None
|
|
|
|
async def test_creates_user_without_avatar(self, user_service):
|
|
"""Test that create works without optional avatar_url."""
|
|
result = await user_service.create(
|
|
email="noavatar@example.com",
|
|
display_name="No Avatar",
|
|
oauth_provider="google",
|
|
oauth_id="google-no-avatar",
|
|
)
|
|
|
|
assert result.avatar_url is None
|
|
|
|
|
|
class TestCreateFromOAuth:
|
|
"""Tests for create_from_oauth method."""
|
|
|
|
async def test_creates_user_from_oauth_info(self, user_service):
|
|
"""Test that create_from_oauth converts OAuthUserInfo to User."""
|
|
oauth_info = OAuthUserInfo(
|
|
provider="google",
|
|
oauth_id="google-oauth-123",
|
|
email="oauthcreate@example.com",
|
|
name="OAuth Created User",
|
|
avatar_url="https://google.com/avatar.jpg",
|
|
)
|
|
|
|
result = await user_service.create_from_oauth(oauth_info)
|
|
|
|
assert result.email == "oauthcreate@example.com"
|
|
assert result.display_name == "OAuth Created User"
|
|
assert result.oauth_provider == "google"
|
|
assert result.oauth_id == "google-oauth-123"
|
|
|
|
|
|
class TestGetOrCreateFromOAuth:
|
|
"""Tests for get_or_create_from_oauth method."""
|
|
|
|
async def test_returns_existing_user_by_oauth(self, db_session, user_service):
|
|
"""Test that existing user is returned when OAuth matches.
|
|
|
|
Verifies the method returns (user, False) for existing users.
|
|
"""
|
|
# Create existing user
|
|
existing = User(
|
|
email="existing@example.com",
|
|
display_name="Existing",
|
|
oauth_provider="google",
|
|
oauth_id="existing-oauth-id",
|
|
)
|
|
db_session.add(existing)
|
|
await db_session.commit()
|
|
|
|
# Try to get or create with same OAuth
|
|
oauth_info = OAuthUserInfo(
|
|
provider="google",
|
|
oauth_id="existing-oauth-id",
|
|
email="existing@example.com",
|
|
name="Existing",
|
|
)
|
|
|
|
result, created = await user_service.get_or_create_from_oauth(oauth_info)
|
|
|
|
assert created is False
|
|
assert str(result.id) == str(existing.id)
|
|
|
|
async def test_links_existing_user_by_email(self, db_session, user_service):
|
|
"""Test that OAuth is linked when email matches existing user.
|
|
|
|
If a user exists with the same email but different OAuth,
|
|
the new OAuth should be linked to the existing account.
|
|
"""
|
|
# Create user with Google
|
|
existing = User(
|
|
email="link@example.com",
|
|
display_name="Link Me",
|
|
oauth_provider="google",
|
|
oauth_id="google-link-id",
|
|
)
|
|
db_session.add(existing)
|
|
await db_session.commit()
|
|
|
|
# Login with Discord (same email)
|
|
oauth_info = OAuthUserInfo(
|
|
provider="discord",
|
|
oauth_id="discord-link-id",
|
|
email="link@example.com",
|
|
name="Link Me",
|
|
avatar_url="https://discord.com/avatar.jpg",
|
|
)
|
|
|
|
result, created = await user_service.get_or_create_from_oauth(oauth_info)
|
|
|
|
assert created is False
|
|
assert str(result.id) == str(existing.id)
|
|
# OAuth should be updated to Discord
|
|
assert result.oauth_provider == "discord"
|
|
assert result.oauth_id == "discord-link-id"
|
|
|
|
async def test_creates_new_user_when_not_found(self, user_service):
|
|
"""Test that new user is created when no match exists.
|
|
|
|
Verifies the method returns (user, True) for new users.
|
|
"""
|
|
oauth_info = OAuthUserInfo(
|
|
provider="discord",
|
|
oauth_id="brand-new-id",
|
|
email="brandnew@example.com",
|
|
name="Brand New",
|
|
)
|
|
|
|
result, created = await user_service.get_or_create_from_oauth(oauth_info)
|
|
|
|
assert created is True
|
|
assert result.email == "brandnew@example.com"
|
|
|
|
|
|
class TestUpdate:
|
|
"""Tests for update method."""
|
|
|
|
async def test_updates_display_name(self, db_session, user_service):
|
|
"""Test that update changes display_name when provided."""
|
|
user = User(
|
|
email="update@example.com",
|
|
display_name="Old Name",
|
|
oauth_provider="google",
|
|
oauth_id="update-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
update_data = UserUpdate(display_name="New Name")
|
|
result = await user_service.update(user_id, update_data)
|
|
|
|
assert result.display_name == "New Name"
|
|
|
|
async def test_updates_avatar_url(self, db_session, user_service):
|
|
"""Test that update changes avatar_url when provided."""
|
|
user = User(
|
|
email="avatar@example.com",
|
|
display_name="Avatar User",
|
|
oauth_provider="google",
|
|
oauth_id="avatar-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
update_data = UserUpdate(avatar_url="https://new-avatar.com/img.jpg")
|
|
result = await user_service.update(user_id, update_data)
|
|
|
|
assert result.avatar_url == "https://new-avatar.com/img.jpg"
|
|
|
|
async def test_ignores_none_values(self, db_session, user_service):
|
|
"""Test that update doesn't change fields set to None.
|
|
|
|
Only explicitly provided fields should be updated.
|
|
"""
|
|
user = User(
|
|
email="keep@example.com",
|
|
display_name="Keep Me",
|
|
avatar_url="https://keep.com/avatar.jpg",
|
|
oauth_provider="google",
|
|
oauth_id="keep-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
# Update only display_name, leave avatar alone
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
update_data = UserUpdate(display_name="Changed")
|
|
result = await user_service.update(user_id, update_data)
|
|
|
|
assert result.display_name == "Changed"
|
|
assert result.avatar_url == "https://keep.com/avatar.jpg"
|
|
|
|
|
|
class TestUpdateLastLogin:
|
|
"""Tests for update_last_login method."""
|
|
|
|
async def test_updates_last_login_timestamp(self, db_session, user_service):
|
|
"""Test that update_last_login sets current timestamp."""
|
|
user = User(
|
|
email="login@example.com",
|
|
display_name="Login User",
|
|
oauth_provider="google",
|
|
oauth_id="login-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
assert user.last_login is None
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
before = datetime.now(UTC)
|
|
result = await user_service.update_last_login(user_id)
|
|
after = datetime.now(UTC)
|
|
|
|
assert result.last_login is not None
|
|
# Allow 1 second tolerance
|
|
assert before - timedelta(seconds=1) <= result.last_login <= after + timedelta(seconds=1)
|
|
|
|
|
|
class TestUpdatePremium:
|
|
"""Tests for update_premium method."""
|
|
|
|
async def test_grants_premium(self, db_session, user_service):
|
|
"""Test that update_premium sets premium status and expiration."""
|
|
user = User(
|
|
email="premium@example.com",
|
|
display_name="Premium User",
|
|
oauth_provider="google",
|
|
oauth_id="premium-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
assert user.is_premium is False
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
expires = datetime.now(UTC) + timedelta(days=30)
|
|
result = await user_service.update_premium(user_id, expires)
|
|
|
|
assert result.is_premium is True
|
|
assert result.premium_until == expires
|
|
|
|
async def test_removes_premium(self, db_session, user_service):
|
|
"""Test that update_premium with None removes premium status."""
|
|
user = User(
|
|
email="unpremium@example.com",
|
|
display_name="Unpremium User",
|
|
oauth_provider="google",
|
|
oauth_id="unpremium-id",
|
|
is_premium=True,
|
|
premium_until=datetime.now(UTC) + timedelta(days=30),
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
result = await user_service.update_premium(user_id, None)
|
|
|
|
assert result.is_premium is False
|
|
assert result.premium_until is None
|
|
|
|
|
|
class TestDelete:
|
|
"""Tests for delete method."""
|
|
|
|
async def test_deletes_user(self, db_session, user_service):
|
|
"""Test that delete removes user from database."""
|
|
user = User(
|
|
email="delete@example.com",
|
|
display_name="Delete Me",
|
|
oauth_provider="google",
|
|
oauth_id="delete-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
await user_service.delete(user_id)
|
|
|
|
# Verify user is gone
|
|
result = await user_service.get_by_id(user_id)
|
|
assert result is None
|
|
|
|
|
|
class TestGetLinkedAccount:
|
|
"""Tests for get_linked_account method."""
|
|
|
|
async def test_returns_linked_account_when_found(self, db_session, user_service):
|
|
"""Test that get_linked_account returns account when it exists.
|
|
|
|
Creates a user with a linked account and verifies it can be retrieved.
|
|
"""
|
|
# Create user
|
|
user = User(
|
|
email="primary@example.com",
|
|
display_name="Primary User",
|
|
oauth_provider="google",
|
|
oauth_id="google-primary",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
|
|
# Create linked account
|
|
linked = OAuthLinkedAccount(
|
|
user_id=user.id,
|
|
provider="discord",
|
|
oauth_id="discord-linked-123",
|
|
email="linked@example.com",
|
|
)
|
|
db_session.add(linked)
|
|
await db_session.commit()
|
|
|
|
# Retrieve linked account
|
|
result = await user_service.get_linked_account("discord", "discord-linked-123")
|
|
|
|
assert result is not None
|
|
assert result.provider == "discord"
|
|
assert result.oauth_id == "discord-linked-123"
|
|
|
|
async def test_returns_none_when_not_found(self, user_service):
|
|
"""Test that get_linked_account returns None for nonexistent accounts."""
|
|
result = await user_service.get_linked_account("discord", "nonexistent-id")
|
|
assert result is None
|
|
|
|
|
|
class TestLinkOAuthAccount:
|
|
"""Tests for link_oauth_account method."""
|
|
|
|
async def test_links_new_provider(self, db_session, user_service):
|
|
"""Test that link_oauth_account successfully links a new provider.
|
|
|
|
Creates a Google user and links Discord to them.
|
|
"""
|
|
# Create user with Google
|
|
user = User(
|
|
email="google-user@example.com",
|
|
display_name="Google User",
|
|
oauth_provider="google",
|
|
oauth_id="google-123",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
|
|
# Link Discord
|
|
discord_info = OAuthUserInfo(
|
|
provider="discord",
|
|
oauth_id="discord-456",
|
|
email="discord@example.com",
|
|
name="Discord Name",
|
|
avatar_url="https://discord.com/avatar.png",
|
|
)
|
|
|
|
result = await user_service.link_oauth_account(user_id, user.oauth_provider, discord_info)
|
|
|
|
assert result is not None
|
|
assert result.provider == "discord"
|
|
assert result.oauth_id == "discord-456"
|
|
assert result.email == "discord@example.com"
|
|
assert result.display_name == "Discord Name"
|
|
assert str(result.user_id) == str(user_id)
|
|
|
|
async def test_raises_error_if_already_linked_to_same_user(self, db_session, user_service):
|
|
"""Test that linking same provider twice raises error.
|
|
|
|
A user cannot have the same provider linked multiple times.
|
|
"""
|
|
user = User(
|
|
email="double-link@example.com",
|
|
display_name="Double Link",
|
|
oauth_provider="google",
|
|
oauth_id="google-double",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
|
|
# Link Discord first time
|
|
discord_info = OAuthUserInfo(
|
|
provider="discord",
|
|
oauth_id="discord-first",
|
|
email="first@discord.com",
|
|
name="First",
|
|
)
|
|
await user_service.link_oauth_account(user_id, user.oauth_provider, discord_info)
|
|
|
|
# Try to link same Discord account again
|
|
with pytest.raises(AccountLinkingError) as exc_info:
|
|
await user_service.link_oauth_account(user_id, user.oauth_provider, discord_info)
|
|
|
|
assert "already linked to your account" in str(exc_info.value)
|
|
|
|
async def test_raises_error_if_linked_to_another_user(self, db_session, user_service):
|
|
"""Test that linking account already linked to another user raises error.
|
|
|
|
The same OAuth provider+ID cannot be linked to multiple users.
|
|
"""
|
|
# Create first user and link Discord
|
|
user1 = User(
|
|
email="user1@example.com",
|
|
display_name="User 1",
|
|
oauth_provider="google",
|
|
oauth_id="google-user1",
|
|
)
|
|
db_session.add(user1)
|
|
await db_session.commit()
|
|
await db_session.refresh(user1)
|
|
|
|
user1_id = UUID(user1.id) if isinstance(user1.id, str) else user1.id
|
|
|
|
discord_info = OAuthUserInfo(
|
|
provider="discord",
|
|
oauth_id="shared-discord",
|
|
email="shared@discord.com",
|
|
name="Shared",
|
|
)
|
|
await user_service.link_oauth_account(user1_id, user1.oauth_provider, discord_info)
|
|
|
|
# Create second user
|
|
user2 = User(
|
|
email="user2@example.com",
|
|
display_name="User 2",
|
|
oauth_provider="google",
|
|
oauth_id="google-user2",
|
|
)
|
|
db_session.add(user2)
|
|
await db_session.commit()
|
|
await db_session.refresh(user2)
|
|
|
|
user2_id = UUID(user2.id) if isinstance(user2.id, str) else user2.id
|
|
|
|
# Try to link same Discord account to second user
|
|
with pytest.raises(AccountLinkingError) as exc_info:
|
|
await user_service.link_oauth_account(user2_id, user2.oauth_provider, discord_info)
|
|
|
|
assert "already linked to another user" in str(exc_info.value)
|
|
|
|
async def test_raises_error_if_linking_primary_provider(self, db_session, user_service):
|
|
"""Test that linking the same provider as primary raises error.
|
|
|
|
User cannot link Google if they already signed up with Google.
|
|
"""
|
|
user = User(
|
|
email="google-primary@example.com",
|
|
display_name="Google Primary",
|
|
oauth_provider="google",
|
|
oauth_id="google-primary-id",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
|
|
# Try to link another Google account
|
|
google_info = OAuthUserInfo(
|
|
provider="google",
|
|
oauth_id="google-different-id",
|
|
email="different@gmail.com",
|
|
name="Different",
|
|
)
|
|
|
|
with pytest.raises(AccountLinkingError) as exc_info:
|
|
await user_service.link_oauth_account(user_id, user.oauth_provider, google_info)
|
|
|
|
assert "primary login provider" in str(exc_info.value)
|
|
|
|
|
|
class TestUnlinkOAuthAccount:
|
|
"""Tests for unlink_oauth_account method."""
|
|
|
|
async def test_unlinks_linked_account(self, db_session, user_service):
|
|
"""Test that unlink_oauth_account removes a linked account.
|
|
|
|
Links Discord then unlinks it successfully.
|
|
"""
|
|
user = User(
|
|
email="unlink@example.com",
|
|
display_name="Unlink User",
|
|
oauth_provider="google",
|
|
oauth_id="google-unlink",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
|
|
# Link Discord
|
|
discord_info = OAuthUserInfo(
|
|
provider="discord",
|
|
oauth_id="discord-unlink",
|
|
email="discord@unlink.com",
|
|
name="Discord Unlink",
|
|
)
|
|
await user_service.link_oauth_account(user_id, user.oauth_provider, discord_info)
|
|
|
|
# Unlink
|
|
result = await user_service.unlink_oauth_account(user_id, user.oauth_provider, "discord")
|
|
|
|
assert result is True
|
|
|
|
# Verify unlinked
|
|
linked = await user_service.get_linked_account("discord", "discord-unlink")
|
|
assert linked is None
|
|
|
|
async def test_returns_false_if_not_linked(self, db_session, user_service):
|
|
"""Test that unlink returns False if provider isn't linked."""
|
|
user = User(
|
|
email="not-linked@example.com",
|
|
display_name="Not Linked",
|
|
oauth_provider="google",
|
|
oauth_id="google-notlinked",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
result = await user_service.unlink_oauth_account(user_id, user.oauth_provider, "discord")
|
|
|
|
assert result is False
|
|
|
|
async def test_raises_error_if_unlinking_primary(self, db_session, user_service):
|
|
"""Test that unlinking primary provider raises error.
|
|
|
|
User cannot unlink their primary OAuth provider.
|
|
"""
|
|
user = User(
|
|
email="primary-unlink@example.com",
|
|
display_name="Primary Unlink",
|
|
oauth_provider="google",
|
|
oauth_id="google-primary-unlink",
|
|
)
|
|
db_session.add(user)
|
|
await db_session.commit()
|
|
await db_session.refresh(user)
|
|
|
|
user_id = UUID(user.id) if isinstance(user.id, str) else user.id
|
|
|
|
with pytest.raises(AccountLinkingError) as exc_info:
|
|
await user_service.unlink_oauth_account(user_id, user.oauth_provider, "google")
|
|
|
|
assert "primary login provider" in str(exc_info.value)
|