import json from django.test import TestCase from django.contrib.auth import get_user_model from django.urls import reverse from django.contrib.gis.geos import Point, Polygon from common.models import UserConfig, Role from assets.models import NatureRWIAsset from interventions.models import Intervention class GIEPAssetsAJAXTests(TestCase): def setUp(self): from django.db import connection with connection.cursor() as cursor: cursor.execute("CREATE EXTENSION IF NOT EXISTS unaccent;") cursor.execute(""" CREATE OR REPLACE FUNCTION public.unaccent_immutable(text) RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $function$ SELECT public.unaccent('public.unaccent', $1) $function$ """) User = get_user_model() self.user = User.objects.create_user(username='giepadmin', password='pwd') self.role_admin, _ = Role.objects.get_or_create(name='admin') self.user_config = UserConfig.objects.create(user=self.user, is_intern=True) self.user_config.roles.add(self.role_admin) # Create correct geospatial polygon geometries by transforming to SRID 3812 geom1 = Polygon(((4.3570, 50.8475), (4.3570, 50.8479), (4.3574, 50.8479), (4.3574, 50.8475), (4.3570, 50.8475)), srid=4326) geom1.transform(3812) geom2 = Polygon(((4.3640, 50.8520), (4.3640, 50.8524), (4.3644, 50.8524), (4.3644, 50.8520), (4.3640, 50.8520)), srid=4326) geom2.transform(3812) self.asset1 = NatureRWIAsset.objects.create( code="BI-01", status="active", structure_type="evc", geom=geom1, lon=4.3572, lat=50.8477, comment="Bouche d'injection EVC" ) self.asset2 = NatureRWIAsset.objects.create( code="MA-02", status="active", structure_type="oed", geom=geom2, lon=4.3642, lat=50.8522, comment="Massif drainant OED" ) # Removed asset should be excluded geom_removed = Polygon(((4.3570, 50.8475), (4.3570, 50.8479), (4.3574, 50.8479), (4.3574, 50.8475), (4.3570, 50.8475)), srid=4326) geom_removed.transform(3812) self.asset_removed = NatureRWIAsset.objects.create( code="BI-REMOVED", status="removed", structure_type="evc", geom=geom_removed, lon=4.3572, lat=50.8477 ) # Create intervention self.intervention = Intervention.objects.create( title="Inspection GIEP", status="to_be_processed", lon=4.3572, lat=50.8477 ) self.client.login(username='giepadmin', password='pwd') def test_autocomplete_giep_assets(self): url = reverse('interventions:autocomplete_giep_assets', kwargs={'intervention_id': self.intervention.id}) # Test query for 'BI' resp = self.client.get(url, {'q': 'BI'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(len(data), 1) self.assertEqual(data[0]['code'], 'BI-01') # Test query for 'removed' should be empty resp = self.client.get(url, {'q': 'REMOVED'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(len(data), 0) def test_nearby_giep_assets(self): url = reverse('interventions:nearby_giep_assets', kwargs={'intervention_id': self.intervention.id}) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) # Should return both active assets, closest first (BI-01 at distance ~0) self.assertTrue(len(data) >= 2) self.assertEqual(data[0]['code'], 'BI-01') self.assertEqual(data[1]['code'], 'MA-02') def test_nearby_giep_assets_geojson(self): url = reverse('interventions:nearby_giep_assets_geojson', kwargs={'intervention_id': self.intervention.id}) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) self.assertEqual(data['type'], 'FeatureCollection') self.assertTrue(len(data['features']) >= 2) # Check GeoJSON properties codes = [feat['properties']['code'] for feat in data['features']] self.assertIn('BI-01', codes) self.assertIn('MA-02', codes) self.assertNotIn('BI-REMOVED', codes) def test_autocomplete_giep_locations(self): from assets.models import NatureRWIZ # Create a GIEP location (NatureRWIZ) rwiz = NatureRWIZ.objects.create( code="RWIZ-TEST-99", name_fr="Zone d'Infiltration Test", name_nl="Infiltratiezone Test", lon=4.35, lat=50.85 ) # Test location autocomplete view with query matching the GIEP code url = reverse('assets:locations_autocomplete') resp = self.client.get(url, {'q': 'RWIZ-TEST', 'thematic': 'water'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) # Verify it was returned self.assertTrue(len(data) >= 1) codes = [item['code'] for item in data] self.assertIn('RWIZ-TEST-99', codes) def test_autocomplete_prioritize_giep_locations(self): from assets.models import NatureRWIZ, NatureLocation # Create a GIEP location (NatureRWIZ) rwiz = NatureRWIZ.objects.create( code="RWIZ-TEST-99", name_fr="Bassin Infiltration", name_nl="Infiltratiebekken", lon=4.35, lat=50.85 ) # Create a standard nature location (NatureLocation) nat_loc = NatureLocation.objects.create( code="NAT-TEST-11", name_fr="Arbre remarquable", name_nl="Opmerkelijke boom", lon=4.35, lat=50.85 ) url = reverse('assets:locations_autocomplete') # Test without prioritize_giep (alphabetical sorting by label: "Arbre..." before "Bassin...") resp = self.client.get(url, {'q': 'TEST', 'thematic': 'nature,water'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) codes = [item['code'] for item in data] self.assertEqual(codes[0], 'NAT-TEST-11') self.assertEqual(codes[1], 'RWIZ-TEST-99') # Test with prioritize=giep (GIEP/NatureRWIZ sorted first) resp = self.client.get(url, {'q': 'TEST', 'thematic': 'nature,water', 'prioritize': 'giep'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) codes = [item['code'] for item in data] self.assertEqual(codes[0], 'RWIZ-TEST-99') self.assertEqual(codes[1], 'NAT-TEST-11') # Test with prioritize=naturerwiz (GIEP/NatureRWIZ sorted first) resp = self.client.get(url, {'q': 'TEST', 'thematic': 'nature,water', 'prioritize': 'naturerwiz'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) codes = [item['code'] for item in data] self.assertEqual(codes[0], 'RWIZ-TEST-99') self.assertEqual(codes[1], 'NAT-TEST-11') # Test with prioritize=NAT_RWI (GIEP/NatureRWIZ sorted first, simulating production GIEP category code) resp = self.client.get(url, {'q': 'TEST', 'thematic': 'nature,water', 'prioritize': 'NAT_RWI'}) self.assertEqual(resp.status_code, 200) data = json.loads(resp.content) codes = [item['code'] for item in data] self.assertEqual(codes[0], 'RWIZ-TEST-99') self.assertEqual(codes[1], 'NAT-TEST-11') def test_zirw_detail_view_and_hierarchy(self): from assets.models import NatureRWIZ, NatureRWIAsset from django.contrib.gis.geos import Polygon, MultiPolygon # Create a GIEP Zone polygon that intersects with asset1 (geom1) # geom1 is around (4.3570, 50.8475) to (4.3574, 50.8479) zone_poly = Polygon(((4.3568, 50.8474), (4.3568, 50.8480), (4.3576, 50.8480), (4.3576, 50.8474), (4.3568, 50.8474)), srid=4326) zone_poly.transform(3812) zone_geom = MultiPolygon(zone_poly, srid=3812) rwiz = NatureRWIZ.objects.create( code="RWIZ-TEST-DETAIL", name_fr="Zone d'Infiltration Test Detail", name_nl="Infiltratiezone Test Detail", geom=zone_geom, lon=4.3572, lat=50.8477 ) # Create a point asset that intersects with both the zone and the polygon asset1 point_geom = Polygon(((4.3571, 50.8476), (4.3571, 50.8478), (4.3573, 50.8478), (4.3573, 50.8476), (4.3571, 50.8476)), srid=4326) point_geom.transform(3812) child_point_asset = NatureRWIAsset.objects.create( code="BI-CHILD-01", status="active", structure_type="bouche injection", geometry_type="point", geom=point_geom, lon=4.3572, lat=50.8477, comment="Bouche d'injection child" ) # Ensure asset1 is categorized as a polygon self.asset1.geometry_type = "polygon" self.asset1.save() # Check model methods active_assets = list(rwiz.get_active_assets()) self.assertIn(self.asset1, active_assets) self.assertIn(child_point_asset, active_assets) self.assertNotIn(self.asset2, active_assets) # Call the detail view url = reverse('assets:zirw_detail', kwargs={'location_id': rwiz.id}) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertContains(resp, "RWIZ-TEST-DETAIL") self.assertContains(resp, "BI-CHILD-01") # Test document config URL config_url = reverse('assets:zirw_document_config', kwargs={'location_id': rwiz.id}) config_resp = self.client.get(config_url) self.assertEqual(config_resp.status_code, 200) config_json = config_resp.json() self.assertIn('use_managed_documents', config_json) self.assertIn('can_add_unique_tag_documents', config_json) self.assertIn('tags', config_json) self.assertIn('folders', config_json) # Test document history URL history_url = reverse('assets:zirw_document_history') history_resp = self.client.get(f"{history_url}?location_id={rwiz.id}&tag_slug=test") self.assertEqual(history_resp.status_code, 200) history_json = history_resp.json() self.assertIn('html', history_json) class GIEPInspectionFormTests(TestCase): def setUp(self): from django.db import connection with connection.cursor() as cursor: cursor.execute("CREATE EXTENSION IF NOT EXISTS unaccent;") cursor.execute(""" CREATE OR REPLACE FUNCTION public.unaccent_immutable(text) RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $function$ SELECT public.unaccent('public.unaccent', $1) $function$ """) User = get_user_model() self.user = User.objects.create_user(username='inspectadmin', password='pwd') self.role_admin, _ = Role.objects.get_or_create(name='admin') self.user_config = UserConfig.objects.create(user=self.user, is_intern=True) self.user_config.roles.add(self.role_admin) from common.models import Thematic self.thematic, _ = Thematic.objects.get_or_create( code='water', defaults={'name_fr': 'Eau', 'name_nl': 'Water'} ) from interventions.models import InspectionConfiguration self.config_with_equip = InspectionConfiguration.objects.create( name_fr="Inspection GIEP Avec Equipement", name_nl="Inspectie GIEP Met Apparatuur", thematic=self.thematic, show_equipment=True ) self.config_no_equip = InspectionConfiguration.objects.create( name_fr="Inspection GIEP Sans Equipement", name_nl="Inspectie GIEP Zonder Apparatuur", thematic=self.thematic, show_equipment=False ) self.client.login(username='inspectadmin', password='pwd') self.client.cookies.load({'django_language': 'fr'}) def test_get_add_inspection_with_equipment(self): url = reverse('interventions:intervention_add', kwargs={'thematic_code': 'water'}) # GET with config showing equipment resp = self.client.get(url, {'inspection_config_id': self.config_with_equip.id}) self.assertEqual(resp.status_code, 200) self.assertContains(resp, "Nouvelle inspection - GIEP Avec Equipement") # Asset category field should be present but in d-none block self.assertContains(resp, "class=\"mb-3 d-none\"") # Sub-category select should have display:none !important; self.assertContains(resp, "display:none !important;") # Verify status is pre-filled as 'assigned' self.assertContains(resp, 'value="assigned" selected') # Verify "Numéro de zone" is rendered instead of "Numéro de section" self.assertContains(resp, "Numéro de zone") self.assertNotContains(resp, "Numéro de section") # Verify that the "Equipement:" summary label is omitted self.assertNotContains(resp, "Equipement:") def test_get_add_inspection_without_equipment(self): url = reverse('interventions:intervention_add', kwargs={'thematic_code': 'water'}) # GET with config hiding equipment resp = self.client.get(url, {'inspection_config_id': self.config_no_equip.id}) self.assertEqual(resp.status_code, 200) self.assertContains(resp, "Nouvelle inspection - GIEP Sans Equipement") # Verify equipment container is hidden self.assertContains(resp, "class=\"mb-3 d-none\"") # Verify "Numéro de zone" is rendered instead of "Numéro de section" self.assertContains(resp, "Numéro de zone") self.assertNotContains(resp, "Numéro de section") # Verify that the "Equipement:" summary label is omitted self.assertNotContains(resp, "Equipement:") def test_post_create_assigned_inspection_auto_assigns_user(self): from contracts.models import Company, CompanyMember, CompanyTeam company = Company.objects.create(name="Test Company") team = CompanyTeam.objects.create(name="Team Admin", company=company) member = CompanyMember.objects.create(user=self.user, name="Inspect Admin") member.teams.add(team) url = reverse('interventions:intervention_add', kwargs={'thematic_code': 'water'}) post_data = { 'inspection_config_id': self.config_no_equip.id, 'title': 'Test GIEP Inspection Title', 'status': 'assigned', 'type': 'inspection', 'maintain_type': 'preventive', 'priority': '3', 'lat': '50.8477', 'lon': '4.3572', } resp = self.client.post(url, post_data) self.assertEqual(resp.status_code, 302) # Verify intervention was created and assigned to the user created_itv = Intervention.objects.filter(title='Test GIEP Inspection Title').first() self.assertIsNotNone(created_itv) self.assertEqual(created_itv.status, 'assigned') self.assertEqual(created_itv.assigned_member, member) self.assertEqual(created_itv.assigned_team, team) def test_resolve_nature_rwiz_location(self): from assets.models import NatureRWIZ from interventions.models import InterventionLocation from interventions.utils import resolve_intervention_localisations # Create an intervention intervention = Intervention.objects.create( title="Test RWIZ Intervention", status="assigned" ) # Create NatureRWIZ location rwiz = NatureRWIZ.objects.create( code="RWIZ-006", name_fr="Bassin d'infiltration", name_nl="Infiltratiebekken" ) # Associate location to intervention from django.contrib.contenttypes.models import ContentType ct = ContentType.objects.get_for_model(NatureRWIZ) InterventionLocation.objects.create( intervention=intervention, content_type=ct, object_id=rwiz.id ) # Call resolve_intervention_localisations locs = resolve_intervention_localisations(intervention) self.assertEqual(len(locs), 1) self.assertEqual(locs[0]['name'], "RWIZ-006 - Bassin d'infiltration") self.assertIn("zirw", locs[0]['url'])