72 KiB
72 KiB
Test Plan
config.py
get_settings()
- Цель: Проверить корректность чтения настроек из переменных окружения и использование значений по умолчанию.
- Тест-кейсы:
- Все переменные установлены:
- Установить все переменные окружения (
TG_API_ID,TG_API_HASH,SESSION_PATH,API_HOST,API_PORT,PYROGRAM_BRIDGE_URL,LOG_LEVEL,DEBUG,TOKEN,TIME_BASED_MERGE,SHOW_BRIDGE_LINK,SHOW_POST_FLAGS). - Вызвать
get_settings(). - Проверить, что возвращенный словарь содержит правильные значения и типы данных (int для
tg_api_id,api_port; bool дляdebug,time_based_merge,show_bridge_link,show_post_flags).
- Установить все переменные окружения (
- Использование значений по умолчанию:
- Установить только обязательные
TG_API_IDиTG_API_HASH. - Не устанавливать остальные переменные.
- Вызвать
get_settings(). - Проверить, что для необязательных переменных используются значения по умолчанию (
data,0.0.0.0,8000,"",INFO,False,"",False,False,False).
- Установить только обязательные
- Частично установленные переменные:
- Установить
TG_API_ID,TG_API_HASHи некоторые необязательные переменные (например,API_PORT,DEBUG="True"). - Вызвать
get_settings(). - Проверить, что установленные переменные считаны корректно, а для остальных используются значения по умолчанию.
- Установить
- Проверка булевых флагов:
- Проверить различные варианты для булевых флагов (
DEBUG,TIME_BASED_MERGE,SHOW_BRIDGE_LINK,SHOW_POST_FLAGS):"True"->True"true"->True(дляTIME_BASED_MERGE,SHOW_BRIDGE_LINK,SHOW_POST_FLAGS)"False"->False"anything else"->False- Переменная не установлена ->
False
- Проверить различные варианты для булевых флагов (
- Проверка обязательных переменных (негативный тест):
- Не устанавливать
TG_API_ID. - Мокнуть
os._exitиprint. - Вызвать
get_settings(). - Проверить, что
printбыл вызван с сообщением об ошибке "TG_API_ID and TG_API_HASH must be set". - Проверить, что
os._exit(1)был вызван. - Повторить тест, не устанавливая
TG_API_HASH. - Повторить тест, не устанавливая обе переменные.
- Не устанавливать
- Проверка типов:
- Убедиться, что
tg_api_idиapi_portвсегда возвращаются какint. - Передать нечисловое значение в
API_PORTи проверить, что используется значение по умолчанию8000. - Передать нечисловое значение в
TG_API_IDи проверить, что возникает ошибка (или как это обрабатывается). Примечание: Текущий код вызоветValueErrorприint(tg_api_id), это тоже можно проверить.
- Убедиться, что
- Все переменные установлены:
url_signer.py
KeyManager
- Цель: Проверить управление секретным ключом (генерация, чтение из файла, кеширование в памяти).
- Замечание: Для тестов необходимо активно использовать моки (
unittest.mock) для изоляции от файловой системы и недетерминированной генерации ключей (os.path.exists,open,secrets.token_hex). Создать временную директориюdataдля тестов или полностью мокать файловые операции. - Тест-кейсы:
- Генерация нового ключа:
- Убедиться, что
SECRET_FILE(data/media_digest.key) не существует (используя мокos.path.exists). - Мокнуть
secrets.token_hexдля возврата предопределенного ключа. - Мокнуть
openдля проверки записи в файл. - Вызвать
KeyManager.get_or_create_signing_key(). - Проверить, что
secrets.token_hex(32)был вызван. - Проверить, что
openбыл вызван для записи ('w') вKeyManager.SECRET_FILE. - Проверить, что в файл был записан сгенерированный ключ.
- Проверить, что метод вернул сгенерированный ключ.
- Сбросить состояние моков
os.path.existsиopen. - Вызвать
KeyManager.get_or_create_signing_key()снова. - Проверить, что
secrets.token_hexиopenдля записи не были вызваны (ключ взят из памятиcls.signing_key). - Проверить, что метод вернул тот же ключ.
- Убедиться, что
- Чтение ключа из файла:
- Установить
KeyManager.signing_key = Noneдля сброса кеша в памяти. - Мокнуть
os.path.existsчтобы он возвращалTrue. - Мокнуть
openдля чтения ('r') изKeyManager.SECRET_FILE, возвращая предопределенный ключ. - Вызвать
KeyManager.get_or_create_signing_key(). - Проверить, что
os.path.existsбыл вызван. - Проверить, что
openбыл вызван для чтения. - Проверить, что
secrets.token_hexне был вызван. - Проверить, что метод вернул ключ, прочитанный из файла.
- Вызвать
KeyManager.get_or_create_signing_key()снова. - Проверить, что
os.path.existsиopenдля чтения не были вызваны (ключ взят из памятиcls.signing_key). - Проверить, что метод вернул тот же ключ.
- Установить
- Использование ключа из памяти:
- Установить
KeyManager.signing_keyв предопределенное значение. - Вызвать
KeyManager.get_or_create_signing_key(). - Проверить, что
os.path.exists,openиsecrets.token_hexне были вызваны. - Проверить, что метод вернул установленное значение
KeyManager.signing_key.
- Установить
- Генерация нового ключа:
generate_media_digest(url)
- Цель: Проверить корректность генерации HMAC-SHA1 дайджеста для URL.
- Замечание: Мокнуть
KeyManager.get_or_create_signing_key, чтобы возвращать фиксированный ключ для предсказуемости тестов. - Тест-кейсы:
- Успешная генерация:
- Мокнуть
KeyManager.get_or_create_signing_keyдля возврата известного ключа (например,'test_key'). - Вызвать
generate_media_digestс тестовым URL (например,'http://example.com/media.jpg'). - Рассчитать ожидаемый HMAC-SHA1 дайджест для
http://example.com/media.jpgс ключомtest_keyи взять первые 8 символов. - Проверить, что функция вернула ожидаемый 8-символьный дайджест.
- Мокнуть
- Разные URL:
- Повторить тест с другим URL, убедиться, что дайджест отличается.
- Пустой URL:
- Вызвать
generate_media_digest(''). - Проверить, что результат является корректным 8-символьным дайджестом для пустой строки и тестового ключа.
- Вызвать
- URL с разными символами:
- Протестировать URL с кириллицей, пробелами, спецсимволами, чтобы убедиться в корректной обработке
url.encode('utf-8').
- Протестировать URL с кириллицей, пробелами, спецсимволами, чтобы убедиться в корректной обработке
- Успешная генерация:
verify_media_digest(url, digest)
- Цель: Проверить корректность верификации дайджеста.
- Замечание: Можно мокнуть
generate_media_digest, чтобы изолировать тесты верификации от тестов генерации, либо использовать общий мок дляKeyManager.get_or_create_signing_key. Использованиеhmac.compare_digestважно для безопасности, тесты должны это учитывать. - Тест-кейсы:
- Успешная верификация:
- Сгенерировать дайджест
d = generate_media_digest(url)(или использовать предрассчитанный). - Вызвать
verify_media_digest(url, d). - Проверить, что результат
True.
- Сгенерировать дайджест
- Неверный дайджест:
- Вызвать
verify_media_digest(url, 'invalid_digest'). - Проверить, что результат
False.
- Вызвать
- Неверный URL:
- Сгенерировать дайджест
d = generate_media_digest(url1). - Вызвать
verify_media_digest(url2, d)гдеurl1 != url2. - Проверить, что результат
False.
- Сгенерировать дайджест
- Пустой дайджест:
- Вызвать
verify_media_digest(url, ''). - Проверить, что результат
False.
- Вызвать
- Дайджест
None:- Вызвать
verify_media_digest(url, None). - Проверить, что результат
False.
- Вызвать
- Дайджест неверной длины:
- Вызвать
verify_media_digest(url, 'short'). - Проверить, что результат
False(из-за несовпадения с ожидаемым 8-символьным дайджестом приhmac.compare_digest). - Вызвать
verify_media_digest(url, 'toolongdigest'). - Проверить, что результат
False.
- Вызвать
- Успешная верификация:
telegram_client.py
TelegramClient
- Цель: Проверить инициализацию, запуск и остановку клиента Pyrogram.
- Замечание: Основное внимание уделяется мокированию зависимостей (
pyrogram.Client,config.get_settings,os.makedirs) и проверке правильности вызовов методов. - Тест-кейсы:
- Инициализация (
__init__):- Мокнуть
config.get_settingsдля возврата тестовых настроек. - Мокнуть
os.makedirs. - Мокнуть
pyrogram.Client. - Создать экземпляр
TelegramClient. - Проверить, что
get_settingsбыл вызван. - Проверить, что
os.makedirsбыл вызван с правильнымsession_pathиз настроек иexist_ok=True. - Проверить, что
pyrogram.Clientбыл вызван с правильнымиname,api_id,api_hash,workdirиз настроек. - Ошибка создания директории:
- Настроить мок
os.makedirsтак, чтобы он вызывал исключение. - Проверить, что при создании экземпляра
TelegramClientвозникает исключение.
- Настроить мок
- Мокнуть
- Запуск (
start):- Мокнуть
self.client(экземплярpyrogram.Client) с атрибутомis_connectedи методомstart. - Клиент не подключен:
- Установить
self.client.is_connected = False. - Вызвать
await client.start(). - Проверить, что
self.client.start()был вызван.
- Установить
- Клиент уже подключен:
- Установить
self.client.is_connected = True. - Вызвать
await client.start(). - Проверить, что
self.client.start()не был вызван.
- Установить
- Ошибка при запуске:
- Настроить мок
self.client.start()так, чтобы он вызывал исключение. - Установить
self.client.is_connected = False. - Проверить, что при вызове
await client.start()возникает исключение.
- Настроить мок
- Мокнуть
- Остановка (
stop):- Мокнуть
self.clientс атрибутомis_connectedи методомstop. - Клиент подключен:
- Установить
self.client.is_connected = True. - Вызвать
await client.stop(). - Проверить, что
self.client.stop()был вызван.
- Установить
- Клиент не подключен:
- Установить
self.client.is_connected = False. - Вызвать
await client.stop(). - Проверить, что
self.client.stop()не был вызван.
- Установить
- Ошибка при остановке (хотя
stopобычно не вызывает исключений, но можно проверить):- Настроить мок
self.client.stop()так, чтобы он вызывал исключение (если это возможно). - Установить
self.client.is_connected = True. - Вызвать
await client.stop()и обработать потенциальное исключение (или убедиться, что оно не мешает).
- Настроить мок
- Мокнуть
- Инициализация (
post_parser.py
PostParser
- Цель: Проверить парсинг и форматирование сообщений Telegram в JSON и HTML.
- Замечание: Это ключевой модуль. Тесты должны покрывать все типы сообщений, медиа, форварды, ответы и другие особенности. Потребуется обширное использование моков для
pyrogram.Client.get_messages,config.get_settings,url_signer.generate_media_digest,os.path,open,json,bleach,datetime. Рекомендуется создать фабрику или набор фикстур для генерации тестовых объектовMessageс различными атрибутами.
Инициализация и Основные методы
__init__(self, client):- Цель: Проверить сохранение клиента.
- Тест-кейс: Создать мок клиента, инициализировать
PostParser, проверить, чтоself.clientравен моку.
channel_name_prepare(self, channel):- Цель: Проверить корректное преобразование ID канала.
- Тест-кейсы:
- Передать строковый ID вида
"-100...". Проверить, что возвращаетсяint. - Передать строковый username (
"mychannel"). Проверить, что возвращается строка без изменений. - Передать числовой ID (если возможно). Проверить поведение (вероятно, вернет как есть).
- Передать пустую строку. Проверить поведение.
- Передать строковый ID вида
get_post(self, channel, post_id, output_type, debug):- Цель: Проверить основную логику получения и форматирования поста.
- Замечание: Мокнуть
self.client.get_messages,self.channel_name_prepare,self._format_html,self.process_message. - Тест-кейсы:
- Успешный вызов (HTML):
- Мокнуть
get_messagesдля возврата тестовогоMessage. - Вызвать
get_postсoutput_type='html'. - Проверить, что
channel_name_prepareбыл вызван сchannel. - Проверить, что
get_messagesбыл вызван с подготовленным каналом иpost_id. - Проверить, что
_format_htmlбыл вызван с полученным сообщением иdebug. - Проверить, что
process_messageне был вызван напрямую (вызывается внутри_format_html). - Проверить, что результат равен значению, возвращенному
_format_html.
- Мокнуть
- Успешный вызов (JSON):
- Мокнуть
get_messagesдля возврата тестовогоMessage. - Вызвать
get_postсoutput_type='json'. - Проверить, что
channel_name_prepareиget_messagesбыли вызваны. - Проверить, что
process_messageбыл вызван с полученным сообщением. - Проверить, что
_format_htmlне был вызван. - Проверить, что результат равен значению, возвращенному
process_message. - Проверить, что результат равен значению, возвращенному
process_message.
- Мокнуть
- Пост не найден:
- Мокнуть
get_messagesдля возвратаNoneили пустого списка. - Вызвать
get_post. - Проверить, что возвращается
None.
- Мокнуть
- Неверный
output_type:- Вызвать
get_postсoutput_type='invalid'. - Проверить, что возвращается
Noneи логируется ошибка.
- Вызвать
- Исключение при
get_messages:- Настроить мок
get_messagesтак, чтобы он вызывал исключение. - Проверить, что
get_postперехватывает исключение, логирует его и вызываетraise.
- Настроить мок
- Включен Debug:
- Мокнуть
print. - Установить
Config["debug"] = True. - Вызвать
get_post. - Проверить, что
print(message)был вызван послеget_messages.
- Мокнуть
- Успешный вызов (HTML):
Вспомогательные функции извлечения информации
_get_author_info(self, message):- Цель: Проверить извлечение имени автора.
- Тест-кейсы:
- Сообщение от канала (есть
sender_chatсtitleиusername). - Сообщение от канала (есть
sender_chatтолько сtitle). - Сообщение от пользователя (есть
from_userсfirst_name,last_name,username). - Сообщение от пользователя (есть
from_userтолько сfirst_name). - Сообщение от пользователя (есть
from_userтолько сusername). - Сообщение без
sender_chatиfrom_user. Проверить возврат"Unknown author". - Поля с
Noneили пустыми строками.
- Сообщение от канала (есть
_generate_title(self, message):- Цель: Проверить генерацию заголовка поста.
- Тест-кейсы:
- Сообщение "Channel created" (
channel_chat_created=True). - Текст содержит только URL YouTube.
- Текст содержит только другой URL.
- Текст содержит только URL и пробельные символы.
- Текст пустой, медиа - фото/видео/GIF/аудио/голос/кружок/стикер/опрос/PDF/документ/webpage. Проверить соответствующие заголовки.
- Текст пустой, медиа нет.
- Текст короткий (до 100 символов), без URL и тегов.
- Текст длинный (больше 100 символов). Проверить обрезку по последнему пробелу и добавление "...".
- Текст содержит URL и HTML-теги. Проверить их удаление перед генерацией заголовка.
- Текст после удаления URL/тегов пустой, но есть
web_page. Проверить заголовок "🔗 Web link". - Текст содержит несколько строк. Проверить, что берется только первая.
- Сообщение "Channel created" (
_format_forward_info(self, message):- Цель: Проверить форматирование информации о пересылке.
- Тест-кейсы:
- Переслано из чата с
username. - Переслано из чата без
username. - Переслано от пользователя с
username. - Переслано от пользователя без
username. - Переслано от скрытого пользователя (
forward_sender_name). - Сообщение не переслано (все соответствующие атрибуты
None). Проверить возвратNone. - Поля
title,first_name,last_nameпустые илиNone.
- Переслано из чата с
_format_reply_info(self, message):- Цель: Проверить форматирование информации об ответе/закрепе.
- Тест-кейсы:
- Закрепленное сообщение (
message.service,reply_to_message). - Ответ на сообщение в чате с
username. - Ответ на сообщение в чате без
username. - Текст ответа длинный (> 100 символов). Проверить обрезку.
- Текст ответа пустой (
textиcaption-None). - Сообщение не является ответом/закрепом. Проверить возврат
None.
- Закрепленное сообщение (
_extract_reactions(self, message):- Цель: Проверить извлечение реакций.
- Тест-кейсы:
- Сообщение с реакциями (
message.reactions.reactions). - Сообщение без реакций (
message.reactions-None). - Реакции разных типов (эмодзи).
- Сообщение с реакциями (
_extract_flags(self, message):- Цель: Проверить извлечение флагов поста.
- Замечание: Мокнуть
self._generate_html_body,self.get_channel_username. - Тест-кейсы:
- Пересланное сообщение (
fwd). - Видео/анимация с коротким текстом (
video). - Пост без медиа или опрос (
no_image). - Стикер (
sticker). - Текст содержит "стрим", "вебинар" и т.д. (
stream). - Текст содержит "донат" (
donat). - Реакции с 30+ 🤡 (
clown). - Реакции с 30+ 💩 (
poo). - Текст содержит "#реклама", "erid" и т.д. (
advert). - Текст/атрибуты содержат http/https ссылки (
link). - Текст содержит упоминания
@channel(mention). - Текст содержит скрытые ссылки
t.me/+...(hid_channel). - Текст содержит ссылки на другие открытые каналы
t.me/otherchannel(foreign_channel). Проверить, что ссылка на текущий канал не добавляет флаг. - Комбинации флагов.
- Сообщение без атрибутов, вызывающих флаги.
- Пересланное сообщение (
get_channel_username(self, message):- Цель: Проверить извлечение username или ID канала.
- Тест-кейсы:
- Чат с активным
usernameвusernames. - Чат с неактивным
usernameвusernames, но естьusername. - Чат только с
username. - Чат без
username, но с ID вида-100.... Проверить возвратstr(ID). - Чат без
usernameи с другим ID. Проверить возвратNone. - Объект
messageне содержитchatилиchatравенNone.
- Чат с активным
Функции форматирования HTML
_format_html(self, message, debug):- Цель: Проверить сборку HTML из частей.
- Замечание: Мокнуть
self.process_messageиjson.dumps. - Тест-кейсы:
- Обычный вызов (
debug=False). Проверить вызовprocess_message, сборку строк из['html']['header'],['html']['media'],['html']['body'],['html']['footer']. - Вызов с
debug=True. Проверить добавление блока<pre>сjson.dumps(message).
- Обычный вызов (
_format_flags(self, message):- Цель: Проверить форматирование флагов в HTML.
- Замечание: Мокнуть
self._extract_flagsиConfig['show_post_flags']. - Тест-кейсы:
show_post_flags = False. Проверить возврат пустой строки.show_post_flags = True,_extract_flagsвозвращает пустой список. Проверить возврат пустой строки.show_post_flags = True,_extract_flagsвозвращает список флагов. Проверить генерациюdiv.message-flagsс флагами.
_generate_html_header(self, message):- Цель: Проверить генерацию HTML-заголовка.
- Замечание: Мокнуть
self._format_forward_info,self._format_reply_info,self._sanitize_html. - Тест-кейсы:
- Есть информация о форварде. Проверить включение результата
_format_forward_info. - Есть информация об ответе (но нет форварда). Проверить включение результата
_format_reply_info. - Нет ни форварда, ни ответа. Проверить пустой результат (до санации).
- Проверить вызов
_sanitize_html.
- Есть информация о форварде. Проверить включение результата
_generate_html_body(self, message):- Цель: Проверить генерацию основного тела HTML.
- Замечание: Мокнуть
self._add_hyperlinks_to_raw_urls,self._format_poll,self._sanitize_html. - Тест-кейсы:
- Есть
message.text. Проверить использованиеmessage.text.html, замену\nна<br>, вызов_add_hyperlinks_to_raw_urls, оборачивание вdiv.message-text. - Есть
message.caption(текста нет). Аналогичноmessage.text. - Есть
message.poll. Проверить вызов_format_pollи добавление результата. - Есть
message.channel_chat_created. Проверить добавлениеdiv.message-service. - Комбинация текста и опроса.
- Нет текста, подписи, опроса, сервис-сообщения. Проверить пустой результат (до санации).
- Проверить вызов
_sanitize_html.
- Есть
_generate_html_media(self, message):- Цель: Проверить генерацию HTML для медиа.
- Замечание: Мокнуть
self._save_media_file_ids,self._get_file_unique_id,self.get_channel_username,Config['pyrogram_bridge_url'],generate_media_digest,self._format_webpage,self._sanitize_html. - Тест-кейсы:
- Разные типы медиа (
PHOTO,VIDEO,ANIMATION,VIDEO_NOTE,AUDIO,VOICE,STICKER,DOCUMENT(обычный),DOCUMENT(PDF)). Проверить генерацию соответствующих тегов (img,video,audio) с правильнымиsrc(включая подпись URL),style,controls. - Медиа без
file_unique_id. - Сообщение без медиа.
- Сообщение с
web_page, короткий текст/подпись. Проверить вызов_format_webpageи добавление результата. - Сообщение с
web_page, длинный текст/подпись. Проверить, что_format_webpageне добавляется. - Проверить вызов
_save_media_file_idsв начале. - Проверить вызов
_sanitize_htmlв конце.
- Разные типы медиа (
_format_webpage(self, webpage, message):- Цель: Проверить форматирование превью веб-страницы.
- Замечание: Мокнуть
Config['pyrogram_bridge_url'],self.get_channel_username,generate_media_digest,self._add_hyperlinks_to_raw_urls. - Тест-кейсы:
- Обычная веб-страница (с
site_name,title,description,display_url,photo). - Ссылка на сообщение Telegram (
type="telegram_message"). Проверить особое форматирование. - Отсутствие некоторых атрибутов (
site_name,title,description,display_url,photo,url). - Описание (
description) с переносами строк и URL. Проверить обработку. - Фото в превью (
webpage.photo). Проверить генерациюimg srcс подписью. - Исключение во время обработки. Проверить логирование и возврат
None.
- Обычная веб-страница (с
_generate_html_footer(self, message):- Цель: Проверить генерацию HTML-подвала.
- Замечание: Мокнуть
self._reactions_views_links,self._format_flags,self._sanitize_html. - Тест-кейсы:
- Есть реакции/просмотры/ссылки и флаги. Проверить включение результатов обеих функций с
<br>. - Есть только реакции/просмотры/ссылки.
- Есть только флаги.
- Нет ни того, ни другого. Проверить возврат
<br>(до санации). - Проверить вызов
_sanitize_html.
- Есть реакции/просмотры/ссылки и флаги. Проверить включение результатов обеих функций с
_add_hyperlinks_to_raw_urls(self, text):- Цель: Проверить добавление тегов
<a>к "сырым" URL. - Тест-кейсы:
- Текст без URL.
- Текст с одним URL.
- Текст с несколькими URL.
- Текст с URL, который уже внутри тега
<a>. Проверить, что он не оборачивается еще раз. - Текст с URL внутри других тегов (
<b>,<i>). Проверить, что они оборачиваются. - Сложный текст с HTML и URL.
- Исключение во время обработки. Проверить логирование и возврат оригинального текста.
- Цель: Проверить добавление тегов
_reactions_views_links(self, message):- Цель: Проверить форматирование реакций, просмотров, даты и ссылок.
- Замечание: Мокнуть
self.get_channel_username,Config['pyrogram_bridge_url'],Config['show_bridge_link'],Config['token'],self._sanitize_html. - Тест-кейсы:
- Есть реакции (обычные, платные
is_paid, кастомныеcustom_emoji_id), просмотры, дата. Проверить корректное форматирование первой строки. - Отсутствуют реакции/просмотры.
- Канал с
username. Проверить генерацию ссылокtg://,https://t.me/. - Канал с ID (
-100...). Проверить генерацию ссылокtg://.../c/...,https://t.me/c/.... Config['show_bridge_link'] = True. Проверить добавление ссылки на бридж с токеном.Config['show_bridge_link'] = False.- Нет ни реакций, ни просмотров, ни даты, ни ссылок. Проверить возврат
None. - Исключение во время обработки. Проверить логирование и возврат
None. - Проверить вызов
_sanitize_html.
- Есть реакции (обычные, платные
_format_poll(self, poll):- Цель: Проверить форматирование опроса.
- Тест-кейсы:
- Обычный опрос с вопросом и вариантами ответа.
- Опрос без вариантов ответа.
- Атрибут
poll.optionsотсутствует илиNone. - Исключение во время обработки. Проверить логирование и возврат строки с ошибкой.
_get_file_unique_id(self, message):- Цель: Проверить извлечение
file_unique_idдля разных типов медиа. - Тест-кейсы:
- Все поддерживаемые
MessageMediaType(photo, video, document и т.д.). MessageMediaType.WEB_PAGEс фото.MessageMediaType.WEB_PAGEбез фото.- Неподдерживаемый тип медиа.
message.media=None.- Отсутствие нужного атрибута (например,
message.photoесть, но у него нетfile_unique_id). Проверить обработку исключения и возвратNone.
- Все поддерживаемые
- Цель: Проверить извлечение
_save_media_file_ids(self, message):- Цель: Проверить сохранение
file_unique_idв JSON-файл. - Замечание: Мокнуть
self.get_channel_username,os.path.abspath,os.path.exists,open,json.load,json.dump,datetime.now. - Тест-кейсы:
- Сообщение с медиа (не большим видео).
- Файл
media_file_ids.jsonне существует. Проверить создание файла и запись данных. - Файл существует, но записи для этого
file_unique_idнет. Проверить добавление записи. - Файл существует, и запись для этого
file_unique_idуже есть. Проверить обновление времениadded.
- Файл
- Сообщение с большим видео (> 100MB). Проверить, что запись не происходит.
- Сообщение без медиа. Проверить, что запись не происходит.
- Не удалось получить
channel_username. Проверить логирование и выход. - Ошибка при чтении/записи JSON-файла. Проверить логирование.
- Ошибка при получении
file_unique_id. Проверить логирование.
- Сообщение с медиа (не большим видео).
- Цель: Проверить сохранение
_sanitize_html(self, html_raw):- Цель: Проверить санацию HTML.
- Замечание: Мокнуть
HTMLSanitizerиCSSSanitizer. - Тест-кейсы:
- Передать валидный HTML с разрешенными тегами и атрибутами. Проверить, что он не изменился.
- Передать HTML с запрещенными тегами (
<script>). Проверить их удаление. - Передать HTML с запрещенными атрибутами (
onclick). Проверить их удаление. - Передать HTML с разрешенными CSS-стилями. Проверить, что они остались.
- Передать HTML с запрещенными CSS-стилями (
position: absolute). Проверить их удаление. - Передать невалидный HTML. Проверить поведение
bleach. - Исключение во время санации. Проверить логирование и возврат исходного
html_raw.
process_message(self, message)
- Цель: Проверить сборку итогового словаря JSON.
- Замечание: Мокнуть все внутренние вызовы (
get_channel_username,_generate_title,_generate_html_header,_generate_html_body,_generate_html_media,_generate_html_footer,_extract_flags,_get_author_info,_extract_reactions,datetime.timestamp). - Тест-кейсы:
- Сообщение со всеми возможными полями (текст, дата, просмотры, реакции, медиа,
media_group_id,web_pageи т.д.). Проверить, что все поля в итоговом словаре заполнены корректно результатами вызовов моков. - Сообщение с минимальным набором полей.
- Сообщение без
web_page. Проверить, что ключwebpageотсутствует в результате. - Проверить корректность форматов (
timestampдля даты, строка дляdate_formatted, список дляflags, словарь дляreactions, словарь дляwebpage).
- Сообщение со всеми возможными полями (текст, дата, просмотры, реакции, медиа,
rss_generator.py
- Цель: Проверить генерацию RSS и HTML лент, включая группировку сообщений, фильтрацию и обработку ошибок.
- Замечание: Много асинхронных функций. Использовать
pytest-asyncio. МокироватьPostParser,pyrogram.Client,feedgen.feed.FeedGenerator,datetime,re,config.get_settings. Понадобятся тестовые объектыMessageи результатыPostParser.process_message.
Вспомогательные функции группировки и рендеринга
_create_time_based_media_groups(messages, merge_seconds):- Цель: Проверить объединение сообщений в группы по времени.
- Тест-кейсы:
- Сообщения без
media_group_id, временной интервал <merge_seconds. Проверить, что они объединяются в одну группу сmedia_group_idвида"time_...". - Сообщения без
media_group_id, временной интервал >merge_seconds. Проверить, что они остаются в разных группах (безmedia_group_idили каждая в своей"time_..."группе, если их >1). - Сообщения с существующими
media_group_id. Проверить, что они не объединяются по времени с другими группами или одиночными сообщениями. - Смешанные сообщения: с
media_group_idи без, с разными интервалами. - Группа сообщений без
media_group_idв начале/конце списка. - Пустой список
messages. - Список с одним сообщением.
- Проверить корректность установки
media_group_idдля всех сообщений в созданной группе.
- Сообщения без
_create_messages_groups(messages):- Цель: Проверить группировку сообщений по
media_group_id. - Тест-кейсы:
- Список сообщений с несколькими
media_group_id. Проверить, что создаются правильные группы. - Список сообщений только с
media_group_id. - Список сообщений без
media_group_id. Проверить, что каждое сообщение становится отдельной группой. - Смешанные сообщения.
- Сообщения с сервисными событиями (
PINNED_MESSAGE,NEW_CHAT_PHOTO,NEW_CHAT_TITLE). Проверить, что они пропускаются. - Пустой список
messages. - Проверить, что сообщения внутри каждой медиа-группы отсортированы по
id(возрастание). - Проверить, что итоговый список
processing_groupsотсортирован по дате первого сообщения в группе (убывание).
- Список сообщений с несколькими
- Цель: Проверить группировку сообщений по
_trim_messages_groups(messages_groups, limit):- Цель: Проверить обрезку списка групп до лимита.
- Тест-кейсы:
- Количество групп <=
limit. Проверить, что удаляется самая старая группа (последняя в списке после сортировки в_create_messages_groups). - Количество групп >
limit. Проверить, что удаляется самая старая и список обрезается доlimitэлементов. - Пустой список
messages_groups. limit = 0(или меньше, хотя проверка есть в вызывающих функциях).
- Количество групп <=
_render_messages_groups(messages_groups, post_parser, exclude_flags, exclude_text):- Цель: Проверить рендеринг групп сообщений в посты и применение фильтров.
- Замечание: Мокнуть
post_parser.process_messageиre.compile/search. - Тест-кейсы:
- Рендеринг одиночной группы:
- Мокнуть
process_messageдля возврата тестовых данных. - Передать группу из одного сообщения.
- Проверить, что
process_messageвызван один раз. - Проверить правильность сборки HTML из
header,media,body,footer. - Проверить корректность полей
date,message_id,title,text,author,flagsв итоговом словаре.
- Мокнуть
- Рендеринг медиа-группы:
- Мокнуть
process_message. - Передать группу из нескольких сообщений.
- Проверить, что
process_messageвызван для каждого сообщения. - Проверить логику выбора
main_message(первое с текстом или первое в группе). - Проверить объединение
html.mediaчерез<br>. - Проверить объединение
textчерез\\n. - Проверить объединение
html.bodyчерез<br><br>. - Проверить использование
header,footer,title,date,message_id,author,flagsотmain_message.
- Мокнуть
- Фильтрация по
exclude_flags:- Задать
exclude_flags="advert,spam". - Передать посты с флагами
advert,spam,other, без флагов. - Проверить, что посты с
advertиspamотфильтрованы. - Задать
exclude_flags="all". Проверить, что все посты с любыми флагами отфильтрованы. exclude_flags = None. Проверить, что фильтрация не применяется.
- Задать
- Фильтрация по
exclude_text:- Задать
exclude_text="купить|продать". - Передать посты с текстом "купить слона", "продам гараж", "просто текст".
- Проверить, что первые два поста отфильтрованы (регистронезависимо).
- Проверить корректность работы с кириллицей (флаг
re.UNICODE). exclude_text = None. Проверить, что фильтрация не применяется.
- Задать
- Итоговая сортировка: Проверить, что результат отсортирован по
date(убывание). - Обработка ошибок: Мокнуть
process_messageчтобы вызвать исключение для одной из групп. Проверить, что ошибка логируется, группа пропускается, остальные обрабатываются.
- Рендеринг одиночной группы:
Основные функции генерации
generate_channel_rss(channel, post_parser, client, limit, exclude_flags, exclude_text, merge_seconds):- Цель: Проверить генерацию полной RSS ленты.
- Замечание: Мокнуть
PostParser(и его методыchannel_name_prepare,get_channel_username,client.get_chat,client.get_chat_history),FeedGenerator,datetime, вспомогательные функции (_create_time_based_media_groups,_create_messages_groups,_trim_messages_groups,_render_messages_groups),Config,create_error_feed. - Тест-кейсы:
- Успешная генерация:
- Мокнуть
get_chatдля возврата информации о канале (title,username). - Мокнуть
get_chat_historyдля возврата списка тестовыхMessage. - Мокнуть
_render_messages_groupsдля возврата списка отрендеренных постов. - Проверить вызов
PostParser(если не передан). - Проверить вызов
channel_name_prepare. - Проверить вызов
get_chat. - Проверить вызов
get_channel_username. - Проверить установку метаданных
FeedGenerator(title,link,description,language,id). - Проверить вызов
get_chat_historyсlimit*2. - Проверить вызов
_create_time_based_media_groups(еслиConfig['time_based_merge']True). - Проверить вызов
_create_messages_groups. - Проверить вызов
_trim_messages_groups. - Проверить вызов
_render_messages_groupsс правильными параметрами (exclude_flags,exclude_text). - Проверить добавление записей (
add_entry) вFeedGeneratorдля каждого поста с правильными полями (title,link,description,content,pubDate,guid,author). - Проверить вызов
fg.rss_str(pretty=True). - Проверить декодирование байтов (если
rss_strвернулbytes).
- Мокнуть
- Канал не найден:
- Мокнуть
get_chatчтобы вызвать исключениеUSERNAME_INVALIDилиUSERNAME_NOT_OCCUPIED. - Проверить, что вызывается
create_error_feedи возвращается его результат.
- Мокнуть
- Ошибка получения
channel_username:- Мокнуть
get_channel_usernameчтобы вернутьNone. - Проверить, что вызывается
create_error_feed.
- Мокнуть
- Другая ошибка при
get_chat:- Мокнуть
get_chatчтобы вызвать другое исключение. - Проверить, что исключение пробрасывается дальше.
- Мокнуть
- Невалидный
limit:- Передать
limit=0илиlimit=201. - Проверить, что вызывается
ValueError.
- Передать
- Исключение на других этапах:
- Мокнуть
get_chat_historyили другие функции для вызова исключения. - Проверить, что исключение логируется и пробрасывается.
- Мокнуть
- Успешная генерация:
generate_channel_html(channel, post_parser, client, limit, exclude_flags, exclude_text, merge_seconds):- Цель: Проверить генерацию HTML-страницы с постами.
- Замечание: Аналогично
generate_channel_rss, но безFeedGenerator. МокироватьPostParser,client,datetime, вспомогательные функции,Config,create_error_feed. - Тест-кейсы:
- Успешная генерация:
- Мокнуть
get_chat,get_chat_history,_render_messages_groups. - Проверить шаги получения и обработки сообщений (аналогично RSS).
- Проверить, что из результатов
_render_messages_groupsизвлекается полеhtml. - Проверить, что HTML-части постов объединяются через
<hr class="post-divider">. - Проверить, что возвращается строка HTML.
- Мокнуть
- Канал не найден / Ошибка
channel_username:- Проверить вызов
create_error_feed.
- Проверить вызов
- Невалидный
limit:- Проверить
ValueError.
- Проверить
- Исключение на других этапах:
- Проверить логирование и проброс исключения.
- Успешная генерация:
create_error_feed(channel, base_url):- Цель: Проверить генерацию RSS-ленты об ошибке.
- Замечание: Мокнуть
FeedGenerator,datetime. - Тест-кейсы:
- Проверить установку метаданных
FeedGenerator(title,link,description,language,id), указывающих на ошибку. - Проверить добавление одной записи (
add_entry) с информацией об ошибке. - Проверить вызов
fg.rss_str(pretty=True). - Проверить декодирование байтов.
- Проверить установку метаданных
api_server.py
- Цель: Протестировать API эндпоинты, логику кеширования, запуск/остановку приложения и обработку ошибок.
- Замечание: Использовать
FastAPI.TestClientдля тестирования эндпоинтов. МокироватьTelegramClient,PostParser,generate_channel_rss,generate_channel_html,verify_media_digest,os,magic,mimetypes,time,asyncio,json,json_repair,uvicorn.
Middleware
RequestLoggingMiddleware:- Цель: Проверить логирование запросов и ответов.
- Замечание: Тестируется косвенно через
TestClient. Нужно настроитьloggingдля перехвата сообщений. - Тест-кейсы:
- Сделать запрос к любому эндпоинту.
- Проверить, что в логах появились записи о запросе (
method,url,headers,query params). - Проверить, что в логах появилась запись о статусе ответа.
- Вызвать эндпоинт, который генерирует исключение. Проверить, что в логах появилась запись об ошибке.
Lifespan Manager (lifespan)
- Цель: Проверить корректный запуск и остановку фоновых задач и клиента.
- Замечание: Сложно тестировать напрямую. Можно выделить логику в отдельные функции или тестировать интеграционно.
- Тест-кейсы (идеи):
- Мокнуть
client.start,client.stop,asyncio.create_task,task.cancel. - Запустить приложение с
TestClient. - Проверить, что
client.startиasyncio.create_task(cache_media_files)были вызваны при старте. - Завершить работу приложения (если
TestClientэто позволяет симулировать). - Проверить, что
task.cancelиclient.stopбыли вызваны при остановке. - Проверить создание директории
data/cache.
- Мокнуть
Вспомогательные функции
mask_sensitive_value(input_str):- Цель: Проверить маскирование чувствительных данных.
- Тест-кейсы:
- Строка длиннее 8 символов.
- Строка короче 8 символов.
- Пустая строка.
None.
find_file_id_in_message(message, file_unique_id):- Цель: Проверить поиск
file_idпоfile_unique_idв сообщении. - Тест-кейсы:
- Сообщение с
photoи совпадающимfile_unique_id. - Сообщение с
videoи совпадающимfile_unique_id. - Аналогично для
animation,video_note,audio,voice,sticker,document,web_page.photo. - Сообщение с медиа, но
file_unique_idне совпадает. - Сообщение без медиа или с
POLL. - Сообщение, где медиа-объект (
message.photo) есть, но у него нетfile_id.
- Сообщение с
- Цель: Проверить поиск
delayed_delete_file(file_path, delay):- Цель: Проверить отложенное удаление файла.
- Замечание: Мокнуть
time.sleepиos.remove. - Тест-кейсы:
- Проверить, что
time.sleepвызывается с правильнымdelay. - Проверить, что
os.removeвызывается с правильнымfile_path. - Мокнуть
os.removeдля вызова исключения. Проверить логирование ошибки.
- Проверить, что
prepare_file_response(file_path, delete_after):- Цель: Проверить подготовку
FileResponseс правильными заголовками и MIME-типом. - Замечание: Мокнуть
os.path.exists,magic.Magic,mimetypes.guess_type,FileResponse,BackgroundTask,delayed_delete_file. - Тест-кейсы:
- Файл существует.
magicуспешно определяет MIME-тип.magicпадает,mimetypesопределяет тип.- Оба падают, используется
application/octet-stream. - Проверить правильность
media_typeи заголовкаContent-Disposition. delete_after = True. Проверить, чтоFileResponseсоздается сBackgroundTask(delayed_delete_file).delete_after = False. Проверить, чтоFileResponseсоздается безBackgroundTask.
- Файл не существует (
os.path.exists->False). Проверить, что вызываетсяHTTPException(404).
- Файл существует.
- Цель: Проверить подготовку
download_media_file(channel, post_id, file_unique_id):- Цель: Проверить логику скачивания и кеширования медиа.
- Замечание: Мокнуть
client.client.get_messages,find_file_id_in_message,client.client.download_media,os.path,os.makedirs,json.load,json.dump,datetime.now. - Тест-кейсы:
- Обычное видео/файл:
- Файл уже в кеше (
os.path.exists(cache_path)->True). Проверить возврат пути к кешу, обновлениеmedia_file_ids.json. - Файла нет в кеше. Проверить вызов
get_messages,find_file_id_in_message,download_mediaс правильным путемcache_path. Проверить возврат пути.
- Файл уже в кеше (
- Большое видео (>100MB):
- Временный файл (
temp_...) уже существует. Проверить возврат пути к временному файлу. - Временного файла нет. Проверить вызов
get_messages,find_file_id_in_message,download_mediaс временным путем (temp_...). Проверить возврат пути.
- Временный файл (
- Сообщение - опрос (
POLL). Проверить возвратNone. file_idне найден: Мокнутьfind_file_id_in_message->None. ПроверитьHTTPException(404).- Ошибка
get_messagesилиdownload_media. Проверить проброс исключения. - Ошибка обновления
media_file_ids.json. Проверить логирование ошибки. - Канал задан как ID (
-100...). Проверить преобразование вintпередget_messages.
- Обычное видео/файл:
remove_old_cached_files(media_files, cache_dir):- Цель: Проверить удаление старых файлов из кеша и временных файлов.
- Замечание: Мокнуть
datetime.now,os.path,os.remove,os.listdir,os.rmdir,os.walk,os.path.getmtime,time.time. - Тест-кейсы:
- Файл старше 20 дней. Проверить удаление файла, удаление пустых родительских директорий, обновление счетчика
files_removed, исключение файла из возвращаемого спискаupdated_media_files. - Файл младше 20 дней. Проверить, что он остается.
- Временный файл (
temp_...) старше 1 часа. Проверить удаление файла, обновление счетчика, исключение изupdated_media_files. - Временный файл младше 1 часа. Проверить, что он остается.
- Ошибка при удалении файла/директории. Проверить логирование.
- Некорректная запись в
media_files. Проверить пропуск записи.
- Файл старше 20 дней. Проверить удаление файла, удаление пустых родительских директорий, обновление счетчика
download_new_files(media_files, cache_dir):- Цель: Проверить фоновую загрузку отсутствующих файлов.
- Замечание: Мокнуть
os.path,os.makedirs,download_media_file,asyncio.sleep. - Тест-кейсы:
- Есть файлы для скачивания (нет в кеше). Проверить вызов
download_media_fileдля каждого, вызовasyncio.sleep(1). - Все файлы уже в кеше. Проверить, что
download_media_fileне вызывается. - Есть временные файлы (
temp_...). Проверить, что они пропускаются. - Некорректная запись в
media_files. Проверить логирование и пропуск. - Ошибка при
download_media_file. Проверить логирование и продолжение работы.
- Есть файлы для скачивания (нет в кеше). Проверить вызов
fix_corrupted_json(file_path):- Цель: Проверить восстановление поврежденного JSON.
- Замечание: Мокнуть
open,json_repair.loads. - Тест-кейсы:
- Успешное восстановление. Проверить возврат списка валидных словарей (с нужными ключами).
- Неудачное восстановление (
json_repairпадает). Проверить логирование и возврат пустого списка. - Ошибка чтения файла. Проверить логирование и возврат пустого списка.
cache_media_files():- Цель: Проверить основной цикл управления кешем.
- Замечание: Сложно тестировать напрямую. Мокнуть
os.path,open,json.load,fix_corrupted_json,remove_old_cached_files,download_new_files,asyncio.sleep. - Тест-кейсы (идеи):
- Проверить основной цикл: чтение
media_file_ids.json, вызовremove_old_cached_files, вызовdownload_new_files,asyncio.sleep. - Файл
media_file_ids.jsonне существует. Проверить ожиданиеdelay. - Файл
media_file_ids.jsonповрежден. Проверить вызовfix_corrupted_json, запись исправленного файла (если успешно). - Ошибка при
remove_old_cached_filesилиdownload_new_files. Проверить логирование и продолжение цикла.
- Проверить основной цикл: чтение
calculate_cache_stats():- Цель: Проверить расчет статистики кеша.
- Замечание: Мокнуть
os.path,os.walk,os.path.getsize,json.load,datetime.now. - Тест-кейсы:
- Пустой кеш.
- Кеш с файлами в разных поддиректориях (каналах). Проверить правильность подсчета общего количества, размера, статистики по каналам.
- Есть
media_file_ids.jsonс записямиadded. Проверить расчетcache_time_diff_days. - Нет
media_file_ids.jsonили он пустой/безadded. Проверитьcache_time_diff_days = 0. - Ошибка чтения
media_file_ids.json. Проверить логирование.
API Эндпоинты (с использованием TestClient)
- Общая проверка токена:
- Для всех защищенных эндпоинтов проверить:
Config["token"]установлен, запрос без токена (или с неверным) от внешнего IP -> 403.Config["token"]установлен, запрос с верным токеном -> 2xx/4xx/5xx (успех или другая ошибка).Config["token"]установлен, запрос от localhost -> 2xx/4xx/5xx (токен не требуется).Config["token"]не установлен -> 2xx/4xx/5xx (токен не требуется).
- Для всех защищенных эндпоинтов проверить:
/html/{channel}/{post_id}и/post/html/{channel}/{post_id}:- Цель: Проверить получение HTML поста.
- Замечание: Мокнуть
PostParser. - Тест-кейсы:
- Успешный запрос. Мокнуть
parser.get_post->html_string. Проверить статус 200,Content-Type: text/html, тело ответа. - Пост не найден. Мокнуть
parser.get_post->None. Проверить статус 404. - Ошибка парсера. Мокнуть
parser.get_post-> Exception. Проверить статус 500. - Проверить проверку токена.
- Успешный запрос. Мокнуть
/json/{channel}/{post_id}и/post/json/{channel}/{post_id}:- Цель: Проверить получение JSON поста.
- Замечание: Мокнуть
PostParser. - Тест-кейсы:
- Успешный запрос. Мокнуть
parser.get_post->dict. Проверить статус 200,Content-Type: application/json, тело ответа. - Пост не найден. Мокнуть
parser.get_post->None. Проверить статус 404. - Ошибка парсера. Мокнуть
parser.get_post-> Exception. Проверить статус 500. - Проверить проверку токена.
- Успешный запрос. Мокнуть
/health:- Цель: Проверить эндпоинт состояния.
- Замечание: Мокнуть
client.client.get_me,calculate_cache_stats,Config. - Тест-кейсы:
- Успешный запрос. Мокнуть
get_meдля возврата объекта пользователя. Проверить статус 200, все поля в ответе. Проверить маскирование чувствительных данных вconfig. - Ошибка
get_me. Проверить статус 500. - Проверить проверку токена.
- Успешный запрос. Мокнуть
/media/{channel}/{post_id}/{file_unique_id}/{digest}:- Цель: Проверить получение медиафайла.
- Замечание: Мокнуть
verify_media_digest,download_media_file,prepare_file_response. - Тест-кейсы:
- Успешный запрос (валидный
digest). Мокнутьverify_media_digest->True,download_media_file->(file_path, delete_after),prepare_file_response->FileResponse. Проверить статус 200, тип ответаFileResponse. - Невалидный
digest. Мокнутьverify_media_digest->False. Проверить статус 403. - Ошибка
download_media_file(например,HTTPException(404)). Проверить проброс исключения (404). - Ошибка
download_media_file(другая). Проверить статус 500. - Ошибка
prepare_file_response. Проверить статус 500. - Ошибка
pyrogram.errors.RPCErrorпри скачивании. Проверить статус 404.
- Успешный запрос (валидный
/rss/{channel}:- Цель: Проверить генерацию RSS/HTML ленты.
- Замечание: Мокнуть
generate_channel_rss,generate_channel_html. - Тест-кейсы:
- Успешный запрос (
output_type='rss', по умолчанию). Мокнутьgenerate_channel_rss. Проверить статус 200,Content-Type: application/xml. - Успешный запрос (
output_type='html'). Мокнутьgenerate_channel_html. Проверить статус 200,Content-Type: text/html. - Невалидный
limit(generate_...вызываетValueError). Проверить статус 400. - Ошибка
FloodWait. Мокнутьgenerate_...для вызоваerrors.FloodWait. Проверить статус 429, заголовокRetry-After. - Другая ошибка генерации. Мокнуть
generate_...для вызоваException. Проверить статус 500. - Проверить передачу параметров
limit,exclude_flags,exclude_text,merge_secondsв функции генерации. - Проверить проверку токена.
- Успешный запрос (