1143 lines
47 KiB
Python
1143 lines
47 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 django.utils import timezone
|
|
|
|
from assets.models import NatureRWIZ, NatureRWIAsset, ParkingSpot
|
|
from interventions.models import Intervention
|
|
from common.models import Thematic
|
|
from .models import Inspection, ParkingSpotInspection, GiepInspection, InspectionDocument
|
|
from .registry import get_inspection_partial_template
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class InspectionModelAndViewsTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client = Client()
|
|
self.user = User.objects.create_user(
|
|
username='inspector_user',
|
|
password='password123',
|
|
first_name='Jean',
|
|
last_name='Valjean'
|
|
)
|
|
from common.models import UserConfig, Role, UserThematics
|
|
admin_role, _ = Role.objects.get_or_create(name='admin')
|
|
config = UserConfig.objects.create(user=self.user)
|
|
config.roles.add(admin_role)
|
|
|
|
self.water_thematic, _ = Thematic.objects.get_or_create(
|
|
code='water',
|
|
defaults={'name_fr': 'Gestion de l\'Eau / GIEP'}
|
|
)
|
|
UserThematics.objects.create(
|
|
user_config=config,
|
|
thematic=self.water_thematic,
|
|
can_view_assets=True,
|
|
can_edit_assets=True
|
|
)
|
|
|
|
self.thematic, _ = Thematic.objects.get_or_create(
|
|
code='parking',
|
|
defaults={'name_fr': 'Stationnement'}
|
|
)
|
|
UserThematics.objects.create(
|
|
user_config=config,
|
|
thematic=self.thematic,
|
|
can_view_assets=True,
|
|
can_edit_assets=True
|
|
)
|
|
|
|
from assets.models import AssetCategory
|
|
self.parking_cat, _ = AssetCategory.objects.get_or_create(
|
|
code='parking_spot',
|
|
thematic=self.thematic,
|
|
defaults={
|
|
'name_fr': 'Emplacements de stationnement',
|
|
'name_nl': 'Parkeerplaatsen'
|
|
}
|
|
)
|
|
self.spot = ParkingSpot.objects.create(
|
|
name_fr='Emplacement Vélo Test',
|
|
category=self.parking_cat
|
|
)
|
|
self.spot_ct = ContentType.objects.get_for_model(ParkingSpot)
|
|
self.parking_cat.allowed_models.add(self.spot_ct)
|
|
|
|
self.rwiz = NatureRWIZ.objects.create(
|
|
name_fr='Zone GIEP Test'
|
|
)
|
|
self.rwiz_ct = ContentType.objects.get_for_model(NatureRWIZ)
|
|
|
|
self.mission_interv = Intervention.objects.create(
|
|
title="Mission inspection GIEP Nord",
|
|
thematic=self.thematic,
|
|
status="in_progress"
|
|
)
|
|
self.work_interv = Intervention.objects.create(
|
|
title="Travaux réfection par Prestataire A",
|
|
thematic=self.thematic,
|
|
status="done"
|
|
)
|
|
|
|
def test_inspection_creation_with_interventions(self):
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
mission_intervention=self.mission_interv,
|
|
checked_intervention=self.work_interv,
|
|
nature='technical',
|
|
inspector_role='provider',
|
|
result_status='compliant',
|
|
notes="Tiers contrôle OK"
|
|
)
|
|
|
|
self.assertTrue(insp.is_mission_inspection)
|
|
self.assertTrue(insp.is_cross_check)
|
|
self.assertEqual(insp.mission_intervention, self.mission_interv)
|
|
self.assertEqual(insp.checked_intervention, self.work_interv)
|
|
self.assertIn(insp, self.mission_interv.generated_inspections.all())
|
|
self.assertIn(insp, self.work_interv.quality_inspections.all())
|
|
|
|
def test_giep_inspection_model(self):
|
|
giep_insp = GiepInspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
date_derniere_pluie='aujourdhui',
|
|
orage_moins_une_heure=True,
|
|
data={
|
|
'ouvrages': [
|
|
{'local_id': '0', 'device_type': 'evc', 'name': 'Noue 1'}
|
|
]
|
|
}
|
|
)
|
|
|
|
self.assertEqual(giep_insp.detailed_inspection, giep_insp)
|
|
self.assertIn("1 ouvrage(s) GIEP", giep_insp.details_summary)
|
|
self.assertIn(str(giep_insp.get_date_derniere_pluie_display()), giep_insp.details_summary)
|
|
|
|
def test_create_inspection_api(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('inspections:create_inspection_api')
|
|
response = self.client.post(url, {
|
|
'content_type_id': self.spot_ct.id,
|
|
'object_id': self.spot.id,
|
|
'result_status': 'compliant',
|
|
'nature': 'visual',
|
|
'inspector_role': 'provider',
|
|
'mission_intervention_id': self.mission_interv.id,
|
|
'checked_intervention_id': self.work_interv.id,
|
|
'notes': 'Inspection via API'
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(data['success'])
|
|
|
|
insp = Inspection.objects.get(pk=data['inspection_id'])
|
|
self.assertEqual(insp.mission_intervention, self.mission_interv)
|
|
self.assertEqual(insp.checked_intervention, self.work_interv)
|
|
|
|
def test_create_giep_inspection_api(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('inspections:create_inspection_api')
|
|
import json
|
|
giep_json = json.dumps({
|
|
'ouvrages': [
|
|
{'local_id': '0', 'device_type': 'evc', 'questions': {'engorgement': 'aucun'}}
|
|
]
|
|
})
|
|
response = self.client.post(url, {
|
|
'content_type_id': self.rwiz_ct.id,
|
|
'object_id': self.rwiz.id,
|
|
'result_status': 'compliant',
|
|
'nature': 'visual',
|
|
'inspector_role': 'observer',
|
|
'date_derniere_pluie': 'moins_24h',
|
|
'orage_moins_une_heure': 'false',
|
|
'giep_data': giep_json,
|
|
'notes': 'Inspection GIEP via API'
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(data['success'])
|
|
|
|
insp = GiepInspection.objects.get(pk=data['inspection_id'])
|
|
self.assertEqual(insp.date_derniere_pluie, 'moins_24h')
|
|
self.assertFalse(insp.orage_moins_une_heure)
|
|
self.assertEqual(len(insp.data.get('ouvrages', [])), 1)
|
|
|
|
def test_registry_giep_template(self):
|
|
tmpl_rwiz = get_inspection_partial_template('naturerwiz')
|
|
tmpl_asset = get_inspection_partial_template('naturerwiasset')
|
|
self.assertEqual(tmpl_rwiz, 'inspections/components/asset_fields/_naturerwiz.html')
|
|
self.assertIsNone(tmpl_asset)
|
|
|
|
def test_registry_detail_templates(self):
|
|
from .registry import get_inspection_detail_partial_template
|
|
giep_insp = GiepInspection(asset=self.rwiz)
|
|
parking_insp = ParkingSpotInspection(asset=self.spot)
|
|
|
|
tmpl_giep = get_inspection_detail_partial_template(giep_insp)
|
|
tmpl_parking = get_inspection_detail_partial_template(parking_insp)
|
|
|
|
self.assertEqual(tmpl_giep, 'inspections/components/asset_details/_naturerwiz.html')
|
|
self.assertEqual(tmpl_parking, 'inspections/components/asset_details/_parkingspot.html')
|
|
|
|
def test_zirw_detail_view_renders_cleanly(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('assets:zirw_detail', kwargs={'location_id': self.rwiz.id})
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_spatial_location_resolution_for_rwi_asset(self):
|
|
from assets.templatetags.asset_tags import asset_location_link
|
|
from django.contrib.gis.geos import Polygon, MultiPolygon, Point
|
|
poly = Polygon(((0, 0), (0, 10), (10, 10), (10, 0), (0, 0)), srid=3812)
|
|
rwiz_poly = NatureRWIZ.objects.create(
|
|
code="RWIZ-TEST",
|
|
name_fr="Zone Test Polygons",
|
|
geom=MultiPolygon(poly, srid=3812)
|
|
)
|
|
rwi_asset = NatureRWIAsset.objects.create(
|
|
code="RWI-TEST-01",
|
|
geom=Point(5, 5, srid=3812)
|
|
)
|
|
ctx = asset_location_link({'request': None}, rwi_asset)
|
|
self.assertEqual(ctx['location'], rwiz_poly)
|
|
|
|
def test_can_edit_inspection_permissions(self):
|
|
other_user = User.objects.create_user(
|
|
username='regular_user',
|
|
password='password123'
|
|
)
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
notes="Initial note"
|
|
)
|
|
|
|
# Author can edit
|
|
self.assertTrue(insp.can_edit(self.user))
|
|
|
|
# Superuser can edit
|
|
admin_user = User.objects.create_superuser(
|
|
username='admin_superuser',
|
|
password='password123'
|
|
)
|
|
self.assertTrue(insp.can_edit(admin_user))
|
|
|
|
# Other non-author non-admin cannot edit
|
|
self.assertFalse(insp.can_edit(other_user))
|
|
|
|
def test_update_inspection_api_author_success(self):
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
notes="Initial note"
|
|
)
|
|
initial_updated_at = insp.updated_at
|
|
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('inspections:update_inspection_api', kwargs={'inspection_id': insp.id})
|
|
response = self.client.post(url, {
|
|
'result_status': 'non_compliant',
|
|
'notes': 'Modifié par auteur',
|
|
'nature': 'detailed'
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(data['success'])
|
|
|
|
insp.refresh_from_db()
|
|
self.assertEqual(insp.result_status, 'non_compliant')
|
|
self.assertEqual(insp.notes, 'Modifié par auteur')
|
|
self.assertEqual(insp.nature, 'detailed')
|
|
self.assertGreaterEqual(insp.updated_at, initial_updated_at)
|
|
|
|
def test_update_inspection_api_with_photos(self):
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from inspections.models import InspectionDocument
|
|
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
notes="Inspection sans photo initiale"
|
|
)
|
|
self.assertEqual(insp.documents.count(), 0)
|
|
|
|
photo1 = SimpleUploadedFile("photo1.jpg", b"fake_jpeg_content_1", content_type="image/jpeg")
|
|
photo2 = SimpleUploadedFile("photo2.jpg", b"fake_jpeg_content_2", content_type="image/jpeg")
|
|
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('inspections:update_inspection_api', kwargs={'inspection_id': insp.id})
|
|
response = self.client.post(url, {
|
|
'result_status': 'non_compliant',
|
|
'notes': 'Ajout de photos lors de la modification',
|
|
'photos': [photo1, photo2]
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(data['success'])
|
|
|
|
insp.refresh_from_db()
|
|
self.assertEqual(insp.documents.count(), 2)
|
|
doc_names = [d.filename for d in insp.documents.all()]
|
|
self.assertTrue(any("photo1" in name for name in doc_names))
|
|
self.assertTrue(any("photo2" in name for name in doc_names))
|
|
self.assertTrue(all(d.is_image for d in insp.documents.all()))
|
|
|
|
def test_update_inspection_api_forbidden(self):
|
|
other_user = User.objects.create_user(
|
|
username='other_user_stranger',
|
|
password='password123'
|
|
)
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
notes="Original note"
|
|
)
|
|
|
|
self.client.login(username='other_user_stranger', password='password123')
|
|
url = reverse('inspections:update_inspection_api', kwargs={'inspection_id': insp.id})
|
|
response = self.client.post(url, {
|
|
'result_status': 'damaged',
|
|
'notes': 'Hacked note'
|
|
})
|
|
self.assertEqual(response.status_code, 403)
|
|
data = response.json()
|
|
self.assertFalse(data['success'])
|
|
|
|
insp.refresh_from_db()
|
|
self.assertEqual(insp.result_status, 'compliant')
|
|
self.assertEqual(insp.notes, 'Original note')
|
|
|
|
def test_update_parking_spot_inspection_api(self):
|
|
from inspections.models import ParkingSpotInspection
|
|
ps_insp = ParkingSpotInspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
notes="Initial parking inspection",
|
|
verified_standard_stand_count=5,
|
|
verified_cargo_stand_count=2,
|
|
verified_marking_quality='good'
|
|
)
|
|
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('inspections:update_inspection_api', kwargs={'inspection_id': ps_insp.id})
|
|
response = self.client.post(url, {
|
|
'result_status': 'partially_compliant',
|
|
'verified_standard_stand_count': '10',
|
|
'verified_cargo_stand_count': '4',
|
|
'verified_drop_off_stand_count': '1',
|
|
'verified_has_scooter_drop_zone': 'true',
|
|
'verified_marking_quality': 'damaged',
|
|
'apply_physical_counts_to_asset': 'true'
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(data['success'])
|
|
|
|
ps_insp.refresh_from_db()
|
|
self.assertEqual(ps_insp.result_status, 'partially_compliant')
|
|
self.assertEqual(ps_insp.verified_standard_stand_count, 10)
|
|
self.assertEqual(ps_insp.verified_cargo_stand_count, 4)
|
|
self.assertEqual(ps_insp.verified_drop_off_stand_count, 1)
|
|
self.assertTrue(ps_insp.verified_has_scooter_drop_zone)
|
|
self.assertEqual(ps_insp.verified_marking_quality, 'damaged')
|
|
|
|
def test_delete_inspection_document_api(self):
|
|
from inspections.models import InspectionDocument
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant'
|
|
)
|
|
dummy_file = SimpleUploadedFile("test_doc.txt", b"file content", content_type="text/plain")
|
|
doc = InspectionDocument.objects.create(
|
|
inspection=insp,
|
|
file=dummy_file
|
|
)
|
|
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('inspections:delete_inspection_document_api', kwargs={'document_id': doc.id})
|
|
response = self.client.post(url)
|
|
self.assertFalse(InspectionDocument.objects.filter(id=doc.id).exists())
|
|
|
|
def test_inspections_list_thematic_filter(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
|
|
# Create inspections on both water and parking
|
|
insp_water = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.user,
|
|
result_status='compliant'
|
|
)
|
|
insp_parking = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
# Mobile list test
|
|
url_mobile = reverse('mobile:inspections_list_mobile')
|
|
resp_water_m = self.client.get(url_mobile, {'thematic': 'water', 'tab': 'all'})
|
|
self.assertEqual(resp_water_m.status_code, 200)
|
|
inspections_water = list(resp_water_m.context['inspections'])
|
|
self.assertIn(insp_water, inspections_water)
|
|
self.assertNotIn(insp_parking, inspections_water)
|
|
|
|
resp_parking_m = self.client.get(url_mobile, {'thematic': 'parking', 'tab': 'all'})
|
|
self.assertEqual(resp_parking_m.status_code, 200)
|
|
inspections_parking = list(resp_parking_m.context['inspections'])
|
|
self.assertIn(insp_parking, inspections_parking)
|
|
self.assertNotIn(insp_water, inspections_parking)
|
|
|
|
# Desktop GeoJSON test
|
|
url_geojson = reverse('inspections:inspections_geojson_get')
|
|
resp_water_g = self.client.get(url_geojson, {'thematic': 'water'})
|
|
self.assertEqual(resp_water_g.status_code, 200)
|
|
feat_ids_water = [f['properties']['id'] for f in resp_water_g.json()['features']]
|
|
self.assertIn(insp_water.id, feat_ids_water)
|
|
self.assertNotIn(insp_parking.id, feat_ids_water)
|
|
|
|
def test_toggle_cancel_inspection_api(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant'
|
|
)
|
|
self.assertFalse(insp.is_cancelled)
|
|
|
|
# Cancel inspection
|
|
url = reverse('inspections:toggle_cancel_inspection_api', kwargs={'inspection_id': insp.id})
|
|
resp = self.client.post(url, {'action': 'cancel'})
|
|
self.assertEqual(resp.status_code, 200)
|
|
data = resp.json()
|
|
self.assertTrue(data['success'])
|
|
self.assertTrue(data['is_cancelled'])
|
|
self.assertEqual(data['result_status'], 'cancelled')
|
|
|
|
insp.refresh_from_db()
|
|
self.assertTrue(insp.is_cancelled)
|
|
self.assertEqual(insp.status_badge_class, 'bg-secondary')
|
|
|
|
# Restore inspection
|
|
resp_restore = self.client.post(url, {'action': 'restore'})
|
|
self.assertEqual(resp_restore.status_code, 200)
|
|
data_restore = resp_restore.json()
|
|
self.assertTrue(data_restore['success'])
|
|
self.assertFalse(data_restore['is_cancelled'])
|
|
self.assertEqual(data_restore['result_status'], 'compliant')
|
|
|
|
insp.refresh_from_db()
|
|
self.assertFalse(insp.is_cancelled)
|
|
self.assertEqual(insp.result_status, 'compliant')
|
|
|
|
def test_toggle_cancel_inspection_permissions(self):
|
|
other_user = User.objects.create_user(
|
|
username='stranger_user',
|
|
password='password123'
|
|
)
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
url = reverse('inspections:toggle_cancel_inspection_api', kwargs={'inspection_id': insp.id})
|
|
|
|
# Stranger cannot cancel
|
|
self.client.login(username='stranger_user', password='password123')
|
|
resp = self.client.post(url, {'action': 'cancel'})
|
|
self.assertEqual(resp.status_code, 403)
|
|
insp.refresh_from_db()
|
|
self.assertEqual(insp.result_status, 'compliant')
|
|
|
|
# Superuser can cancel
|
|
other_user.is_superuser = True
|
|
other_user.save()
|
|
resp_admin = self.client.post(url, {'action': 'cancel'})
|
|
self.assertEqual(resp_admin.status_code, 200)
|
|
insp.refresh_from_db()
|
|
self.assertEqual(insp.result_status, 'cancelled')
|
|
|
|
def test_asset_compliance_recalculation_on_cancellation(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
|
|
# 1st inspection (compliant)
|
|
date1 = timezone.now() - timezone.timedelta(days=2)
|
|
insp1 = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
inspection_date=date1,
|
|
result_status='compliant',
|
|
apply_changes_to_asset=True
|
|
)
|
|
self.spot.refresh_from_db()
|
|
self.assertEqual(self.spot.inspection_status, 'compliant')
|
|
self.assertTrue(self.spot.is_compliant)
|
|
|
|
# 2nd inspection (damaged)
|
|
date2 = timezone.now()
|
|
insp2 = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
inspection_date=date2,
|
|
result_status='damaged',
|
|
notes="Arceau cassé",
|
|
apply_changes_to_asset=True
|
|
)
|
|
self.spot.refresh_from_db()
|
|
self.assertEqual(self.spot.inspection_status, 'damaged')
|
|
self.assertFalse(self.spot.is_compliant)
|
|
self.assertEqual(self.spot.non_compliance_reason, "Arceau cassé")
|
|
|
|
# Cancel 2nd inspection -> asset should fall back to 1st inspection (compliant)
|
|
url2 = reverse('inspections:toggle_cancel_inspection_api', kwargs={'inspection_id': insp2.id})
|
|
self.client.post(url2, {'action': 'cancel'})
|
|
|
|
self.spot.refresh_from_db()
|
|
self.assertEqual(self.spot.inspection_status, 'compliant')
|
|
self.assertTrue(self.spot.is_compliant)
|
|
self.assertIsNone(self.spot.non_compliance_reason)
|
|
self.assertEqual(self.spot.last_inspection_date, date1)
|
|
|
|
# Cancel 1st inspection -> no active inspections remain, reset to default
|
|
url1 = reverse('inspections:toggle_cancel_inspection_api', kwargs={'inspection_id': insp1.id})
|
|
self.client.post(url1, {'action': 'cancel'})
|
|
|
|
self.spot.refresh_from_db()
|
|
self.assertEqual(self.spot.inspection_status, 'unknown')
|
|
self.assertIsNone(self.spot.last_inspection_date)
|
|
|
|
def test_desktop_inspections_list_view(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('inspections:inspections_list')
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inspections/inspections_index.html')
|
|
self.assertIn('thematic_choices', response.context)
|
|
self.assertIn('result_status_choices', response.context)
|
|
self.assertIn('saved_sort', response.context)
|
|
|
|
def test_mobile_inspections_list_view(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
response = self.client.get(reverse('mobile:inspections_list_mobile'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inspections/inspections_list_mobile.html')
|
|
|
|
def test_inspections_geojson_get_endpoint(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
insp1 = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
notes='Test note search query'
|
|
)
|
|
url = reverse('inspections:inspections_geojson_get')
|
|
|
|
# Test basic GET
|
|
resp = self.client.get(url)
|
|
self.assertEqual(resp.status_code, 200)
|
|
data = resp.json()
|
|
self.assertEqual(data['type'], 'FeatureCollection')
|
|
self.assertTrue(any(f['properties']['id'] == insp1.id for f in data['features']))
|
|
|
|
# Test thematic filter
|
|
resp_water = self.client.get(url, {'thematic': 'water'})
|
|
self.assertEqual(resp_water.status_code, 200)
|
|
data_water = resp_water.json()
|
|
self.assertFalse(any(f['properties']['id'] == insp1.id for f in data_water['features']))
|
|
|
|
# Test search filter (q)
|
|
resp_q = self.client.get(url, {'q': 'search query'})
|
|
self.assertEqual(resp_q.status_code, 200)
|
|
data_q = resp_q.json()
|
|
self.assertTrue(any(f['properties']['id'] == insp1.id for f in data_q['features']))
|
|
|
|
def test_inspections_export_excel(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.user,
|
|
result_status='compliant',
|
|
notes='Test Excel Export Note'
|
|
)
|
|
url = reverse('inspections:inspections_export_excel')
|
|
resp = self.client.get(url)
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(
|
|
resp['Content-Type'],
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
)
|
|
self.assertTrue(resp.has_header('Content-Disposition'))
|
|
self.assertIn('attachment; filename="inspections_export_', resp['Content-Disposition'])
|
|
|
|
def test_save_inspection_list_preferences(self):
|
|
self.client.login(username='inspector_user', password='password123')
|
|
url = reverse('common:save_inspection_list_preferences')
|
|
import json
|
|
payload = {
|
|
'filters': {'thematic': ['parking'], 'mine': '1'},
|
|
'sort': {'sortName': 'inspection_date', 'sortOrder': 'asc'}
|
|
}
|
|
resp = self.client.post(url, json.dumps(payload), content_type='application/json')
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertTrue(resp.json()['success'])
|
|
|
|
self.user.config.refresh_from_db()
|
|
self.assertEqual(self.user.config.inspection_list_filters, payload['filters'])
|
|
self.assertEqual(self.user.config.inspection_list_sort, payload['sort'])
|
|
|
|
|
|
class ExternalUserInspectionVisibilityTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client = Client()
|
|
|
|
# Utilisateur interne
|
|
self.internal_user = User.objects.create_user(
|
|
username='intern_user',
|
|
password='password123',
|
|
first_name='Intern',
|
|
last_name='User'
|
|
)
|
|
from common.models import UserConfig, Role, UserThematics, UserContractAccess
|
|
from contracts.models import Contract
|
|
from assets.models import Structure, StructureContract, AssetCategory
|
|
|
|
config_intern = UserConfig.objects.create(
|
|
user=self.internal_user,
|
|
is_intern=True,
|
|
limit_assets_to_contracts=False
|
|
)
|
|
|
|
# Utilisateur externe
|
|
self.external_user = User.objects.create_user(
|
|
username='extern_user',
|
|
password='password123',
|
|
first_name='Extern',
|
|
last_name='User'
|
|
)
|
|
self.external_config = UserConfig.objects.create(
|
|
user=self.external_user,
|
|
is_intern=False,
|
|
limit_assets_to_contracts=True
|
|
)
|
|
role_ext, _ = Role.objects.get_or_create(name='external_manager')
|
|
self.external_config.roles.add(role_ext)
|
|
|
|
# Thématiques
|
|
self.thematic_parking, _ = Thematic.objects.get_or_create(
|
|
code='parking',
|
|
defaults={'name_fr': 'Stationnement'}
|
|
)
|
|
self.thematic_water, _ = Thematic.objects.get_or_create(
|
|
code='water',
|
|
defaults={'name_fr': 'Gestion de l\'Eau'}
|
|
)
|
|
self.thematic_structures, _ = Thematic.objects.get_or_create(
|
|
code='structures',
|
|
defaults={'name_fr': 'Ouvrages d\'art'}
|
|
)
|
|
|
|
# L'externe a accès aux assets Parking et Structures, mais PAS à Water
|
|
UserThematics.objects.create(
|
|
user_config=self.external_config,
|
|
thematic=self.thematic_parking,
|
|
can_view_assets=True,
|
|
can_view_interventions=True
|
|
)
|
|
UserThematics.objects.create(
|
|
user_config=self.external_config,
|
|
thematic=self.thematic_structures,
|
|
can_view_assets=True,
|
|
can_view_interventions=True
|
|
)
|
|
UserThematics.objects.create(
|
|
user_config=self.external_config,
|
|
thematic=self.thematic_water,
|
|
can_view_assets=False,
|
|
can_view_interventions=False
|
|
)
|
|
|
|
# Contrats
|
|
from contracts.models import Company, Contract
|
|
self.company = Company.objects.create(name="Company Ext Test")
|
|
today = timezone.now().date()
|
|
self.contract_a = Contract.objects.create(
|
|
company=self.company,
|
|
contract_number="CTR-EXT-A",
|
|
start_date=today,
|
|
end_date=today + timezone.timedelta(days=365),
|
|
is_active=True
|
|
)
|
|
self.contract_a.thematics.add(self.thematic_structures)
|
|
|
|
self.contract_b = Contract.objects.create(
|
|
company=self.company,
|
|
contract_number="CTR-EXT-B",
|
|
start_date=today,
|
|
end_date=today + timezone.timedelta(days=365),
|
|
is_active=True
|
|
)
|
|
self.contract_b.thematics.add(self.thematic_structures)
|
|
|
|
# L'externe a accès au contrat A mais pas au B
|
|
uca_a = UserContractAccess.objects.create(
|
|
user_config=self.external_config,
|
|
contract=self.contract_a,
|
|
can_view_assets=True,
|
|
can_view_interventions=True
|
|
)
|
|
from common.models import UserContractStatusPermission
|
|
UserContractStatusPermission.objects.create(
|
|
user_contract=uca_a,
|
|
status='in_progress',
|
|
can_view=True
|
|
)
|
|
|
|
# Assets
|
|
self.spot = ParkingSpot.objects.create(name_fr="Emplacement Ext")
|
|
self.spot_ct = ContentType.objects.get_for_model(ParkingSpot)
|
|
|
|
self.rwiz = NatureRWIZ.objects.create(name_fr="Bassin Water")
|
|
self.rwiz_ct = ContentType.objects.get_for_model(NatureRWIZ)
|
|
|
|
self.struct_a = Structure.objects.create(code="STR-A", name_fr="Pont A")
|
|
StructureContract.objects.create(structure=self.struct_a, contract=self.contract_a, status='active')
|
|
self.struct_ct = ContentType.objects.get_for_model(Structure)
|
|
|
|
self.struct_b = Structure.objects.create(code="STR-B", name_fr="Pont B")
|
|
StructureContract.objects.create(structure=self.struct_b, contract=self.contract_b, status='active')
|
|
|
|
# Interventions
|
|
self.interv_a = Intervention.objects.create(
|
|
title="Intervention Contrat A",
|
|
contract=self.contract_a,
|
|
thematic=self.thematic_structures,
|
|
status="in_progress"
|
|
)
|
|
self.interv_b = Intervention.objects.create(
|
|
title="Intervention Contrat B",
|
|
contract=self.contract_b,
|
|
thematic=self.thematic_structures,
|
|
status="in_progress"
|
|
)
|
|
|
|
def test_external_user_thematic_filtering(self):
|
|
from inspections.permissions import can_view_inspection, filter_viewable_inspections_for_user
|
|
|
|
insp_parking = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
insp_water = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
# L'externe peut voir le parking (can_view_assets=True sur parking)
|
|
self.assertTrue(can_view_inspection(self.external_user, insp_parking))
|
|
# L'externe ne peut pas voir water (can_view_assets=False sur water)
|
|
self.assertFalse(can_view_inspection(self.external_user, insp_water))
|
|
|
|
# QuerySet filtering
|
|
qs = filter_viewable_inspections_for_user(self.external_user)
|
|
self.assertIn(insp_parking, qs)
|
|
self.assertNotIn(insp_water, qs)
|
|
|
|
def test_external_user_contract_filtering(self):
|
|
from inspections.permissions import can_view_inspection, filter_viewable_inspections_for_user
|
|
|
|
insp_struct_a = Inspection.objects.create(
|
|
asset_content_type=self.struct_ct,
|
|
asset_object_id=self.struct_a.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
insp_struct_b = Inspection.objects.create(
|
|
asset_content_type=self.struct_ct,
|
|
asset_object_id=self.struct_b.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
# L'externe a accès au contrat A -> voit insp_struct_a
|
|
self.assertTrue(can_view_inspection(self.external_user, insp_struct_a))
|
|
# L'externe n'a pas accès au contrat B -> ne voit pas insp_struct_b
|
|
self.assertFalse(can_view_inspection(self.external_user, insp_struct_b))
|
|
|
|
# QuerySet filtering
|
|
qs = filter_viewable_inspections_for_user(self.external_user)
|
|
self.assertIn(insp_struct_a, qs)
|
|
self.assertNotIn(insp_struct_b, qs)
|
|
|
|
def test_external_user_intervention_linked_inspection(self):
|
|
from inspections.permissions import can_view_inspection, filter_viewable_inspections_for_user
|
|
|
|
# Inspection sur asset d'une thématique non autorisée, mais liée à une intervention autorisée (mission)
|
|
insp_mission = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.internal_user,
|
|
mission_intervention=self.interv_a,
|
|
result_status='compliant'
|
|
)
|
|
# Inspection liée à une intervention non autorisée
|
|
insp_mission_unauth = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.internal_user,
|
|
mission_intervention=self.interv_b,
|
|
result_status='compliant'
|
|
)
|
|
|
|
self.assertTrue(can_view_inspection(self.external_user, insp_mission))
|
|
self.assertFalse(can_view_inspection(self.external_user, insp_mission_unauth))
|
|
|
|
qs = filter_viewable_inspections_for_user(self.external_user)
|
|
self.assertIn(insp_mission, qs)
|
|
self.assertNotIn(insp_mission_unauth, qs)
|
|
|
|
def test_external_user_is_inspector_always_visible(self):
|
|
from inspections.permissions import can_view_inspection, filter_viewable_inspections_for_user
|
|
|
|
# Inspection réalisée par l'externe lui-même sur une thématique normalement restreinte
|
|
insp_by_extern = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.external_user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
self.assertTrue(can_view_inspection(self.external_user, insp_by_extern))
|
|
qs = filter_viewable_inspections_for_user(self.external_user)
|
|
self.assertIn(insp_by_extern, qs)
|
|
|
|
def test_external_user_inspections_list_view(self):
|
|
insp_parking = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
insp_water = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
self.client.login(username='extern_user', password='password123')
|
|
# Test mobile
|
|
url_mobile = reverse('mobile:inspections_list_mobile')
|
|
response = self.client.get(url_mobile, {'tab': 'all'})
|
|
self.assertEqual(response.status_code, 200)
|
|
inspections = list(response.context['inspections'])
|
|
self.assertIn(insp_parking, inspections)
|
|
self.assertNotIn(insp_water, inspections)
|
|
|
|
# Test desktop GeoJSON
|
|
url_geojson = reverse('inspections:inspections_geojson_get')
|
|
resp_geojson = self.client.get(url_geojson)
|
|
self.assertEqual(resp_geojson.status_code, 200)
|
|
feat_ids = [f['properties']['id'] for f in resp_geojson.json()['features']]
|
|
self.assertIn(insp_parking.id, feat_ids)
|
|
self.assertNotIn(insp_water.id, feat_ids)
|
|
|
|
def test_external_user_inspection_detail_api(self):
|
|
insp_parking = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
insp_water = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
self.client.login(username='extern_user', password='password123')
|
|
|
|
# Inspection autorisée -> 200
|
|
url_ok = reverse('inspections:get_inspection_detail_api', kwargs={'inspection_id': insp_parking.id})
|
|
res_ok = self.client.get(url_ok)
|
|
self.assertEqual(res_ok.status_code, 200)
|
|
self.assertTrue(res_ok.json()['success'])
|
|
|
|
# Inspection non autorisée -> 403
|
|
url_forbidden = reverse('inspections:get_inspection_detail_api', kwargs={'inspection_id': insp_water.id})
|
|
res_forbidden = self.client.get(url_forbidden)
|
|
self.assertEqual(res_forbidden.status_code, 403)
|
|
self.assertFalse(res_forbidden.json()['success'])
|
|
|
|
def test_guard_inspection_document(self):
|
|
from inspections.models import InspectionDocument
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from inspections.apps import InspectionsConfig
|
|
|
|
insp_parking = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
insp_water = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.internal_user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
dummy_file = SimpleUploadedFile("doc.txt", b"content", content_type="text/plain")
|
|
doc_parking = InspectionDocument.objects.create(inspection=insp_parking, file=dummy_file)
|
|
doc_water = InspectionDocument.objects.create(inspection=insp_water, file=dummy_file)
|
|
|
|
from inspections.permissions import can_view_inspection
|
|
self.assertTrue(can_view_inspection(self.external_user, doc_parking.inspection))
|
|
self.assertFalse(can_view_inspection(self.external_user, doc_water.inspection))
|
|
|
|
def test_internal_user_sees_all_inspections(self):
|
|
from inspections.permissions import can_view_inspection, filter_viewable_inspections_for_user
|
|
|
|
insp_parking = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=self.external_user,
|
|
result_status='compliant'
|
|
)
|
|
insp_water = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=self.external_user,
|
|
result_status='compliant'
|
|
)
|
|
insp_struct_b = Inspection.objects.create(
|
|
asset_content_type=self.struct_ct,
|
|
asset_object_id=self.struct_b.id,
|
|
inspector=self.external_user,
|
|
result_status='compliant'
|
|
)
|
|
|
|
self.assertTrue(can_view_inspection(self.internal_user, insp_parking))
|
|
self.assertTrue(can_view_inspection(self.internal_user, insp_water))
|
|
self.assertTrue(can_view_inspection(self.internal_user, insp_struct_b))
|
|
|
|
qs = filter_viewable_inspections_for_user(self.internal_user)
|
|
self.assertIn(insp_parking, qs)
|
|
self.assertIn(insp_water, qs)
|
|
self.assertIn(insp_struct_b, qs)
|
|
|
|
def test_signpanel_inspection_creation_and_asset_sync(self):
|
|
from assets.models import SignPanel, SignPanelType, SignPole, SignStreet
|
|
from inspections.models import SignPanelInspection
|
|
|
|
street = SignStreet.objects.create(code='STR-TEST-1', name_fr='Rue Test Signalisation')
|
|
type_c1, _ = SignPanelType.objects.get_or_create(code='C1', defaults={'name_fr': 'Sens interdit'})
|
|
type_d7, _ = SignPanelType.objects.get_or_create(code='D7', defaults={'name_fr': 'Piste cyclable'})
|
|
|
|
pole = SignPole.objects.create(code='P-TEST-1', street=street, status='active')
|
|
panel = SignPanel.objects.create(
|
|
pole=pole,
|
|
code='PAN-TEST-1',
|
|
signpanel_type=type_c1,
|
|
signpanel_text='Sauf bus',
|
|
inspection_status='compliant',
|
|
status='active'
|
|
)
|
|
panel_ct = ContentType.objects.get_for_model(SignPanel)
|
|
|
|
# 1. Inspection avec modification du type et de la propreté (dégradé -> is_compliant=False)
|
|
insp = SignPanelInspection.objects.create(
|
|
asset_content_type=panel_ct,
|
|
asset_object_id=panel.id,
|
|
inspector=self.internal_user,
|
|
result_status='damaged',
|
|
verified_signpanel_type=type_d7,
|
|
verified_signpanel_text='Sauf vélos',
|
|
verified_cleanliness='vandalized',
|
|
ai_detected_code='D7',
|
|
ai_confidence=0.97,
|
|
apply_sign_updates_to_asset=True
|
|
)
|
|
|
|
panel.refresh_from_db()
|
|
self.assertEqual(panel.signpanel_type, type_d7)
|
|
self.assertEqual(panel.signpanel_text, 'Sauf vélos')
|
|
self.assertFalse(panel.is_compliant)
|
|
self.assertEqual(insp.detailed_inspection, insp)
|
|
self.assertIn('Tagué', insp.details_summary)
|
|
|
|
# 2. Test API create_inspection_api pour SignPanel
|
|
self.client.force_login(self.internal_user)
|
|
response = self.client.post(reverse('inspections:create_inspection_api'), {
|
|
'content_type_id': panel_ct.id,
|
|
'object_id': panel.id,
|
|
'result_status': 'missing',
|
|
'verified_cleanliness': 'missing',
|
|
'verified_signpanel_type_id': type_c1.id,
|
|
'verified_signpanel_text': 'Texte test',
|
|
'apply_changes_to_asset': 'true'
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
panel.refresh_from_db()
|
|
self.assertEqual(panel.status, 'inactive')
|
|
self.assertFalse(panel.is_compliant)
|
|
|
|
# 3. Test API get_inspection_detail_api
|
|
created_insp = SignPanelInspection.objects.filter(asset_object_id=panel.id).order_by('-id').first()
|
|
detail_res = self.client.get(reverse('inspections:get_inspection_detail_api', kwargs={'inspection_id': created_insp.id}))
|
|
self.assertEqual(detail_res.status_code, 200)
|
|
data = detail_res.json()
|
|
self.assertTrue(data['is_signpanel'])
|
|
|
|
|
|
class ViewerInspectionPermissionsAndGeojsonTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client = Client()
|
|
self.viewer_user = User.objects.create_user(username='viewer_user', password='password123')
|
|
from common.models import UserConfig, Role, UserThematics
|
|
viewer_role, _ = Role.objects.get_or_create(name='viewer')
|
|
self.viewer_config = UserConfig.objects.create(user=self.viewer_user, is_intern=False)
|
|
self.viewer_config.roles.add(viewer_role)
|
|
|
|
self.water_thematic, _ = Thematic.objects.get_or_create(
|
|
code='water',
|
|
defaults={'name_fr': "Gestion de l'Eau"}
|
|
)
|
|
self.parking_thematic, _ = Thematic.objects.get_or_create(
|
|
code='parking',
|
|
defaults={'name_fr': "Stationnement"}
|
|
)
|
|
|
|
# Viewer only has access to water
|
|
UserThematics.objects.create(
|
|
user_config=self.viewer_config,
|
|
thematic=self.water_thematic,
|
|
can_view_assets=True,
|
|
can_view_interventions=True,
|
|
can_view_projects=True
|
|
)
|
|
|
|
# Create assets
|
|
from assets.models import NatureRWIZ, ParkingSpot
|
|
from django.contrib.gis.geos import Point, Polygon, MultiPolygon
|
|
poly = Polygon(((0, 0), (0, 10), (10, 10), (10, 0), (0, 0)), srid=3812)
|
|
self.rwiz = NatureRWIZ.objects.create(
|
|
code='RWIZ-001',
|
|
geom=MultiPolygon(poly, srid=3812)
|
|
)
|
|
self.spot = ParkingSpot.objects.create(
|
|
code='SPOT-001',
|
|
geom=poly
|
|
)
|
|
self.rwiz_ct = ContentType.objects.get_for_model(NatureRWIZ)
|
|
self.spot_ct = ContentType.objects.get_for_model(ParkingSpot)
|
|
|
|
inspector = User.objects.create_user(username='insp_user', password='password123')
|
|
self.insp_water = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=inspector,
|
|
result_status='compliant'
|
|
)
|
|
self.insp_parking = Inspection.objects.create(
|
|
asset_content_type=self.spot_ct,
|
|
asset_object_id=self.spot.id,
|
|
inspector=inspector,
|
|
result_status='compliant'
|
|
)
|
|
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
dummy_file = SimpleUploadedFile("doc.txt", b"content", content_type="text/plain")
|
|
InspectionDocument.objects.create(inspection=self.insp_water, file=dummy_file)
|
|
|
|
def test_viewer_thematic_permissions(self):
|
|
from inspections.permissions import can_view_inspection, filter_viewable_inspections_for_user
|
|
|
|
self.assertTrue(can_view_inspection(self.viewer_user, self.insp_water))
|
|
self.assertFalse(can_view_inspection(self.viewer_user, self.insp_parking))
|
|
|
|
qs = filter_viewable_inspections_for_user(self.viewer_user)
|
|
self.assertIn(self.insp_water, qs)
|
|
self.assertNotIn(self.insp_parking, qs)
|
|
|
|
def test_inspections_geojson_for_viewer(self):
|
|
self.client.login(username='viewer_user', password='password123')
|
|
url = reverse('inspections:inspections_geojson')
|
|
|
|
# Test GET response correctness
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertEqual(data['type'], 'FeatureCollection')
|
|
|
|
feature_ids = [f['id'] for f in data['features']]
|
|
self.assertIn(self.insp_water.id, feature_ids)
|
|
self.assertNotIn(self.insp_parking.id, feature_ids)
|
|
|
|
water_feature = next(f for f in data['features'] if f['id'] == self.insp_water.id)
|
|
self.assertEqual(water_feature['properties']['documents_count'], 1)
|
|
self.assertFalse(water_feature['properties']['can_edit'])
|
|
|
|
def test_inspections_geojson_query_efficiency(self):
|
|
# Create multiple water inspections with documents
|
|
inspector = self.insp_water.inspector
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from django.test.utils import CaptureQueriesContext
|
|
from django.db import connection
|
|
|
|
for i in range(5):
|
|
insp = Inspection.objects.create(
|
|
asset_content_type=self.rwiz_ct,
|
|
asset_object_id=self.rwiz.id,
|
|
inspector=inspector,
|
|
result_status='compliant'
|
|
)
|
|
dummy = SimpleUploadedFile(f"doc_{i}.txt", b"content", content_type="text/plain")
|
|
InspectionDocument.objects.create(inspection=insp, file=dummy)
|
|
|
|
self.client.login(username='viewer_user', password='password123')
|
|
url = reverse('inspections:inspections_geojson')
|
|
|
|
# Ensure query count doesn't blow up (O(1) database queries instead of O(N))
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
# Number of queries should be bounded and independent of inspection count
|
|
# (O(1) queries instead of O(N), unaffected by inspection count)
|
|
self.assertLessEqual(len(ctx), 28)
|
|
|