Игорь Надеин 64e033d72e fix: исправление модуля уведомлений и добавление тестов
- Исправлена маршрутизация API: обновлены JS файлы для использования /api/v1/notifications/
- Исправлена ошибка в api_views.py: last_error='' вместо None (TextField не может быть NULL)
- Исправлена ошибка в services.py: использование метода reset_errors()
- Добавлено 36 комплексных тестов (модели, API, сервисы) - все проходят
- Создана документация аудита модуля (NOTIFICATIONS_AUDIT.md)
- Создано руководство по тестированию (NOTIFICATIONS_TESTING.md)
- Добавлены management команды: cleanup_push_subscriptions, notification_stats
- Тесты перемещены в backend/tests/notifications/ для правильной организации

Вся функциональность уведомлений проверена и работает корректно.
2026-01-05 15:38:36 +03:00

275 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Тесты для API endpoints уведомлений
"""
from django.test import TestCase
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient
from rest_framework import status
from notifications.models import (
NotificationCategory,
NotificationType,
Notification,
WebPushSubscription,
)
User = get_user_model()
def create_test_user(email='test@example.com', phone='+79990000001'):
"""Helper для создания тестового пользователя с обязательным phone_number"""
return User.objects.create_user(
email=email,
phone_number=phone,
password='testpass123',
first_name='Test',
last_name='User',
is_active=True
)
class NotificationAPITest(TestCase):
"""Тесты API уведомлений"""
def setUp(self):
self.client = APIClient()
self.user = create_test_user()
self.client.force_authenticate(user=self.user)
# Создаем категорию и тип
self.category = NotificationCategory.objects.create(
code='test_category',
name='Test Category'
)
self.notification_type = NotificationType.objects.create(
category=self.category,
code='test_type',
name='Test Type'
)
# Создаем тестовые уведомления
for i in range(5):
Notification.objects.create(
recipient=self.user,
notification_type=self.notification_type,
title=f'Notification {i+1}',
message=f'Message {i+1}',
short_message=f'Short {i+1}'
)
def test_get_notifications(self):
"""Получение списка уведомлений"""
response = self.client.get('/api/v1/notifications/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['total'], 5)
self.assertEqual(len(response.data['notifications']), 5)
def test_get_notifications_with_pagination(self):
"""Пагинация"""
response = self.client.get('/api/v1/notifications/?page=1&page_size=2')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['total'], 5)
self.assertEqual(len(response.data['notifications']), 2)
self.assertEqual(response.data['page'], 1)
def test_get_unread_notifications(self):
"""Фильтр непрочитанных"""
response = self.client.get('/api/v1/notifications/?unread_only=true')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['total'], 5) # Все непрочитанные
def test_get_unread_count(self):
"""Получение количества непрочитанных"""
response = self.client.get('/api/v1/notifications/count/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 5)
def test_mark_as_read(self):
"""Отметка уведомления как прочитанное"""
notification = Notification.objects.filter(recipient=self.user).first()
response = self.client.post(f'/api/v1/notifications/{notification.id}/read/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
notification.refresh_from_db()
self.assertTrue(notification.is_read)
def test_mark_all_as_read(self):
"""Отметка всех уведомлений как прочитанные"""
response = self.client.post('/api/v1/notifications/read-all/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
unread_count = Notification.objects.filter(
recipient=self.user,
is_read=False
).count()
self.assertEqual(unread_count, 0)
def test_delete_notification(self):
"""Удаление уведомления"""
notification = Notification.objects.filter(recipient=self.user).first()
response = self.client.delete(f'/api/v1/notifications/{notification.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Проверяем что уведомление заархивировано, а не удалено
notification.refresh_from_db()
self.assertTrue(notification.is_archived)
def test_search_notifications(self):
"""Поиск уведомлений"""
response = self.client.get('/api/v1/notifications/?search=Notification 3')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['total'], 1)
self.assertEqual(response.data['notifications'][0]['title'], 'Notification 3')
def test_unauthorized_access(self):
"""Доступ без авторизации"""
self.client.force_authenticate(user=None)
response = self.client.get('/api/v1/notifications/')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class WebPushAPITest(TestCase):
"""Тесты Web Push API"""
def setUp(self):
self.client = APIClient()
self.user = create_test_user()
self.client.force_authenticate(user=self.user)
def test_get_vapid_public_key(self):
"""Получение VAPID публичного ключа"""
response = self.client.get('/api/v1/notifications/push/vapid-key/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('vapid_public_key', response.data)
def test_subscribe_push(self):
"""Подписка на push-уведомления"""
data = {
'endpoint': 'https://fcm.googleapis.com/fcm/send/test123',
'keys': {
'p256dh': 'test_p256dh_key',
'auth': 'test_auth_key'
},
'device_name': 'Test Browser'
}
response = self.client.post('/api/v1/notifications/push/subscribe/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['status'], 'success')
# Проверяем что подписка создана
subscription = WebPushSubscription.objects.get(user=self.user)
self.assertEqual(subscription.endpoint, data['endpoint'])
self.assertEqual(subscription.device_name, 'Test Browser')
def test_subscribe_push_missing_fields(self):
"""Подписка с отсутствующими полями"""
data = {
'endpoint': 'https://fcm.googleapis.com/fcm/send/test123'
# Нет keys
}
response = self.client.post('/api/v1/notifications/push/subscribe/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_update_existing_subscription(self):
"""Обновление существующей подписки"""
# Создаем первую подписку
data1 = {
'endpoint': 'https://fcm.googleapis.com/fcm/send/test123',
'keys': {
'p256dh': 'old_key',
'auth': 'old_auth'
},
'device_name': 'Old Browser'
}
self.client.post('/api/v1/notifications/push/subscribe/', data1, format='json')
# Обновляем подписку с новыми ключами
data2 = {
'endpoint': 'https://fcm.googleapis.com/fcm/send/test123',
'keys': {
'p256dh': 'new_key',
'auth': 'new_auth'
},
'device_name': 'New Browser'
}
response = self.client.post('/api/v1/notifications/push/subscribe/', data2, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertFalse(response.data['created']) # Обновлена, не создана
# Проверяем обновление
subscription = WebPushSubscription.objects.get(user=self.user)
self.assertEqual(subscription.p256dh_key, 'new_key')
self.assertEqual(subscription.device_name, 'New Browser')
def test_unsubscribe_push(self):
"""Отписка от push-уведомлений"""
# Создаем подписку
subscription = WebPushSubscription.objects.create(
user=self.user,
endpoint='https://fcm.googleapis.com/fcm/send/test123',
p256dh_key='test_key',
auth_key='test_auth'
)
# Отписываемся
data = {'endpoint': subscription.endpoint}
response = self.client.delete('/api/v1/notifications/push/unsubscribe/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Проверяем что подписка удалена
self.assertFalse(
WebPushSubscription.objects.filter(id=subscription.id).exists()
)
def test_unsubscribe_all_push(self):
"""Отписка от всех подписок"""
# Создаем несколько подписок
for i in range(3):
WebPushSubscription.objects.create(
user=self.user,
endpoint=f'https://fcm.googleapis.com/fcm/send/test{i}',
p256dh_key=f'test_key_{i}',
auth_key=f'test_auth_{i}'
)
# Отписываемся от всех
response = self.client.delete('/api/v1/notifications/push/unsubscribe/', {}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Проверяем что все подписки удалены
self.assertEqual(
WebPushSubscription.objects.filter(user=self.user).count(),
0
)
def test_get_subscriptions(self):
"""Получение списка подписок"""
# Создаем подписки
for i in range(3):
WebPushSubscription.objects.create(
user=self.user,
endpoint=f'https://fcm.googleapis.com/fcm/send/test{i}',
p256dh_key=f'test_key_{i}',
auth_key=f'test_auth_{i}',
device_name=f'Device {i}'
)
response = self.client.get('/api/v1/notifications/push/subscriptions/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['subscriptions']), 3)