from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.forms import Textarea
from django.test import RequestFactory, SimpleTestCase, TestCase
from django.urls import reverse
from etpgrf.config import MODE_UNICODE, SANITIZE_ETPGRF
from taggit.models import Tag
from web.admin import AdminContentForm
from web.add_function import clean_text_to_slug, safe_html_special_symbols
from web.legacy_links import build_canonical_url, replace_legacy_links
from web.models import TbContent
# Этот файл смешивает два типа проверок:
# - простые тесты чистой логики без базы данных (`SimpleTestCase`),
# - интеграционные Django-тесты с базой и HTTP-клиентом (`TestCase`).
class LegacyLinksTests(SimpleTestCase):
# `SimpleTestCase` подходит для функций, которые не ходят в базу и не
# требуют полноценного HTTP-запроса: здесь мы просто проверяем строки.
def test_build_canonical_url_without_slug(self):
self.assertEqual(build_canonical_url(123, ''), '/item/123-')
def test_replace_legacy_links_rewrites_internal_urls(self):
text = (
'link'
'x'
'external'
)
new_text, matches = replace_legacy_links(text, {123: 'new-title', 456: 'article-title'})
self.assertIn('/item/123-new-title', new_text)
self.assertIn('/item/456-article-title', new_text)
self.assertIn('https://example.com/news/1-latest-news/123-old-title.html', new_text)
self.assertEqual(len(matches), 2)
def test_replace_legacy_links_does_not_touch_image_urls(self):
text = (
'link'
''
)
new_text, matches = replace_legacy_links(text, {123: 'new-title'})
self.assertIn('/item/123-new-title', new_text)
self.assertIn('/images/stories/news/photo123.jpg', new_text)
self.assertEqual(len(matches), 1)
# Эти тесты тоже без базы: проверяем очистку HTML и подготовку slug'ов.
class SafeHtmlSpecialSymbolsTests(SimpleTestCase):
def test_strips_html_tags_and_decodes_entities(self):
text = '
«Привет мир»
'
self.assertEqual(safe_html_special_symbols(text), '«Привет мир»')
def test_clean_text_to_slug_normalizes_non_latin_symbols(self):
self.assertEqual(clean_text_to_slug('αβγ ΔΩ'), 'content')
self.assertEqual(clean_text_to_slug('₽ € $ ₴ ₿'), 'content')
# Здесь проверяем форму админки: её поля, виджеты и виртуальные настройки.
class AdminTypographFormTests(SimpleTestCase):
def test_admin_form_exposes_virtual_typograph_fields(self):
form = AdminContentForm()
self.assertIn('typograph_enabled', form.fields)
self.assertIn('typograph_strip_soft_hyphens', form.fields)
self.assertIn('typograph_mode', form.fields)
self.assertIn('typograph_hyphenation', form.fields)
self.assertIn('typograph_sanitizer', form.fields)
self.assertEqual(form.fields['typograph_mode'].initial, 'mixed')
self.assertTrue(form.fields['typograph_strip_soft_hyphens'].initial)
self.assertTrue(form.fields['typograph_hyphenation'].initial)
self.assertEqual(form.fields['typograph_sanitizer'].initial, 'None')
def test_admin_form_adds_codemirror_attrs_and_media(self):
form = AdminContentForm()
for field_name in ('szContentHead', 'szContentIntro', 'szContentBody'):
self.assertIsInstance(form.fields[field_name].widget, Textarea)
self.assertEqual(
form.fields[field_name].widget.attrs.get('data-codemirror-editor'),
'1',
)
self.assertEqual(
form.fields[field_name].widget.attrs.get('data-language'),
'html',
)
for field_name in ('szContentKeywords', 'szContentDescription'):
self.assertIsInstance(form.fields[field_name].widget, Textarea)
self.assertNotEqual(
form.fields[field_name].widget.attrs.get('data-codemirror-editor'),
'1',
)
self.assertIn('codemirror/editor.js', str(form.media))
def test_tbcontent_model_has_no_btypograf_field(self):
self.assertNotIn('bTypograf', [field.name for field in TbContent._meta.fields])
def test_tbcontent_str_uses_clean_text(self):
item = TbContent(id=7, szContentHead='«Привет мир»')
self.assertEqual(str(item), '007: «Привет мир»')
# В этих тестах уже нужна база и `client`, потому что мы проверяем Django-views
# и JSON-ответы как реальные запросы из браузера.
class TagAutocompleteTests(TestCase):
# `TestCase` поднимает тестовую БД, а `client` умеет делать полноценные запросы
# к Django, как будто их отправил браузер.
def setUp(self):
user_model = get_user_model()
self.user = user_model.objects.create_superuser(
username='admin',
email='admin@example.com',
password='password',
)
Tag.objects.create(name='alpha')
Tag.objects.create(name='beta')
Tag.objects.create(name='gamma')
self.client.force_login(self.user)
def test_returns_tag_results_for_term(self):
response = self.client.get(
reverse('web_tag_autocomplete'),
{'term': 'al'},
)
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(payload['pagination']['more'], False)
self.assertEqual([item['text'] for item in payload['results']], ['alpha'])
def test_returns_initial_tag_batch_without_term(self):
response = self.client.get(reverse('web_tag_autocomplete'))
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(len(payload['results']), 3)
self.assertEqual(payload['pagination']['more'], False)
def test_paginates_tag_results(self):
Tag.objects.all().delete()
for index in range(30):
Tag.objects.create(name=f'tag-{index:02d}')
response = self.client.get(reverse('web_tag_autocomplete'), {'page': 1})
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(len(payload['results']), 25)
self.assertEqual(payload['pagination']['more'], True)
response = self.client.get(reverse('web_tag_autocomplete'), {'page': 2})
self.assertEqual(response.status_code, 200)
payload = response.json()
self.assertEqual(len(payload['results']), 5)
self.assertEqual(payload['pagination']['more'], False)
# Страница со всеми тегами — тоже обычный Django-response, поэтому тут `TestCase`.
class AllTagsPageTests(TestCase):
def setUp(self):
user_model = get_user_model()
self.user = user_model.objects.create_superuser(
username='admin',
email='admin@example.com',
password='password',
)
self.client.force_login(self.user)
item1 = TbContent.objects.create(
szContentHead='Тест 1',
szContentIntro='Анонс 1',
szContentBody='Тело 1',
szContentSlug='test-1',
bContentPublish=True,
)
item2 = TbContent.objects.create(
szContentHead='Тест 2',
szContentIntro='Анонс 2',
szContentBody='Тело 2',
szContentSlug='test-2',
bContentPublish=True,
)
item1.tags.add('alpha', 'beta')
item2.tags.add('alpha')
def test_alltags_page_lists_all_tags_with_counts(self):
response = self.client.get(reverse('web_alltags'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, '')
self.assertContains(response, 'Все теги сайта')
self.assertContains(response, '/tag_alpha')
self.assertContains(response, '/tag_beta')
self.assertContains(response, '2')
self.assertContains(response, '1')
def test_footer_counters_are_loaded_from_static_js(self):
response = self.client.get(reverse('web_alltags'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'js/footer-counters.js')
self.assertNotContains(response, 'googletagmanager.com/gtag/js?id=UA-9116991-1')
self.assertNotContains(response, 'mc.yandex.ru/metrika/tag.js')
self.assertNotContains(response, 'top-fwz1.mail.ru/js/code.js')
def test_accept_cookies_banner_loads_static_js(self):
response = self.client.get(reverse('web_alltags'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'js/accept-cookies.js')
self.assertContains(response, 'id="cookies_accept_button"')
self.assertNotContains(response, 'CookieAcceptDate = new Date()')
self.assertNotContains(response, 'onclick="CookieAcceptDate')
# Здесь проверяем поведение страницы тега, включая пустое состояние.
class TagEmptyStateTests(TestCase):
def setUp(self):
self.item = TbContent.objects.create(
szContentHead='Тест 1',
szContentIntro='Анонс 1',
szContentBody='Тело 1',
szContentSlug='test-1',
bContentPublish=True,
)
self.item.tags.add('alpha')
Tag.objects.create(name='lonely')
def test_tag_page_with_news_still_renders_entries(self):
response = self.client.get('/tag_alpha')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Тест 1')
self.assertNotContains(response, 'Новостей не найдено')
def test_tag_page_without_news_shows_empty_state(self):
response = self.client.get('/tag_lonely')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Новостей не найдено')
self.assertContains(response, 'По этому тегу пока нет опубликованных новостей.')
self.assertNotContains(response, 'Тест 1')
def test_tag_page_for_missing_slug_shows_missing_tag_message(self):
response = self.client.get('/tag_rebranded-tag')
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Тег не найден')
self.assertContains(response, 'не найден или был переименован')
# Тут мы проверяем модель и `save()`: slug, типограф, счётчик просмотров и SEO-поля.
class TypographTests(TestCase):
def test_save_generates_slug_from_clean_text(self):
item = TbContent(szContentHead='Привет мир')
item.save()
self.assertEqual(item.szContentSlug, 'privet-mir')
def test_save_normalizes_non_latin_slug_to_default(self):
item = TbContent(szContentHead='αβγ ΔΩ')
item.save()
self.assertEqual(item.szContentSlug, 'content')
def test_save_uses_etpgrf_and_clears_flag(self):
item = TbContent(
szContentHead='«Привет»',
szContentIntro='