Repository: DDMAL/CantusDB

Issue:
make a test case to test the permissions
we have implemented a lot of different restrictions to views. create a unit test to automate the testing process.


Discussion:

Resolving patch:
diff --git a/django/cantusdb_project/main_app/views/sequence.py b/django/cantusdb_project/main_app/views/sequence.py
--- a/django/cantusdb_project/main_app/views/sequence.py
+++ b/django/cantusdb_project/main_app/views/sequence.py
@@ -76,27 +76,10 @@ def form_valid(self, form):
 
     def test_func(self):
         user = self.request.user
-        sequence_id = self.kwargs.get(self.pk_url_kwarg)
-        try:
-            sequence = Sequence.objects.get(id=sequence_id)
-        except:
-            raise Http404("This sequence does not exist")
-        # find the source of this sequence
-        source = sequence.source
-        # checks if the user is an editor or a proofreader,
-        # and if the user is given privilege to edit this source and thus, it's sequences
-        is_editor_proofreader = user.groups.filter(Q(name="editor")|Q(name="proofreader")).exists()
-        can_edit_sequences_in_source = user.sources_user_can_edit.filter(id=source.id)
         # checks if the user is a project manager (they should have the privilege to edit any sequence)
         is_project_manager = user.groups.filter(name="project manager").exists()
-        # checks if the user is a contributor,
-        # and if the user is the creator of this source 
-        # (they should only have the privilege to edit sequences in a source they have created)
-        is_contributor = user.groups.filter(name="contributor").exists()
-
-        if ((is_editor_proofreader and can_edit_sequences_in_source) 
-            or (is_project_manager) 
-            or (is_contributor and source.created_by == user)):
+
+        if is_project_manager:
             return True
         else:
             return False


Test patch:
diff --git a/django/cantusdb_project/main_app/tests/test_views_general.py b/django/cantusdb_project/main_app/tests/test_views_general.py
--- a/django/cantusdb_project/main_app/tests/test_views_general.py
+++ b/django/cantusdb_project/main_app/tests/test_views_general.py
@@ -4,6 +4,10 @@
 from django.http.response import JsonResponse
 import json
 from .make_fakes import *
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+from django.test import Client
+from django.db.models import Q
 
 # run with `python -Wa manage.py test main_app.tests.test_views_general`
 # the -Wa flag tells Python to display deprecation warnings
@@ -1390,3 +1394,246 @@ def test_nonexistent_cantus_id(self):
 
 
 
