loko/streetup/sign/tests.py
2026-07-22 14:48:40 +02:00

657 lines
26 KiB
Python

from django.contrib.auth.models import User
from django.test import TestCase, Client
from django.urls import reverse
from common.models import UserConfig, Thematic, UserThematics, AppView
class SignViewTests(TestCase):
"""Tests for the sign view functionality."""
def setUp(self):
"""Set up test data."""
self.client = Client()
# Create application views
self.asset_view = AppView.objects.create(
code='asset_management',
name_fr='Gestion des assets',
name_nl='Asset Management',
icon='bi-database',
url_name='home:index',
is_default=True,
order=1
)
self.sign_view = AppView.objects.create(
code='sign',
name_fr='Signalisation',
name_nl='Signalisatie',
icon='bi-signpost',
url_name='sign:index',
order=2
)
# Create a thematic for sign
self.sign_thematic = Thematic.objects.create(
code='sign',
name_fr='Signalisation',
name_nl='Signalisatie',
icon='bi-signpost',
color='#0022ff'
)
# Create a user with access to sign view
self.user_with_access = User.objects.create_user(
username='signuser',
password='testpass123'
)
self.config_with_access = UserConfig.objects.create(
user=self.user_with_access
)
self.config_with_access.accessible_views.add(self.asset_view, self.sign_view)
# Give the user access to sign thematic
UserThematics.objects.create(
user_config=self.config_with_access,
thematic=self.sign_thematic,
can_view_assets=True,
can_edit_assets=False
)
# Create a user without access to sign view
self.user_without_access = User.objects.create_user(
username='notsignuser',
password='testpass123'
)
self.config_without_access = UserConfig.objects.create(
user=self.user_without_access
)
self.config_without_access.accessible_views.add(self.asset_view)
# Create a user with only sign access
self.user_sign_only = User.objects.create_user(
username='signonly',
password='testpass123'
)
self.config_sign_only = UserConfig.objects.create(
user=self.user_sign_only
)
self.config_sign_only.accessible_views.add(self.sign_view)
UserThematics.objects.create(
user_config=self.config_sign_only,
thematic=self.sign_thematic,
can_view_assets=True
)
def test_sign_index_access_granted(self):
"""Test that users with sign access can view the page."""
self.client.force_login(self.user_with_access)
response = self.client.get(reverse('sign:index'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'sign/index.html')
def test_sign_index_access_denied(self):
"""Test that users without sign access get 403 Forbidden."""
self.client.force_login(self.user_without_access)
response = self.client.get(reverse('sign:index'))
self.assertEqual(response.status_code, 403)
def test_sign_requires_authentication(self):
"""Test that unauthenticated users are redirected to login."""
response = self.client.get(reverse('sign:index'))
self.assertRedirects(
response,
f"{reverse('login')}?next={reverse('sign:index')}"
)
def test_sign_index_contains_required_elements(self):
"""Test that the sign page contains required UI elements."""
self.client.force_login(self.user_with_access)
response = self.client.get(reverse('sign:index'))
# Check for main container
self.assertContains(response, 'sign-container')
# Check for left panel
self.assertContains(response, 'sign-panel')
# Check for map container
self.assertContains(response, 'sign-map')
# Check for tabs
self.assertContains(response, 'tasks-tab')
self.assertContains(response, 'filters-tab')
self.assertContains(response, 'regulations-tab')
def test_sign_filter_parameters(self):
"""Test that filter parameters are passed to context."""
self.client.force_login(self.user_with_access)
response = self.client.get(
reverse('sign:index'),
{'municipality': ['1', '2'], 'panel': 'filters'}
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['active_panel'], 'filters')
self.assertIn('1', response.context['selected_municipalities'])
self.assertIn('2', response.context['selected_municipalities'])
class ViewSwitcherTests(TestCase):
"""Tests for the view switcher functionality in the header."""
def setUp(self):
"""Set up test data."""
self.client = Client()
# Create application views
self.asset_view = AppView.objects.create(
code='asset_management',
name_fr='Gestion des assets',
name_nl='Asset Management',
icon='bi-database',
url_name='home:index',
is_default=True,
order=1
)
self.sign_view = AppView.objects.create(
code='sign',
name_fr='Signalisation',
name_nl='Signalisatie',
icon='bi-signpost',
url_name='sign:index',
order=2
)
# Create users with different access levels
self.user_both_views = User.objects.create_user(
username='bothviews',
password='testpass123'
)
self.config_both = UserConfig.objects.create(
user=self.user_both_views
)
self.config_both.accessible_views.add(self.asset_view, self.sign_view)
self.user_asset_only = User.objects.create_user(
username='assetonly',
password='testpass123'
)
self.config_asset = UserConfig.objects.create(
user=self.user_asset_only
)
self.config_asset.accessible_views.add(self.asset_view)
self.user_sign_only = User.objects.create_user(
username='signonly',
password='testpass123'
)
self.config_sign = UserConfig.objects.create(
user=self.user_sign_only
)
self.config_sign.accessible_views.add(self.sign_view)
def test_view_switcher_shown_for_both_views(self):
"""Test that view switcher is shown for users with access to both views."""
self.client.force_login(self.user_both_views)
response = self.client.get(reverse('home:index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'viewSwitcherDropdown')
def test_view_switcher_hidden_for_single_view_asset(self):
"""Test that view switcher dropdown is not shown for asset-only users."""
self.client.force_login(self.user_asset_only)
response = self.client.get(reverse('home:index'))
self.assertEqual(response.status_code, 200)
# The dropdown should not be shown
self.assertNotContains(response, 'viewSwitcherDropdown')
class UserConfigViewAccessTests(TestCase):
"""Tests for UserConfig view access with generic AppView model."""
def setUp(self):
"""Set up test data."""
self.asset_view = AppView.objects.create(
code='asset_management',
name_fr='Gestion des assets',
name_nl='Asset Management',
icon='bi-database',
url_name='home:index',
is_default=True,
order=1
)
self.sign_view = AppView.objects.create(
code='sign',
name_fr='Signalisation',
name_nl='Signalisatie',
icon='bi-signpost',
url_name='sign:index',
order=2
)
def test_can_access_view_method(self):
"""Test the can_access_view method."""
user = User.objects.create_user(username='testuser', password='pass')
config = UserConfig.objects.create(user=user)
config.accessible_views.add(self.sign_view)
self.assertTrue(config.can_access_view('sign'))
self.assertFalse(config.can_access_view('asset_management'))
def test_can_add_multiple_views(self):
"""Test that multiple views can be added."""
user = User.objects.create_user(username='testuser', password='pass')
config = UserConfig.objects.create(user=user)
config.accessible_views.add(self.asset_view, self.sign_view)
self.assertTrue(config.can_access_view('asset_management'))
self.assertTrue(config.can_access_view('sign'))
self.assertEqual(config.accessible_views.count(), 2)
def test_get_accessible_views_ordered(self):
"""Test that get_accessible_views returns views in order."""
user = User.objects.create_user(username='testuser', password='pass')
config = UserConfig.objects.create(user=user)
config.accessible_views.add(self.sign_view, self.asset_view)
views = list(config.get_accessible_views())
self.assertEqual(views[0].code, 'asset_management') # order=1
self.assertEqual(views[1].code, 'sign') # order=2
class AppViewModelTests(TestCase):
"""Tests for the AppView model."""
def test_app_view_creation(self):
"""Test creating an AppView."""
view = AppView.objects.create(
code='test_view',
name_fr='Vue de test',
name_nl='Test view',
icon='bi-test',
url_name='test:index',
is_default=False,
order=10
)
self.assertEqual(view.code, 'test_view')
self.assertEqual(view.name_fr, 'Vue de test')
self.assertEqual(view.url_name, 'test:index')
def test_app_view_name_property(self):
"""Test the name property returns the correct language."""
view = AppView.objects.create(
code='test_view',
name_fr='Vue de test',
name_nl='Test view',
icon='bi-test',
url_name='test:index'
)
# Default language should be French
self.assertEqual(view.name, 'Vue de test')
class ESignPermissionTests(TestCase):
"""Tests for the refined eSign permissions."""
def setUp(self):
from django.contrib.auth.models import Permission
from common.models import Role
# Create basic roles
self.admin_role, _ = Role.objects.get_or_create(name='admin')
self.manager_role, _ = Role.objects.get_or_create(name='manager')
# Create superuser
self.superuser = User.objects.create_superuser(username='super', password='password')
# Create normal users
self.user_none = User.objects.create_user(username='none', password='password')
self.config_none = UserConfig.objects.create(user=self.user_none)
self.user_edit = User.objects.create_user(username='edit', password='password')
self.config_edit = UserConfig.objects.create(user=self.user_edit, esign_can_edit=True)
self.user_manage = User.objects.create_user(username='manage', password='password')
self.config_manage = UserConfig.objects.create(user=self.user_manage, esign_can_manage_rcp=True)
self.user_validate = User.objects.create_user(username='validate', password='password')
self.config_validate = UserConfig.objects.create(user=self.user_validate, esign_can_validate_rcp=True)
# Users with roles
self.user_admin = User.objects.create_user(username='admin_user', password='password')
self.config_admin = UserConfig.objects.create(user=self.user_admin)
self.config_admin.roles.add(self.admin_role)
self.user_manager = User.objects.create_user(username='manager_user', password='password')
self.config_manager = UserConfig.objects.create(user=self.user_manager)
self.config_manager.roles.add(self.manager_role)
# User with Django built-in permission
self.user_django_perm = User.objects.create_user(username='django_perm', password='password')
self.config_django_perm = UserConfig.objects.create(user=self.user_django_perm)
add_perm = Permission.objects.filter(codename='add_signpole', content_type__app_label='sign').first()
if not add_perm:
add_perm = Permission.objects.filter(codename='add_signpole').first()
self.user_django_perm.user_permissions.add(add_perm)
def test_user_can_edit_esign_assets(self):
from sign.permissions import user_can_edit_esign_assets
# Superuser should be able to edit
self.assertTrue(user_can_edit_esign_assets(self.superuser))
# Normal user should not be able to edit
self.assertFalse(user_can_edit_esign_assets(self.user_none))
# User with esign_can_edit should be able to edit
self.assertTrue(user_can_edit_esign_assets(self.user_edit))
# User with django perm should be able to edit
# Refresh user instance to load permissions
user_django = User.objects.get(pk=self.user_django_perm.pk)
self.assertTrue(user_can_edit_esign_assets(user_django))
def test_user_can_manage_regulations(self):
from sign.permissions import user_can_manage_regulations
# Superuser should be able to manage
self.assertTrue(user_can_manage_regulations(self.superuser))
# Normal user should not be able to manage
self.assertFalse(user_can_manage_regulations(self.user_none))
# User with esign_can_manage_rcp should be able to manage
self.assertTrue(user_can_manage_regulations(self.user_manage))
# Admin and manager roles should be able to manage
self.assertTrue(user_can_manage_regulations(self.user_admin))
self.assertTrue(user_can_manage_regulations(self.user_manager))
def test_user_can_approve_regulation(self):
from sign.permissions import user_can_approve_regulation
# Superuser should be able to approve
self.assertTrue(user_can_approve_regulation(self.superuser))
# Normal user should not be able to approve
self.assertFalse(user_can_approve_regulation(self.user_none))
# User with esign_can_validate_rcp should be able to approve
self.assertTrue(user_can_approve_regulation(self.user_validate))
# Admin role should be able to approve
self.assertTrue(user_can_approve_regulation(self.user_admin))
# Manager role should NOT be able to approve (requires admin or validate permission)
self.assertFalse(user_can_approve_regulation(self.user_manager))
class ESignOrganizationRestrictionsTests(TestCase):
"""Tests for eSign Organization restrictions and validation."""
def setUp(self):
from sign.models import Organization, ComplementaryPoliceRegulation
self.client = Client()
# Create basic app views so we can access 'sign' view
self.sign_view = AppView.objects.create(
code='sign',
name_fr='Signalisation',
name_nl='Signalisatie',
icon='bi-signpost',
url_name='sign:index',
order=2
)
# Create Organizations
self.org_a = Organization.objects.create(
name_fr="Commune A (FR)",
name_nl="Commune A (NL)",
code="ORG_A",
organization_type="municipality"
)
self.org_b = Organization.objects.create(
name_fr="Commune B (FR)",
name_nl="Commune B (NL)",
code="ORG_B",
organization_type="municipality"
)
# Create users
# 1. Superuser
self.superuser = User.objects.create_superuser(
username='super_org',
password='testpass123'
)
self.config_super = UserConfig.objects.create(user=self.superuser)
self.config_super.accessible_views.add(self.sign_view)
# 2. Restricted User for Org A (can manage RCP)
self.user_a = User.objects.create_user(
username='user_a',
password='testpass123'
)
self.config_a = UserConfig.objects.create(
user=self.user_a,
esign_can_manage_rcp=True
)
self.config_a.accessible_views.add(self.sign_view)
self.config_a.esign_organizations.add(self.org_a)
# 3. Restricted User for Org B (can manage RCP)
self.user_b = User.objects.create_user(
username='user_b',
password='testpass123'
)
self.config_b = UserConfig.objects.create(
user=self.user_b,
esign_can_manage_rcp=True
)
self.config_b.accessible_views.add(self.sign_view)
self.config_b.esign_organizations.add(self.org_b)
# 4. User without any allowed organizations (Visitor / read-only)
self.user_visitor = User.objects.create_user(
username='user_visitor',
password='testpass123'
)
self.config_visitor = UserConfig.objects.create(
user=self.user_visitor
)
self.config_visitor.accessible_views.add(self.sign_view)
# Create some complementary police regulations (RCPs)
self.reg_a = ComplementaryPoliceRegulation.objects.create(
title="Regulation Org A",
reference="REF-A-001",
description="Test A",
organization=self.org_a,
status="in_preparation",
uploaded_by=self.user_a
)
self.reg_b = ComplementaryPoliceRegulation.objects.create(
title="Regulation Org B",
reference="REF-B-001",
description="Test B",
organization=self.org_b,
status="in_preparation",
uploaded_by=self.user_b
)
def test_asset_forms_organization_required(self):
"""Test that SignPoleForm and SignPanelForm require an organization."""
import json
from sign.forms import SignPoleForm, SignPanelForm
# 1. SignPoleForm validation
pole_data_no_org = {
'orientation': 90,
'lat': 50.85,
'lon': 4.35
}
form_pole = SignPoleForm(data=pole_data_no_org)
self.assertFalse(form_pole.is_valid())
self.assertIn('organization', form_pole.errors)
pole_data_with_org = pole_data_no_org.copy()
pole_data_with_org['organization'] = self.org_a.id
form_pole_ok = SignPoleForm(data=pole_data_with_org)
self.assertTrue(form_pole_ok.is_valid())
# 2. SignPanelForm validation
panel_data_no_org = {
'orientation': 180,
'support_type': 'pole',
'is_temporary': False,
'lat': 50.85,
'lon': 4.35
}
form_panel = SignPanelForm(data=panel_data_no_org)
self.assertFalse(form_panel.is_valid())
self.assertIn('organization', form_panel.errors)
panel_data_with_org = panel_data_no_org.copy()
panel_data_with_org['organization'] = self.org_a.id
form_panel_ok = SignPanelForm(data=panel_data_with_org)
self.assertTrue(form_panel_ok.is_valid())
def test_universal_asset_visibility(self):
"""Test that any logged-in user can view assets without being blocked by organization permissions."""
import json
self.client.force_login(self.user_visitor)
response = self.client.get(reverse('sign:index'))
self.assertEqual(response.status_code, 200)
response_poles = self.client.post(
reverse('sign:sign_poles_overview_geojson'),
data=json.dumps({}),
content_type='application/json'
)
self.assertEqual(response_poles.status_code, 200)
def test_superuser_bypass_restrictions(self):
"""Test that superusers can see and modify any regulation across all organizations."""
import json
self.client.force_login(self.superuser)
# 1. Superuser should see both regulations in list
response = self.client.get(reverse('sign:regulations_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, self.reg_a.title)
self.assertContains(response, self.reg_b.title)
# 2. Superuser should see both regulations in search
response_search = self.client.get(reverse('sign:regulations_search'), {'q': ''})
self.assertEqual(response_search.status_code, 200)
results = response_search.json().get('results', [])
self.assertEqual(len(results), 2)
# 3. Superuser can access detail of both
response_detail_a = self.client.get(reverse('sign:regulation_detail', kwargs={'regulation_id': self.reg_a.id}))
self.assertEqual(response_detail_a.status_code, 200)
response_detail_b = self.client.get(reverse('sign:regulation_detail', kwargs={'regulation_id': self.reg_b.id}))
self.assertEqual(response_detail_b.status_code, 200)
def test_restricted_user_visibility_and_access(self):
"""Test that a restricted user can only see and access regulations of their organization."""
import json
self.client.force_login(self.user_a)
# 1. List only contains reg_a
response_list = self.client.get(reverse('sign:regulations_list'))
self.assertEqual(response_list.status_code, 200)
self.assertContains(response_list, self.reg_a.title)
self.assertNotContains(response_list, self.reg_b.title)
# 2. Search only returns reg_a
response_search = self.client.get(reverse('sign:regulations_search'), {'q': ''})
self.assertEqual(response_search.status_code, 200)
results = response_search.json().get('results', [])
self.assertEqual(len(results), 1)
self.assertEqual(results[0]['id'], self.reg_a.id)
# 3. Detail view for reg_a works, but reg_b raises PermissionDenied (403)
response_detail_a = self.client.get(reverse('sign:regulation_detail', kwargs={'regulation_id': self.reg_a.id}))
self.assertEqual(response_detail_a.status_code, 200)
response_detail_b = self.client.get(reverse('sign:regulation_detail', kwargs={'regulation_id': self.reg_b.id}))
self.assertEqual(response_detail_b.status_code, 403)
# 4. Docx generation for reg_a works (or handles it, but not 403), but reg_b raises 403
response_docx_b = self.client.get(reverse('sign:regulation_generate_docx', kwargs={'regulation_id': self.reg_b.id}))
self.assertEqual(response_docx_b.status_code, 403)
def test_regulation_creation_organization_check(self):
"""Test that creating a regulation is limited to the user's allowed organizations."""
self.client.force_login(self.user_a)
# 1. Try creating a regulation for org_b (not allowed)
response_create_forbidden = self.client.post(
reverse('sign:regulation_create'),
{
'title': 'New Reg Forbidden',
'reference': 'REF-F-001',
'description': 'Description',
'organization': self.org_b.id
}
)
self.assertEqual(response_create_forbidden.status_code, 403)
# 2. Try creating a regulation for org_a (allowed)
response_create_ok = self.client.post(
reverse('sign:regulation_create'),
{
'title': 'New Reg Allowed',
'reference': 'REF-OK-001',
'description': 'Description',
'organization': self.org_a.id
}
)
self.assertEqual(response_create_ok.status_code, 200)
self.assertTrue(response_create_ok.json().get('success'))
def test_regulation_editing_organization_check(self):
"""Test that editing a regulation and updating its organization checks user permissions."""
self.client.force_login(self.user_a)
# 1. Edit reg_a: change title, keep org_a -> OK
response_edit_ok = self.client.post(
reverse('sign:regulation_edit', kwargs={'regulation_id': self.reg_a.id}),
{
'title': 'Updated Title',
'reference': self.reg_a.reference,
'description': self.reg_a.description,
'organization': self.org_a.id
}
)
self.assertEqual(response_edit_ok.status_code, 200)
self.assertTrue(response_edit_ok.json().get('success'))
# 2. Edit reg_a: try to change organization to org_b -> Forbidden
response_edit_forbidden = self.client.post(
reverse('sign:regulation_edit', kwargs={'regulation_id': self.reg_a.id}),
{
'title': 'Updated Title',
'reference': self.reg_a.reference,
'description': self.reg_a.description,
'organization': self.org_b.id
}
)
self.assertEqual(response_edit_forbidden.status_code, 403)
# 3. Try to edit reg_b (which belongs to org_b) -> Forbidden (user_can_edit_regulation checks organization)
response_edit_other_rcp = self.client.post(
reverse('sign:regulation_edit', kwargs={'regulation_id': self.reg_b.id}),
{
'title': 'Updated Title Other',
'reference': self.reg_b.reference,
'description': self.reg_b.description,
'organization': self.org_b.id
}
)
self.assertEqual(response_edit_other_rcp.status_code, 403)