"""Тесты обработки подписок пользователей через invite_link.""" import datetime import pytest from src import domain, usecase from tests.conftest import MockDatabase @pytest.mark.asyncio async def test_handle_subscription_success(usecases: usecase.Usecase, mock_database: MockDatabase) -> None: """Первая подписка пользователя по invite_link успешно создаётся.""" user = domain.User(telegram_id=123456, username='testuser') await mock_database.create_user(user) target_channel = domain.TargetChannel( telegram_id=-1001234567890, title='Target Channel', username='targetchannel', user_id=user.id, is_active=True, ) await mock_database.upsert_target_channel(target_channel) external_channel = domain.ExternalChannel( telegram_id=-1009876543210, title='External Channel', username='externalchannel', description=None, subscribers_count=50000, user_id=user.id, ) await mock_database.create_external_channel(external_channel) creative = domain.Creative( name='Test Creative', text='Test text', target_channel_id=target_channel.id, user_id=user.id, status=domain.CreativeStatus.ACTIVE, ) await mock_database.create_creative(creative) invite_link = 'https://t.me/+unique_link_123' purchase = domain.Purchase( target_channel_id=target_channel.id, external_channel_id=external_channel.id, creative_id=creative.id, user_id=user.id, placement_date=datetime.datetime.now(datetime.UTC), invite_link=invite_link, invite_link_type=domain.InviteLinkType.PUBLIC, status=domain.PurchaseStatus.ACTIVE, subscriptions_count=0, ) await mock_database.create_purchase(purchase) # Симулируем подписку пользователя subscriber_id = 999888777 subscriber_username = 'new_subscriber' await usecases.handle_subscription( user_telegram_id=subscriber_id, username=subscriber_username, invite_link=invite_link, ) # Проверяем, что подписка создана subscription = await mock_database.get_subscription_by_user_and_purchase(subscriber_id, purchase.id) assert subscription is not None assert subscription.user_telegram_id == subscriber_id assert subscription.username == subscriber_username assert subscription.invite_link == invite_link # Проверяем, что счётчик подписок у закупа увеличился updated_purchase = await mock_database.get_purchase(user.id, purchase.id) assert updated_purchase is not None assert updated_purchase.subscriptions_count == 1 @pytest.mark.asyncio async def test_handle_subscription_duplicate_ignored(usecases: usecase.Usecase, mock_database: MockDatabase) -> None: """Повторная подписка того же пользователя игнорируется - не создаёт дубликат.""" user = domain.User(telegram_id=123456, username='testuser') await mock_database.create_user(user) target_channel = domain.TargetChannel( telegram_id=-1001234567890, title='Target Channel', username='targetchannel', user_id=user.id, is_active=True, ) await mock_database.upsert_target_channel(target_channel) external_channel = domain.ExternalChannel( telegram_id=-1009876543210, title='External Channel', username='externalchannel', description=None, subscribers_count=30000, user_id=user.id, ) await mock_database.create_external_channel(external_channel) creative = domain.Creative( name='Test Creative', text='Test text', target_channel_id=target_channel.id, user_id=user.id, status=domain.CreativeStatus.ACTIVE, ) await mock_database.create_creative(creative) invite_link = 'https://t.me/+unique_link_456' purchase = domain.Purchase( target_channel_id=target_channel.id, external_channel_id=external_channel.id, creative_id=creative.id, user_id=user.id, placement_date=datetime.datetime.now(datetime.UTC), invite_link=invite_link, invite_link_type=domain.InviteLinkType.APPROVAL, status=domain.PurchaseStatus.ACTIVE, subscriptions_count=0, ) await mock_database.create_purchase(purchase) subscriber_id = 888777666 subscriber_username = 'duplicate_user' # Первая подписка await usecases.handle_subscription( user_telegram_id=subscriber_id, username=subscriber_username, invite_link=invite_link, ) # Проверяем счётчик updated_purchase = await mock_database.get_purchase(user.id, purchase.id) assert updated_purchase is not None assert updated_purchase.subscriptions_count == 1 # Повторная попытка подписки того же пользователя await usecases.handle_subscription( user_telegram_id=subscriber_id, username=subscriber_username, invite_link=invite_link, ) # Проверяем, что счётчик не увеличился updated_purchase_again = await mock_database.get_purchase(user.id, purchase.id) assert updated_purchase_again is not None assert updated_purchase_again.subscriptions_count == 1 # Остался 1, не увеличился # Проверяем, что в БД только одна подписка этого пользователя all_subscriptions = [s for s in mock_database.subscriptions.values() if s.user_telegram_id == subscriber_id] assert len(all_subscriptions) == 1 @pytest.mark.asyncio async def test_handle_subscription_invalid_invite_link(usecases: usecase.Usecase, mock_database: MockDatabase) -> None: """Подписка по несуществующему invite_link не создаётся.""" fake_invite_link = 'https://t.me/+fake_link_999' # Пытаемся обработать подписку по несуществующей ссылке await usecases.handle_subscription( user_telegram_id=777666555, username='unknown_user', invite_link=fake_invite_link, ) # Проверяем, что подписки не создано assert len(mock_database.subscriptions) == 0 @pytest.mark.asyncio async def test_multiple_users_subscription_same_purchase( usecases: usecase.Usecase, mock_database: MockDatabase ) -> None: """Несколько пользователей подписываются через один закуп - каждый учитывается.""" user = domain.User(telegram_id=123456, username='testuser') await mock_database.create_user(user) target_channel = domain.TargetChannel( telegram_id=-1001234567890, title='Target Channel', username='targetchannel', user_id=user.id, is_active=True, ) await mock_database.upsert_target_channel(target_channel) external_channel = domain.ExternalChannel( telegram_id=-1009876543210, title='External Channel', username='externalchannel', description=None, subscribers_count=40000, user_id=user.id, ) await mock_database.create_external_channel(external_channel) creative = domain.Creative( name='Viral Creative', text='Viral content', target_channel_id=target_channel.id, user_id=user.id, status=domain.CreativeStatus.ACTIVE, ) await mock_database.create_creative(creative) invite_link = 'https://t.me/+popular_link_789' purchase = domain.Purchase( target_channel_id=target_channel.id, external_channel_id=external_channel.id, creative_id=creative.id, user_id=user.id, placement_date=datetime.datetime.now(datetime.UTC), invite_link=invite_link, invite_link_type=domain.InviteLinkType.PUBLIC, status=domain.PurchaseStatus.ACTIVE, subscriptions_count=0, ) await mock_database.create_purchase(purchase) # Подписываются три разных пользователя users_data = [ (111222333, 'user_one'), (444555666, 'user_two'), (777888999, 'user_three'), ] for telegram_id, username in users_data: await usecases.handle_subscription( user_telegram_id=telegram_id, username=username, invite_link=invite_link, ) # Проверяем, что создано 3 подписки purchase_subscriptions = [s for s in mock_database.subscriptions.values() if s.purchase_id == purchase.id] assert len(purchase_subscriptions) == 3 # Проверяем, что счётчик правильный updated_purchase = await mock_database.get_purchase(user.id, purchase.id) assert updated_purchase is not None assert updated_purchase.subscriptions_count == 3 @pytest.mark.asyncio async def test_subscription_tracking_across_different_purchases( usecases: usecase.Usecase, mock_database: MockDatabase ) -> None: """Один пользователь может подписаться через разные закупы - каждая подписка учитывается отдельно.""" user = domain.User(telegram_id=123456, username='testuser') await mock_database.create_user(user) target_channel = domain.TargetChannel( telegram_id=-1001234567890, title='Target Channel', username='targetchannel', user_id=user.id, is_active=True, ) await mock_database.upsert_target_channel(target_channel) external_channel = domain.ExternalChannel( telegram_id=-1009876543210, title='External Channel', username='externalchannel', description=None, subscribers_count=25000, user_id=user.id, ) await mock_database.create_external_channel(external_channel) creative = domain.Creative( name='Test Creative', text='Test text', target_channel_id=target_channel.id, user_id=user.id, status=domain.CreativeStatus.ACTIVE, ) await mock_database.create_creative(creative) # Создаём два разных закупа с разными invite_link purchase1 = domain.Purchase( target_channel_id=target_channel.id, external_channel_id=external_channel.id, creative_id=creative.id, user_id=user.id, placement_date=datetime.datetime.now(datetime.UTC), invite_link='https://t.me/+link_a', invite_link_type=domain.InviteLinkType.PUBLIC, status=domain.PurchaseStatus.ACTIVE, subscriptions_count=0, ) purchase2 = domain.Purchase( target_channel_id=target_channel.id, external_channel_id=external_channel.id, creative_id=creative.id, user_id=user.id, placement_date=datetime.datetime.now(datetime.UTC), invite_link='https://t.me/+link_b', invite_link_type=domain.InviteLinkType.PUBLIC, status=domain.PurchaseStatus.ACTIVE, subscriptions_count=0, ) await mock_database.create_purchase(purchase1) await mock_database.create_purchase(purchase2) subscriber_id = 555444333 # Пользователь подписывается через первый закуп await usecases.handle_subscription( user_telegram_id=subscriber_id, username='multi_subscriber', invite_link=purchase1.invite_link, ) # Тот же пользователь "подписывается" через второй закуп # (технически невозможно, но тестируем логику) await usecases.handle_subscription( user_telegram_id=subscriber_id, username='multi_subscriber', invite_link=purchase2.invite_link, ) # Проверяем, что у каждого закупа свой счётчик updated_purchase1 = await mock_database.get_purchase(user.id, purchase1.id) updated_purchase2 = await mock_database.get_purchase(user.id, purchase2.id) assert updated_purchase1 is not None assert updated_purchase1.subscriptions_count == 1 assert updated_purchase2 is not None assert updated_purchase2.subscriptions_count == 1 # Проверяем, что создано 2 подписки user_subscriptions = [s for s in mock_database.subscriptions.values() if s.user_telegram_id == subscriber_id] assert len(user_subscriptions) == 2