loko/loko/interventions/tests/test_symptom_notifications.py

246 lines
11 KiB
Python

from django.test import TestCase, override_settings
from django.utils import timezone
from django.core.exceptions import ValidationError
from common.models import Thematic
from contracts.models import Company, Contract
from assets.models import AssetCategory
from interventions.models import Intervention, Symptom
from interventions.admin import SymptomAdminForm
from interventions.views.helpers import get_contract_recipients, get_contract_cc_recipients
@override_settings(EMAIL_OVERRIDE=None)
class SymptomNotificationRecipientsTests(TestCase):
def setUp(self):
self.thematic, _ = Thematic.objects.get_or_create(code='trafficlights', defaults={'name': 'Feux de signalisation'})
self.category, _ = AssetCategory.objects.get_or_create(name_fr='Test Category', thematic=self.thematic)
self.company = Company.objects.create(name='Prestataire Feux')
today = timezone.now().date()
self.contract = Contract.objects.create(
contract_number='CTR-FEUX-001',
company=self.company,
start_date=today,
end_date=today + timezone.timedelta(days=365),
is_active=True,
is_blocked=False,
notification_emails='contract_to1@example.com; contract_to2@example.com',
cc_notification_emails='contract_cc1@example.com, contract_cc2@example.com',
send_email_notifications=True,
)
self.contract.thematics.add(self.thematic)
self.contract_blocked = Contract.objects.create(
contract_number='CTR-FEUX-BLOCKED',
company=self.company,
start_date=today,
end_date=today + timezone.timedelta(days=365),
is_active=True,
is_blocked=True,
blocked_reason='Gel budget',
notification_emails='blocked_contract@example.com',
cc_notification_emails='blocked_cc@example.com',
send_email_notifications=True,
)
self.contract_blocked.thematics.add(self.thematic)
def test_default_symptom_notifies_contract_recipients(self):
"""Par défaut (notify_contract_recipients=True, sans emails supplémentaires), seuls les emails du contrat sont retournés."""
symptom = Symptom.objects.create(
name_fr='Ampoule grillée',
thematic=self.thematic,
contract=self.contract,
notify_contract_recipients=True,
)
intervention = Intervention.objects.create(
title='Remplacement ampoule',
thematic=self.thematic,
contract=self.contract,
symptom=symptom,
)
recipients = get_contract_recipients(self.contract, intervention)
self.assertIn('contract_to1@example.com', recipients)
self.assertIn('contract_to2@example.com', recipients)
self.assertEqual(len(recipients), 2)
cc_recipients = get_contract_cc_recipients(self.contract, intervention)
self.assertIn('contract_cc1@example.com', cc_recipients)
self.assertIn('contract_cc2@example.com', cc_recipients)
self.assertIn('bm.perm.tech@sprb.brussels', cc_recipients)
def test_symptom_with_additional_recipients_and_cc(self):
"""Avec notify_contract_recipients=True et des emails supplémentaires, les emails du contrat et du symptôme sont cumulés."""
symptom = Symptom.objects.create(
name_fr='Panne contrôleur critique',
thematic=self.thematic,
contract=self.contract,
notify_contract_recipients=True,
notification_emails='extra_expert@sprb.brussels; contract_to1@example.com', # Doublon volontaire avec contrat
cc_notification_emails='extra_supervision@sprb.brussels',
)
intervention = Intervention.objects.create(
title='Contrôleur HS',
thematic=self.thematic,
contract=self.contract,
symptom=symptom,
)
recipients = get_contract_recipients(self.contract, intervention)
self.assertEqual(
set(recipients),
{'contract_to1@example.com', 'contract_to2@example.com', 'extra_expert@sprb.brussels'}
)
cc_recipients = get_contract_cc_recipients(self.contract, intervention)
self.assertEqual(
set(cc_recipients),
{'contract_cc1@example.com', 'contract_cc2@example.com', 'extra_supervision@sprb.brussels', 'bm.perm.tech@sprb.brussels'}
)
def test_symptom_disabling_contract_recipients(self):
"""Avec notify_contract_recipients=False, les emails du contrat sont exclus, seuls les emails du symptôme sont prévenus."""
symptom = Symptom.objects.create(
name_fr='Vandalisme interne',
thematic=self.thematic,
contract=self.contract,
notify_contract_recipients=False,
notification_emails='police_bruxelles@sprb.brussels, securite@sprb.brussels',
cc_notification_emails='responsable_securite@sprb.brussels',
)
intervention = Intervention.objects.create(
title='Dégradation armoire',
thematic=self.thematic,
contract=self.contract,
symptom=symptom,
)
recipients = get_contract_recipients(self.contract, intervention)
self.assertEqual(
set(recipients),
{'police_bruxelles@sprb.brussels', 'securite@sprb.brussels'}
)
self.assertNotIn('contract_to1@example.com', recipients)
self.assertNotIn('contract_to2@example.com', recipients)
cc_recipients = get_contract_cc_recipients(self.contract, intervention)
self.assertIn('responsable_securite@sprb.brussels', cc_recipients)
self.assertIn('bm.perm.tech@sprb.brussels', cc_recipients)
self.assertNotIn('contract_cc1@example.com', cc_recipients)
self.assertNotIn('contract_cc2@example.com', cc_recipients)
def test_symptom_disabling_contract_recipients_without_extra_emails(self):
"""Avec notify_contract_recipients=False et sans emails supplémentaires, aucun destinataire principal n'est retourné."""
symptom = Symptom.objects.create(
name_fr='Test sans notification',
thematic=self.thematic,
contract=self.contract,
notify_contract_recipients=False,
)
intervention = Intervention.objects.create(
title='Intervention silencieuse',
thematic=self.thematic,
contract=self.contract,
symptom=symptom,
)
recipients = get_contract_recipients(self.contract, intervention)
self.assertEqual(recipients, [])
def test_blocked_contract_returns_empty_even_with_symptom_emails(self):
"""Un marché bloqué ne doit envoyer aucun email, même si le symptôme configure des destinataires."""
symptom = Symptom.objects.create(
name_fr='Panne sur marché bloqué',
thematic=self.thematic,
contract=self.contract_blocked,
notify_contract_recipients=True,
notification_emails='extra@sprb.brussels',
cc_notification_emails='extra_cc@sprb.brussels',
)
intervention = Intervention.objects.create(
title='Intervention bloquée',
thematic=self.thematic,
contract=self.contract_blocked,
symptom=symptom,
)
recipients = get_contract_recipients(self.contract_blocked, intervention)
self.assertEqual(recipients, [])
cc_recipients = get_contract_cc_recipients(self.contract_blocked, intervention)
self.assertEqual(cc_recipients, [])
def test_intervention_without_contract_uses_symptom_recipients(self):
"""Une intervention sans contrat mais avec un symptôme configuré peut quand même notifier ses destinataires spécifiques."""
symptom = Symptom.objects.create(
name_fr='Alerte voirie spéciale',
thematic=self.thematic,
contract=None,
notify_contract_recipients=True,
notification_emails='service_voirie@sprb.brussels',
cc_notification_emails='cc_voirie@sprb.brussels',
)
intervention = Intervention.objects.create(
title='Intervention sans contrat',
thematic=self.thematic,
contract=None,
symptom=symptom,
)
recipients = get_contract_recipients(None, intervention)
self.assertEqual(recipients, ['service_voirie@sprb.brussels'])
cc_recipients = get_contract_cc_recipients(None, intervention)
self.assertIn('cc_voirie@sprb.brussels', cc_recipients)
self.assertIn('bm.perm.tech@sprb.brussels', cc_recipients)
def test_backward_compatibility_without_intervention(self):
"""L'appel sans intervention (intervention=None) conserve le comportement historique du contrat."""
recipients = get_contract_recipients(self.contract)
self.assertEqual(
set(recipients),
{'contract_to1@example.com', 'contract_to2@example.com'}
)
cc_recipients = get_contract_cc_recipients(self.contract)
self.assertIn('contract_cc1@example.com', cc_recipients)
self.assertIn('contract_cc2@example.com', cc_recipients)
self.assertIn('bm.perm.tech@sprb.brussels', cc_recipients)
def test_symptom_admin_form_email_validation(self):
"""Le formulaire d'administration valide et normalise les listes d'adresses email."""
# Emails valides avec mélange de virgules et points-virgules
form = SymptomAdminForm(data={
'name_fr': 'Test Form',
'name_nl': 'Test Form NL',
'thematic': self.thematic.pk,
'priority': '3',
'is_active': True,
'notify_contract_recipients': True,
'notification_emails': 'alpha@example.com; beta@example.com, gamma@example.com',
'cc_notification_emails': 'delta@example.com; epsilon@example.com',
})
self.assertTrue(form.is_valid(), form.errors)
self.assertEqual(
form.cleaned_data['notification_emails'],
'alpha@example.com, beta@example.com, gamma@example.com'
)
self.assertEqual(
form.cleaned_data['cc_notification_emails'],
'delta@example.com, epsilon@example.com'
)
# Email invalide
form_invalid = SymptomAdminForm(data={
'name_fr': 'Test Form Invalid',
'name_nl': 'Test Form Invalid NL',
'thematic': self.thematic.pk,
'priority': '3',
'is_active': True,
'notify_contract_recipients': True,
'notification_emails': 'adresse_pas_valide',
})
self.assertFalse(form_invalid.is_valid())
self.assertIn('notification_emails', form_invalid.errors)