Files
tgex-backend/tests/test_subscriptions.py
Artem Tsyrulnikov 0bed5c266d feat: subs and views
2025-11-10 17:23:58 +03:00

343 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Тесты обработки подписок пользователей через 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