Добавлен функционал для отслеживания истории банов пользователей.
- Введена новая модель `BlacklistHistoryRecord` для хранения информации о банах и разблокировках. - Обновлены методы `set_user_blacklist` и `delete_user_blacklist` в `AsyncBotDB` для логирования событий в историю. - Обновлена схема базы данных для создания таблицы `blacklist_history` и соответствующих индексов. - Обновлены тесты для проверки нового функционала и обработки ошибок при записи в историю.
This commit is contained in:
@@ -14,6 +14,13 @@ class TestAsyncBotDB:
|
||||
mock_factory.audio.delete_audio_moderate_record = AsyncMock()
|
||||
mock_factory.users = Mock()
|
||||
mock_factory.users.logger = Mock()
|
||||
# Моки для blacklist и blacklist_history
|
||||
mock_factory.blacklist = Mock()
|
||||
mock_factory.blacklist.add_user = AsyncMock()
|
||||
mock_factory.blacklist.remove_user = AsyncMock(return_value=True)
|
||||
mock_factory.blacklist_history = Mock()
|
||||
mock_factory.blacklist_history.add_record_on_ban = AsyncMock()
|
||||
mock_factory.blacklist_history.set_unban_date = AsyncMock(return_value=True)
|
||||
return mock_factory
|
||||
|
||||
@pytest.fixture
|
||||
@@ -102,3 +109,107 @@ class TestAsyncBotDB:
|
||||
await async_bot_db.delete_audio_moderate_record(message_id)
|
||||
|
||||
mock_factory.audio.delete_audio_moderate_record.assert_called_once_with(message_id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_user_blacklist_calls_history(self, async_bot_db, mock_factory):
|
||||
"""Тест что set_user_blacklist вызывает добавление в историю"""
|
||||
user_id = 12345
|
||||
message_for_user = "Нарушение правил"
|
||||
date_to_unban = 1234567890
|
||||
ban_author = 999
|
||||
|
||||
await async_bot_db.set_user_blacklist(
|
||||
user_id=user_id,
|
||||
user_name=None,
|
||||
message_for_user=message_for_user,
|
||||
date_to_unban=date_to_unban,
|
||||
ban_author=ban_author
|
||||
)
|
||||
|
||||
# Проверяем, что сначала добавлен в blacklist
|
||||
mock_factory.blacklist.add_user.assert_called_once()
|
||||
|
||||
# Проверяем, что затем добавлена запись в историю
|
||||
mock_factory.blacklist_history.add_record_on_ban.assert_called_once()
|
||||
|
||||
# Проверяем параметры записи в историю
|
||||
history_call = mock_factory.blacklist_history.add_record_on_ban.call_args[0][0]
|
||||
assert history_call.user_id == user_id
|
||||
assert history_call.message_for_user == message_for_user
|
||||
assert history_call.date_ban is not None
|
||||
assert history_call.date_unban is None
|
||||
assert history_call.ban_author == ban_author
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_user_blacklist_history_error_does_not_fail(self, async_bot_db, mock_factory):
|
||||
"""Тест что ошибка записи в историю не ломает процесс бана"""
|
||||
user_id = 12345
|
||||
mock_factory.blacklist_history.add_record_on_ban.side_effect = Exception("History error")
|
||||
|
||||
# Бан должен пройти успешно, несмотря на ошибку в истории
|
||||
await async_bot_db.set_user_blacklist(
|
||||
user_id=user_id,
|
||||
message_for_user="Тест",
|
||||
date_to_unban=None,
|
||||
ban_author=None
|
||||
)
|
||||
|
||||
# Проверяем, что пользователь все равно добавлен в blacklist
|
||||
mock_factory.blacklist.add_user.assert_called_once()
|
||||
|
||||
# Проверяем, что попытка записи в историю была
|
||||
mock_factory.blacklist_history.add_record_on_ban.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user_blacklist_calls_history(self, async_bot_db, mock_factory):
|
||||
"""Тест что delete_user_blacklist вызывает обновление истории"""
|
||||
user_id = 12345
|
||||
|
||||
result = await async_bot_db.delete_user_blacklist(user_id)
|
||||
|
||||
# Проверяем, что сначала обновлена история
|
||||
mock_factory.blacklist_history.set_unban_date.assert_called_once()
|
||||
history_call = mock_factory.blacklist_history.set_unban_date.call_args
|
||||
assert history_call[0][0] == user_id
|
||||
assert history_call[0][1] is not None # date_unban timestamp
|
||||
|
||||
# Проверяем, что затем удален из blacklist
|
||||
mock_factory.blacklist.remove_user.assert_called_once_with(user_id)
|
||||
|
||||
# Проверяем результат
|
||||
assert result is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user_blacklist_history_error_does_not_fail(self, async_bot_db, mock_factory):
|
||||
"""Тест что ошибка обновления истории не ломает процесс разбана"""
|
||||
user_id = 12345
|
||||
mock_factory.blacklist_history.set_unban_date.side_effect = Exception("History error")
|
||||
|
||||
# Разбан должен пройти успешно, несмотря на ошибку в истории
|
||||
result = await async_bot_db.delete_user_blacklist(user_id)
|
||||
|
||||
# Проверяем, что попытка обновления истории была
|
||||
mock_factory.blacklist_history.set_unban_date.assert_called_once()
|
||||
|
||||
# Проверяем, что пользователь все равно удален из blacklist
|
||||
mock_factory.blacklist.remove_user.assert_called_once_with(user_id)
|
||||
|
||||
# Проверяем результат
|
||||
assert result is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user_blacklist_returns_false_on_blacklist_error(self, async_bot_db, mock_factory):
|
||||
"""Тест что delete_user_blacklist возвращает False при ошибке удаления из blacklist"""
|
||||
user_id = 12345
|
||||
mock_factory.blacklist.remove_user.return_value = False
|
||||
|
||||
result = await async_bot_db.delete_user_blacklist(user_id)
|
||||
|
||||
# Проверяем, что история обновлена
|
||||
mock_factory.blacklist_history.set_unban_date.assert_called_once()
|
||||
|
||||
# Проверяем, что удаление из blacklist было попытка
|
||||
mock_factory.blacklist.remove_user.assert_called_once_with(user_id)
|
||||
|
||||
# Проверяем результат
|
||||
assert result is False
|
||||
|
||||
@@ -17,7 +17,7 @@ class TestAutoUnbanIntegration:
|
||||
|
||||
@pytest.fixture
|
||||
def setup_test_db(self, test_db_path):
|
||||
"""Создает тестовую базу данных с таблицей blacklist"""
|
||||
"""Создает тестовую базу данных с таблицами blacklist, our_users и blacklist_history"""
|
||||
# Удаляем старую тестовую базу если она существует
|
||||
if os.path.exists(test_db_path):
|
||||
os.remove(test_db_path)
|
||||
@@ -26,30 +26,112 @@ class TestAutoUnbanIntegration:
|
||||
conn = sqlite3.connect(test_db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Создаем таблицу blacklist
|
||||
# Включаем поддержку внешних ключей
|
||||
cursor.execute("PRAGMA foreign_keys = ON")
|
||||
|
||||
# Создаем таблицу our_users (нужна для внешних ключей)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS blacklist (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
user_name TEXT,
|
||||
message_for_user TEXT,
|
||||
date_to_unban INTEGER
|
||||
CREATE TABLE IF NOT EXISTS our_users (
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
first_name TEXT,
|
||||
full_name TEXT,
|
||||
username TEXT,
|
||||
is_bot BOOLEAN DEFAULT 0,
|
||||
language_code TEXT,
|
||||
has_stickers BOOLEAN DEFAULT 0 NOT NULL,
|
||||
emoji TEXT,
|
||||
date_added INTEGER NOT NULL,
|
||||
date_changed INTEGER NOT NULL,
|
||||
voice_bot_welcome_received BOOLEAN DEFAULT 0
|
||||
)
|
||||
''')
|
||||
|
||||
# Добавляем тестовые данные
|
||||
# Создаем таблицу blacklist
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS blacklist (
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
message_for_user TEXT,
|
||||
date_to_unban INTEGER,
|
||||
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
ban_author INTEGER,
|
||||
FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
|
||||
# Создаем таблицу blacklist_history
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS blacklist_history (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
message_for_user TEXT,
|
||||
date_ban INTEGER NOT NULL,
|
||||
date_unban INTEGER,
|
||||
ban_author INTEGER,
|
||||
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL
|
||||
)
|
||||
''')
|
||||
|
||||
# Создаем индексы для blacklist_history
|
||||
cursor.execute('''
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_user_id ON blacklist_history(user_id)
|
||||
''')
|
||||
cursor.execute('''
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_ban ON blacklist_history(date_ban)
|
||||
''')
|
||||
cursor.execute('''
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_unban ON blacklist_history(date_unban)
|
||||
''')
|
||||
|
||||
# Добавляем тестовых пользователей в our_users
|
||||
current_time = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
users_data = [
|
||||
(123, "Test", "Test User 1", "test_user1", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
(456, "Test", "Test User 2", "test_user2", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
(789, "Test", "Test User 3", "test_user3", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
(999, "Test", "Test User 4", "test_user4", 0, "ru", 0, "😊", current_time, current_time, 0),
|
||||
]
|
||||
cursor.executemany(
|
||||
"""INSERT INTO our_users (user_id, first_name, full_name, username, is_bot,
|
||||
language_code, has_stickers, emoji, date_added, date_changed, voice_bot_welcome_received)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
users_data
|
||||
)
|
||||
|
||||
# Добавляем тестовые данные в blacklist
|
||||
today_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
tomorrow_timestamp = int((datetime.now(timezone(timedelta(hours=3))) + timedelta(days=1)).timestamp())
|
||||
|
||||
test_data = [
|
||||
(123, "test_user1", "Test ban 1", today_timestamp), # Разблокируется сегодня
|
||||
(456, "test_user2", "Test ban 2", today_timestamp), # Разблокируется сегодня
|
||||
(789, "test_user3", "Test ban 3", tomorrow_timestamp), # Разблокируется завтра
|
||||
(999, "test_user4", "Test ban 4", None), # Навсегда заблокирован
|
||||
blacklist_data = [
|
||||
(123, "Test ban 1", today_timestamp, current_time, None), # Разблокируется сегодня
|
||||
(456, "Test ban 2", today_timestamp, current_time, None), # Разблокируется сегодня
|
||||
(789, "Test ban 3", tomorrow_timestamp, current_time, None), # Разблокируется завтра
|
||||
(999, "Test ban 4", None, current_time, None), # Навсегда заблокирован
|
||||
]
|
||||
|
||||
cursor.executemany(
|
||||
"INSERT INTO blacklist (user_id, user_name, message_for_user, date_to_unban) VALUES (?, ?, ?, ?)",
|
||||
test_data
|
||||
"INSERT INTO blacklist (user_id, message_for_user, date_to_unban, created_at, ban_author) VALUES (?, ?, ?, ?, ?)",
|
||||
blacklist_data
|
||||
)
|
||||
|
||||
# Добавляем тестовые данные в blacklist_history
|
||||
# Для пользователей 123 и 456 (которые будут разблокированы) создаем записи с date_unban = NULL
|
||||
yesterday_timestamp = int((datetime.now(timezone(timedelta(hours=3))) - timedelta(days=1)).timestamp())
|
||||
|
||||
history_data = [
|
||||
(123, "Test ban 1", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Будет разблокирован
|
||||
(456, "Test ban 2", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Будет разблокирован
|
||||
(789, "Test ban 3", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Не будет разблокирован сегодня
|
||||
(999, "Test ban 4", yesterday_timestamp, None, None, yesterday_timestamp, yesterday_timestamp), # Навсегда заблокирован
|
||||
]
|
||||
|
||||
cursor.executemany(
|
||||
"""INSERT INTO blacklist_history
|
||||
(user_id, message_for_user, date_ban, date_unban, ban_author, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
||||
history_data
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
@@ -105,9 +187,20 @@ class TestAutoUnbanIntegration:
|
||||
initial_count = cursor.fetchone()[0]
|
||||
assert initial_count == 4
|
||||
|
||||
# Проверяем начальное состояние истории: должно быть 2 записи с date_unban IS NULL для user_id 123 и 456
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE user_id IN (123, 456) AND date_unban IS NULL")
|
||||
initial_open_history = cursor.fetchone()[0]
|
||||
assert initial_open_history == 2
|
||||
|
||||
# Запоминаем время до разбана для проверки updated_at
|
||||
before_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
# Выполняем автоматический разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
# Запоминаем время после разбана для проверки updated_at
|
||||
after_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
# Проверяем, что пользователи с сегодняшней датой разблокированы
|
||||
current_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?",
|
||||
@@ -131,6 +224,32 @@ class TestAutoUnbanIntegration:
|
||||
final_count = cursor.fetchone()[0]
|
||||
assert final_count == 2 # Остались только завтрашние и навсегда заблокированные
|
||||
|
||||
# Проверяем историю банов: для user_id 123 и 456 должны быть установлены date_unban
|
||||
cursor.execute("SELECT user_id, date_unban, updated_at FROM blacklist_history WHERE user_id IN (123, 456) ORDER BY user_id")
|
||||
history_records = cursor.fetchall()
|
||||
|
||||
assert len(history_records) == 2
|
||||
|
||||
for user_id, date_unban, updated_at in history_records:
|
||||
# Проверяем, что date_unban установлен (не NULL)
|
||||
assert date_unban is not None, f"date_unban должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(date_unban, int), f"date_unban должен быть integer для user_id={user_id}"
|
||||
|
||||
# Проверяем, что date_unban находится в разумных пределах (между before и after)
|
||||
assert before_unban_timestamp <= date_unban <= after_unban_timestamp, \
|
||||
f"date_unban для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}, получен {date_unban}"
|
||||
|
||||
# Проверяем, что updated_at обновлен
|
||||
assert updated_at is not None, f"updated_at должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(updated_at, int), f"updated_at должен быть integer для user_id={user_id}"
|
||||
assert before_unban_timestamp <= updated_at <= after_unban_timestamp, \
|
||||
f"updated_at для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}, получен {updated_at}"
|
||||
|
||||
# Проверяем, что для user_id 789 и 999 записи в истории остались без изменений (date_unban все еще NULL)
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE user_id IN (789, 999) AND date_unban IS NULL")
|
||||
unchanged_history = cursor.fetchone()[0]
|
||||
assert unchanged_history == 2, "Записи для user_id 789 и 999 должны остаться с date_unban = NULL"
|
||||
|
||||
conn.close()
|
||||
|
||||
# Проверяем, что отчет был отправлен
|
||||
@@ -148,6 +267,12 @@ class TestAutoUnbanIntegration:
|
||||
cursor = conn.cursor()
|
||||
current_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
cursor.execute("DELETE FROM blacklist WHERE date_to_unban IS NOT NULL AND date_to_unban <= ?", (current_timestamp,))
|
||||
|
||||
# Проверяем начальное состояние истории: все записи должны иметь date_unban = NULL
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE date_unban IS NULL")
|
||||
initial_open_history = cursor.fetchone()[0]
|
||||
assert initial_open_history == 4 # Все 4 записи должны быть открытыми
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -159,6 +284,14 @@ class TestAutoUnbanIntegration:
|
||||
# Выполняем автоматический разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
# Проверяем, что история не изменилась (все записи все еще с date_unban = NULL)
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT COUNT(*) FROM blacklist_history WHERE date_unban IS NULL")
|
||||
final_open_history = cursor.fetchone()[0]
|
||||
assert final_open_history == 4, "История не должна изменяться, если нет пользователей для разблокировки"
|
||||
conn.close()
|
||||
|
||||
# Проверяем, что отчет не был отправлен (нет пользователей для разблокировки)
|
||||
mock_bot.send_message.assert_not_called()
|
||||
|
||||
@@ -190,6 +323,100 @@ class TestAutoUnbanIntegration:
|
||||
assert call_args[1]['chat_id'] == '-1001234567891' # important_logs
|
||||
assert "Ошибка автоматического разбана" in call_args[1]['text']
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('helper_bot.utils.auto_unban_scheduler.get_global_instance')
|
||||
async def test_auto_unban_updates_history(self, mock_get_instance, setup_test_db, mock_bdf, mock_bot):
|
||||
"""Тест что автоматический разбан обновляет историю банов"""
|
||||
# Настройка моков
|
||||
mock_get_instance.return_value = mock_bdf
|
||||
|
||||
# Создаем планировщик
|
||||
scheduler = AutoUnbanScheduler()
|
||||
scheduler.bot_db = mock_bdf.database
|
||||
scheduler.set_bot(mock_bot)
|
||||
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Проверяем начальное состояние: для user_id 123 и 456 должны быть записи с date_unban = NULL
|
||||
cursor.execute("""
|
||||
SELECT id, user_id, date_ban, date_unban, updated_at
|
||||
FROM blacklist_history
|
||||
WHERE user_id IN (123, 456) AND date_unban IS NULL
|
||||
ORDER BY user_id
|
||||
""")
|
||||
initial_records = cursor.fetchall()
|
||||
assert len(initial_records) == 2, "Должно быть 2 открытые записи для user_id 123 и 456"
|
||||
|
||||
# Запоминаем ID записей и их начальные значения updated_at
|
||||
record_ids = {row[0]: (row[1], row[4]) for row in initial_records}
|
||||
|
||||
# Запоминаем время до разбана
|
||||
before_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
conn.close()
|
||||
|
||||
# Выполняем автоматический разбан
|
||||
await scheduler.auto_unban_users()
|
||||
|
||||
# Запоминаем время после разбана
|
||||
after_unban_timestamp = int(datetime.now(timezone(timedelta(hours=3))).timestamp())
|
||||
|
||||
# Проверяем, что записи обновлены
|
||||
conn = sqlite3.connect(setup_test_db)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, user_id, date_ban, date_unban, updated_at
|
||||
FROM blacklist_history
|
||||
WHERE user_id IN (123, 456)
|
||||
ORDER BY user_id
|
||||
""")
|
||||
updated_records = cursor.fetchall()
|
||||
|
||||
assert len(updated_records) == 2, "Должно быть 2 записи для user_id 123 и 456"
|
||||
|
||||
for record_id, user_id, date_ban, date_unban, updated_at in updated_records:
|
||||
# Проверяем, что это одна из наших записей
|
||||
assert record_id in record_ids, f"Запись с id={record_id} должна быть в исходных записях"
|
||||
|
||||
# Проверяем, что date_unban установлен
|
||||
assert date_unban is not None, f"date_unban должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(date_unban, int), f"date_unban должен быть integer для user_id={user_id}"
|
||||
|
||||
# Проверяем, что date_unban находится в разумных пределах
|
||||
assert before_unban_timestamp <= date_unban <= after_unban_timestamp, \
|
||||
f"date_unban для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}"
|
||||
|
||||
# Проверяем, что updated_at обновлен (должен быть больше начального значения)
|
||||
assert updated_at is not None, f"updated_at должен быть установлен для user_id={user_id}"
|
||||
assert isinstance(updated_at, int), f"updated_at должен быть integer для user_id={user_id}"
|
||||
assert before_unban_timestamp <= updated_at <= after_unban_timestamp, \
|
||||
f"updated_at для user_id={user_id} должен быть между {before_unban_timestamp} и {after_unban_timestamp}"
|
||||
|
||||
# Проверяем, что updated_at действительно обновлен (больше начального значения)
|
||||
initial_updated_at = record_ids[record_id][1]
|
||||
assert updated_at >= initial_updated_at, \
|
||||
f"updated_at для user_id={user_id} должен быть больше или равен начальному значению"
|
||||
|
||||
# Проверяем, что обновлена только последняя запись для каждого пользователя
|
||||
# (если бы было несколько записей, обновилась бы только последняя)
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM blacklist_history
|
||||
WHERE user_id IN (123, 456) AND date_unban IS NOT NULL
|
||||
""")
|
||||
closed_records = cursor.fetchone()[0]
|
||||
assert closed_records == 2, "Должно быть закрыто 2 записи (по одной для каждого пользователя)"
|
||||
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM blacklist_history
|
||||
WHERE user_id IN (123, 456) AND date_unban IS NULL
|
||||
""")
|
||||
open_records = cursor.fetchone()[0]
|
||||
assert open_records == 0, "Не должно быть открытых записей для user_id 123 и 456"
|
||||
|
||||
conn.close()
|
||||
|
||||
def test_date_format_consistency(self, setup_test_db, mock_bdf):
|
||||
"""Тест консистентности формата дат"""
|
||||
scheduler = AutoUnbanScheduler()
|
||||
|
||||
257
tests/test_blacklist_history_repository.py
Normal file
257
tests/test_blacklist_history_repository.py
Normal file
@@ -0,0 +1,257 @@
|
||||
import pytest
|
||||
from unittest.mock import Mock, AsyncMock, patch
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from database.repositories.blacklist_history_repository import BlacklistHistoryRepository
|
||||
from database.models import BlacklistHistoryRecord
|
||||
|
||||
|
||||
class TestBlacklistHistoryRepository:
|
||||
"""Тесты для BlacklistHistoryRepository"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_connection(self):
|
||||
"""Мок для DatabaseConnection"""
|
||||
mock_connection = Mock()
|
||||
mock_connection._execute_query = AsyncMock()
|
||||
mock_connection._execute_query_with_result = AsyncMock()
|
||||
mock_connection.logger = Mock()
|
||||
return mock_connection
|
||||
|
||||
@pytest.fixture
|
||||
def blacklist_history_repository(self, mock_db_connection):
|
||||
"""Экземпляр BlacklistHistoryRepository для тестов"""
|
||||
# Патчим наследование от DatabaseConnection
|
||||
with patch.object(BlacklistHistoryRepository, '__init__', return_value=None):
|
||||
repo = BlacklistHistoryRepository()
|
||||
repo._execute_query = mock_db_connection._execute_query
|
||||
repo._execute_query_with_result = mock_db_connection._execute_query_with_result
|
||||
repo.logger = mock_db_connection.logger
|
||||
return repo
|
||||
|
||||
@pytest.fixture
|
||||
def sample_history_record(self):
|
||||
"""Тестовая запись истории бана"""
|
||||
current_time = int(time.time())
|
||||
return BlacklistHistoryRecord(
|
||||
user_id=12345,
|
||||
message_for_user="Нарушение правил",
|
||||
date_ban=current_time,
|
||||
date_unban=None,
|
||||
ban_author=999,
|
||||
created_at=current_time,
|
||||
updated_at=current_time,
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def sample_history_record_with_unban(self):
|
||||
"""Тестовая запись истории бана с датой разбана"""
|
||||
current_time = int(time.time())
|
||||
return BlacklistHistoryRecord(
|
||||
user_id=12345,
|
||||
message_for_user="Нарушение правил",
|
||||
date_ban=current_time - 86400, # Бан был вчера
|
||||
date_unban=current_time, # Разбан сегодня
|
||||
ban_author=999,
|
||||
created_at=current_time - 86400,
|
||||
updated_at=current_time,
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_tables(self, blacklist_history_repository):
|
||||
"""Тест создания таблицы истории банов/разбанов"""
|
||||
await blacklist_history_repository.create_tables()
|
||||
|
||||
# Проверяем, что метод вызван (4 раза: таблица + 3 индекса)
|
||||
assert blacklist_history_repository._execute_query.call_count == 4
|
||||
calls = blacklist_history_repository._execute_query.call_args_list
|
||||
|
||||
# Проверяем, что создается таблица с правильной структурой
|
||||
create_table_call = calls[0]
|
||||
assert "CREATE TABLE IF NOT EXISTS blacklist_history" in create_table_call[0][0]
|
||||
assert "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" in create_table_call[0][0]
|
||||
assert "user_id INTEGER NOT NULL" in create_table_call[0][0]
|
||||
assert "message_for_user TEXT" in create_table_call[0][0]
|
||||
assert "date_ban INTEGER NOT NULL" in create_table_call[0][0]
|
||||
assert "date_unban INTEGER" in create_table_call[0][0]
|
||||
assert "ban_author INTEGER" in create_table_call[0][0]
|
||||
assert "created_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0]
|
||||
assert "updated_at INTEGER DEFAULT (strftime('%s', 'now'))" in create_table_call[0][0]
|
||||
assert "FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE" in create_table_call[0][0]
|
||||
assert "FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL" in create_table_call[0][0]
|
||||
|
||||
# Проверяем создание индексов
|
||||
index_calls = calls[1:4]
|
||||
index_names = [call[0][0] for call in index_calls]
|
||||
assert any("idx_blacklist_history_user_id" in idx for idx in index_names)
|
||||
assert any("idx_blacklist_history_date_ban" in idx for idx in index_names)
|
||||
assert any("idx_blacklist_history_date_unban" in idx for idx in index_names)
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_history_repository.logger.info.assert_called_once_with(
|
||||
"Таблица истории банов/разбанов создана"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_record_on_ban(self, blacklist_history_repository, sample_history_record):
|
||||
"""Тест добавления записи о бане в историю"""
|
||||
await blacklist_history_repository.add_record_on_ban(sample_history_record)
|
||||
|
||||
# Проверяем, что метод вызван с правильными параметрами
|
||||
blacklist_history_repository._execute_query.assert_called_once()
|
||||
call_args = blacklist_history_repository._execute_query.call_args
|
||||
|
||||
# Проверяем SQL запрос
|
||||
sql_query = call_args[0][0].replace('\n', ' ').replace(' ', ' ').strip()
|
||||
assert "INSERT INTO blacklist_history" in sql_query
|
||||
assert "user_id, message_for_user, date_ban, date_unban, ban_author, created_at, updated_at" in sql_query
|
||||
|
||||
# Проверяем параметры
|
||||
params = call_args[0][1]
|
||||
assert params[0] == 12345 # user_id
|
||||
assert params[1] == "Нарушение правил" # message_for_user
|
||||
assert params[2] == sample_history_record.date_ban # date_ban
|
||||
assert params[3] is None # date_unban
|
||||
assert params[4] == 999 # ban_author
|
||||
assert params[5] == sample_history_record.created_at # created_at
|
||||
assert params[6] == sample_history_record.updated_at # updated_at
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_history_repository.logger.info.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.info.call_args[0][0]
|
||||
assert "Запись о бане добавлена в историю" in log_call
|
||||
assert "user_id=12345" in log_call
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_record_on_ban_with_defaults(self, blacklist_history_repository):
|
||||
"""Тест добавления записи о бане с дефолтными значениями created_at и updated_at"""
|
||||
record = BlacklistHistoryRecord(
|
||||
user_id=12345,
|
||||
message_for_user="Тест",
|
||||
date_ban=int(time.time()),
|
||||
date_unban=None,
|
||||
ban_author=None,
|
||||
created_at=None, # Будет установлено автоматически
|
||||
updated_at=None, # Будет установлено автоматически
|
||||
)
|
||||
|
||||
await blacklist_history_repository.add_record_on_ban(record)
|
||||
|
||||
# Проверяем, что метод вызван
|
||||
blacklist_history_repository._execute_query.assert_called_once()
|
||||
call_args = blacklist_history_repository._execute_query.call_args
|
||||
|
||||
# Проверяем, что created_at и updated_at установлены (не None)
|
||||
params = call_args[0][1]
|
||||
assert params[5] is not None # created_at
|
||||
assert params[6] is not None # updated_at
|
||||
assert isinstance(params[5], int)
|
||||
assert isinstance(params[6], int)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_success(self, blacklist_history_repository):
|
||||
"""Тест успешного обновления даты разбана"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
# Мокируем результат проверки - находим открытую запись
|
||||
blacklist_history_repository._execute_query_with_result.return_value = [(100,)] # id записи
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
# Проверяем, что сначала проверяется наличие записи
|
||||
assert blacklist_history_repository._execute_query_with_result.call_count == 1
|
||||
check_call = blacklist_history_repository._execute_query_with_result.call_args
|
||||
assert "SELECT id FROM blacklist_history" in check_call[0][0]
|
||||
assert check_call[0][1] == (user_id,)
|
||||
|
||||
# Проверяем, что затем обновляется запись
|
||||
assert blacklist_history_repository._execute_query.call_count == 1
|
||||
update_call = blacklist_history_repository._execute_query.call_args
|
||||
update_query = update_call[0][0].replace('\n', ' ').replace(' ', ' ').strip()
|
||||
assert "UPDATE blacklist_history" in update_query
|
||||
assert "SET date_unban = ?" in update_query
|
||||
assert "updated_at = ?" in update_query
|
||||
|
||||
# Проверяем параметры обновления
|
||||
update_params = update_call[0][1]
|
||||
assert update_params[0] == date_unban
|
||||
assert update_params[1] is not None # updated_at (текущее время)
|
||||
assert isinstance(update_params[1], int)
|
||||
assert update_params[2] == 100 # id записи
|
||||
|
||||
# Проверяем результат
|
||||
assert result is True
|
||||
|
||||
# Проверяем логирование
|
||||
blacklist_history_repository.logger.info.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.info.call_args[0][0]
|
||||
assert "Дата разбана обновлена в истории" in log_call
|
||||
assert f"user_id={user_id}" in log_call
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_no_open_record(self, blacklist_history_repository):
|
||||
"""Тест обновления даты разбана когда нет открытой записи"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
# Мокируем результат проверки - нет открытых записей
|
||||
blacklist_history_repository._execute_query_with_result.return_value = []
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
# Проверяем, что проверка была выполнена
|
||||
assert blacklist_history_repository._execute_query_with_result.call_count == 1
|
||||
|
||||
# Проверяем, что UPDATE не был вызван (нет записей для обновления)
|
||||
blacklist_history_repository._execute_query.assert_not_called()
|
||||
|
||||
# Проверяем результат
|
||||
assert result is False
|
||||
|
||||
# Проверяем логирование предупреждения
|
||||
blacklist_history_repository.logger.warning.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.warning.call_args[0][0]
|
||||
assert "Не найдена открытая запись в истории для обновления" in log_call
|
||||
assert f"user_id={user_id}" in log_call
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_exception(self, blacklist_history_repository):
|
||||
"""Тест обработки исключения при обновлении даты разбана"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
# Мокируем исключение при проверке
|
||||
blacklist_history_repository._execute_query_with_result.side_effect = Exception("Database error")
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
# Проверяем, что метод вернул False при ошибке
|
||||
assert result is False
|
||||
|
||||
# Проверяем логирование ошибки
|
||||
blacklist_history_repository.logger.error.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.error.call_args[0][0]
|
||||
assert "Ошибка обновления даты разбана в истории" in log_call
|
||||
assert f"user_id={user_id}" in log_call
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_unban_date_update_exception(self, blacklist_history_repository):
|
||||
"""Тест обработки исключения при обновлении записи"""
|
||||
user_id = 12345
|
||||
date_unban = int(time.time())
|
||||
|
||||
# Мокируем успешную проверку, но ошибку при обновлении
|
||||
blacklist_history_repository._execute_query_with_result.return_value = [(100,)]
|
||||
blacklist_history_repository._execute_query.side_effect = Exception("Update error")
|
||||
|
||||
result = await blacklist_history_repository.set_unban_date(user_id, date_unban)
|
||||
|
||||
# Проверяем, что метод вернул False при ошибке
|
||||
assert result is False
|
||||
|
||||
# Проверяем логирование ошибки
|
||||
blacklist_history_repository.logger.error.assert_called_once()
|
||||
log_call = blacklist_history_repository.logger.error.call_args[0][0]
|
||||
assert "Ошибка обновления даты разбана в истории" in log_call
|
||||
Reference in New Issue
Block a user