Files
tgex-backend/tests/test_views.py

425 lines
15 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.
"""Тесты работы с просмотрами рекламных постов."""
import datetime
import pytest
from src import domain, dto, usecase
from tests.conftest import MockDatabase
@pytest.mark.asyncio
async def test_fetch_views_no_ad_post_url(usecases: usecase.Usecase, mock_database: MockDatabase) -> None:
"""Попытка получить просмотры для закупа без ad_post_url возвращает unavailable."""
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,
status=domain.TargetChannelStatus.ACTIVE,
)
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)
placement = domain.Placement(
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/+link1',
invite_link_type=domain.InviteLinkType.PUBLIC,
status=domain.PlacementStatus.ACTIVE,
ad_post_url=None, # Нет ссылки на пост
)
await mock_database.create_placement(placement)
input_data = dto.FetchViewsInputManually(
placement_id=placement.id,
user_id=user.id,
)
result = await usecases.fetch_views(input=input_data)
assert result.views_count is None
assert result.views_availability == domain.PostViewsAvailability.UNAVAILABLE
assert result.error_message == 'No ad_post_url specified'
@pytest.mark.asyncio
async def test_fetch_views_api_unavailable(usecases: usecase.Usecase, mock_database: MockDatabase) -> None:
"""Если Telegram API недоступен (возвращает None), статус = unavailable."""
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,
status=domain.TargetChannelStatus.ACTIVE,
)
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)
placement = domain.Placement(
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/+link2',
invite_link_type=domain.InviteLinkType.PUBLIC,
status=domain.PlacementStatus.ACTIVE,
ad_post_url='https://t.me/externalchannel/123',
)
await mock_database.create_placement(placement)
input_data = dto.FetchViewsInputManually(
placement_id=placement.id,
user_id=user.id,
)
# Mock telegram writer returns None (TDLib not integrated)
result = await usecases.fetch_views(input=input_data)
assert result.views_count is None
assert result.views_availability == domain.PostViewsAvailability.UNAVAILABLE
assert 'Failed to fetch views' in result.error_message
# Проверяем, что placement обновлён
updated_placement = await mock_database.get_placement(user.id, placement.id)
assert updated_placement is not None
assert updated_placement.views_availability == domain.PostViewsAvailability.UNAVAILABLE
assert updated_placement.last_views_fetch_at is not None
@pytest.mark.asyncio
async def test_update_views_manually(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,
status=domain.TargetChannelStatus.ACTIVE,
)
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='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)
placement = domain.Placement(
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/+link3',
invite_link_type=domain.InviteLinkType.PUBLIC,
status=domain.PlacementStatus.ACTIVE,
ad_post_url='https://t.me/externalchannel/456',
)
await mock_database.create_placement(placement)
input_data = dto.UpdateViewsManuallyInput(
placement_id=placement.id,
user_id=user.id,
views_count=15000,
)
result = await usecases.update_views_manually(input=input_data)
# Проверяем возвращённый результат
assert result.views_count == 15000
assert result.views_availability == domain.PostViewsAvailability.MANUAL
assert result.last_views_fetch_at is not None
# Проверяем, что создана история просмотров
histories = [h for h in mock_database.placement_views_histories.values() if h.placement_id == placement.id]
assert len(histories) == 1
assert histories[0].views_count == 15000
# Проверяем, что закуп обновлён
updated_placement = await mock_database.get_placement(user.id, placement.id)
assert updated_placement is not None
assert updated_placement.views_count == 15000
assert updated_placement.views_availability == domain.PostViewsAvailability.MANUAL
@pytest.mark.asyncio
async def test_get_views_history_empty(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,
status=domain.TargetChannelStatus.ACTIVE,
)
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)
placement = domain.Placement(
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/+link4',
invite_link_type=domain.InviteLinkType.PUBLIC,
status=domain.PlacementStatus.ACTIVE,
)
await mock_database.create_placement(placement)
input_data = dto.GetViewsHistoryInput(
placement_id=placement.id,
user_id=user.id,
)
result = await usecases.get_views_history(input=input_data)
assert len(result.histories) == 0
@pytest.mark.asyncio
async def test_get_views_history_with_snapshots(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,
status=domain.TargetChannelStatus.ACTIVE,
)
await mock_database.upsert_target_channel(target_channel)
external_channel = domain.ExternalChannel(
telegram_id=-1009876543210,
title='External Channel',
username='externalchannel',
description=None,
subscribers_count=35000,
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)
placement = domain.Placement(
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/+link5',
invite_link_type=domain.InviteLinkType.PUBLIC,
status=domain.PlacementStatus.ACTIVE,
)
await mock_database.create_placement(placement)
# Создаём несколько записей истории
now = datetime.datetime.now(datetime.UTC)
history1 = domain.PlacementViewsHistory(
placement_id=placement.id,
views_count=1000,
fetched_at=now - datetime.timedelta(hours=2),
)
history2 = domain.PlacementViewsHistory(
placement_id=placement.id,
views_count=1500,
fetched_at=now - datetime.timedelta(hours=1),
)
history3 = domain.PlacementViewsHistory(
placement_id=placement.id,
views_count=2000,
fetched_at=now,
)
await mock_database.create_placement_views_history(history1)
await mock_database.create_placement_views_history(history2)
await mock_database.create_placement_views_history(history3)
input_data = dto.GetViewsHistoryInput(
placement_id=placement.id,
user_id=user.id,
)
result = await usecases.get_views_history(input=input_data)
assert len(result.histories) == 3
# Проверяем сортировку по времени (старые первые)
assert result.histories[0].views_count == 1000
assert result.histories[1].views_count == 1500
assert result.histories[2].views_count == 2000
@pytest.mark.asyncio
async def test_get_views_history_with_date_filter(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,
status=domain.TargetChannelStatus.ACTIVE,
)
await mock_database.upsert_target_channel(target_channel)
external_channel = domain.ExternalChannel(
telegram_id=-1009876543210,
title='External Channel',
username='externalchannel',
description=None,
subscribers_count=28000,
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)
placement = domain.Placement(
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/+link6',
invite_link_type=domain.InviteLinkType.PUBLIC,
status=domain.PlacementStatus.ACTIVE,
)
await mock_database.create_placement(placement)
# Создаём записи с разными датами
now = datetime.datetime.now(datetime.UTC)
history1 = domain.PlacementViewsHistory(
placement_id=placement.id,
views_count=1000,
fetched_at=now - datetime.timedelta(days=3),
)
history2 = domain.PlacementViewsHistory(
placement_id=placement.id,
views_count=1500,
fetched_at=now - datetime.timedelta(days=1),
)
history3 = domain.PlacementViewsHistory(
placement_id=placement.id,
views_count=2000,
fetched_at=now,
)
await mock_database.create_placement_views_history(history1)
await mock_database.create_placement_views_history(history2)
await mock_database.create_placement_views_history(history3)
# Запрашиваем историю за последние 2 дня
input_data = dto.GetViewsHistoryInput(
placement_id=placement.id,
user_id=user.id,
from_date=now - datetime.timedelta(days=2),
)
result = await usecases.get_views_history(input=input_data)
# Должны вернуться только history2 и history3
assert len(result.histories) == 2
assert result.histories[0].views_count == 1500
assert result.histories[1].views_count == 2000