264 lines
11 KiB
Python
264 lines
11 KiB
Python
import io
|
|
import json
|
|
from django.test import TestCase
|
|
from django.contrib.auth import get_user_model
|
|
from django.urls import reverse
|
|
from openpyxl import load_workbook
|
|
|
|
from common.models import Thematic, Role, UserConfig, UserThematics
|
|
from assets.models import AssetCategory
|
|
from projects.models import Project, ProjectUserAccess
|
|
from controls.models import Control, Remark
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class ControlsIndexTests(TestCase):
|
|
def setUp(self):
|
|
# Thematics
|
|
self.thematic_a = Thematic.objects.create(
|
|
code="THEME_A",
|
|
name_fr="Voirie",
|
|
name_nl="Wegenis",
|
|
icon="bi-cone-striped"
|
|
)
|
|
self.thematic_b = Thematic.objects.create(
|
|
code="THEME_B",
|
|
name_fr="Signalisation",
|
|
name_nl="Signalisatie",
|
|
icon="bi-signpost"
|
|
)
|
|
|
|
# Categories
|
|
self.cat_a = AssetCategory.objects.create(
|
|
thematic=self.thematic_a,
|
|
code="CAT_A",
|
|
name_fr="Trottoir",
|
|
name_nl="Voetpad"
|
|
)
|
|
|
|
# Users
|
|
self.admin_user = User.objects.create_user("admin_user", password="password123")
|
|
self.admin_config = UserConfig.objects.create(user=self.admin_user, is_intern=True)
|
|
self.admin_role, _ = Role.objects.get_or_create(name="admin")
|
|
self.admin_config.roles.add(self.admin_role)
|
|
|
|
self.internal_user = User.objects.create_user("internal_user", password="password123")
|
|
self.internal_config = UserConfig.objects.create(user=self.internal_user, is_intern=True)
|
|
UserThematics.objects.create(
|
|
user_config=self.internal_config,
|
|
thematic=self.thematic_a,
|
|
can_view_interventions=True
|
|
)
|
|
|
|
self.other_user = User.objects.create_user("other_user", password="password123")
|
|
self.other_config = UserConfig.objects.create(user=self.other_user, is_intern=False)
|
|
|
|
# Project
|
|
self.project = Project.objects.create(
|
|
name="Projet Test",
|
|
code="PRJ-001",
|
|
created_by=self.admin_user
|
|
)
|
|
ProjectUserAccess.objects.create(
|
|
project=self.project,
|
|
user=self.internal_user,
|
|
can_view=True
|
|
)
|
|
|
|
# Controls
|
|
self.ctrl1 = Control.objects.create(
|
|
control_code="CTRL-0001",
|
|
description="Contrôle bordure rue Royale",
|
|
location="Rue Royale 10",
|
|
status="in_progress",
|
|
control_type="control",
|
|
control_author=self.internal_user,
|
|
project=self.project,
|
|
lon=4.36,
|
|
lat=50.85,
|
|
visibility="intern"
|
|
)
|
|
self.ctrl1.thematics.add(self.thematic_a)
|
|
self.ctrl1.categories.add(self.cat_a)
|
|
Remark.objects.create(control=self.ctrl1, description="Remarque 1")
|
|
|
|
self.ctrl2 = Control.objects.create(
|
|
control_code="CTRL-0002",
|
|
description="Constat marquage au sol",
|
|
location="Avenue Louise 20",
|
|
status="to_be_processed",
|
|
control_type="statement",
|
|
control_author=self.admin_user,
|
|
lon=4.37,
|
|
lat=50.84,
|
|
visibility="all"
|
|
)
|
|
self.ctrl2.thematics.add(self.thematic_b)
|
|
|
|
self.ctrl_private = Control.objects.create(
|
|
control_code="CTRL-0003",
|
|
description="Contrôle privé",
|
|
status="information",
|
|
control_type="meeting_report",
|
|
control_author=self.admin_user,
|
|
visibility="private"
|
|
)
|
|
|
|
def test_controls_index_page_loads(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_index"))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, "controls/controls_index.html")
|
|
self.assertIn("thematic_choices", response.context)
|
|
self.assertIn("status_choices", response.context)
|
|
self.assertIn("control_type_choices", response.context)
|
|
self.assertIn("category_choices", response.context)
|
|
self.assertIn("project_choices", response.context)
|
|
|
|
def test_controls_index_saved_preferences_redirect(self):
|
|
self.admin_config.control_list_filters = {"thematic": ["THEME_A"]}
|
|
self.admin_config.save()
|
|
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_index"))
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertIn("thematic=THEME_A", response.url)
|
|
|
|
def test_controls_geojson_endpoint(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_geojson"))
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertEqual(data["type"], "FeatureCollection")
|
|
self.assertTrue(len(data["features"]) >= 2)
|
|
|
|
# Check properties on first feature
|
|
feature = next(f for f in data["features"] if f["id"] == self.ctrl1.id)
|
|
self.assertEqual(feature["properties"]["control_code"], "CTRL-0001")
|
|
self.assertEqual(feature["properties"]["status"], "in_progress")
|
|
self.assertEqual(feature["properties"]["control_type"], "control")
|
|
self.assertEqual(feature["properties"]["project_name"], "Projet Test")
|
|
self.assertEqual(feature["properties"]["remarks_count"], 1)
|
|
self.assertEqual(feature["geometry"]["coordinates"], [4.36, 50.85])
|
|
|
|
def test_controls_geojson_filter_by_thematic(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_geojson"), {"thematic": ["THEME_A"]})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
codes = [f["properties"]["control_code"] for f in data["features"]]
|
|
self.assertIn("CTRL-0001", codes)
|
|
self.assertNotIn("CTRL-0002", codes)
|
|
|
|
def test_controls_geojson_filter_by_status_and_type(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_geojson"), {
|
|
"status": ["to_be_processed"],
|
|
"type": ["statement"]
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
codes = [f["properties"]["control_code"] for f in data["features"]]
|
|
self.assertEqual(codes, ["CTRL-0002"])
|
|
|
|
def test_controls_geojson_filter_by_search_query(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_geojson"), {"q": "Royale"})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
codes = [f["properties"]["control_code"] for f in data["features"]]
|
|
self.assertEqual(codes, ["CTRL-0001"])
|
|
|
|
def test_controls_geojson_filter_mine(self):
|
|
self.client.login(username="internal_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_geojson"), {"mine": "1"})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
codes = [f["properties"]["control_code"] for f in data["features"]]
|
|
self.assertEqual(codes, ["CTRL-0001"])
|
|
|
|
def test_controls_geojson_permissions(self):
|
|
# Internal user without access to thematic B and not author of private control
|
|
self.client.login(username="internal_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_geojson"))
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
codes = [f["properties"]["control_code"] for f in data["features"]]
|
|
self.assertIn("CTRL-0001", codes)
|
|
self.assertNotIn("CTRL-0003", codes) # private to admin_user
|
|
|
|
def test_controls_export_excel(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_export_excel"))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(
|
|
response["Content-Type"],
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
)
|
|
wb = load_workbook(filename=io.BytesIO(response.content))
|
|
ws = wb.active
|
|
self.assertIn(ws.title, ["Contrôles", "Controls"])
|
|
self.assertGreaterEqual(ws.max_row, 2) # Header + rows
|
|
|
|
def test_save_control_list_preferences(self):
|
|
self.client.login(username="internal_user", password="password123")
|
|
payload = {
|
|
"filters": {"thematic": ["THEME_A"], "mine": "1"},
|
|
"sort": {"sortName": "created_at", "sortOrder": "asc"}
|
|
}
|
|
response = self.client.post(
|
|
reverse("common:save_control_list_preferences"),
|
|
data=json.dumps(payload),
|
|
content_type="application/json"
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.internal_config.refresh_from_db()
|
|
self.assertEqual(self.internal_config.control_list_filters, payload["filters"])
|
|
self.assertEqual(self.internal_config.control_list_sort, payload["sort"])
|
|
|
|
def test_projects_autocomplete(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:projects_autocomplete"), {"q": "Test"})
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(len(data) >= 1)
|
|
self.assertEqual(data[0]["id"], self.project.id)
|
|
self.assertEqual(data[0]["name"], "Projet Test")
|
|
|
|
def test_controls_index_selected_projects_objs(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_index"), {"project": [str(self.project.id)]})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn("selected_projects_objs", response.context)
|
|
selected_objs = response.context["selected_projects_objs"]
|
|
self.assertEqual(len(selected_objs), 1)
|
|
self.assertEqual(selected_objs[0].id, self.project.id)
|
|
|
|
def test_controls_index_mobile_get(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(reverse("controls:controls_index"), {"v": "mobile"})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, "controls/controls_index_mobile.html")
|
|
self.assertIn("status_filters", response.context)
|
|
self.assertIn("type_filters", response.context)
|
|
self.assertIn("markers_json", response.context)
|
|
self.assertContains(response, "CTRL-0001")
|
|
|
|
def test_controls_index_mobile_ajax(self):
|
|
self.client.login(username="admin_user", password="password123")
|
|
response = self.client.get(
|
|
reverse("controls:controls_index"),
|
|
{"v": "mobile", "status": "in_progress"},
|
|
HTTP_X_REQUESTED_WITH="XMLHttpRequest"
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertIn("html", data)
|
|
self.assertIn("markers", data)
|
|
self.assertIn("count", data)
|
|
self.assertIn("CTRL-0001", data["html"])
|
|
self.assertEqual(len(data["markers"]), 1)
|
|
self.assertEqual(data["markers"][0]["code"], "CTRL-0001")
|
|
|
|
|