loko/loko/assets/tests_inspections_views.py

306 lines
14 KiB
Python

from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from assets.models.parking import ParkingSpot
from inspections.models import Inspection as AssetInspection, ParkingSpotInspection
from interventions.models import Intervention
User = get_user_model()
class InspectionViewsTestCase(TestCase):
def setUp(self):
self.user, _ = User.objects.get_or_create(
username='test_inspector',
defaults={
'first_name': 'Jean',
'last_name': 'Dupont'
}
)
self.user.set_password('password123')
self.user.save()
from common.models import UserConfig, UserThematics, Thematic
user_config, _ = UserConfig.objects.get_or_create(user=self.user)
user_config.is_intern = True
user_config.save()
t_parking, _ = Thematic.objects.get_or_create(code='parking', defaults={'name_fr': 'Stationnement'})
UserThematics.objects.update_or_create(user_config=user_config, thematic=t_parking, defaults={'can_inspect': True, 'can_view_assets': True})
self.client.force_login(self.user)
from django.contrib.gis.geos import Polygon
self.spot, _ = ParkingSpot.objects.get_or_create(
code='PK-001',
defaults={
'name_fr': 'Emplacement Vélo Test',
'spot_type': 'bicycle',
'standard_stand_count': 5
}
)
self.spot.geom = Polygon(((150000, 170000), (150010, 170000), (150010, 170010), (150000, 170010), (150000, 170000)), srid=3812)
self.spot.save()
self.spot_ct = ContentType.objects.get_for_model(ParkingSpot)
def test_inspections_list_view(self):
insp = AssetInspection.objects.create(
asset_content_type=self.spot_ct,
asset_object_id=self.spot.id,
inspector=self.user,
result_status='compliant',
nature='visual'
)
response = self.client.get(reverse('inspections:inspections_list'))
self.assertEqual(response.status_code, 200)
response_mobile = self.client.get(reverse('mobile:inspections_list_mobile'))
self.assertEqual(response_mobile.status_code, 200)
self.assertContains(response_mobile, 'PK-001')
def test_quick_inspection_map_view(self):
response = self.client.get(reverse('inspections:quick_inspection_map'))
self.assertEqual(response.status_code, 200)
def test_quick_inspection_map_mobile_view(self):
response = self.client.get(reverse('mobile:quick_inspection_map_mobile'))
self.assertEqual(response.status_code, 200)
def test_create_inspection_api_compliant(self):
response = self.client.post(reverse('inspections:create_inspection_api'), {
'content_type_id': self.spot_ct.id,
'object_id': self.spot.id,
'result_status': 'compliant',
'nature': 'visual',
'inspector_role': 'observer',
'notes': 'Tout est parfait',
})
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertTrue(data['success'])
self.spot.refresh_from_db()
self.assertEqual(self.spot.inspection_status, 'compliant')
self.assertTrue(self.spot.is_compliant)
def test_create_inspection_api_damaged_creates_intervention(self):
response = self.client.post(reverse('inspections:create_inspection_api'), {
'content_type_id': self.spot_ct.id,
'object_id': self.spot.id,
'result_status': 'damaged',
'nature': 'visual',
'notes': 'Arceau tordu',
'create_intervention': 'true',
})
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertTrue(data['success'])
self.assertIsNotNone(data['created_intervention_code'])
self.spot.refresh_from_db()
self.assertEqual(self.spot.inspection_status, 'damaged')
self.assertFalse(self.spot.is_compliant)
interv = Intervention.objects.filter(code=data['created_intervention_code']).first()
self.assertIsNotNone(interv)
self.assertEqual(interv.status, 'in_preparation')
def test_quick_inspection_map_permissions_can_inspect(self):
from common.models import UserConfig, UserThematics, Thematic
t_parking = Thematic.objects.get(code='parking')
t_other, _ = Thematic.objects.get_or_create(code='publiclighting', defaults={'name_fr': 'Éclairage public'})
user_perm = User.objects.create_user(username='user_perm_1', password='pwd')
user_config, _ = UserConfig.objects.get_or_create(user=user_perm)
UserThematics.objects.create(user_config=user_config, thematic=t_parking, can_inspect=True)
UserThematics.objects.create(user_config=user_config, thematic=t_other, can_inspect=False)
self.client.force_login(user_perm)
response = self.client.get(reverse('inspections:quick_inspection_map'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'parking')
self.assertNotContains(response, 'publiclighting')
self.client.force_login(self.user)
def test_no_inspectable_thematic_hides_button_and_returns_403(self):
from common.models import UserConfig, UserThematics, Thematic
t_parking = Thematic.objects.get(code='parking')
user_no_inspect = User.objects.create_user(username='user_no_inspect', password='pwd')
user_config, _ = UserConfig.objects.get_or_create(user=user_no_inspect)
UserThematics.objects.create(user_config=user_config, thematic=t_parking, can_inspect=False)
self.client.force_login(user_no_inspect)
# 1. Page liste des inspections -> Bouton 'Nouvelle inspection' masqué
resp_list = self.client.get(reverse('inspections:inspections_list'))
self.assertEqual(resp_list.status_code, 200)
self.assertNotContains(resp_list, reverse('inspections:quick_inspection_map'))
# 2. Tentative d'accès direct à quick_inspection_map -> HTTP 403 Forbidden
resp_map = self.client.get(reverse('inspections:quick_inspection_map'))
self.assertEqual(resp_map.status_code, 403)
self.client.force_login(self.user)
def test_create_inspection_api_with_photo(self):
from django.core.files.uploadedfile import SimpleUploadedFile
test_photo = SimpleUploadedFile("test_inspection.jpg", b"fake_image_bytes", content_type="image/jpeg")
response = self.client.post(reverse('inspections:create_inspection_api'), {
'content_type_id': self.spot_ct.id,
'object_id': self.spot.id,
'result_status': 'compliant',
'nature': 'visual',
'notes': 'Inspection avec photo',
'photos': [test_photo],
})
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertTrue(data['success'])
inspection = AssetInspection.objects.get(id=data['inspection_id'])
self.assertEqual(inspection.documents.count(), 1)
doc = inspection.documents.first()
self.assertTrue(doc.is_image)
self.assertTrue(doc.filename.startswith('test_inspection'))
def test_inspections_geojson_view(self):
from django.contrib.gis.geos import Polygon
self.spot.geom = Polygon(((150000, 170000), (150010, 170000), (150010, 170010), (150000, 170010), (150000, 170000)), srid=3812)
self.spot.save()
AssetInspection.objects.filter(asset_content_type=self.spot_ct, asset_object_id=self.spot.id).delete()
insp = AssetInspection.objects.create(
asset_content_type=self.spot_ct,
asset_object_id=self.spot.id,
inspector=self.user,
result_status='compliant',
nature='visual'
)
import json
response = self.client.post(
reverse('inspections:inspections_geojson'),
data=json.dumps({'thematic': 'parking', 'period': '3m'}),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertEqual(data['type'], 'FeatureCollection')
self.assertEqual(data['features'][0]['properties']['inspections'][0]['id'], insp.id)
self.assertEqual(data['features'][0]['properties']['inspections_count'], 1)
def test_inspections_list_search_by_asset_code(self):
self.spot.code = "SPOT-TEST-SEARCH-99"
self.spot.save()
insp = AssetInspection.objects.create(
asset_content_type=self.spot_ct,
asset_object_id=self.spot.id,
inspector=self.user,
result_status='compliant',
nature='visual'
)
response = self.client.get(reverse('mobile:inspections_list_mobile') + '?tab=all&q=SEARCH-99')
self.assertEqual(response.status_code, 200)
self.assertIn(insp, response.context['inspections'])
def test_inspections_list_filter_thematics_uses_can_view_assets(self):
from common.models import UserConfig, UserThematics, Thematic
t_parking = Thematic.objects.get(code='parking')
t_lighting, _ = Thematic.objects.get_or_create(code='publiclighting', defaults={'name_fr': 'Éclairage public'})
user_custom = User.objects.create_user(username='user_custom_perm', password='pwd')
user_config, _ = UserConfig.objects.get_or_create(user=user_custom)
user_config.is_intern = True
user_config.save()
UserThematics.objects.create(user_config=user_config, thematic=t_parking, can_view_assets=True, can_inspect=False)
UserThematics.objects.create(user_config=user_config, thematic=t_lighting, can_view_assets=False, can_inspect=False)
self.client.force_login(user_custom)
resp = self.client.get(reverse('mobile:inspections_list_mobile'))
self.assertEqual(resp.status_code, 200)
self.assertIn(t_parking, resp.context['thematics'])
self.assertNotIn(t_lighting, resp.context['thematics'])
self.assertFalse(resp.context['can_inspect'])
self.client.force_login(self.user)
def test_user_without_config_returns_none(self):
from inspections.views import get_user_viewable_thematics, get_user_inspectable_thematics
user_no_config, _ = User.objects.get_or_create(username='no_config_user', defaults={'password': 'pwd'})
from common.models import UserConfig
UserConfig.objects.filter(user=user_no_config).delete()
self.assertIsNone(get_user_viewable_thematics(user_no_config))
self.assertIsNone(get_user_inspectable_thematics(user_no_config))
def test_asset_inspections_templatetag(self):
from assets.templatetags.asset_tags import asset_inspections
from assets.models import AssetInspection
AssetInspection.objects.filter(asset_content_type=self.spot_ct, asset_object_id=self.spot.id).delete()
insp = AssetInspection.objects.create(
asset=self.spot,
inspector=self.user,
result_status='compliant',
nature='visual'
)
request = self.client.get(reverse('inspections:inspections_list')).wsgi_request
dummy_context = {'request': request}
data = asset_inspections(dummy_context, self.spot)
self.assertEqual(data['total_count'], 1)
self.assertIn(insp, data['inspections'])
self.assertEqual(data['asset_pk'], self.spot.pk)
def test_asset_inspections_templatetag_renders_with_null_inspector(self):
"""Vérifie que le composant asset_inspections se rend sans erreur quand inspector est None."""
from assets.models import AssetInspection
from django.template import Template, Context
AssetInspection.objects.filter(asset_content_type=self.spot_ct, asset_object_id=self.spot.id).delete()
insp = AssetInspection.objects.create(
asset=self.spot,
inspector=None,
result_status='pending',
nature='post_intervention'
)
request = self.client.get(reverse('inspections:inspections_list')).wsgi_request
t = Template('{% load asset_tags %}{% asset_inspections asset %}')
rendered = t.render(Context({'request': request, 'asset': self.spot}))
self.assertIn('assetInspectionDetailModal', rendered)
self.assertIn('-', rendered)
def test_roadway_detail_renders_with_inspection_without_inspector(self):
"""Vérifie que la page de détail d'une Roadway avec une inspection planifiée (sans inspecteur) ne plante pas (500)."""
from assets.models.roads import RoadStreet, Roadway
from common.models import UserThematics, Thematic, UserConfig
from inspections.models import Inspection
from django.contrib.gis.geos import MultiPolygon, Polygon
t_roads, _ = Thematic.objects.get_or_create(code='roads', defaults={'name_fr': 'Voiries'})
user_config = UserConfig.objects.get(user=self.user)
UserThematics.objects.update_or_create(user_config=user_config, thematic=t_roads, defaults={'can_view_assets': True})
street = RoadStreet.objects.create(code='WAT_R22_TEST_VIEW', name_fr='Chaussée Test')
p = Polygon(((150000, 170000), (150010, 170000), (150010, 170010), (150000, 170010), (150000, 170000)))
roadway = Roadway.objects.create(
street=street,
geom=MultiPolygon(p, srid=3812)
)
# Créer une inspection ciblant la Roadway sans inspecteur (comme une inspection de durabilité automatique)
insp = Inspection.objects.create(
asset_content_type=ContentType.objects.get_for_model(Roadway),
asset_object_id=roadway.id,
inspector=None,
nature='post_intervention',
status='scheduled'
)
url = reverse('assets:roads_assets_detail', kwargs={'asset_model': 'roadway', 'asset_id': roadway.id})
response = self.client.get(url)
self.assertEqual(response.status_code, 200)