- Добавлен content_bot/landing_utils.py: получение ссылок MAX и Telegram из lk_integration по slug партнёра, нормализация URL и автоисправление записей в БД. - landing_page больше не берёт Telegram из настроек max_bot; удалён устаревший fallback на url_telegram. - Добавлены тесты нормализации URL и формирования кнопок лендинга.main
parent
e5a823c99f
commit
a88aeba01a
@ -0,0 +1,123 @@
|
|||||||
|
import json
|
||||||
|
from typing import Callable, Optional, Tuple
|
||||||
|
|
||||||
|
from max_bot.models import Client, Integration
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_telegram_url(url: Optional[str]) -> Optional[str]:
|
||||||
|
"""Приводит ссылку на Telegram-бота к виду https://t.me/bot_name."""
|
||||||
|
if not url or not str(url).strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
value = str(url).strip()
|
||||||
|
if value.startswith('@'):
|
||||||
|
value = value[1:]
|
||||||
|
|
||||||
|
if value.startswith('https://'):
|
||||||
|
return value
|
||||||
|
if value.startswith('http://'):
|
||||||
|
return f'https://{value[7:]}'
|
||||||
|
if value.startswith('t.me/'):
|
||||||
|
return f'https://{value}'
|
||||||
|
|
||||||
|
return f'https://t.me/{value.lstrip("/")}'
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_max_bot_url(url: Optional[str]) -> Optional[str]:
|
||||||
|
"""Приводит ссылку на MAX-бота к виду https://max.ru/bot_id."""
|
||||||
|
if not url or not str(url).strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
value = str(url).strip()
|
||||||
|
if value.startswith('https://max.ru/'):
|
||||||
|
return value
|
||||||
|
if value.startswith('http://max.ru/'):
|
||||||
|
return f'https://max.ru/{value[len("http://max.ru/"):]}'
|
||||||
|
if value.startswith('max.ru/'):
|
||||||
|
return f'https://{value}'
|
||||||
|
|
||||||
|
return f'https://max.ru/{value.lstrip("/")}'
|
||||||
|
|
||||||
|
|
||||||
|
def _load_settings(integration: Integration) -> dict:
|
||||||
|
if not integration.setting:
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
settings = json.loads(integration.setting)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return {}
|
||||||
|
return settings if isinstance(settings, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_settings(integration: Integration, settings: dict) -> None:
|
||||||
|
integration.setting = json.dumps(settings, ensure_ascii=False)
|
||||||
|
integration.save(update_fields=['setting', 'updated_at'])
|
||||||
|
|
||||||
|
|
||||||
|
def get_integration_url(
|
||||||
|
client: Client,
|
||||||
|
partner_slug: str,
|
||||||
|
setting_key: str = 'url',
|
||||||
|
normalizer: Optional[Callable[[Optional[str]], Optional[str]]] = None,
|
||||||
|
persist_fix: bool = True,
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Возвращает URL из lk_integration по slug партнёра.
|
||||||
|
При необходимости нормализует и сохраняет исправленное значение в БД.
|
||||||
|
"""
|
||||||
|
integration = Integration.objects.filter(
|
||||||
|
client=client,
|
||||||
|
partner__slug=partner_slug,
|
||||||
|
status=True,
|
||||||
|
).select_related('partner').first()
|
||||||
|
if not integration:
|
||||||
|
return None
|
||||||
|
|
||||||
|
settings = _load_settings(integration)
|
||||||
|
raw_url = settings.get(setting_key)
|
||||||
|
if not raw_url:
|
||||||
|
return None
|
||||||
|
|
||||||
|
normalized = normalizer(raw_url) if normalizer else raw_url
|
||||||
|
if persist_fix and normalized and normalized != raw_url:
|
||||||
|
settings[setting_key] = normalized
|
||||||
|
_save_settings(integration, settings)
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_max_bot_integration(client: Client) -> None:
|
||||||
|
"""Удаляет устаревший ключ url_telegram из настроек max_bot."""
|
||||||
|
integration = Integration.objects.filter(
|
||||||
|
client=client,
|
||||||
|
partner__slug='max_bot',
|
||||||
|
status=True,
|
||||||
|
).first()
|
||||||
|
if not integration:
|
||||||
|
return
|
||||||
|
|
||||||
|
settings = _load_settings(integration)
|
||||||
|
if 'url_telegram' not in settings:
|
||||||
|
return
|
||||||
|
|
||||||
|
del settings['url_telegram']
|
||||||
|
_save_settings(integration, settings)
|
||||||
|
|
||||||
|
|
||||||
|
def get_landing_bot_urls(client: Client) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
"""Возвращает (max_bot_url, telegram_url) для лендинга."""
|
||||||
|
cleanup_max_bot_integration(client)
|
||||||
|
|
||||||
|
max_bot_url = get_integration_url(
|
||||||
|
client=client,
|
||||||
|
partner_slug='max_bot',
|
||||||
|
setting_key='url',
|
||||||
|
normalizer=normalize_max_bot_url,
|
||||||
|
)
|
||||||
|
telegram_url = get_integration_url(
|
||||||
|
client=client,
|
||||||
|
partner_slug='telegram_bot',
|
||||||
|
setting_key='url',
|
||||||
|
normalizer=normalize_telegram_url,
|
||||||
|
)
|
||||||
|
return max_bot_url, telegram_url
|
||||||
@ -1,3 +1,99 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase, Client as DjangoClient
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
# Create your tests here.
|
from content_bot.landing_utils import (
|
||||||
|
normalize_telegram_url,
|
||||||
|
normalize_max_bot_url,
|
||||||
|
get_landing_bot_urls,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LandingUrlNormalizationTests(TestCase):
|
||||||
|
def test_normalize_telegram_full_url(self):
|
||||||
|
self.assertEqual(
|
||||||
|
normalize_telegram_url('https://t.me/cheguevaraclub_bot'),
|
||||||
|
'https://t.me/cheguevaraclub_bot',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_normalize_telegram_without_scheme(self):
|
||||||
|
self.assertEqual(
|
||||||
|
normalize_telegram_url('t.me/celentanouu_bot'),
|
||||||
|
'https://t.me/celentanouu_bot',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_normalize_telegram_bot_name(self):
|
||||||
|
self.assertEqual(
|
||||||
|
normalize_telegram_url('barLeto_bot'),
|
||||||
|
'https://t.me/barLeto_bot',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_normalize_max_bot_full_url(self):
|
||||||
|
self.assertEqual(
|
||||||
|
normalize_max_bot_url('https://max.ru/id0323343968_bot'),
|
||||||
|
'https://max.ru/id0323343968_bot',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_normalize_max_bot_short_id(self):
|
||||||
|
self.assertEqual(
|
||||||
|
normalize_max_bot_url('d0323350531_bot'),
|
||||||
|
'https://max.ru/d0323350531_bot',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LandingBotUrlsTests(TestCase):
|
||||||
|
@patch('content_bot.landing_utils.Integration')
|
||||||
|
def test_get_landing_bot_urls_uses_separate_integrations(self, mock_integration):
|
||||||
|
client = MagicMock()
|
||||||
|
max_integration = MagicMock()
|
||||||
|
max_integration.setting = '{"url":"d0323350531_bot"}'
|
||||||
|
telegram_integration = MagicMock()
|
||||||
|
telegram_integration.setting = '{"url":"https://t.me/cheguevaraclub_bot"}'
|
||||||
|
|
||||||
|
def filter_side_effect(**kwargs):
|
||||||
|
qs = MagicMock()
|
||||||
|
slug = kwargs.get('partner__slug')
|
||||||
|
if slug == 'max_bot':
|
||||||
|
qs.first.return_value = max_integration
|
||||||
|
elif slug == 'telegram_bot':
|
||||||
|
qs.first.return_value = telegram_integration
|
||||||
|
else:
|
||||||
|
qs.first.return_value = None
|
||||||
|
return qs
|
||||||
|
|
||||||
|
mock_integration.objects.filter.side_effect = filter_side_effect
|
||||||
|
|
||||||
|
with patch('content_bot.landing_utils._save_settings') as mock_save:
|
||||||
|
max_url, telegram_url = get_landing_bot_urls(client)
|
||||||
|
|
||||||
|
self.assertEqual(max_url, 'https://max.ru/d0323350531_bot')
|
||||||
|
self.assertEqual(telegram_url, 'https://t.me/cheguevaraclub_bot')
|
||||||
|
self.assertEqual(mock_save.call_count, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class LandingPageViewTests(TestCase):
|
||||||
|
@patch('content_bot.views.get_landing_bot_urls')
|
||||||
|
@patch('content_bot.views.Qr')
|
||||||
|
@patch('content_bot.views.get_object_or_404')
|
||||||
|
def test_landing_page_passes_urls_to_template(self, mock_get_object, mock_qr, mock_get_urls):
|
||||||
|
client_obj = MagicMock()
|
||||||
|
client_obj.id = 1089
|
||||||
|
client_obj.brand = 'Test Brand'
|
||||||
|
client_obj.logo = ''
|
||||||
|
mock_get_object.return_value = client_obj
|
||||||
|
|
||||||
|
qr_obj = MagicMock()
|
||||||
|
qr_obj.brand = 'QR Brand'
|
||||||
|
qr_obj.logo = 'logo.png'
|
||||||
|
qr_obj.description = 'Description'
|
||||||
|
mock_qr.objects.filter.return_value.first.return_value = qr_obj
|
||||||
|
|
||||||
|
mock_get_urls.return_value = (
|
||||||
|
'https://max.ru/d0323350531_bot',
|
||||||
|
'https://t.me/cheguevaraclub_bot',
|
||||||
|
)
|
||||||
|
|
||||||
|
response = DjangoClient().get('/content/landing/1089/')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'https://max.ru/d0323350531_bot')
|
||||||
|
self.assertContains(response, 'https://t.me/cheguevaraclub_bot')
|
||||||
|
|||||||
Loading…
Reference in new issue