loko/loko/projects/tests.py

256 lines
11 KiB
Python

from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
from common.models import UserConfig, Thematic
from projects.models import Project, ProjectTag
import json
User = get_user_model()
class ProjectFiltersPreferencesTest(TestCase):
def setUp(self):
# Create users
self.user = User.objects.create_user(username="testuser", password="password123")
self.user_config = UserConfig.objects.create(user=self.user, is_intern=True)
# Create thematics
self.thematic_a = Thematic.objects.create(code="them_a", name_fr="Thématique A", name_nl="Thema A")
self.thematic_b = Thematic.objects.create(code="them_b", name_fr="Thématique B", name_nl="Thema B")
# Create projects
self.project_a = Project.objects.create(
name="Project A",
created_by=self.user,
status="in_preparation",
start_date="2026-06-01",
end_date="2026-06-30"
)
self.project_a.thematics.add(self.thematic_a)
self.project_b = Project.objects.create(
name="Project B",
created_by=self.user,
status="in_progress",
start_date="2026-07-01",
end_date="2026-07-31"
)
self.project_b.thematics.add(self.thematic_b)
# Client setup
self.client = Client()
self.client.login(username="testuser", password="password123")
def test_projects_index_view(self):
# Ensure view loads and context contains necessary choices
response = self.client.get(reverse('projects:projects_index'))
self.assertEqual(response.status_code, 200)
self.assertIn('thematic_choices', response.context)
self.assertIn('selected_thematics', response.context)
self.assertIn('saved_sort', response.context)
# Check that thematic choices are populated
thematics = list(response.context['thematic_choices'])
self.assertIn(self.thematic_a, thematics)
self.assertIn(self.thematic_b, thematics)
def test_projects_geojson_thematic_filter(self):
# Without filter, return all (within permissions limit)
response = self.client.get(reverse('projects:projects_geojson'))
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertEqual(len(data['features']), 2)
# Filter by thematic_a
response = self.client.get(reverse('projects:projects_geojson'), {'thematic': ['them_a']})
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertEqual(len(data['features']), 1)
self.assertEqual(data['features'][0]['id'], self.project_a.id)
# Filter by thematic_b
response = self.client.get(reverse('projects:projects_geojson'), {'thematic': ['them_b']})
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertEqual(len(data['features']), 1)
self.assertEqual(data['features'][0]['id'], self.project_b.id)
def test_projects_geojson_date_filter(self):
# Date filter should successfully map start_date / end_date
response = self.client.get(reverse('projects:projects_geojson'), {'begin_date': '2026-06-15'})
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
# Should return Project B since its start_date is 2026-07-01 >= 2026-06-15 (Project A start_date is 2026-06-01 < 2026-06-15)
self.assertEqual(len(data['features']), 1)
self.assertEqual(data['features'][0]['id'], self.project_b.id)
response = self.client.get(reverse('projects:projects_geojson'), {'end_date': '2026-07-15'})
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
# Should return Project A since its end_date is 2026-06-30 <= 2026-07-15 (Project B end_date is 2026-07-31 > 2026-07-15)
self.assertEqual(len(data['features']), 1)
self.assertEqual(data['features'][0]['id'], self.project_a.id)
def test_save_preferences_endpoint(self):
filters_data = {
'status': ['in_preparation'],
'thematic': ['them_a'],
'begin_date': '2026-06-01'
}
sort_data = {
'sortName': 'title',
'sortOrder': 'desc'
}
# Save preferences POST request
response = self.client.post(
reverse('common:save_project_list_preferences'),
data=json.dumps({'filters': filters_data, 'sort': sort_data}),
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
)
self.assertEqual(response.status_code, 200)
# Verify db persistence
self.user_config.refresh_from_db()
self.assertEqual(self.user_config.project_list_filters, filters_data)
self.assertEqual(self.user_config.project_list_sort, sort_data)
def test_saved_preferences_redirect(self):
# Populate saved preferences first
filters_data = {'status': ['in_preparation'], 'thematic': ['them_a']}
self.user_config.project_list_filters = filters_data
self.user_config.save()
# Visit index page without GET params
response = self.client.get(reverse('projects:projects_index'))
# Should redirect to index page with query parameters
self.assertEqual(response.status_code, 302)
self.assertIn("status=in_preparation", response.url)
self.assertIn("thematic=them_a", response.url)
class ProjectViewerPermissionsTest(TestCase):
def setUp(self):
from common.models import Role, UserThematics
from projects.permissions import can_view_project, can_add_or_edit_project, filter_viewable_projects_for_user
self.viewer_user = User.objects.create_user(username="viewer_user", password="password123")
self.viewer_config = UserConfig.objects.create(user=self.viewer_user, is_intern=False)
viewer_role, _ = Role.objects.get_or_create(name='viewer')
self.viewer_config.roles.add(viewer_role)
self.thematic_roads = Thematic.objects.create(code="roads", name_fr="Voirie", name_nl="Wegen")
self.thematic_water = Thematic.objects.create(code="water_proj", name_fr="Eau", name_nl="Water")
# Viewer has can_view_projects on roads, but NOT on water
UserThematics.objects.create(
user_config=self.viewer_config,
thematic=self.thematic_roads,
can_view_projects=True,
can_view_assets=True
)
UserThematics.objects.create(
user_config=self.viewer_config,
thematic=self.thematic_water,
can_view_projects=False,
can_view_assets=True
)
creator = User.objects.create_user(username="creator", password="password123")
# Project 1: roads, non-strict
self.proj_roads = Project.objects.create(
name="Projet Voirie",
created_by=creator,
strict_access=False
)
self.proj_roads.thematics.add(self.thematic_roads)
# Project 2: water, non-strict
self.proj_water = Project.objects.create(
name="Projet Eau",
created_by=creator,
strict_access=False
)
self.proj_water.thematics.add(self.thematic_water)
# Project 3: roads, strict access
self.proj_roads_strict = Project.objects.create(
name="Projet Voirie Strict",
created_by=creator,
strict_access=True
)
self.proj_roads_strict.thematics.add(self.thematic_roads)
def test_viewer_project_permissions(self):
from projects.permissions import can_view_project, can_add_or_edit_project, filter_viewable_projects_for_user
# Can view roads project (non-strict, can_view_projects=True)
self.assertTrue(can_view_project(self.viewer_user, self.proj_roads))
# Cannot view water project (can_view_projects=False)
self.assertFalse(can_view_project(self.viewer_user, self.proj_water))
# Cannot view strict project without explicit access
self.assertFalse(can_view_project(self.viewer_user, self.proj_roads_strict))
# Viewer cannot add or edit projects
self.assertFalse(can_add_or_edit_project(self.viewer_user, self.proj_roads))
self.assertFalse(can_add_or_edit_project(self.viewer_user))
# filter_viewable_projects_for_user returns only proj_roads
viewable = filter_viewable_projects_for_user(self.viewer_user)
self.assertIn(self.proj_roads, viewable)
self.assertNotIn(self.proj_water, viewable)
self.assertNotIn(self.proj_roads_strict, viewable)
class ProjectAddDocumentTest(TestCase):
def setUp(self):
from common.models import Role
from projects.signals import ensure_project_document_folder
self.user = User.objects.create_user(username="projadmin", password="password123")
self.user_config = UserConfig.objects.create(user=self.user, is_intern=True)
admin_role, _ = Role.objects.get_or_create(name='admin')
self.user_config.roles.add(admin_role)
self.thematic = Thematic.objects.create(code="them_test", name_fr="Them Test", name_nl="Them Test")
self.project = Project.objects.create(
name="Test Project",
created_by=self.user,
status="in_progress"
)
self.project.thematics.add(self.thematic)
ensure_project_document_folder(self.project)
self.client = Client()
self.client.login(username="projadmin", password="password123")
def test_visibility_scope_choices_compatibility(self):
from documents.models import VisibilityScope
self.assertTrue(hasattr(VisibilityScope, 'CHOICES'))
self.assertEqual(dict(VisibilityScope.CHOICES), dict(VisibilityScope.choices))
def test_add_document_with_visibility(self):
from django.core.files.uploadedfile import SimpleUploadedFile
from documents.models import ManagedDocument, VisibilityScope
upload = SimpleUploadedFile("test_doc.pdf", b"%PDF-1.4 test content", content_type="application/pdf")
post_data = {
"files": [upload],
"file_count": "1",
"visibility_0": "internal",
}
url = reverse("projects:projects_add_document", kwargs={"project_id": self.project.id})
response = self.client.post(url, post_data, follow=True)
self.assertEqual(response.status_code, 200)
doc = ManagedDocument.objects.filter(title="test_doc").first()
self.assertIsNotNone(doc)
self.assertEqual(doc.visibility, VisibilityScope.INTERNAL)