from django.contrib.auth import get_user_model from rest_framework.test import APITestCase from .models import PermissionDefaults, UserPermissions from .permissions import PERMISSION_NAMES class PermissionAPITests(APITestCase): def setUp(self): self.admin = get_user_model().objects.create_user(username='manager', is_staff=True) self.alice = get_user_model().objects.create_user(username='alice') self.bob = get_user_model().objects.create_user(username='bob') self.event = dict(name='Rendez-vous', start_date='2030-01-07', end_date='2030-01-07', is_public=False) self.note = dict(title='Information', content='', kind='info', is_public=True, recipient_ids=[]) self.leave = dict(kind='leave', start_date='2030-01-07', proposed_slots=[]) def edit_permissions(self, scope='team', user=None, reset=False, **values): self.client.force_authenticate(self.admin) data = self.client.get('/api/users/permissions/').data settings = dict(data['defaults']) if scope == 'team' else dict.fromkeys(PERMISSION_NAMES, None) settings.update(values) return self.client.put('/api/users/permissions/', dict(scope=scope, user_id=(user or self.alice).pk, expected_revision=data['revision'], reset_individual=reset, permissions=settings), format='json') def test_current_and_future_users_inherit_team_rules(self): self.assertEqual(self.edit_permissions(request_leave=False, create_personal_event=False, create_publication=False).status_code, 200) newcomer = get_user_model().objects.create_user(username='new') for person in [self.alice, newcomer]: self.client.force_authenticate(person) self.assertEqual(self.client.get('/api/users/me/').data['permissions'], dict.fromkeys(PERMISSION_NAMES, False)) def test_individual_allow_deny_and_return_to_team(self): self.edit_permissions(request_leave=False) self.edit_permissions(scope='user', request_leave=True, create_publication=False) self.client.force_authenticate(self.alice) self.assertEqual(self.client.get('/api/users/me/').data['permissions'], dict(request_leave=True, create_personal_event=True, create_publication=False)) self.edit_permissions(scope='user') self.client.force_authenticate(self.alice) self.assertFalse(self.client.get('/api/users/me/').data['permissions']['request_leave']) self.assertTrue(self.client.get('/api/users/me/').data['permissions']['create_publication']) def test_team_change_preserves_individual_overrides_unless_reset(self): self.edit_permissions(scope='user', create_publication=True) self.edit_permissions(create_publication=False) self.client.force_authenticate(self.alice) self.assertTrue(self.client.get('/api/users/me/').data['permissions']['create_publication']) self.edit_permissions(reset=True, create_publication=False) self.client.force_authenticate(self.alice) self.assertFalse(self.client.get('/api/users/me/').data['permissions']['create_publication']) self.assertIsNone(UserPermissions.objects.get(user=self.alice).create_publication) def test_employee_cannot_manage_permissions(self): self.client.force_authenticate(self.alice) self.assertEqual(self.client.get('/api/users/permissions/').status_code, 403) self.assertEqual(self.client.put('/api/users/permissions/', {}, format='json').status_code, 403) self.assertEqual(self.client.patch('/api/users/me/', {'permissions': {'create_publication': True}}, format='json').status_code, 405) def test_admin_always_allowed_and_permissions_not_mass_assignable(self): self.edit_permissions(request_leave=False, create_personal_event=False, create_publication=False) self.client.force_authenticate(self.admin) self.assertEqual(self.client.get('/api/users/me/').data['permissions'], dict.fromkeys(PERMISSION_NAMES, True)) self.assertEqual(self.client.post('/api/notes/', self.note, format='json').status_code, 201) result = self.client.patch(f'/api/users/{self.alice.pk}/', {'permissions': dict.fromkeys(PERMISSION_NAMES, True)}, format='json') self.assertEqual(result.status_code, 200) self.assertFalse(result.data['permissions']['create_publication']) def test_stale_permission_update_returns_conflict(self): result = self.edit_permissions(request_leave=False) old_revision = result.data['revision'] self.edit_permissions(request_leave=True) result = self.client.put('/api/users/permissions/', dict(scope='team', expected_revision=old_revision, permissions=dict.fromkeys(PERMISSION_NAMES, False)), format='json') self.assertEqual(result.status_code, 409) self.assertTrue(PermissionDefaults.objects.get().request_leave) def test_team_cannot_inherit_and_invalid_requests_do_not_save(self): result = self.edit_permissions(request_leave=None) self.assertEqual(result.status_code, 400) self.assertTrue(PermissionDefaults.objects.get().request_leave) def test_leave_creation_and_preview_blocked_but_hours_allowed(self): self.edit_permissions(request_leave=False) self.client.force_authenticate(self.alice) for path in ['/api/planning/requests/', '/api/planning/requests/preview/']: self.assertEqual(self.client.post(path, self.leave, format='json').status_code, 403) request = dict(kind='hours', start_date='2030-01-07', proposed_slots=[dict(start_time='08:00', end_time='12:00', end_day_offset=0)]) self.assertEqual(self.client.post('/api/planning/requests/', request, format='json').status_code, 201) def test_pending_leave_can_be_read_and_cancelled_after_revocation(self): self.client.force_authenticate(self.alice) request = self.client.post('/api/planning/requests/', self.leave, format='json') self.assertEqual(request.status_code, 201) self.edit_permissions(request_leave=False) self.client.force_authenticate(self.alice) self.assertEqual(self.client.get('/api/planning/requests/').data['count'], 1) self.assertEqual(self.client.post(f"/api/planning/requests/{request.data['id']}/decision/", {'action': 'cancel'}, format='json').status_code, 200) def test_both_event_creation_routes_enforce_permission(self): self.edit_permissions(create_personal_event=False) self.client.force_authenticate(self.alice) for path in ['/api/event/', '/api/event/create/']: self.assertEqual(self.client.post(path, self.event, format='json').status_code, 403) self.edit_permissions(scope='user', create_personal_event=True) self.client.force_authenticate(self.alice) self.assertEqual(self.client.post('/api/event/', self.event, format='json').status_code, 201) def test_employee_events_stay_private_and_admin_can_publish(self): self.client.force_authenticate(self.alice) result = self.client.post('/api/event/', {**self.event, 'is_public': True}, format='json') self.assertEqual(result.status_code, 201) self.assertFalse(result.data['is_public']) self.client.force_authenticate(self.admin) result = self.client.post('/api/event/', {**self.event, 'is_public': True}, format='json') self.assertTrue(result.data['is_public']) def test_existing_personal_event_can_be_edited_after_creation_revoked(self): self.client.force_authenticate(self.alice) event = self.client.post('/api/event/', self.event, format='json').data self.edit_permissions(create_personal_event=False) self.client.force_authenticate(self.alice) self.assertEqual(self.client.patch(f"/api/event/{event['id']}/", {'name': 'Rendez-vous modifié'}, format='json').status_code, 200) def test_publications_blocked_but_reading_and_voting_still_allowed(self): self.client.force_authenticate(self.admin) poll = self.client.post('/api/notes/', dict(title='Question', kind='poll', is_public=True, recipient_ids=[], poll_settings=dict(vote_mode='single', options=['Oui', 'Non'])), format='json') self.assertEqual(poll.status_code, 201, poll.data) self.edit_permissions(create_publication=False) self.client.force_authenticate(self.alice) self.assertEqual(self.client.post('/api/notes/', self.note, format='json').status_code, 403) self.assertEqual(self.client.get('/api/notes/').status_code, 200) result = self.client.put(f"/api/notes/{poll.data['id']}/vote/", {'option_ids': [poll.data['poll']['options'][0]['id']]}, format='json') self.assertEqual(result.status_code, 200, result.data) def test_default_permissions_preserve_existing_behaviour(self): self.client.force_authenticate(self.alice) self.assertEqual(self.client.get('/api/users/me/').data['permissions'], dict.fromkeys(PERMISSION_NAMES, True)) self.assertEqual(self.client.post('/api/notes/', self.note, format='json').status_code, 201)