306 lines
14 KiB
Python
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)
|
|
|