+class PermissionsTest(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        Group.objects.create(name="project manager")
+        Group.objects.create(name="contributor")
+        Group.objects.create(name="editor")
+
+        for i in range(5):
+            source = make_fake_source()
+            for i in range(5):
+                Chant.objects.create(source = source)
+                Sequence.objects.create(source = source)
+                
+    def setUp(self):
+        self.user = get_user_model().objects.create(username='user')
+        self.user.set_password('pass')
+        self.user.save()
+        self.client = Client()
+
+    def test_login(self):
+        source = make_fake_source()
+        chant = make_fake_chant()
+        sequence = make_fake_sequence()
+
+        # currently not logged in, should redirect
+
+        # ChantCreateView
+        response = self.client.get(f'/chant-create/{source.id}')
+        self.assertRedirects(response, f'/login/?next=/chant-create/{source.id}')       
+
+        # ChantDeleteView
+        response = self.client.get(f'/chant-delete/{chant.id}')
+        self.assertRedirects(response, f'/login/?next=/chant-delete/{chant.id}')        
+        
+        # ChantEditVolpianoView
+        response = self.client.get(f'/edit-volpiano/{source.id}')
+        self.assertRedirects(response, f'/login/?next=/edit-volpiano/{source.id}')        
+
+        # SequenceEditView
+        response = self.client.get(f'/edit-sequence/{sequence.id}')
+        self.assertRedirects(response, f'/login/?next=/edit-sequence/{sequence.id}')        
+
+        # SourceCreateView
+        response = self.client.get('/source-create/')
+        self.assertRedirects(response, '/login/?next=/source-create/')    
+
+        # SourceEditView
+        response = self.client.get(f'/edit-source/{source.id}')
+        self.assertRedirects(response, f'/login/?next=/edit-source/{source.id}')
+
+        # UserSourceListView
+        response = self.client.get('/my-sources/')
+        self.assertRedirects(response, '/login/?next=/my-sources/')        
+
+    def test_permissions_project_manager(self):
+        project_manager = Group.objects.get(name='project manager') 
+        project_manager.user_set.add(self.user)
+        self.client.login(username='user', password='pass')
+
+        # get random source, chant and sequence
+        source = Source.objects.order_by('?').first()
+        chant = Chant.objects.order_by('?').first()
+        sequence = Sequence.objects.order_by('?').first()
+
+        # ChantCreateView
+        response = self.client.get(f'/chant-create/{source.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # ChantDeleteView
+        response = self.client.get(f'/chant-delete/{chant.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # ChantEditVolpianoView
+        response = self.client.get(f'/edit-volpiano/{source.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # SequenceEditView
+        response = self.client.get(f'/edit-sequence/{sequence.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # SourceCreateView
+        response = self.client.get('/source-create/')
+        self.assertEqual(response.status_code, 200)
+
+        # SourceEditView
+        response = self.client.get(f'/edit-source/{source.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # UserSourceListView
+        response = self.client.get('/my-sources/')
+        self.assertEqual(response.status_code, 200)
+
+    def test_permissions_contributor(self):
+        contributor = Group.objects.get(name='contributor') 
+        contributor.user_set.add(self.user)
+        self.client.login(username='user', password='pass')
+
+        # a source created by the current user
+        source_created_by_contributor = make_fake_source()
+        source_created_by_contributor.created_by = self.user
+        source_created_by_contributor.save()
+        for i in range(5):
+            Chant.objects.create(source = source_created_by_contributor)
+        chant_in_source_created_by_contributor = Chant.objects.filter(source = source_created_by_contributor).order_by('?').first()
+
+        # source and chant that the user should not have access to (did not create the source)
+        restricted_source = Source.objects.filter(~Q(created_by = self.user)).order_by('?').first()
+        restricted_chant = Chant.objects.filter(~Q(source = source_created_by_contributor)).order_by('?').first()
+        # a random sequence
+        sequence = Sequence.objects.order_by('?').first()
+
+        # ChantCreateView
+        response = self.client.get(f'/chant-create/{restricted_source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/chant-create/{source_created_by_contributor.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # ChantDeleteView
+        response = self.client.get(f'/chant-delete/{restricted_chant.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/chant-delete/{chant_in_source_created_by_contributor.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # ChantEditVolpianoView
+        response = self.client.get(f'/edit-volpiano/{restricted_source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/edit-volpiano/{source_created_by_contributor.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # SequenceEditView
+        response = self.client.get(f'/edit-sequence/{sequence.id}')
+        self.assertEqual(response.status_code, 403)
+
+        # SourceCreateView
+        response = self.client.get('/source-create/')
+        self.assertEqual(response.status_code, 200)
+
+        # SourceEditView
+        response = self.client.get(f'/edit-source/{restricted_source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/edit-source/{source_created_by_contributor.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # UserSourceListView
+        response = self.client.get('/my-sources/')
+        self.assertEqual(response.status_code, 200)
+
+    def test_permissions_editor(self):
+        editor = Group.objects.get(name='editor') 
+        editor.user_set.add(self.user)
+        self.client.login(username='user', password='pass')
+
+        # designate a random source to the user
+        allowed_source = Source.objects.order_by('?').first()
+        self.user.sources_user_can_edit.add(allowed_source)
+        # a random chant in the designated source
+        allowed_chant = Chant.objects.filter(source = allowed_source).order_by('?').first()
+
+        # source and chant that the user should not have access to (was not designated to this source)
+        restricted_source = Source.objects.filter(~Q(id = allowed_source.id)).order_by('?').first()
+        restricted_chant = Chant.objects.filter(~Q(source = allowed_source)).order_by('?').first()
+        # a random sequence
+        sequence = Sequence.objects.order_by('?').first()
+
+        # ChantCreateView
+        response = self.client.get(f'/chant-create/{restricted_source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/chant-create/{allowed_source.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # ChantDeleteView
+        response = self.client.get(f'/chant-delete/{restricted_chant.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/chant-delete/{allowed_chant.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # ChantEditVolpianoView
+        response = self.client.get(f'/edit-volpiano/{restricted_source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/edit-volpiano/{allowed_source.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # SequenceEditView
+        response = self.client.get(f'/edit-sequence/{sequence.id}')
+        self.assertEqual(response.status_code, 403)
+
+        # SourceCreateView
+        response = self.client.get('/source-create/')
+        self.assertEqual(response.status_code, 403)
+
+        # SourceEditView
+        response = self.client.get(f'/edit-source/{restricted_source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        response = self.client.get(f'/edit-source/{allowed_source.id}')
+        self.assertEqual(response.status_code, 200)
+
+        # UserSourceListView
+        response = self.client.get('/my-sources/')
+        self.assertEqual(response.status_code, 200)
+
+    def test_permissions_anon(self):
+        self.client.login(username='user', password='pass')
+
+        # get random source, chant and sequence
+        source = Source.objects.order_by('?').first()
+        chant = Chant.objects.order_by('?').first()
+        sequence = Sequence.objects.order_by('?').first()
+
+        # ChantCreateView
+        response = self.client.get(f'/chant-create/{source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        # ChantDeleteView
+        response = self.client.get(f'/chant-delete/{chant.id}')
+        self.assertEqual(response.status_code, 403)
+
+        # ChantEditVolpianoView
+        response = self.client.get(f'/edit-volpiano/{source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        # SequenceEditView
+        response = self.client.get(f'/edit-sequence/{sequence.id}')
+        self.assertEqual(response.status_code, 403)
+
+        # SourceCreateView
+        response = self.client.get('/source-create/')
+        self.assertEqual(response.status_code, 403)
+
+        # SourceEditView
+        response = self.client.get(f'/edit-source/{source.id}')
+        self.assertEqual(response.status_code, 403)
+
+        # UserSourceListView
+        response = self.client.get('/my-sources/')
+        self.assertEqual(response.status_code, 200)