623 lines
29 KiB
Python
623 lines
29 KiB
Python
from django.test import TestCase
|
|
from django.urls import reverse
|
|
from django.contrib.auth import get_user_model
|
|
from notifications.models import Discussion, Message
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class DiscussionListTests(TestCase):
|
|
def setUp(self):
|
|
self.user1 = User.objects.create_user(username='user1', password='pass')
|
|
self.user2 = User.objects.create_user(username='user2', password='pass')
|
|
self.user3 = User.objects.create_user(username='user3', password='pass')
|
|
|
|
def test_discussion_list_filtering_on_inbox(self):
|
|
# Discussion 1: created by user1
|
|
d1 = Discussion.objects.create(title="Discussion 1", created_by=self.user1)
|
|
d1.participants.add(self.user1, self.user2, self.user3)
|
|
|
|
# Discussion 2: created by user2, user1 wrote a message
|
|
d2 = Discussion.objects.create(title="Discussion 2", created_by=self.user2)
|
|
d2.participants.add(self.user1, self.user2, self.user3)
|
|
Message.objects.create(discussion=d2, author=self.user1, content="Hello from user1")
|
|
|
|
# Discussion 3: created by user2, user1 is tagged
|
|
d3 = Discussion.objects.create(title="Discussion 3", created_by=self.user2)
|
|
d3.participants.add(self.user1, self.user2, self.user3)
|
|
Message.objects.create(discussion=d3, author=self.user2, content=f"Hello @[User 1](u:{self.user1.pk})")
|
|
|
|
# Discussion 4: created by user2, user3 participant but user1 is NOT author and NOT tagged
|
|
d4 = Discussion.objects.create(title="Discussion 4", created_by=self.user2)
|
|
d4.participants.add(self.user1, self.user2, self.user3)
|
|
|
|
# Discussion 5: auto-created intervention discussion with created_by=user1, but user1 never posted a message and was never tagged
|
|
from django.contrib.contenttypes.models import ContentType
|
|
ct = ContentType.objects.get_for_model(User)
|
|
d5 = Discussion.objects.create(title="Discussion 5", created_by=self.user1, content_type=ct, object_id=1)
|
|
Message.objects.create(discussion=d5, author=self.user2, content="Hello user3")
|
|
|
|
self.client.force_login(self.user1)
|
|
response = self.client.get(reverse('notifications:inbox') + '?tab=discussions')
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.context['active_tab'], 'discussions')
|
|
|
|
discussions_in_context = [item['discussion'] for item in response.context['discussion_items']]
|
|
self.assertIn(d1, discussions_in_context)
|
|
self.assertIn(d2, discussions_in_context)
|
|
self.assertIn(d3, discussions_in_context)
|
|
self.assertNotIn(d4, discussions_in_context)
|
|
self.assertNotIn(d5, discussions_in_context)
|
|
|
|
def test_discussion_list_url_redirect(self):
|
|
self.client.force_login(self.user1)
|
|
response = self.client.get(reverse('notifications:discussion_list'))
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertIn('tab=discussions', response.url)
|
|
|
|
def test_discussion_create(self):
|
|
self.client.force_login(self.user1)
|
|
response = self.client.post(reverse('notifications:discussion_create'), {
|
|
'title': 'Sujet test',
|
|
'content': f'Bonjour @[User 2](u:{self.user2.pk})',
|
|
})
|
|
self.assertEqual(response.status_code, 302)
|
|
discussion = Discussion.objects.get(title='Sujet test')
|
|
self.assertEqual(discussion.created_by, self.user1)
|
|
self.assertIn(self.user1, discussion.participants.all())
|
|
self.assertIn(self.user2, discussion.participants.all())
|
|
self.assertEqual(discussion.messages.count(), 1)
|
|
|
|
|
|
class InboxTests(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(username='test_inbox_user', password='pass')
|
|
from notifications.models import Notification, NotificationRecipient, Discussion
|
|
# Create a notification for user
|
|
notif = Notification.objects.create(
|
|
title="Test notification",
|
|
content="Notification content",
|
|
notification_type=Notification.TYPE_INFO,
|
|
)
|
|
NotificationRecipient.objects.create(
|
|
notification=notif,
|
|
user=self.user,
|
|
)
|
|
# Create a discussion for user
|
|
d = Discussion.objects.create(title="Test discussion", created_by=self.user)
|
|
d.participants.add(self.user)
|
|
|
|
def test_inbox_renders_both_notifications_and_discussions(self):
|
|
self.client.force_login(self.user)
|
|
response = self.client.get(reverse('notifications:inbox'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.context['active_tab'], 'notifications')
|
|
self.assertEqual(response.context['unread_count'], 1)
|
|
self.assertEqual(len(response.context['notifications']), 1)
|
|
self.assertEqual(len(response.context['discussion_items']), 1)
|
|
self.assertContains(response, 'Test notification')
|
|
self.assertContains(response, 'Test discussion')
|
|
self.assertContains(response, 'id="pane-notifications"')
|
|
self.assertContains(response, 'id="pane-discussions"')
|
|
self.assertContains(response, 'id="tab-notifications-btn"')
|
|
self.assertContains(response, 'id="tab-discussions-btn"')
|
|
|
|
def test_inbox_tab_discussions_parameter(self):
|
|
self.client.force_login(self.user)
|
|
response = self.client.get(reverse('notifications:inbox') + '?tab=discussions')
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.context['active_tab'], 'discussions')
|
|
|
|
|
|
class NotificationReminderTests(TestCase):
|
|
def setUp(self):
|
|
from common.models import UserConfig
|
|
self.user = User.objects.create_user(username='testuser', email='test@example.com', password='pass')
|
|
self.config, _ = UserConfig.objects.get_or_create(user=self.user)
|
|
|
|
def test_notification_settings_view(self):
|
|
self.client.force_login(self.user)
|
|
# GET settings page
|
|
response = self.client.get(reverse('notifications:settings'))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# POST updated settings
|
|
response = self.client.post(reverse('notifications:settings'), {
|
|
'preferred_language': 'nl',
|
|
'notification_reminder_enabled': 'on',
|
|
'notification_reminder_emails': 'custom@example.com, other@example.com',
|
|
'notification_reminder_delay_hours': 12,
|
|
})
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
self.config.refresh_from_db()
|
|
self.assertEqual(self.config.preferred_language, 'nl')
|
|
self.assertTrue(self.config.notification_reminder_enabled)
|
|
self.assertEqual(self.config.notification_reminder_emails, 'custom@example.com, other@example.com')
|
|
self.assertEqual(self.config.notification_reminder_delay_hours, 12)
|
|
|
|
from django.test import override_settings
|
|
|
|
@override_settings(EMAIL_OVERRIDE=None)
|
|
def test_send_unread_notification_reminders_command(self):
|
|
from datetime import timedelta
|
|
from django.utils import timezone
|
|
from django.core import mail
|
|
from django.core.management import call_command
|
|
from notifications.models import Notification, NotificationRecipient
|
|
|
|
# Configure user for reminders
|
|
self.config.notification_reminder_enabled = True
|
|
self.config.notification_reminder_delay_hours = 2
|
|
self.config.save()
|
|
|
|
# Create unread notification created 3 hours ago (> 2h delay)
|
|
past_time = timezone.now() - timedelta(hours=3)
|
|
notif = Notification.objects.create(
|
|
title="Notification urgente non lue",
|
|
content="Détail de la notification",
|
|
notification_type=Notification.TYPE_ALERT,
|
|
)
|
|
recipient = NotificationRecipient.objects.create(
|
|
notification=notif,
|
|
user=self.user,
|
|
)
|
|
# Backdate notification creation
|
|
Notification.objects.filter(pk=notif.pk).update(created_at=past_time)
|
|
|
|
# Run management command
|
|
call_command('send_unread_notification_reminders')
|
|
|
|
# Check that 1 email was sent
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
sent_email = mail.outbox[0]
|
|
self.assertIn("Notification urgente non lue", sent_email.body)
|
|
self.assertIn('test@example.com', sent_email.to)
|
|
|
|
# Verify reminder_sent_at is set
|
|
recipient.refresh_from_db()
|
|
self.assertIsNotNone(recipient.reminder_sent_at)
|
|
|
|
@override_settings(EMAIL_OVERRIDE=None)
|
|
def test_send_unread_notification_reminders_multilingual(self):
|
|
from datetime import timedelta
|
|
from django.utils import timezone
|
|
from django.core import mail
|
|
from django.core.management import call_command
|
|
from notifications.models import Notification, NotificationRecipient
|
|
|
|
self.config.notification_reminder_enabled = True
|
|
self.config.notification_reminder_delay_hours = 1
|
|
self.config.preferred_language = 'nl'
|
|
self.config.save()
|
|
|
|
past_time = timezone.now() - timedelta(hours=2)
|
|
notif = Notification.objects.create(
|
|
title="Dringende melding",
|
|
content="Details",
|
|
notification_type=Notification.TYPE_ALERT,
|
|
)
|
|
recipient = NotificationRecipient.objects.create(
|
|
notification=notif,
|
|
user=self.user,
|
|
)
|
|
Notification.objects.filter(pk=notif.pk).update(created_at=past_time)
|
|
|
|
mail.outbox.clear()
|
|
call_command('send_unread_notification_reminders')
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
sent_email = mail.outbox[0]
|
|
# In Dutch, subject contains 'Herinnering'
|
|
self.assertIn("Herinnering", sent_email.subject)
|
|
|
|
|
|
class WebPushTests(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(username='pushuser', password='password123')
|
|
self.client.force_login(self.user)
|
|
|
|
def test_device_info_parsing(self):
|
|
from notifications.webpush import parse_device_info
|
|
ua_win_chrome = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
res = parse_device_info(ua_win_chrome)
|
|
self.assertEqual(res['browser'], 'Google Chrome')
|
|
self.assertEqual(res['os'], 'Windows')
|
|
self.assertEqual(res['device_name'], 'Google Chrome sur Windows')
|
|
|
|
ua_ios_safari = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
|
|
res_ios = parse_device_info(ua_ios_safari)
|
|
self.assertEqual(res_ios['browser'], 'Safari')
|
|
self.assertEqual(res_ios['os'], 'iOS')
|
|
|
|
def test_service_worker_route_and_headers(self):
|
|
response = self.client.get('/service-worker.js')
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn('application/javascript', response['Content-Type'])
|
|
self.assertEqual(response['Service-Worker-Allowed'], '/')
|
|
|
|
def test_api_vapid_public_key(self):
|
|
response = self.client.get(reverse('notifications:api_webpush_vapid_key'))
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertIn('publicKey', data)
|
|
self.assertTrue(len(data['publicKey']) > 10)
|
|
|
|
def test_api_subscribe_and_unsubscribe(self):
|
|
from notifications.models import WebPushSubscription
|
|
payload = {
|
|
'endpoint': 'https://fcm.googleapis.com/fcm/send/test-sub-12345',
|
|
'p256dh': 'BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA_0QTpQtUbVlUls0VJXg7A8u-Ts1XbjhazAkj7I99e8QcYP7DkM',
|
|
'auth': 'tBHItJI5svbpez7KI4CCXg',
|
|
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0'
|
|
}
|
|
|
|
# 1. Subscribe
|
|
response = self.client.post(
|
|
reverse('notifications:api_webpush_subscribe'),
|
|
data=payload,
|
|
content_type='application/json'
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(WebPushSubscription.objects.filter(user=self.user).count(), 1)
|
|
sub = WebPushSubscription.objects.get(user=self.user)
|
|
self.assertEqual(sub.endpoint, payload['endpoint'])
|
|
self.assertEqual(sub.browser_name, 'Google Chrome')
|
|
self.assertEqual(sub.os_name, 'Windows')
|
|
|
|
# 2. Delete device via delete route
|
|
del_resp = self.client.post(reverse('notifications:api_webpush_delete_device', args=[sub.pk]))
|
|
self.assertEqual(del_resp.status_code, 302)
|
|
self.assertEqual(WebPushSubscription.objects.filter(user=self.user).count(), 0)
|
|
|
|
# 3. Subscribe again and unsubscribe via API
|
|
self.client.post(
|
|
reverse('notifications:api_webpush_subscribe'),
|
|
data=payload,
|
|
content_type='application/json'
|
|
)
|
|
self.assertEqual(WebPushSubscription.objects.filter(user=self.user).count(), 1)
|
|
|
|
unsub_resp = self.client.post(
|
|
reverse('notifications:api_webpush_unsubscribe'),
|
|
data={'endpoint': payload['endpoint']},
|
|
content_type='application/json'
|
|
)
|
|
self.assertEqual(unsub_resp.status_code, 200)
|
|
self.assertEqual(WebPushSubscription.objects.filter(user=self.user).count(), 0)
|
|
|
|
def test_send_webpush_mocked(self):
|
|
import base64
|
|
import os
|
|
from unittest.mock import patch, MagicMock
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
from cryptography.hazmat.primitives import serialization
|
|
from notifications.models import WebPushSubscription
|
|
from notifications.webpush import send_webpush_to_user
|
|
|
|
fake_client_priv = ec.generate_private_key(ec.SECP256R1())
|
|
fake_pub = fake_client_priv.public_key().public_bytes(
|
|
encoding=serialization.Encoding.X962,
|
|
format=serialization.PublicFormat.UncompressedPoint
|
|
)
|
|
fake_auth = os.urandom(16)
|
|
sub = WebPushSubscription.objects.create(
|
|
user=self.user,
|
|
endpoint='https://fcm.googleapis.com/fcm/send/mock-123',
|
|
p256dh=base64.urlsafe_b64encode(fake_pub).decode('utf-8').rstrip('='),
|
|
auth=base64.urlsafe_b64encode(fake_auth).decode('utf-8').rstrip('='),
|
|
device_name='Test Device'
|
|
)
|
|
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 201
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
with patch('notifications.webpush.requests.post', return_value=mock_resp) as mock_post:
|
|
res = send_webpush_to_user(
|
|
user=self.user,
|
|
title=_("Alerte Intervention"),
|
|
body=_("Nouvelle intervention assignée"),
|
|
action_url="/interventions/1/",
|
|
async_send=False
|
|
)
|
|
self.assertEqual(res.get('success'), 1)
|
|
mock_post.assert_called_once()
|
|
called_endpoint = mock_post.call_args[0][0]
|
|
self.assertEqual(called_endpoint, sub.endpoint)
|
|
headers = mock_post.call_args[1]['headers']
|
|
self.assertIn('Authorization', headers)
|
|
self.assertEqual(headers['Content-Encoding'], 'aes128gcm')
|
|
|
|
def test_send_webpush_410_auto_cleanup(self):
|
|
import base64
|
|
import os
|
|
from unittest.mock import patch, MagicMock
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
from cryptography.hazmat.primitives import serialization
|
|
from notifications.models import WebPushSubscription
|
|
from notifications.webpush import send_webpush_to_user
|
|
|
|
fake_client_priv = ec.generate_private_key(ec.SECP256R1())
|
|
fake_pub = fake_client_priv.public_key().public_bytes(
|
|
encoding=serialization.Encoding.X962,
|
|
format=serialization.PublicFormat.UncompressedPoint
|
|
)
|
|
fake_auth = os.urandom(16)
|
|
sub = WebPushSubscription.objects.create(
|
|
user=self.user,
|
|
endpoint='https://fcm.googleapis.com/fcm/send/expired-123',
|
|
p256dh=base64.urlsafe_b64encode(fake_pub).decode('utf-8').rstrip('='),
|
|
auth=base64.urlsafe_b64encode(fake_auth).decode('utf-8').rstrip('='),
|
|
)
|
|
|
|
mock_resp = MagicMock()
|
|
mock_resp.status_code = 410
|
|
|
|
with patch('notifications.webpush.requests.post', return_value=mock_resp):
|
|
send_webpush_to_user(
|
|
user=self.user,
|
|
title="Test",
|
|
async_send=False
|
|
)
|
|
# Subscription should be automatically deleted from DB
|
|
self.assertFalse(WebPushSubscription.objects.filter(pk=sub.pk).exists())
|
|
|
|
def test_notify_user_triggers_webpush(self):
|
|
from unittest.mock import patch
|
|
from notifications.models import WebPushSubscription
|
|
from notifications.services import notify_user
|
|
|
|
WebPushSubscription.objects.create(
|
|
user=self.user,
|
|
endpoint='https://fcm.googleapis.com/fcm/send/auto-123',
|
|
p256dh='fake-key',
|
|
auth='fake-auth',
|
|
)
|
|
|
|
with patch('notifications.webpush.send_webpush_to_user') as mock_send:
|
|
notify_user(
|
|
recipient=self.user,
|
|
title="Nouvelle tâche",
|
|
content="Veuillez vérifier l'intervention.",
|
|
action_url="/interventions/99/",
|
|
)
|
|
mock_send.assert_called_once_with(
|
|
user=self.user,
|
|
title="Nouvelle tâche",
|
|
body="Veuillez vérifier l'intervention.",
|
|
action_url="/interventions/99/",
|
|
)
|
|
|
|
|
|
class TeamMentionTests(TestCase):
|
|
def setUp(self):
|
|
from contracts.models import Company, CompanyTeam, CompanyMember, UserCompanyTeamAccess
|
|
from common.models import UserConfig
|
|
self.company = Company.objects.create(name="Bruxelles Mobilité")
|
|
self.team = CompanyTeam.objects.create(name="BM - Régie - Team 1", company=self.company)
|
|
|
|
self.author = User.objects.create_user(username='author_user', password='pass')
|
|
self.author_config = UserConfig.objects.create(user=self.author, is_intern=True)
|
|
|
|
self.team_member_user = User.objects.create_user(username='team_user', password='pass')
|
|
self.other_user = User.objects.create_user(username='other_user', password='pass')
|
|
|
|
self.company_member = CompanyMember.objects.create(
|
|
name="Agent Régie",
|
|
user=self.team_member_user
|
|
)
|
|
self.company_member.teams.add(self.team)
|
|
|
|
def test_api_mention_search_finds_company_team_by_name(self):
|
|
self.client.force_login(self.author)
|
|
url = reverse('notifications:api_mention_search') + '?q=Régie'
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
results = data.get('results', [])
|
|
team_results = [r for r in results if r['type'] == 'team']
|
|
self.assertTrue(any(r['id'] == self.team.pk and r['display'] == "BM - Régie - Team 1" for r in team_results))
|
|
|
|
def test_api_mention_search_finds_company_team_by_company_name(self):
|
|
self.client.force_login(self.author)
|
|
url = reverse('notifications:api_mention_search') + '?q=Bruxelles'
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
results = data.get('results', [])
|
|
team_results = [r for r in results if r['type'] == 'team']
|
|
self.assertTrue(any(r['id'] == self.team.pk for r in team_results))
|
|
|
|
def test_external_user_can_only_search_teams_with_can_tag_true(self):
|
|
from contracts.models import Company, CompanyTeam, UserCompanyTeamAccess
|
|
from common.models import UserConfig
|
|
|
|
provider_co = Company.objects.create(name="Société Prestataire")
|
|
team_own_allowed = CompanyTeam.objects.create(name="Prestataire - Team Alpha", company=provider_co)
|
|
team_own_forbidden = CompanyTeam.objects.create(name="Prestataire - Team Beta", company=provider_co)
|
|
team_internal = CompanyTeam.objects.create(name="BM - Régie - Team 2", company=self.company)
|
|
|
|
ext_user = User.objects.create_user(username='ext_user', password='pass')
|
|
ext_config = UserConfig.objects.create(user=ext_user, is_intern=False, company=provider_co)
|
|
|
|
# Autoriser explicitement team_own_allowed et team_internal avec can_tag=True
|
|
UserCompanyTeamAccess.objects.create(user_config=ext_config, team=team_own_allowed, can_tag=True, can_view=True)
|
|
UserCompanyTeamAccess.objects.create(user_config=ext_config, team=team_internal, can_tag=True, can_view=True)
|
|
# team_own_forbidden a can_tag=False même si c'est la même société
|
|
UserCompanyTeamAccess.objects.create(user_config=ext_config, team=team_own_forbidden, can_tag=False, can_view=True)
|
|
|
|
self.client.force_login(ext_user)
|
|
|
|
# Recherche de toutes les équipes prestataires
|
|
url = reverse('notifications:api_mention_search') + '?q=Prestataire'
|
|
res = self.client.get(url)
|
|
self.assertEqual(res.status_code, 200)
|
|
team_ids = [r['id'] for r in res.json().get('results', []) if r['type'] == 'team']
|
|
self.assertIn(team_own_allowed.pk, team_ids)
|
|
self.assertNotIn(team_own_forbidden.pk, team_ids)
|
|
|
|
# Recherche de l'équipe interne autorisée
|
|
url = reverse('notifications:api_mention_search') + '?q=Régie'
|
|
res = self.client.get(url)
|
|
self.assertEqual(res.status_code, 200)
|
|
team_ids = [r['id'] for r in res.json().get('results', []) if r['type'] == 'team']
|
|
self.assertIn(team_internal.pk, team_ids)
|
|
# L'équipe self.team (BM - Régie - Team 1) n'a pas d'accès configuré, donc elle ne doit PAS apparaître
|
|
self.assertNotIn(self.team.pk, team_ids)
|
|
|
|
def test_external_user_without_config_sees_no_teams(self):
|
|
user_no_cfg = User.objects.create_user(username='user_no_cfg', password='pass')
|
|
self.client.force_login(user_no_cfg)
|
|
url = reverse('notifications:api_mention_search') + '?q=Régie'
|
|
res = self.client.get(url)
|
|
self.assertEqual(res.status_code, 200)
|
|
team_results = [r for r in res.json().get('results', []) if r['type'] == 'team']
|
|
self.assertEqual(team_results, [])
|
|
|
|
def test_team_mention_notifies_company_team_members_and_adds_participants(self):
|
|
from notifications.models import Discussion, NotificationRecipient
|
|
from notifications.services import post_message
|
|
from notifications.views import _get_user_discussions_data
|
|
|
|
discussion = Discussion.objects.create(title="Discussion Chantier", created_by=self.author)
|
|
discussion.participants.add(self.author)
|
|
|
|
content = f"Bonjour @[BM - Régie - Team 1](t:{self.team.pk}), intervention requise."
|
|
post_message(discussion=discussion, author=self.author, content=content)
|
|
|
|
# Vérifier que le membre de l'équipe a été ajouté aux participants
|
|
self.assertIn(self.team_member_user, discussion.participants.all())
|
|
self.assertNotIn(self.other_user, discussion.participants.all())
|
|
|
|
# Vérifier que le membre a reçu une NotificationRecipient
|
|
recipients = NotificationRecipient.objects.filter(user=self.team_member_user)
|
|
self.assertTrue(recipients.exists())
|
|
notif = recipients.first().notification
|
|
self.assertIn("BM - Régie - Team 1", notif.title)
|
|
|
|
# Vérifier que la discussion apparaît dans _get_user_discussions_data pour ce membre
|
|
items, unread_count = _get_user_discussions_data(self.team_member_user)
|
|
discussion_ids = [it['discussion'].pk for it in items]
|
|
self.assertIn(discussion.pk, discussion_ids)
|
|
|
|
def test_reply_in_discussion_detail_with_team_mention(self):
|
|
from notifications.models import Discussion, NotificationRecipient
|
|
|
|
discussion = Discussion.objects.create(title="Discussion Test Reply", created_by=self.author)
|
|
discussion.participants.add(self.author)
|
|
|
|
self.client.force_login(self.author)
|
|
url = reverse('notifications:discussion_detail', args=[discussion.pk])
|
|
response = self.client.post(url, {
|
|
'content': f"Message via form @[BM - Régie - Team 1](t:{self.team.pk})"
|
|
})
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(discussion.messages.count(), 1)
|
|
self.assertIn(self.team_member_user, discussion.participants.all())
|
|
self.assertTrue(NotificationRecipient.objects.filter(user=self.team_member_user).exists())
|
|
|
|
|
|
class DiscussionFlagTests(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create_user(username='flag_user', password='pass', first_name='Alice', last_name='Smith')
|
|
self.other_user = User.objects.create_user(username='other_user', password='pass', first_name='Bob')
|
|
self.discussion = Discussion.objects.create(title="Discussion Flag Test", created_by=self.user)
|
|
self.discussion.participants.add(self.user)
|
|
|
|
def test_set_discussion_flag_service(self):
|
|
from notifications.services import set_discussion_flag
|
|
|
|
# 1. Set red flag
|
|
msg = set_discussion_flag(self.discussion, 'red', user=self.user)
|
|
self.discussion.refresh_from_db()
|
|
self.assertEqual(self.discussion.flag, 'red')
|
|
self.assertEqual(self.discussion.flag_set_by, self.user)
|
|
self.assertIsNotNone(self.discussion.flag_set_at)
|
|
self.assertIsNotNone(msg)
|
|
self.assertTrue(msg.is_system)
|
|
self.assertIn("Alice Smith", msg.content)
|
|
self.assertIn("🚩", msg.content)
|
|
|
|
# 2. Change to green flag
|
|
msg2 = set_discussion_flag(self.discussion, 'green', user=self.user)
|
|
self.discussion.refresh_from_db()
|
|
self.assertEqual(self.discussion.flag, 'green')
|
|
self.assertIn("🟩", msg2.content)
|
|
|
|
# 3. Setting same flag returns None and doesn't create new message
|
|
msg_noop = set_discussion_flag(self.discussion, 'green', user=self.user)
|
|
self.assertIsNone(msg_noop)
|
|
|
|
# 4. Remove flag
|
|
msg_clear = set_discussion_flag(self.discussion, '', user=self.user)
|
|
self.discussion.refresh_from_db()
|
|
self.assertEqual(self.discussion.flag, '')
|
|
self.assertIsNone(self.discussion.flag_set_at)
|
|
self.assertIn("retiré", msg_clear.content)
|
|
|
|
def test_discussion_set_flag_view_ajax(self):
|
|
self.client.force_login(self.user)
|
|
url = reverse('notifications:discussion_set_flag', args=[self.discussion.pk])
|
|
res = self.client.post(url, {'flag': 'orange'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
self.assertEqual(res.status_code, 200)
|
|
data = res.json()
|
|
self.assertTrue(data['ok'])
|
|
self.assertEqual(data['flag'], 'orange')
|
|
self.assertIn(data['flag_info']['label'], ['En cours', 'In progress'])
|
|
self.discussion.refresh_from_db()
|
|
self.assertEqual(self.discussion.flag, 'orange')
|
|
|
|
# Non-participant receives 403
|
|
self.client.force_login(self.other_user)
|
|
res_forbidden = self.client.post(url, {'flag': 'green'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
|
self.assertEqual(res_forbidden.status_code, 403)
|
|
|
|
def test_discussion_filter_by_flag(self):
|
|
from notifications.views import _get_user_discussions_data, _get_inbox_context
|
|
from django.test import RequestFactory
|
|
|
|
# Create discussions with different flags
|
|
d_red = Discussion.objects.create(title="Red Disc", created_by=self.user, flag='red')
|
|
d_red.participants.add(self.user)
|
|
d_green = Discussion.objects.create(title="Green Disc", created_by=self.user, flag='green')
|
|
d_green.participants.add(self.user)
|
|
|
|
# Filter red
|
|
items_red, _ = _get_user_discussions_data(self.user, flag='red')
|
|
pks_red = [i['discussion'].pk for i in items_red]
|
|
self.assertIn(d_red.pk, pks_red)
|
|
self.assertNotIn(d_green.pk, pks_red)
|
|
|
|
# Filter green
|
|
items_green, _ = _get_user_discussions_data(self.user, flag='green')
|
|
pks_green = [i['discussion'].pk for i in items_green]
|
|
self.assertIn(d_green.pk, pks_green)
|
|
self.assertNotIn(d_red.pk, pks_green)
|
|
|
|
# Inbox context with flag filter
|
|
factory = RequestFactory()
|
|
req = factory.get('/notifications/', {'tab': 'discussions', 'flag': 'red'})
|
|
req.user = self.user
|
|
ctx = _get_inbox_context(req)
|
|
self.assertEqual(ctx['filter_flag'], 'red')
|
|
self.assertEqual(ctx['flag_counts']['red'], 1)
|
|
self.assertEqual(ctx['flag_counts']['green'], 1)
|
|
|
|
def test_discussion_create_with_flag(self):
|
|
self.client.force_login(self.user)
|
|
url = reverse('notifications:discussion_create')
|
|
res = self.client.post(url, {
|
|
'title': 'New Flagged Discussion',
|
|
'content': 'First message',
|
|
'flag': 'red',
|
|
})
|
|
self.assertEqual(res.status_code, 302)
|
|
disc = Discussion.objects.filter(title='New Flagged Discussion').first()
|
|
self.assertIsNotNone(disc)
|
|
self.assertEqual(disc.flag, 'red')
|
|
self.assertEqual(disc.flag_set_by, self.user)
|
|
|
|
|
|
|
|
|