loko/loko/interventions/tests/test_calendar_notes_docs.py

321 lines
13 KiB
Python

import json
from unittest import mock
from datetime import datetime, timedelta
from django.test import SimpleTestCase, RequestFactory
from django.contrib.auth.models import AnonymousUser
from django.urls import reverse
from django.utils import timezone
from interventions.views.calendar import intervention_calendar_details_ajax
from interventions.models import InterventionNote
class DummyConfig:
regional_standard = 'FR'
linked_configs = mock.MagicMock(values_list=lambda *args, **kwargs: [])
class DummyUser:
def __init__(self, id=1, first_name="Jean", last_name="Dupont"):
self.id = id
self.pk = id
self.first_name = first_name
self.last_name = last_name
self.is_authenticated = True
self.config = DummyConfig()
def get_full_name(self):
return f"{self.first_name} {self.last_name}"
def __str__(self):
return self.get_full_name()
class DummyNote:
def __init__(self, id, note_type, type_display, badge_class, icon_class, content, note_time, note_author, updated_at=None, updated_by=None):
self.id = id
self.pk = id
self.note_type = note_type
self._type_display = type_display
self.type_badge_class = badge_class
self.type_icon_class = icon_class
self.content = content
self.note_time = note_time
self.note_author = note_author
self.updated_at = updated_at
self.updated_by = updated_by
def get_note_type_display(self):
return self._type_display
class DummyDoc:
def __init__(self, id, filename, thumbnail_path=None, upload_date=None, uploaded_by=None):
self.id = id
self.pk = id
self.filename = filename
self.upload_date = upload_date or timezone.now()
self.uploaded_by = uploaded_by
self.file = type('DummyFile', (), {'name': filename})()
if thumbnail_path:
self.thumbnail = type('DummyThumb', (), {'name': thumbnail_path})()
else:
self.thumbnail = None
self._meta = type('DummyMeta', (), {
'app_label': 'interventions',
'model_name': 'interventiondocument',
'object_name': 'InterventionDocument',
})()
class CalendarInterventionDetailsAjaxSimpleTests(SimpleTestCase):
databases = set()
def setUp(self):
self.factory = RequestFactory()
self.url = reverse('interventions:intervention_calendar_details_ajax', args=[123])
def _create_request(self, user=None):
request = self.factory.get(self.url)
request.user = user if user is not None else AnonymousUser()
request.session = {
'account_switch_allowed_user_ids': [getattr(request.user, 'pk', 1)],
'account_switch_origin_user_id': getattr(request.user, 'pk', 1),
}
return request
def test_url_resolves(self):
self.assertEqual(self.url, '/interventions/calendar/intervention/123/details/')
def test_anonymous_user_redirects_to_login(self):
request = self._create_request(AnonymousUser())
response = intervention_calendar_details_ajax(request, intervention_id=123)
self.assertEqual(response.status_code, 302)
self.assertIn('/accounts/login/', response.url)
@mock.patch('interventions.views.calendar.can_view_intervention_summary', return_value=False)
@mock.patch('interventions.views.calendar.can_view_intervention', return_value=False)
@mock.patch('interventions.views.calendar.get_object_or_404')
def test_permission_denied_returns_403(self, mock_get_obj, mock_can_view, mock_can_view_summary):
mock_itv = mock.MagicMock(id=123)
mock_get_obj.return_value = mock_itv
user = DummyUser(id=99)
request = self._create_request(user)
response = intervention_calendar_details_ajax(request, intervention_id=123)
self.assertEqual(response.status_code, 403)
@mock.patch('interventions.views.calendar.can_view_intervention_summary', return_value=False)
@mock.patch('interventions.views.calendar.can_view_intervention', return_value=True)
@mock.patch('interventions.views.calendar.InterventionNote.objects')
@mock.patch('interventions.views.calendar.get_object_or_404')
def test_authorized_user_receives_notes_and_documents(
self, mock_get_obj, mock_notes_qs, mock_can_view, mock_can_view_summary
):
now = timezone.now()
mock_itv = mock.MagicMock(id=123, title="Intervention Réparation Éclairage")
mock_itv.begin_time = None
mock_itv.end_time = None
mock_itv.creation_time = now
mock_itv.events.filter.return_value.order_by.return_value.first.return_value = None
mock_itv.realized_events.filter.return_value.order_by.return_value.first.return_value = None
mock_get_obj.return_value = mock_itv
author = DummyUser(id=1, first_name="Jean", last_name="Dupont")
# Notes
note1 = DummyNote(
id=1,
note_type=InterventionNote.NoteType.EQUIPMENT,
type_display="Matériel",
badge_class="text-bg-warning text-dark",
icon_class="bi-tools",
content="Nacelle 15m requise pour intervention sur mât",
note_time=now,
note_author=author,
)
note2 = DummyNote(
id=2,
note_type=InterventionNote.NoteType.IMPORTANT_INFO,
type_display="Information importante",
badge_class="text-bg-danger text-white",
icon_class="bi-exclamation-triangle-fill",
content="Attention présence de câbles haute tension à proximité",
note_time=now,
note_author=author,
updated_at=now,
updated_by=author,
)
mock_filtered_notes = mock.MagicMock()
mock_filtered_notes.select_related.return_value = mock_filtered_notes
mock_filtered_notes.annotate.return_value = mock_filtered_notes
mock_filtered_notes.order_by.return_value = [note1, note2]
mock_notes_qs.filter.return_value = mock_filtered_notes
# Documents
doc_img = DummyDoc(
id=10,
filename="photo_degats.jpg",
thumbnail_path="thumbnails/photo_degats.jpg",
upload_date=now,
uploaded_by=author,
)
doc_pdf = DummyDoc(
id=11,
filename="rapport_securite.pdf",
thumbnail_path=None,
upload_date=now,
uploaded_by=author,
)
mock_docs_qs = mock.MagicMock()
mock_docs_qs.all.return_value = mock_docs_qs
mock_docs_qs.select_related.return_value = mock_docs_qs
mock_docs_qs.order_by.return_value = [doc_img, doc_pdf]
mock_itv.documents = mock_docs_qs
user = DummyUser(id=99)
request = self._create_request(user)
from django.utils import translation
with translation.override('fr'):
response = intervention_calendar_details_ajax(request, intervention_id=123)
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf-8'))
self.assertTrue(data['success'])
self.assertEqual(data['notes_count'], 2)
self.assertEqual(data['documents_count'], 2)
self.assertEqual(data['total_count'], 4)
# Content assertions
self.assertIn("Avant réalisation", data['html'])
self.assertIn("Nacelle 15m requise", data['html'])
self.assertIn("Attention présence de câbles haute tension", data['html'])
self.assertIn("photo_degats.jpg", data['html'])
self.assertIn("rapport_securite.pdf", data['html'])
self.assertIn("Jean D.", data['html'])
# Document flags computed by view
self.assertTrue(doc_img.is_image)
self.assertFalse(doc_img.is_pdf)
self.assertEqual(doc_img.ext, '.jpg')
self.assertFalse(doc_pdf.is_image)
self.assertTrue(doc_pdf.is_pdf)
self.assertEqual(doc_pdf.ext, '.pdf')
@mock.patch('interventions.views.calendar.can_view_intervention_summary', return_value=False)
@mock.patch('interventions.views.calendar.can_view_intervention', return_value=True)
@mock.patch('interventions.views.calendar.InterventionNote.objects')
@mock.patch('interventions.views.calendar.get_object_or_404')
def test_empty_notes_and_documents(
self, mock_get_obj, mock_notes_qs, mock_can_view, mock_can_view_summary
):
mock_itv = mock.MagicMock(id=456, title="Intervention Sans Notes")
mock_itv.begin_time = None
mock_itv.end_time = None
mock_itv.creation_time = timezone.now()
mock_itv.events.filter.return_value.order_by.return_value.first.return_value = None
mock_itv.realized_events.filter.return_value.order_by.return_value.first.return_value = None
mock_get_obj.return_value = mock_itv
mock_filtered_notes = mock.MagicMock()
mock_filtered_notes.select_related.return_value = mock_filtered_notes
mock_filtered_notes.annotate.return_value = mock_filtered_notes
mock_filtered_notes.order_by.return_value = []
mock_notes_qs.filter.return_value = mock_filtered_notes
mock_docs_qs = mock.MagicMock()
mock_docs_qs.all.return_value = mock_docs_qs
mock_docs_qs.select_related.return_value = mock_docs_qs
mock_docs_qs.order_by.return_value = []
mock_itv.documents = mock_docs_qs
user = DummyUser(id=99)
request = self._create_request(user)
response = intervention_calendar_details_ajax(request, intervention_id=456)
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf-8'))
self.assertTrue(data['success'])
self.assertEqual(data['notes_count'], 0)
self.assertEqual(data['documents_count'], 0)
self.assertEqual(data['total_count'], 0)
self.assertIn("Aucune note ni document", data['html'])
@mock.patch('interventions.views.calendar.can_view_intervention_summary', return_value=False)
@mock.patch('interventions.views.calendar.can_view_intervention', return_value=True)
@mock.patch('interventions.views.calendar.InterventionNote.objects')
@mock.patch('interventions.views.calendar.get_object_or_404')
def test_chronological_sequencing_multiple_blocks(
self, mock_get_obj, mock_notes_qs, mock_can_view, mock_can_view_summary
):
now = timezone.now()
author = DummyUser(id=1, first_name="Jean", last_name="Dupont")
mock_itv = mock.MagicMock(id=789, title="Intervention Multi Phases")
mock_itv.begin_time = now - timedelta(hours=2)
mock_itv.end_time = now + timedelta(hours=2)
mock_itv.creation_time = now - timedelta(days=2)
mock_itv.events.filter.return_value.order_by.return_value.first.return_value = None
mock_itv.realized_events.filter.return_value.order_by.return_value.first.return_value = None
mock_get_obj.return_value = mock_itv
# Note during work
note_during = DummyNote(
id=1,
note_type=InterventionNote.NoteType.WORK_DONE,
type_display="Travaux réalisés",
badge_class="text-bg-success text-white",
icon_class="bi-check-circle",
content="Remplacement du ballast effectué avec succès",
note_time=now,
note_author=author,
)
mock_filtered_notes = mock.MagicMock()
mock_filtered_notes.select_related.return_value = mock_filtered_notes
mock_filtered_notes.annotate.return_value = mock_filtered_notes
mock_filtered_notes.order_by.return_value = [note_during]
mock_notes_qs.filter.return_value = mock_filtered_notes
# Document before work (yesterday)
doc_before = DummyDoc(
id=20,
filename="constat_initial.jpg",
thumbnail_path="thumbnails/constat_initial.jpg",
upload_date=now - timedelta(days=1),
uploaded_by=author,
)
mock_docs_qs = mock.MagicMock()
mock_docs_qs.all.return_value = mock_docs_qs
mock_docs_qs.select_related.return_value = mock_docs_qs
mock_docs_qs.order_by.return_value = [doc_before]
mock_itv.documents = mock_docs_qs
user = DummyUser(id=99)
request = self._create_request(user)
from django.utils import translation
with translation.override('fr'):
response = intervention_calendar_details_ajax(request, intervention_id=789)
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf-8'))
self.assertTrue(data['success'])
self.assertEqual(data['notes_count'], 1)
self.assertEqual(data['documents_count'], 1)
self.assertEqual(data['total_count'], 2)
import html as html_module
unescaped_html = html_module.unescape(data['html'])
# Both chronological blocks are rendered
self.assertIn("Avant réalisation", unescaped_html)
self.assertIn("constat_initial.jpg", unescaped_html)
self.assertIn("Exécution de l'intervention", unescaped_html)
self.assertIn("Remplacement du ballast effectué avec succès", unescaped_html)