add: тесты
This commit is contained in:
10
tests/__init__.py
Normal file
10
tests/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Пакет тестов для проекта Окнардия.
|
||||||
|
|
||||||
|
Содержит все юнит-тесты и функциональные тесты для валидации:
|
||||||
|
- Функций обработки текста (sanitize_slug, safe_html)
|
||||||
|
- Функций SEO генерации
|
||||||
|
- JSON парсинга и валидации
|
||||||
|
"""
|
||||||
|
|
||||||
124
tests/test_safe_html.py
Normal file
124
tests/test_safe_html.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Тест для функции safe_html_spec_symbols
|
||||||
|
Демонстрирует улучшенную очистку HTML-разметки
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import django
|
||||||
|
|
||||||
|
# Добавляем путь к проекту (подъём на одну папку выше, т.к. тесты в папке tests/)
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oknardia.settings')
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from oknardia.web.add_func import safe_html_spec_symbols
|
||||||
|
|
||||||
|
|
||||||
|
def test_safe_html_spec_symbols():
|
||||||
|
"""Набор тестов для функции safe_html_spec_symbols"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
# (входная строка, ожидаемый результат, описание)
|
||||||
|
(
|
||||||
|
'Текст с неразрывным пробелом',
|
||||||
|
'Текст с неразрывным пробелом',
|
||||||
|
'Замена на обычный пробел'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Текст с <span class="laquo">«</span> кавычками »',
|
||||||
|
'Текст с « кавычками »',
|
||||||
|
'Удаление span-тегов и замена кавычек'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Текст <script>alert("вредоносный код")</script> после скрипта',
|
||||||
|
'Текст после скрипта',
|
||||||
|
'Удаление содержимого script-тега'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Текст <style>.class { color: red; }</style> со стилем',
|
||||||
|
'Текст со стилем',
|
||||||
|
'Удаление содержимого style-тега'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Цена: 100 № (№ = №)',
|
||||||
|
'Цена: 100 № ( № = №)',
|
||||||
|
'Замена числовых мнемоник (№) на Unicode'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Символы: — … © ®',
|
||||||
|
'Символы: — … © ®',
|
||||||
|
'Замена именованных мнемоник'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'<br>Новая<br />строка',
|
||||||
|
'Новая строка',
|
||||||
|
'Удаление br-тегов'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'<p>Текст</p><nobr>без разрывов</nobr>',
|
||||||
|
'Текст без разрывов',
|
||||||
|
'Удаление nobr-тегов'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Множество пробелов\n\n\tи табуляций',
|
||||||
|
'Множество пробелов и табуляций',
|
||||||
|
'Очистка множественных пробелов и переносов'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'<code>function foo() { return 42; }</code> остаток',
|
||||||
|
'остаток',
|
||||||
|
'Удаление содержимого code-тега'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'<pre>preformatted\n text</pre> после',
|
||||||
|
'после',
|
||||||
|
'Удаление содержимого pre-тега'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
' Текст с пробелами в начале и конце ',
|
||||||
|
'Текст с пробелами в начале и конце',
|
||||||
|
'Trim пробелов в начале/конце'
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'Число A (A) и B (B)',
|
||||||
|
'Число A (A) и B (B)',
|
||||||
|
'Замена десятичных и шестнадцатеричных мнемоник'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
print("=" * 80)
|
||||||
|
print("ТЕСТЫ ДЛЯ safe_html_spec_symbols")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for idx, (input_str, expected, description) in enumerate(test_cases, 1):
|
||||||
|
result = safe_html_spec_symbols(input_str)
|
||||||
|
is_passed = result == expected
|
||||||
|
|
||||||
|
status = "✓ PASS" if is_passed else "✗ FAIL"
|
||||||
|
print(f"\n{idx}. {status}: {description}")
|
||||||
|
print(f" Вход: {repr(input_str[:60])}")
|
||||||
|
print(f" Ожидаемо: {repr(expected)}")
|
||||||
|
print(f" Получено: {repr(result)}")
|
||||||
|
|
||||||
|
if is_passed:
|
||||||
|
passed += 1
|
||||||
|
else:
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print(f"Результаты: {passed} пройдено, {failed} не пройдено из {len(test_cases)}")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
return failed == 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
success = test_safe_html_spec_symbols()
|
||||||
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
236
tests/test_safe_html_standalone.py
Normal file
236
tests/test_safe_html_standalone.py
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Тесты для функции safe_html_spec_symbols() из oknardia/web/add_func.py
|
||||||
|
|
||||||
|
Проверяет:
|
||||||
|
1. Удаление содержимого исключённых тегов (script, style, code, kbd, pre, var, samp)
|
||||||
|
2. Удаление обычных HTML-тегов
|
||||||
|
3. Замену HTML-мнемоник на Unicode (именованные, десятичные, шестнадцатеричные)
|
||||||
|
4. Очистку лишних пробелов
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Добавим путь к проекту для импорта (подъём на одну папку выше, т.к. тесты в папке tests/)
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'oknardia'))
|
||||||
|
|
||||||
|
from web.add_func import safe_html_spec_symbols
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_script_tags():
|
||||||
|
"""Тест 1: Удаление содержимого тегов <script>"""
|
||||||
|
html = 'Текст <script>alert("hack");</script> после'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert 'alert' not in result, f"Script-содержимое не удалено: {result}"
|
||||||
|
assert 'Текст' in result and 'после' in result, f"Обычный текст потеряется: {result}"
|
||||||
|
print("✓ Тест 1 (удаление <script>): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_style_tags():
|
||||||
|
"""Тест 2: Удаление содержимого тегов <style>"""
|
||||||
|
html = 'Блок <style>#id { color: red; }</style> текста'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert 'color' not in result, f"Style-содержимое не удалено: {result}"
|
||||||
|
assert 'Блок' in result and 'текста' in result, f"Обычный текст потеряется: {result}"
|
||||||
|
print("✓ Тест 2 (удаление <style>): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_code_tags():
|
||||||
|
"""Тест 3: Удаление содержимого тегов <code>, <kbd>, <pre>"""
|
||||||
|
html = 'Команда <code>def foo():</code> в тексте. <kbd>Ctrl+C</kbd> текст.'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert 'def foo' not in result, f"Code-содержимое не удалено: {result}"
|
||||||
|
assert 'Ctrl' not in result, f"Kbd-содержимое не удалено: {result}"
|
||||||
|
assert 'Команда' in result and 'тексте' in result, f"Обычный текст потеряется: {result}"
|
||||||
|
print("✓ Тест 3 (удаление <code>, <kbd>): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_object_tags():
|
||||||
|
"""Тест 3a: Удаление содержимого тегов <object>, <embed>"""
|
||||||
|
html = 'Текст <object data="malicious.swf"></object> и <embed src="bad.swf"/> после'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert 'malicious.swf' not in result, f"Object не удалён: {result}"
|
||||||
|
assert 'bad.swf' not in result, f"Embed не удалён: {result}"
|
||||||
|
assert 'Текст' in result and 'после' in result, f"Обычный текст потеряется: {result}"
|
||||||
|
print("✓ Тест 3a (удаление <object>, <embed>): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_form_tags():
|
||||||
|
"""Тест 3b: Удаление содержимого тегов <form>, <input>, <textarea>"""
|
||||||
|
html = 'Текст <form><input type="password"/><textarea>secret</textarea></form> после'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert 'secret' not in result and 'password' not in result, f"Form содержимое не удалено: {result}"
|
||||||
|
assert 'password' not in result, f"Input атрибут не удалён: {result}"
|
||||||
|
assert 'Текст' in result and 'после' in result, f"Обычный текст потеряется: {result}"
|
||||||
|
print("✓ Тест 3b (удаление <form>, <input>, <textarea>): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_svg_canvas():
|
||||||
|
"""Тест 3c: Удаление содержимого тегов <svg>, <canvas>"""
|
||||||
|
html = 'Текст <svg><script>alert("xss")</script></svg> и <canvas id="c"></canvas> после'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert 'xss' not in result and 'script' not in result, f"SVG содержимое не удалено: {result}"
|
||||||
|
assert 'Текст' in result and 'после' in result, f"Обычный текст потеряется: {result}"
|
||||||
|
print("✓ Тест 3c (удаление <svg>, <canvas>): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_html_tags():
|
||||||
|
"""Тест 4: Удаление обычных HTML-тегов"""
|
||||||
|
html = '<p>Параграф <b>с полужирным</b> <i>и курсивом</i></p> <span>спан</span>'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert '<' not in result and '>' not in result, f"HTML-теги не удалены: {result}"
|
||||||
|
assert 'Параграф' in result and 'полужирным' in result and 'курсивом' in result, \
|
||||||
|
f"Текст из тегов потеряется: {result}"
|
||||||
|
print("✓ Тест 4 (удаление HTML-тегов): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_named_entities():
|
||||||
|
"""Тест 5: Замена именованных HTML-мнемоник"""
|
||||||
|
html = ' < > " ' & € © ®'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
# html.unescape преобразует мнемоники в символы
|
||||||
|
assert '&' not in result or 'amp' not in result, f"Мнемоники не заменены: {result}"
|
||||||
|
assert '€' in result, f"Euro не заменён: {result}"
|
||||||
|
assert '©' in result, f"Copyright не заменён: {result}"
|
||||||
|
assert '®' in result, f"Registered не заменён: {result}"
|
||||||
|
print("✓ Тест 5 (именованные мнемоники): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_numeric_entities_decimal():
|
||||||
|
"""Тест 6: Замена десятичных числовых мнемоник (&#ЧИСЛО;)"""
|
||||||
|
html = '№ © €' # № © €
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert '№' in result, f"Decimal entity № не заменена: {result}"
|
||||||
|
assert '©' in result, f"Decimal entity © не заменена: {result}"
|
||||||
|
assert '€' in result, f"Decimal entity € не заменена: {result}"
|
||||||
|
print("✓ Тест 6 (десятичные мнемоники): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_numeric_entities_hex():
|
||||||
|
"""Тест 7: Замена шестнадцатеричных числовых мнемоник (&#xHEX;)"""
|
||||||
|
html = '€ © №' # € © №
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert '€' in result, f"Hex entity € не заменена: {result}"
|
||||||
|
assert '©' in result, f"Hex entity © не заменена: {result}"
|
||||||
|
assert '№' in result, f"Hex entity № не заменена: {result}"
|
||||||
|
print("✓ Тест 7 (шестнадцатеричные мнемоники): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_whitespace_cleanup():
|
||||||
|
"""Тест 8: Очистка лишних пробелов"""
|
||||||
|
html = 'Текст с множественными пробелами\nи\tтабуляцией'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert ' ' not in result, f"Лишние пробелы не удалены: {repr(result)}"
|
||||||
|
assert 'Текст с множественными пробелами и табуляцией' == result, \
|
||||||
|
f"Ожидается 'Текст с множественными пробелами и табуляцией', получено: {repr(result)}"
|
||||||
|
print("✓ Тест 8 (очистка пробелов): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_strip_edges():
|
||||||
|
"""Тест 9: Удаление пробелов в начале и конце"""
|
||||||
|
html = ' Текст '
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert result == 'Текст', f"Пробелы не удалены: {repr(result)}"
|
||||||
|
print("✓ Тест 9 (удаление пробелов в начале/конце): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_html():
|
||||||
|
"""Тест 10: Комплексный тест с комбинацией всего"""
|
||||||
|
html = '''
|
||||||
|
<div class="content">
|
||||||
|
<p>Текст с <b>мнемониками</b>: € №№ €</p>
|
||||||
|
<script>malicious_code()</script>
|
||||||
|
<style>.hide { display: none; }</style>
|
||||||
|
<span>Ещё текст © 2024</span>
|
||||||
|
</div>
|
||||||
|
'''
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
|
||||||
|
# Проверяем, что исключены опасные теги
|
||||||
|
assert 'malicious_code' not in result, f"Script не удалён: {result}"
|
||||||
|
assert 'display: none' not in result, f"Style не удалён: {result}"
|
||||||
|
|
||||||
|
# Проверяем, что обычный текст остался
|
||||||
|
assert 'Текст' in result and 'Ещё текст' in result, f"Обычный текст потеряился: {result}"
|
||||||
|
|
||||||
|
# Проверяем, что HTML-теги удалены
|
||||||
|
assert '<' not in result and '>' not in result, f"HTML-теги не удалены: {result}"
|
||||||
|
|
||||||
|
# Проверяем, что мнемоники заменены
|
||||||
|
assert '€' in result, f"Мнемоники не заменены: {result}"
|
||||||
|
assert '©' in result, f"Copyright не заменён: {result}"
|
||||||
|
|
||||||
|
print(f"✓ Тест 10 (комплексный): пройден")
|
||||||
|
print(f" Результат: {result[:80]}...")
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_string():
|
||||||
|
"""Тест 11: Пустая строка"""
|
||||||
|
result = safe_html_spec_symbols('')
|
||||||
|
assert result == '', f"Ожидается пустая строка, получено: {repr(result)}"
|
||||||
|
print("✓ Тест 11 (пустая строка): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_html_tags():
|
||||||
|
"""Тест 12: Строка только с HTML-тегами"""
|
||||||
|
html = '<div><p></p></div>'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert result == '', f"Ожидается пустая строка, получено: {repr(result)}"
|
||||||
|
print("✓ Тест 12 (только теги): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_russian_text():
|
||||||
|
"""Тест 13: Русский текст с мнемониками"""
|
||||||
|
html = 'Цена: <b>1000 ₽</b> «Российский» — лучший выбор'
|
||||||
|
result = safe_html_spec_symbols(html)
|
||||||
|
assert 'Цена' in result, f"Русский текст потеряется: {result}"
|
||||||
|
assert '«' in result and '»' in result, f"Кавычки не заменены: {result}"
|
||||||
|
assert '—' in result, f"Длинное тире не заменено: {result}"
|
||||||
|
print(f"✓ Тест 13 (русский текст): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("=" * 60)
|
||||||
|
print("Запуск тестов функции safe_html_spec_symbols()")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
test_remove_script_tags,
|
||||||
|
test_remove_style_tags,
|
||||||
|
test_remove_code_tags,
|
||||||
|
test_remove_object_tags,
|
||||||
|
test_remove_form_tags,
|
||||||
|
test_remove_svg_canvas,
|
||||||
|
test_remove_html_tags,
|
||||||
|
test_named_entities,
|
||||||
|
test_numeric_entities_decimal,
|
||||||
|
test_numeric_entities_hex,
|
||||||
|
test_whitespace_cleanup,
|
||||||
|
test_strip_edges,
|
||||||
|
test_complex_html,
|
||||||
|
test_empty_string,
|
||||||
|
test_only_html_tags,
|
||||||
|
test_russian_text,
|
||||||
|
]
|
||||||
|
|
||||||
|
failed = 0
|
||||||
|
for test in tests:
|
||||||
|
try:
|
||||||
|
test()
|
||||||
|
except AssertionError as e:
|
||||||
|
print(f"✗ {test.__name__} ОШИБКА: {e}")
|
||||||
|
failed += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ {test.__name__} ИСКЛЮЧЕНИЕ: {e}")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
if failed == 0:
|
||||||
|
print(f"✅ Все {len(tests)} тестов пройдены успешно!")
|
||||||
|
else:
|
||||||
|
print(f"❌ Провалено {failed} из {len(tests)} тестов")
|
||||||
|
sys.exit(1)
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
202
tests/test_sanitize_slug.py
Normal file
202
tests/test_sanitize_slug.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Тесты для функции sanitize_slug() из oknardia/web/add_func.py
|
||||||
|
|
||||||
|
Проверяет:
|
||||||
|
1. Очистку от HTML-разметки
|
||||||
|
2. Транслитерацию русского текста
|
||||||
|
3. Замену пробелов и недопустимых символов на дефисы
|
||||||
|
4. Удаление множественных дефисов
|
||||||
|
5. Обрезку по максимальной длине
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Добавим путь к проекту для импорта (подъём на одну папку выше, т.к. тесты в папке tests/)
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'oknardia'))
|
||||||
|
|
||||||
|
from web.add_func import sanitize_slug
|
||||||
|
|
||||||
|
|
||||||
|
def test_russian_text_simple():
|
||||||
|
"""Тест 1: Простой русский текст"""
|
||||||
|
result = sanitize_slug('Привет мир')
|
||||||
|
assert result == 'privet-mir', f"Ожидается 'privet-mir', получено: {result}"
|
||||||
|
print("✓ Тест 1 (простой русский текст): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_russian_text_with_special_chars():
|
||||||
|
"""Тест 2: Русский текст со спецсимволами"""
|
||||||
|
result = sanitize_slug('Тест!!! @#$%% текст')
|
||||||
|
assert result == 'test-tekst', f"Ожидается 'test-tekst', получено: {result}"
|
||||||
|
assert '!' not in result and '@' not in result and '#' not in result, \
|
||||||
|
f"Спецсимволы не удалены: {result}"
|
||||||
|
print("✓ Тест 2 (русский текст со спецсимволами): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_tags_removal():
|
||||||
|
"""Тест 3: Удаление HTML-тегов"""
|
||||||
|
text = '<p>Русский <b>текст</b> в <i>тегах</i></p>'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
assert '<' not in result and '>' not in result, f"HTML-теги не удалены: {result}"
|
||||||
|
# pytils транслитирует по-своему (может быть 'russkij' вместо 'russkii', 'tegah' вместо 'tagah')
|
||||||
|
assert 'russ' in result and 'tekst' in result and 'teg' in result, f"Текст потеряился: {result}"
|
||||||
|
print("✓ Тест 3 (удаление HTML-тегов): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_entities():
|
||||||
|
"""Тест 4: Обработка HTML-мнемоник"""
|
||||||
|
text = 'Цена: 100 рублей — отличный © 2024'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
# Проверяем основной смысл: есть слова, есть цифры, нет пробелов и HTML
|
||||||
|
assert 'tsena' in result and '100' in result and '2024' in result, \
|
||||||
|
f"Мнемоники не обработаны правильно: {result}"
|
||||||
|
assert ' ' not in result and '&' not in result and '<' not in result, \
|
||||||
|
f"Остались проблемные символы: {result}"
|
||||||
|
print("✓ Тест 4 (HTML-мнемоники): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiple_spaces():
|
||||||
|
"""Тест 5: Множественные пробелы и табуляция"""
|
||||||
|
text = 'Текст с множественными пробелами\n\tи табуляцией'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
assert '--' not in result, f"Множественные дефисы не удалены: {result}"
|
||||||
|
# Проверяем что результат - это слаг (только буквы, цифры и дефисы)
|
||||||
|
assert all(c.isalnum() or c == '-' for c in result), f"Недопустимые символы в результате: {result}"
|
||||||
|
print("✓ Тест 5 (множественные пробелы): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_leading_trailing_dashes():
|
||||||
|
"""Тест 6: Дефисы в начале и конце"""
|
||||||
|
text = ' - - - Текст - - - '
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
assert not result.startswith('-'), f"Дефис в начале не удалён: {result}"
|
||||||
|
assert not result.endswith('-'), f"Дефис в конце не удалён: {result}"
|
||||||
|
print("✓ Тест 6 (дефисы в начале/конце): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_html_and_text():
|
||||||
|
"""Тест 7: Комплексный тест с HTML и текстом"""
|
||||||
|
text = '<div class="content"><p>Мой блюдо — это традиционный борщ (<b>украинский</b>)</p></div>'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
# Проверяем основной смысл: есть ключевые слова, нет HTML, нет пробелов
|
||||||
|
assert 'moj' in result and 'blyudo' in result and 'borsch' in result and 'ukrainskij' in result, \
|
||||||
|
f"Основной текст потеряился: {result}"
|
||||||
|
assert '<' not in result and '>' not in result and '&' not in result, f"HTML остался: {result}"
|
||||||
|
assert ' ' not in result, f"Пробелы не удалены: {result}"
|
||||||
|
print(f"✓ Тест 7 (комплексный): пройден")
|
||||||
|
print(f" Результат: {result}")
|
||||||
|
|
||||||
|
|
||||||
|
def test_numbers_preserved():
|
||||||
|
"""Тест 8: Цифры сохраняются"""
|
||||||
|
text = 'Выпуск 2024-05-10 номер 42'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
assert '2024' in result and '05' in result and '10' in result and '42' in result, \
|
||||||
|
f"Цифры потеряны: {result}"
|
||||||
|
print("✓ Тест 8 (цифры сохраняются): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_english_text():
|
||||||
|
"""Тест 9: Английский текст"""
|
||||||
|
text = 'Hello World - English Text'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
assert result == 'hello-world-english-text', f"Ожидается 'hello-world-english-text', получено: {result}"
|
||||||
|
print("✓ Тест 9 (английский текст): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_mixed_languages():
|
||||||
|
"""Тест 10: Смешанные языки"""
|
||||||
|
text = 'Python программирование для всех'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
assert 'python' in result and 'programmirovanie' in result, f"Смешанные языки обработаны неправильно: {result}"
|
||||||
|
print("✓ Тест 10 (смешанные языки): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_max_length():
|
||||||
|
"""Тест 11: Ограничение по длине"""
|
||||||
|
long_text = 'А ' * 100 # Очень длинный текст
|
||||||
|
result = sanitize_slug(long_text, max_length=50)
|
||||||
|
assert len(result) <= 52, f"Слишком длинный результат: {len(result)} > 52" # +2 для границы
|
||||||
|
print(f"✓ Тест 11 (ограничение по длине): пройден (длина: {len(result)})")
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_separator():
|
||||||
|
"""Тест 12: Пользовательский разделитель"""
|
||||||
|
text = 'Русский текст для проверки'
|
||||||
|
result_dash = sanitize_slug(text, separator='-')
|
||||||
|
result_underscore = sanitize_slug(text, separator='_')
|
||||||
|
assert '-' in result_dash and '_' not in result_dash, f"Дефис не использован: {result_dash}"
|
||||||
|
assert '_' in result_underscore and '-' not in result_underscore, f"Подчеркивание не использовано: {result_underscore}"
|
||||||
|
print("✓ Тест 12 (пользовательский разделитель): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_string():
|
||||||
|
"""Тест 13: Пустая строка"""
|
||||||
|
result = sanitize_slug('')
|
||||||
|
assert result == '', f"Ожидается пустая строка, получено: {result}"
|
||||||
|
print("✓ Тест 13 (пустая строка): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_only_special_chars():
|
||||||
|
"""Тест 14: Только спецсимволы или пустые результаты"""
|
||||||
|
# pytils.slugify() может вернуть 'and' для некоторых типов спецсимволов
|
||||||
|
result = sanitize_slug('!@#$%^&*()')
|
||||||
|
# Проверяем что результат "пустой" или очень короткий
|
||||||
|
assert len(result) <= 4, f"Слишком длинный результат для только спецсимволов: {result}"
|
||||||
|
print("✓ Тест 14 (только спецсимволы): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
def test_cyrillic_numbers():
|
||||||
|
"""Тест 15: Кириллица с числами"""
|
||||||
|
text = 'Статья № 42 от 2024-01-15'
|
||||||
|
result = sanitize_slug(text)
|
||||||
|
assert '42' in result and '2024' in result, f"Числа потеряны: {result}"
|
||||||
|
assert 'stat' in result, f"Основной текст потеряился: {result}"
|
||||||
|
print("✓ Тест 15 (кириллица с числами): пройден")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("=" * 70)
|
||||||
|
print("Запуск тестов функции sanitize_slug()")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
test_russian_text_simple,
|
||||||
|
test_russian_text_with_special_chars,
|
||||||
|
test_html_tags_removal,
|
||||||
|
test_html_entities,
|
||||||
|
test_multiple_spaces,
|
||||||
|
test_leading_trailing_dashes,
|
||||||
|
test_complex_html_and_text,
|
||||||
|
test_numbers_preserved,
|
||||||
|
test_english_text,
|
||||||
|
test_mixed_languages,
|
||||||
|
test_max_length,
|
||||||
|
test_custom_separator,
|
||||||
|
test_empty_string,
|
||||||
|
test_only_special_chars,
|
||||||
|
test_cyrillic_numbers,
|
||||||
|
]
|
||||||
|
|
||||||
|
failed = 0
|
||||||
|
for test in tests:
|
||||||
|
try:
|
||||||
|
test()
|
||||||
|
except AssertionError as e:
|
||||||
|
print(f"✗ {test.__name__} ОШИБКА: {e}")
|
||||||
|
failed += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ {test.__name__} ИСКЛЮЧЕНИЕ: {e}")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
if failed == 0:
|
||||||
|
print(f"✅ Все {len(tests)} тестов пройдены успешно!")
|
||||||
|
else:
|
||||||
|
print(f"❌ Провалено {failed} из {len(tests)} тестов")
|
||||||
|
sys.exit(1)
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
87
tests/test_seo_autogen.py
Normal file
87
tests/test_seo_autogen.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Тестирование автогенерации SEO-полей в BlogPosts.save()
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import django
|
||||||
|
|
||||||
|
# Добавим путь к проекту (подъём на одну папку выше, т.к. тесты в папке tests/)
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oknardia.settings')
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from oknardia.models import BlogPosts, OurUser
|
||||||
|
|
||||||
|
print("=" * 70)
|
||||||
|
print("ТЕСТИРОВАНИЕ АВТОГЕНЕРАЦИИ SEO-ПОЛЕЙ В save()")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
# Получим автора
|
||||||
|
our_user = OurUser.objects.first()
|
||||||
|
if not our_user:
|
||||||
|
print("❌ Не найден пользователь OurUser")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Создаём тестовый пост (БЕЗ сохранения)
|
||||||
|
post = BlogPosts(
|
||||||
|
sPostHeader="Тест: <b>Привет</b> мир!!! @#$",
|
||||||
|
sPostContent="<cut text='Читать...'/><p>Это содержание поста для тестирования. Здесь может быть много текста с HTML-разметкой. Давайте посмотрим, как работает автогенерация тизера и ключевых слов.</p><p>Вторая строка текста.</p>",
|
||||||
|
bPublished=True,
|
||||||
|
kBlogAuthorUser=our_user,
|
||||||
|
# Оставляем SEO-поля пустыми, чтобы они автогенерировались
|
||||||
|
sSlug="",
|
||||||
|
sMetaDescription="",
|
||||||
|
sMetaKeywords=""
|
||||||
|
)
|
||||||
|
|
||||||
|
print("\n✓ ИСХОДНЫЕ ДАННЫЕ:")
|
||||||
|
print(f" Заголовок: {post.sPostHeader}")
|
||||||
|
print(f" Содержание: {post.sPostContent[:100]}...")
|
||||||
|
print(f" sSlug (пусто): '{post.sSlug}'")
|
||||||
|
print(f" sMetaDescription: '{post.sMetaDescription}'")
|
||||||
|
print(f" sMetaKeywords: '{post.sMetaKeywords}'")
|
||||||
|
|
||||||
|
# Вызываем логику save() вручную (без сохранения в БД)
|
||||||
|
print("\n✓ ПРИМЕНЕНИЕ ЛОГИКИ save()...")
|
||||||
|
|
||||||
|
# Генерируем слаг
|
||||||
|
if not post.sSlug and post.sPostHeader:
|
||||||
|
from web.add_func import sanitize_slug
|
||||||
|
post.sSlug = sanitize_slug(post.sPostHeader, max_length=200)
|
||||||
|
|
||||||
|
# Генерируем description
|
||||||
|
if not post.sMetaDescription and post.sPostContent:
|
||||||
|
import re
|
||||||
|
from web.add_func import sanitize_slug
|
||||||
|
|
||||||
|
content_clean = re.sub(r'<cut[\s\S]*?>', '', post.sPostContent, flags=re.IGNORECASE)
|
||||||
|
tizer = sanitize_slug(content_clean, max_length=200)
|
||||||
|
|
||||||
|
if len(tizer) > 160:
|
||||||
|
tizer = tizer[:160].rsplit(' ', 1)[0] + '...' if ' ' in tizer[:160] else tizer[:160]
|
||||||
|
|
||||||
|
post.sMetaDescription = tizer
|
||||||
|
|
||||||
|
# Генерируем keywords
|
||||||
|
if not post.sMetaKeywords and post.sPostHeader:
|
||||||
|
from web.add_func import sanitize_slug
|
||||||
|
import re
|
||||||
|
|
||||||
|
header_clean = re.sub(r'<[^>]+>', '', post.sPostHeader)
|
||||||
|
header_clean = header_clean.strip()
|
||||||
|
|
||||||
|
fixed_keywords = "oknardia, окнардия, блог, публикация"
|
||||||
|
post.sMetaKeywords = f"{fixed_keywords}, {header_clean}"[:256]
|
||||||
|
|
||||||
|
print("\n✓ РЕЗУЛЬТАТ ПОСЛЕ save():")
|
||||||
|
print(f" sSlug: {post.sSlug}")
|
||||||
|
print(f" sMetaDescription: {post.sMetaDescription} (длина: {len(post.sMetaDescription)})")
|
||||||
|
print(f" sMetaKeywords: {post.sMetaKeywords} (длина: {len(post.sMetaKeywords)})")
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("✅ Все SEO-поля сгенерированы корректно!")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
Reference in New Issue
Block a user