diff --git a/backend/api/tests/api/test_document.py b/backend/api/tests/api/test_document.py index ee7b035f..7a1279fe 100644 --- a/backend/api/tests/api/test_document.py +++ b/backend/api/tests/api/test_document.py @@ -1,447 +1,96 @@ -from django.conf import settings -from django.contrib.auth.models import User -from model_mommy import mommy from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from ...models import DOCUMENT_CLASSIFICATION, Document -from .utils import (TestUtilsMixin, assign_user_to_role, create_default_roles, - remove_all_role_mappings) +from ...models import DOCUMENT_CLASSIFICATION +from .utils import CRUDMixin, make_doc, make_user, prepare_project -class TestDocumentListAPI(APITestCase, TestUtilsMixin): +class TestDocumentListAPI(CRUDMixin): - @classmethod - def setUpTestData(cls): - cls.project_member_name = 'project_member_name' - cls.project_member_pass = 'project_member_pass' - cls.non_project_member_name = 'non_project_member_name' - cls.non_project_member_pass = 'non_project_member_pass' - cls.super_user_name = 'super_user_name' - cls.super_user_pass = 'super_user_pass' - create_default_roles() - project_member = User.objects.create_user(username=cls.project_member_name, - password=cls.project_member_pass) - non_project_member = User.objects.create_user(username=cls.non_project_member_name, - password=cls.non_project_member_pass) - super_user = User.objects.create_superuser(username=cls.super_user_name, - password=cls.super_user_pass, - email='fizz@buzz.com') + def setUp(self): + self.project = prepare_project(task=DOCUMENT_CLASSIFICATION) + self.non_member = make_user() + make_doc(self.project.item) + self.data = {'text': 'example'} + self.url = reverse(viewname='doc_list', args=[self.project.item.id]) - cls.main_project = mommy.make( - _model='TextClassificationProject', - users=[project_member, super_user], - project_type=DOCUMENT_CLASSIFICATION - ) - doc1 = mommy.make('Document', project=cls.main_project) - doc2 = mommy.make('Document', project=cls.main_project) - mommy.make('Document', project=cls.main_project) + def test_allows_project_member_to_list_docs(self): + for member in self.project.users: + self.assert_fetch(member, status.HTTP_200_OK) - cls.random_order_project = mommy.make( - _model='TextClassificationProject', - project_type=DOCUMENT_CLASSIFICATION, - users=[project_member, super_user], - randomize_document_order=True - ) - mommy.make('Document', 100, project=cls.random_order_project) + def test_denies_non_project_member_to_list_docs(self): + self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN) - sub_project = mommy.make( - _model='TextClassificationProject', - project_type=DOCUMENT_CLASSIFICATION, - users=[non_project_member] - ) - mommy.make('Document', project=sub_project) - cls.url = reverse(viewname='doc_list', args=[cls.main_project.id]) - cls.random_order_project_url = reverse(viewname='doc_list', args=[cls.random_order_project.id]) - cls.data = {'text': 'example'} - assign_user_to_role(project_member=project_member, project=cls.main_project, - role_name=settings.ROLE_ANNOTATOR) - assign_user_to_role(project_member=project_member, project=cls.random_order_project, - role_name=settings.ROLE_ANNOTATOR) + def test_denies_unauthenticated_user_to_list_docs(self): + self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) - mommy.make('DocumentAnnotation', document=doc1, user=project_member) - mommy.make('DocumentAnnotation', document=doc2, user=project_member) + def test_allows_project_admin_to_create_doc(self): + self.assert_create(self.project.users[0], status.HTTP_201_CREATED) - def _test_list(self, url, username, password, expected_num_results): - self.client.login(username=username, password=password) - response = self.client.get(url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.json().get('results')), expected_num_results) + def test_denies_non_project_admin_to_create_doc(self): + for member in self.project.users[1:]: + self.assert_create(member, status.HTTP_403_FORBIDDEN) - def test_returns_docs_to_project_member(self): - self._test_list(self.url, - username=self.project_member_name, - password=self.project_member_pass, - expected_num_results=3) + def test_denies_unauthenticated_user_to_create_doc(self): + self.assert_create(expected=status.HTTP_403_FORBIDDEN) - def test_returns_docs_to_project_member_filtered_to_active(self): - self._test_list('{}?doc_annotations__isnull=true'.format(self.url), - username=self.project_member_name, - password=self.project_member_pass, - expected_num_results=1) - def test_returns_docs_to_project_member_filtered_to_completed(self): - self._test_list('{}?doc_annotations__isnull=false'.format(self.url), - username=self.project_member_name, - password=self.project_member_pass, - expected_num_results=2) +class TestDocumentDetail(CRUDMixin): - def test_returns_docs_to_project_member_filtered_to_active_with_collaborative_annotation(self): - self._test_list('{}?doc_annotations__isnull=true'.format(self.url), - username=self.super_user_name, - password=self.super_user_pass, - expected_num_results=3) + def setUp(self): + self.project = prepare_project(task=DOCUMENT_CLASSIFICATION) + self.non_member = make_user() + doc = make_doc(self.project.item) + self.data = {'text': 'example'} + self.url = reverse(viewname='doc_detail', args=[self.project.item.id, doc.id]) - self._patch_project(self.main_project, 'collaborative_annotation', True) + def test_allows_project_member_to_get_doc(self): + for member in self.project.users: + self.assert_fetch(member, status.HTTP_200_OK) - self._test_list('{}?doc_annotations__isnull=true'.format(self.url), - username=self.super_user_name, - password=self.super_user_pass, - expected_num_results=1) + def test_denies_non_project_member_to_get_doc(self): + self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN) - def test_returns_docs_to_project_member_filtered_to_completed_with_collaborative_annotation(self): - self._test_list('{}?doc_annotations__isnull=false'.format(self.url), - username=self.super_user_name, - password=self.super_user_pass, - expected_num_results=0) + def test_denies_unauthenticated_user_to_get_doc(self): + self.assert_fetch(expected=status.HTTP_403_FORBIDDEN) - self._patch_project(self.main_project, 'collaborative_annotation', True) + def test_allows_project_admin_to_update_doc(self): + self.assert_update(self.project.users[0], status.HTTP_200_OK) - self._test_list('{}?doc_annotations__isnull=false'.format(self.url), - username=self.super_user_name, - password=self.super_user_pass, - expected_num_results=2) + def test_denies_non_project_admin_to_update_doc(self): + for member in self.project.users[1:]: + self.assert_update(member, status.HTTP_403_FORBIDDEN) - def test_returns_docs_in_consistent_order_for_all_users(self): - self.client.login(username=self.project_member_name, password=self.project_member_pass) - user1_documents = self.client.get(self.url, format='json').json().get('results') - self.client.logout() + def test_denies_non_project_member_to_update_doc(self): + self.assert_update(self.non_member, status.HTTP_403_FORBIDDEN) - self.client.login(username=self.super_user_name, password=self.super_user_pass) - user2_documents = self.client.get(self.url, format='json').json().get('results') - self.client.logout() + def test_allows_project_admin_to_delete_doc(self): + self.assert_delete(self.project.users[0], status.HTTP_204_NO_CONTENT) - self.assertEqual([doc['id'] for doc in user1_documents], [doc['id'] for doc in user2_documents]) + def test_denies_non_project_admin_to_delete_doc(self): + for member in self.project.users[1:]: + self.assert_delete(member, status.HTTP_403_FORBIDDEN) - def test_can_return_docs_in_consistent_random_order(self): - self.client.login(username=self.project_member_name, password=self.project_member_pass) - user1_documents1 = self.client.get(self.random_order_project_url, format='json').json().get('results') - user1_documents2 = self.client.get(self.random_order_project_url, format='json').json().get('results') - self.client.logout() - self.assertEqual(user1_documents1, user1_documents2) + def test_denies_non_project_member_to_delete_doc(self): + self.assert_delete(self.non_member, status.HTTP_403_FORBIDDEN) - self.client.login(username=self.super_user_name, password=self.super_user_pass) - user2_documents1 = self.client.get(self.random_order_project_url, format='json').json().get('results') - user2_documents2 = self.client.get(self.random_order_project_url, format='json').json().get('results') - self.client.logout() - self.assertEqual(user2_documents1, user2_documents2) - self.assertNotEqual(user1_documents1, user2_documents1) - self.assertNotEqual(user1_documents2, user2_documents2) +class TestApproveLabelsAPI(CRUDMixin): - def test_do_not_return_docs_to_non_project_member(self): - self.client.login(username=self.non_project_member_name, - password=self.non_project_member_pass) - response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def setUp(self): + self.project = prepare_project(task=DOCUMENT_CLASSIFICATION) + self.non_member = make_user() + doc = make_doc(self.project.item) + self.url = reverse(viewname='approve_labels', args=[self.project.item.id, doc.id]) - def test_do_not_return_docs_of_other_projects(self): - self._test_list(self.url, - username=self.project_member_name, - password=self.project_member_pass, - expected_num_results=self.main_project.documents.count()) + def test_allow_project_admin_and_approver_to_approve_and_disapprove(self): + for member in self.project.users[:2]: + self.data = {'approved': True} + response = self.assert_create(member, status.HTTP_200_OK) + self.assertEqual(response.data['annotation_approver'], member.username) + self.data = {'approved': False} + response = self.assert_create(member, status.HTTP_200_OK) + self.assertIsNone(response.data['annotation_approver']) - def test_allows_superuser_to_create_doc(self): - self.client.login(username=self.super_user_name, - password=self.super_user_pass) - response = self.client.post(self.url, format='json', data=self.data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - def test_disallows_project_member_to_create_doc(self): - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.post(self.url, format='json', data=self.data) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - @classmethod - def doCleanups(cls): - remove_all_role_mappings() - - -class TestDocumentDetailAPI(APITestCase): - - @classmethod - def setUpTestData(cls): - cls.project_member_name = 'project_member_name' - cls.project_member_pass = 'project_member_pass' - cls.non_project_member_name = 'non_project_member_name' - cls.non_project_member_pass = 'non_project_member_pass' - cls.super_user_name = 'super_user_name' - cls.super_user_pass = 'super_user_pass' - create_default_roles() - project_member = User.objects.create_user(username=cls.project_member_name, - password=cls.project_member_pass) - non_project_member = User.objects.create_user(username=cls.non_project_member_name, - password=cls.non_project_member_pass) - # Todo: change super_user to project_admin. - super_user = User.objects.create_superuser(username=cls.super_user_name, - password=cls.super_user_pass, - email='fizz@buzz.com') - project = mommy.make( - _model='TextClassificationProject', - project_type=DOCUMENT_CLASSIFICATION, - users=[project_member, super_user] - ) - cls.doc = mommy.make('Document', project=project) - cls.url = reverse(viewname='doc_detail', args=[project.id, cls.doc.id]) - cls.data = {'text': 'example'} - assign_user_to_role(project_member=project_member, project=project, - role_name=settings.ROLE_ANNOTATOR) - - def test_returns_doc_to_project_member(self): - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json') - self.assertEqual(response.data['id'], self.doc.id) - - def test_do_not_return_doc_to_non_project_member(self): - self.client.login(username=self.non_project_member_name, - password=self.non_project_member_pass) - response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_allows_superuser_to_update_doc(self): - self.client.login(username=self.super_user_name, - password=self.super_user_pass) - response = self.client.patch(self.url, format='json', data=self.data) - self.assertEqual(response.data['text'], self.data['text']) - - def test_disallows_project_member_to_update_doc(self): - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.patch(self.url, format='json', data=self.data) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_allows_superuser_to_delete_doc(self): - self.client.login(username=self.super_user_name, - password=self.super_user_pass) - response = self.client.delete(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - def test_disallows_project_member_to_delete_doc(self): - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.delete(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - @classmethod - def doCleanups(cls): - remove_all_role_mappings() - - -class TestApproveLabelsAPI(APITestCase): - @classmethod - def setUpTestData(cls): - cls.annotator_name = 'annotator_name' - cls.annotator_pass = 'annotator_pass' - cls.approver_name = 'approver_name_name' - cls.approver_pass = 'approver_pass' - cls.project_admin_name = 'project_admin_name' - cls.project_admin_pass = 'project_admin_pass' - create_default_roles() - annotator = User.objects.create_user(username=cls.annotator_name, - password=cls.annotator_pass) - approver = User.objects.create_user(username=cls.approver_name, - password=cls.approver_pass) - project_admin = User.objects.create_user(username=cls.project_admin_name, - password=cls.project_admin_pass) - project = mommy.make( - _model='TextClassificationProject', - project_type=DOCUMENT_CLASSIFICATION, - users=[annotator, approver, project_admin] - ) - cls.doc = mommy.make('Document', project=project) - cls.url = reverse(viewname='approve_labels', args=[project.id, cls.doc.id]) - assign_user_to_role(project_member=annotator, project=project, - role_name=settings.ROLE_ANNOTATOR) - assign_user_to_role(project_member=approver, project=project, - role_name=settings.ROLE_ANNOTATION_APPROVER) - assign_user_to_role(project_member=project_admin, project=project, - role_name=settings.ROLE_PROJECT_ADMIN) - - def test_allow_project_admin_to_approve_and_disapprove_labels(self): - self.client.login(username=self.project_admin_name, password=self.project_admin_pass) - - response = self.client.post(self.url, format='json', data={'approved': True}) - self.assertEqual(response.data['annotation_approver'], self.project_admin_name) - - response = self.client.post(self.url, format='json', data={'approved': False}) - self.assertIsNone(response.data['annotation_approver']) - - def test_allow_approver_to_approve_and_disapprove_labels(self): - self.client.login(username=self.approver_name, password=self.approver_pass) - - response = self.client.post(self.url, format='json', data={'approved': True}) - self.assertEqual(response.data['annotation_approver'], self.approver_name) - - response = self.client.post(self.url, format='json', data={'approved': False}) - self.assertIsNone(response.data['annotation_approver']) - - def test_disallows_non_annotation_approver_to_approve_and_disapprove_labels(self): - self.client.login(username=self.annotator_name, password=self.annotator_pass) - - response = self.client.post(self.url, format='json', data={'approved': True}) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - @classmethod - def doCleanups(cls): - remove_all_role_mappings() - - -class TestSearch(APITestCase): - - @classmethod - def setUpTestData(cls): - cls.project_member_name = 'project_member_name' - cls.project_member_pass = 'project_member_pass' - cls.non_project_member_name = 'non_project_member_name' - cls.non_project_member_pass = 'non_project_member_pass' - create_default_roles() - project_member = User.objects.create_user(username=cls.project_member_name, - password=cls.project_member_pass) - non_project_member = User.objects.create_user(username=cls.non_project_member_name, - password=cls.non_project_member_pass) - - cls.main_project = mommy.make( - _model='TextClassificationProject', - project_type=DOCUMENT_CLASSIFICATION, - users=[project_member] - ) - cls.search_term = 'example' - doc1 = mommy.make('Document', text=cls.search_term, project=cls.main_project) - doc2 = mommy.make('Document', text='Lorem', project=cls.main_project) - label1 = mommy.make('Label', project=cls.main_project) - label2 = mommy.make('Label', project=cls.main_project) - mommy.make('SequenceAnnotation', document=doc1, user=project_member, label=label1) - mommy.make('SequenceAnnotation', document=doc2, user=project_member, label=label2) - - sub_project = mommy.make( - _model='TextClassificationProject', - project_type=DOCUMENT_CLASSIFICATION, - users=[non_project_member] - ) - mommy.make('Document', text=cls.search_term, project=sub_project) - cls.url = reverse(viewname='doc_list', args=[cls.main_project.id]) - cls.data = {'q': cls.search_term} - assign_user_to_role(project_member=project_member, project=cls.main_project, - role_name=settings.ROLE_ANNOTATOR) - - def test_can_filter_doc_by_term(self): - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=self.data) - count = Document.objects.filter(text__contains=self.search_term, - project=self.main_project).count() - self.assertEqual(response.data['count'], count) - - def test_can_order_doc_by_created_at_ascending(self): - params = {'ordering': 'created_at'} - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=params) - docs = Document.objects.filter(project=self.main_project).order_by('created_at').values() - for d1, d2 in zip(response.data['results'], docs): - self.assertEqual(d1['id'], d2['id']) - - def test_can_order_doc_by_created_at_descending(self): - params = {'ordering': '-created_at'} - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=params) - docs = Document.objects.filter(project=self.main_project).order_by('-created_at').values() - for d1, d2 in zip(response.data['results'], docs): - self.assertEqual(d1['id'], d2['id']) - - def test_can_order_doc_by_annotation_updated_at_ascending(self): - params = {'ordering': 'seq_annotations__updated_at'} - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=params) - docs = Document.objects.filter(project=self.main_project).order_by('seq_annotations__updated_at').values() - for d1, d2 in zip(response.data['results'], docs): - self.assertEqual(d1['id'], d2['id']) - - def test_can_order_doc_by_annotation_updated_at_descending(self): - params = {'ordering': '-seq_annotations__updated_at'} - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=params) - docs = Document.objects.filter(project=self.main_project).order_by('-seq_annotations__updated_at').values() - for d1, d2 in zip(response.data['results'], docs): - self.assertEqual(d1['id'], d2['id']) - - @classmethod - def doCleanups(cls): - remove_all_role_mappings() - - -class TestFilter(APITestCase): - - @classmethod - def setUpTestData(cls): - cls.project_member_name = 'project_member_name' - cls.project_member_pass = 'project_member_pass' - create_default_roles() - project_member = User.objects.create_user(username=cls.project_member_name, - password=cls.project_member_pass) - cls.main_project = mommy.make( - _model='SequenceLabelingProject', - users=[project_member], - project_type='SequenceLabeling' - ) - cls.label1 = mommy.make('Label', project=cls.main_project) - cls.label2 = mommy.make('Label', project=cls.main_project) - doc1 = mommy.make('Document', project=cls.main_project) - doc2 = mommy.make('Document', project=cls.main_project) - mommy.make('Document', project=cls.main_project) - mommy.make('SequenceAnnotation', document=doc1, user=project_member, label=cls.label1) - mommy.make('SequenceAnnotation', document=doc2, user=project_member, label=cls.label2) - cls.url = reverse(viewname='doc_list', args=[cls.main_project.id]) - cls.params = {'seq_annotations__label__id': cls.label1.id} - assign_user_to_role(project_member=project_member, project=cls.main_project, - role_name=settings.ROLE_ANNOTATOR) - - def test_can_filter_by_label(self): - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=self.params) - docs = Document.objects.filter(project=self.main_project, - seq_annotations__label__id=self.label1.id).values() - for d1, d2 in zip(response.data['results'], docs): - self.assertEqual(d1['id'], d2['id']) - - def test_can_filter_doc_with_annotation(self): - params = {'seq_annotations__isnull': False} - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=params) - docs = Document.objects.filter(project=self.main_project, seq_annotations__isnull=False).values() - self.assertEqual(response.data['count'], docs.count()) - for d1, d2 in zip(response.data['results'], docs): - self.assertEqual(d1['id'], d2['id']) - - def test_can_filter_doc_without_anotation(self): - params = {'seq_annotations__isnull': True} - self.client.login(username=self.project_member_name, - password=self.project_member_pass) - response = self.client.get(self.url, format='json', data=params) - docs = Document.objects.filter(project=self.main_project, seq_annotations__isnull=True).values() - self.assertEqual(response.data['count'], docs.count()) - for d1, d2 in zip(response.data['results'], docs): - self.assertEqual(d1['id'], d2['id']) - - @classmethod - def doCleanups(cls): - remove_all_role_mappings() + def test_denies_annotator_to_approve_and_disapprove(self): + self.assert_create(self.project.users[2], status.HTTP_403_FORBIDDEN)