You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1871 lines
96 KiB

5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. import os
  2. from django.conf import settings
  3. from django.test import override_settings
  4. from rest_framework import status
  5. from rest_framework.reverse import reverse
  6. from rest_framework.test import APITestCase
  7. from model_mommy import mommy
  8. from ..models import User, SequenceAnnotation, Document, Role, RoleMapping
  9. from ..models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ, SPEECH2TEXT
  10. from ..utils import PlainTextParser, CoNLLParser, JSONParser, CSVParser, FastTextParser
  11. from ..exceptions import FileParseException
  12. DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
  13. def create_default_roles():
  14. Role.objects.get_or_create(name=settings.ROLE_PROJECT_ADMIN)
  15. Role.objects.get_or_create(name=settings.ROLE_ANNOTATOR)
  16. Role.objects.get_or_create(name=settings.ROLE_ANNOTATION_APPROVER)
  17. def assign_user_to_role(project_member, project, role_name):
  18. role, _ = Role.objects.get_or_create(name=role_name)
  19. RoleMapping.objects.get_or_create(role_id=role.id, user_id=project_member.id, project_id=project.id)
  20. def remove_all_role_mappings():
  21. RoleMapping.objects.all().delete()
  22. class TestHealthEndpoint(APITestCase):
  23. @classmethod
  24. def setUpTestData(cls):
  25. cls.url = reverse(viewname='health')
  26. def test_returns_green_status_on_health_endpoint(self):
  27. response = self.client.get(self.url, format='json')
  28. self.assertEqual(response.data['status'], 'green')
  29. class TestUtilsMixin:
  30. def _patch_project(self, project, attribute, value):
  31. old_value = getattr(project, attribute, None)
  32. setattr(project, attribute, value)
  33. project.save()
  34. def cleanup_project():
  35. setattr(project, attribute, old_value)
  36. project.save()
  37. self.addCleanup(cleanup_project)
  38. @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
  39. class TestProjectListAPI(APITestCase):
  40. @classmethod
  41. def setUpTestData(cls):
  42. cls.main_project_member_name = 'project_member_name'
  43. cls.main_project_member_pass = 'project_member_pass'
  44. cls.sub_project_member_name = 'sub_project_member_name'
  45. cls.sub_project_member_pass = 'sub_project_member_pass'
  46. cls.approver_name = 'approver_name_name'
  47. cls.approver_pass = 'approver_pass'
  48. cls.super_user_name = 'super_user_name'
  49. cls.super_user_pass = 'super_user_pass'
  50. create_default_roles()
  51. main_project_member = User.objects.create_user(username=cls.main_project_member_name,
  52. password=cls.main_project_member_pass)
  53. sub_project_member = User.objects.create_user(username=cls.sub_project_member_name,
  54. password=cls.sub_project_member_pass)
  55. approver = User.objects.create_user(username=cls.approver_name,
  56. password=cls.approver_pass)
  57. User.objects.create_superuser(username=cls.super_user_name,
  58. password=cls.super_user_pass,
  59. email='fizz@buzz.com')
  60. cls.main_project = mommy.make('TextClassificationProject', users=[main_project_member])
  61. cls.sub_project = mommy.make('TextClassificationProject', users=[sub_project_member])
  62. assign_user_to_role(project_member=main_project_member, project=cls.main_project,
  63. role_name=settings.ROLE_ANNOTATOR)
  64. assign_user_to_role(project_member=sub_project_member, project=cls.sub_project,
  65. role_name=settings.ROLE_ANNOTATOR)
  66. assign_user_to_role(project_member=approver, project=cls.main_project,
  67. role_name=settings.ROLE_ANNOTATION_APPROVER)
  68. cls.url = reverse(viewname='project_list')
  69. cls.data = {'name': 'example', 'project_type': 'DocumentClassification',
  70. 'description': 'example', 'guideline': 'example',
  71. 'resourcetype': 'TextClassificationProject'}
  72. cls.num_project = main_project_member.projects.count()
  73. def test_returns_main_project_to_approver(self):
  74. self.client.login(username=self.approver_name,
  75. password=self.approver_pass)
  76. response = self.client.get(self.url, format='json')
  77. project = response.data[0]
  78. num_project = len(response.data)
  79. self.assertEqual(num_project, self.num_project)
  80. self.assertEqual(project['id'], self.main_project.id)
  81. def test_returns_main_project_to_main_project_member(self):
  82. self.client.login(username=self.main_project_member_name,
  83. password=self.main_project_member_pass)
  84. response = self.client.get(self.url, format='json')
  85. project = response.data[0]
  86. num_project = len(response.data)
  87. self.assertEqual(num_project, self.num_project)
  88. self.assertEqual(project['id'], self.main_project.id)
  89. def test_do_not_return_main_project_to_sub_project_member(self):
  90. self.client.login(username=self.sub_project_member_name,
  91. password=self.sub_project_member_pass)
  92. response = self.client.get(self.url, format='json')
  93. project = response.data[0]
  94. num_project = len(response.data)
  95. self.assertEqual(num_project, self.num_project)
  96. self.assertNotEqual(project['id'], self.main_project.id)
  97. def test_allows_superuser_to_create_project(self):
  98. self.client.login(username=self.super_user_name,
  99. password=self.super_user_pass)
  100. response = self.client.post(self.url, format='json', data=self.data)
  101. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  102. self.assertFalse(response.json().get('collaborative_annotation'))
  103. self.assertFalse(response.json().get('randomize_document_order'))
  104. def test_allows_superuser_to_create_project_with_flags(self):
  105. self.client.login(username=self.super_user_name,
  106. password=self.super_user_pass)
  107. data = dict(self.data)
  108. data['collaborative_annotation'] = True
  109. data['randomize_document_order'] = True
  110. response = self.client.post(self.url, format='json', data=data)
  111. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  112. self.assertTrue(response.json().get('collaborative_annotation'))
  113. self.assertTrue(response.json().get('randomize_document_order'))
  114. def test_disallows_project_member_to_create_project(self):
  115. self.client.login(username=self.main_project_member_name,
  116. password=self.main_project_member_pass)
  117. response = self.client.post(self.url, format='json', data=self.data)
  118. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  119. @classmethod
  120. def doCleanups(cls):
  121. remove_all_role_mappings()
  122. @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
  123. class TestProjectDetailAPI(APITestCase):
  124. @classmethod
  125. def setUpTestData(cls):
  126. cls.project_member_name = 'project_member_name'
  127. cls.project_member_pass = 'project_member_pass'
  128. cls.non_project_member_name = 'non_project_member_name'
  129. cls.non_project_member_pass = 'non_project_member_pass'
  130. cls.admin_user_name = 'admin_user_name'
  131. cls.admin_user_pass = 'admin_user_pass'
  132. create_default_roles()
  133. cls.project_member = User.objects.create_user(username=cls.project_member_name,
  134. password=cls.project_member_pass)
  135. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  136. password=cls.non_project_member_pass)
  137. project_admin = User.objects.create_superuser(username=cls.admin_user_name,
  138. password=cls.admin_user_pass,
  139. email='fizz@buzz.com')
  140. cls.main_project = mommy.make('TextClassificationProject', users=[cls.project_member, project_admin])
  141. mommy.make('TextClassificationProject', users=[non_project_member])
  142. cls.url = reverse(viewname='project_detail', args=[cls.main_project.id])
  143. cls.data = {'description': 'lorem'}
  144. assign_user_to_role(project_member=cls.project_member, project=cls.main_project,
  145. role_name=settings.ROLE_ANNOTATOR)
  146. assign_user_to_role(project_member=project_admin, project=cls.main_project,
  147. role_name=settings.ROLE_PROJECT_ADMIN)
  148. def test_returns_main_project_detail_to_main_project_member(self):
  149. self.client.login(username=self.project_member_name,
  150. password=self.project_member_pass)
  151. response = self.client.get(self.url, format='json')
  152. self.assertEqual(response.data['id'], self.main_project.id)
  153. def test_do_not_return_main_project_to_sub_project_member(self):
  154. self.client.login(username=self.non_project_member_name,
  155. password=self.non_project_member_pass)
  156. response = self.client.get(self.url, format='json')
  157. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  158. def test_allows_admin_to_update_project(self):
  159. self.client.login(username=self.admin_user_name,
  160. password=self.admin_user_pass)
  161. response = self.client.patch(self.url, format='json', data=self.data)
  162. self.assertEqual(response.data['description'], self.data['description'])
  163. def test_disallows_non_project_member_to_update_project(self):
  164. self.client.login(username=self.non_project_member_name,
  165. password=self.non_project_member_pass)
  166. response = self.client.patch(self.url, format='json', data=self.data)
  167. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  168. def test_allows_admin_to_delete_project(self):
  169. self.client.login(username=self.admin_user_name,
  170. password=self.admin_user_pass)
  171. response = self.client.delete(self.url, format='json')
  172. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  173. def test_disallows_non_project_member_to_delete_project(self):
  174. self.client.login(username=self.non_project_member_name,
  175. password=self.non_project_member_pass)
  176. response = self.client.delete(self.url, format='json')
  177. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  178. @classmethod
  179. def doCleanups(cls):
  180. remove_all_role_mappings()
  181. class TestLabelListAPI(APITestCase):
  182. @classmethod
  183. def setUpTestData(cls):
  184. cls.project_member_name = 'project_member_name'
  185. cls.project_member_pass = 'project_member_pass'
  186. cls.non_project_member_name = 'non_project_member_name'
  187. cls.non_project_member_pass = 'non_project_member_pass'
  188. cls.admin_user_name = 'admin_user_name'
  189. cls.admin_user_pass = 'admin_user_pass'
  190. create_default_roles()
  191. cls.project_member = User.objects.create_user(username=cls.project_member_name,
  192. password=cls.project_member_pass)
  193. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  194. password=cls.non_project_member_pass)
  195. project_admin = User.objects.create_superuser(username=cls.admin_user_name,
  196. password=cls.admin_user_pass,
  197. email='fizz@buzz.com')
  198. cls.main_project = mommy.make('Project', users=[cls.project_member, project_admin])
  199. cls.main_project_label = mommy.make('Label', project=cls.main_project)
  200. sub_project = mommy.make('Project', users=[non_project_member])
  201. other_project = mommy.make('Project', users=[project_admin])
  202. mommy.make('Label', project=sub_project)
  203. cls.url = reverse(viewname='label_list', args=[cls.main_project.id])
  204. cls.other_url = reverse(viewname='label_list', args=[other_project.id])
  205. cls.data = {'text': 'example'}
  206. assign_user_to_role(project_member=cls.project_member, project=cls.main_project,
  207. role_name=settings.ROLE_ANNOTATOR)
  208. def test_returns_labels_to_project_member(self):
  209. self.client.login(username=self.project_member_name,
  210. password=self.project_member_pass)
  211. response = self.client.get(self.url, format='json')
  212. self.assertEqual(response.status_code, status.HTTP_200_OK)
  213. def test_do_not_return_labels_to_non_project_member(self):
  214. self.client.login(username=self.non_project_member_name,
  215. password=self.non_project_member_pass)
  216. response = self.client.get(self.url, format='json')
  217. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  218. def test_do_not_return_labels_of_other_projects(self):
  219. self.client.login(username=self.project_member_name,
  220. password=self.project_member_pass)
  221. response = self.client.get(self.url, format='json')
  222. label = response.data[0]
  223. num_labels = len(response.data)
  224. self.assertEqual(num_labels, len(self.main_project.labels.all()))
  225. self.assertEqual(label['id'], self.main_project_label.id)
  226. def test_allows_admin_to_create_label(self):
  227. self.client.login(username=self.admin_user_name,
  228. password=self.admin_user_pass)
  229. response = self.client.post(self.url, format='json', data=self.data)
  230. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  231. def test_can_create_multiple_labels_without_shortcut_key(self):
  232. self.client.login(username=self.admin_user_name,
  233. password=self.admin_user_pass)
  234. labels = [
  235. {'text': 'Ruby', 'prefix_key': None, 'suffix_key': None},
  236. {'text': 'PHP', 'prefix_key': None, 'suffix_key': None}
  237. ]
  238. for label in labels:
  239. response = self.client.post(self.url, format='json', data=label)
  240. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  241. def test_can_create_same_label_in_multiple_projects(self):
  242. self.client.login(username=self.admin_user_name,
  243. password=self.admin_user_pass)
  244. label = {'text': 'LOC', 'prefix_key': None, 'suffix_key': 'l'}
  245. response = self.client.post(self.url, format='json', data=label)
  246. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  247. response = self.client.post(self.other_url, format='json', data=label)
  248. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  249. def test_can_create_same_suffix_with_different_prefix(self):
  250. self.client.login(username=self.admin_user_name,
  251. password=self.admin_user_pass)
  252. label = {'text': 'Person', 'prefix_key': None, 'suffix_key': 'p'}
  253. response = self.client.post(self.url, format='json', data=label)
  254. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  255. label = {'text': 'Percentage', 'prefix_key': 'ctrl', 'suffix_key': 'p'}
  256. response = self.client.post(self.url, format='json', data=label)
  257. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  258. def test_cannot_create_same_shortcut_key(self):
  259. self.client.login(username=self.admin_user_name,
  260. password=self.admin_user_pass)
  261. label = {'text': 'Person', 'prefix_key': None, 'suffix_key': 'p'}
  262. response = self.client.post(self.url, format='json', data=label)
  263. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  264. label = {'text': 'Percentage', 'prefix_key': None, 'suffix_key': 'p'}
  265. response = self.client.post(self.url, format='json', data=label)
  266. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  267. def test_disallows_project_member_to_create_label(self):
  268. self.client.login(username=self.project_member_name,
  269. password=self.project_member_pass)
  270. response = self.client.post(self.url, format='json', data=self.data)
  271. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  272. @classmethod
  273. def doCleanups(cls):
  274. remove_all_role_mappings()
  275. class TestLabelDetailAPI(APITestCase):
  276. @classmethod
  277. def setUpTestData(cls):
  278. cls.project_member_name = 'project_member_name'
  279. cls.project_member_pass = 'project_member_pass'
  280. cls.non_project_member_name = 'non_project_member_name'
  281. cls.non_project_member_pass = 'non_project_member_pass'
  282. cls.super_user_name = 'super_user_name'
  283. cls.super_user_pass = 'super_user_pass'
  284. create_default_roles()
  285. project_member = User.objects.create_user(username=cls.project_member_name,
  286. password=cls.project_member_pass)
  287. User.objects.create_user(username=cls.non_project_member_name, password=cls.non_project_member_pass)
  288. # Todo: change super_user to project_admin.
  289. super_user = User.objects.create_superuser(username=cls.super_user_name,
  290. password=cls.super_user_pass,
  291. email='fizz@buzz.com')
  292. project = mommy.make('Project', users=[project_member, super_user])
  293. cls.label = mommy.make('Label', project=project)
  294. cls.label_with_shortcut = mommy.make('Label', suffix_key='l', project=project)
  295. cls.url = reverse(viewname='label_detail', args=[project.id, cls.label.id])
  296. cls.url_with_shortcut = reverse(viewname='label_detail', args=[project.id, cls.label_with_shortcut.id])
  297. cls.data = {'text': 'example'}
  298. create_default_roles()
  299. assign_user_to_role(project_member=project_member, project=project,
  300. role_name=settings.ROLE_ANNOTATOR)
  301. def test_returns_label_to_project_member(self):
  302. self.client.login(username=self.project_member_name,
  303. password=self.project_member_pass)
  304. response = self.client.get(self.url, format='json')
  305. self.assertEqual(response.data['id'], self.label.id)
  306. def test_do_not_return_label_to_non_project_member(self):
  307. self.client.login(username=self.non_project_member_name,
  308. password=self.non_project_member_pass)
  309. response = self.client.get(self.url, format='json')
  310. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  311. def test_allows_superuser_to_update_label(self):
  312. self.client.login(username=self.super_user_name,
  313. password=self.super_user_pass)
  314. response = self.client.patch(self.url, format='json', data=self.data)
  315. self.assertEqual(response.data['text'], self.data['text'])
  316. def test_allows_superuser_to_update_label_with_shortcut(self):
  317. self.client.login(username=self.super_user_name,
  318. password=self.super_user_pass)
  319. response = self.client.patch(self.url_with_shortcut, format='json', data={'suffix_key': 's'})
  320. self.assertEqual(response.data['suffix_key'], 's')
  321. def test_disallows_project_member_to_update_label(self):
  322. self.client.login(username=self.project_member_name,
  323. password=self.project_member_pass)
  324. response = self.client.patch(self.url, format='json', data=self.data)
  325. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  326. def test_allows_superuser_to_delete_label(self):
  327. self.client.login(username=self.super_user_name,
  328. password=self.super_user_pass)
  329. response = self.client.delete(self.url, format='json')
  330. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  331. def test_disallows_project_member_to_delete_label(self):
  332. self.client.login(username=self.project_member_name,
  333. password=self.project_member_pass)
  334. response = self.client.delete(self.url, format='json')
  335. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  336. @classmethod
  337. def doCleanups(cls):
  338. remove_all_role_mappings()
  339. class TestLabelUploadAPI(APITestCase):
  340. @classmethod
  341. def setUpTestData(cls):
  342. cls.project_member_name = 'project_member_name'
  343. cls.project_member_pass = 'project_member_pass'
  344. cls.non_project_member_name = 'non_project_member_name'
  345. cls.non_project_member_pass = 'non_project_member_pass'
  346. cls.super_user_name = 'super_user_name'
  347. cls.super_user_pass = 'super_user_pass'
  348. create_default_roles()
  349. project_member = User.objects.create_user(username=cls.project_member_name,
  350. password=cls.project_member_pass)
  351. User.objects.create_user(username=cls.non_project_member_name, password=cls.non_project_member_pass)
  352. project_admin = User.objects.create_user(username=cls.super_user_name,
  353. password=cls.super_user_pass)
  354. project = mommy.make('Project', users=[project_member, project_admin])
  355. cls.url = reverse(viewname='label_upload', args=[project.id])
  356. create_default_roles()
  357. assign_user_to_role(project_member=project_admin, project=project, role_name=settings.ROLE_PROJECT_ADMIN)
  358. assign_user_to_role(project_member=project_member, project=project, role_name=settings.ROLE_ANNOTATOR)
  359. def help_to_upload_file(self, filename, expected_status):
  360. with open(os.path.join(DATA_DIR, filename), 'rb') as f:
  361. response = self.client.post(self.url, data={'file': f})
  362. self.assertEqual(response.status_code, expected_status)
  363. def test_allows_project_admin_to_upload_label(self):
  364. self.client.login(username=self.super_user_name,
  365. password=self.super_user_pass)
  366. self.help_to_upload_file('valid_labels.json', status.HTTP_201_CREATED)
  367. def test_disallows_project_member_to_upload_label(self):
  368. self.client.login(username=self.project_member_name,
  369. password=self.project_member_pass)
  370. self.help_to_upload_file('valid_labels.json', status.HTTP_403_FORBIDDEN)
  371. def test_try_to_upload_invalid_file(self):
  372. self.client.login(username=self.super_user_name,
  373. password=self.super_user_pass)
  374. self.help_to_upload_file('invalid_labels.json', status.HTTP_400_BAD_REQUEST)
  375. @classmethod
  376. def doCleanups(cls):
  377. remove_all_role_mappings()
  378. class TestDocumentListAPI(APITestCase, TestUtilsMixin):
  379. @classmethod
  380. def setUpTestData(cls):
  381. cls.project_member_name = 'project_member_name'
  382. cls.project_member_pass = 'project_member_pass'
  383. cls.non_project_member_name = 'non_project_member_name'
  384. cls.non_project_member_pass = 'non_project_member_pass'
  385. cls.super_user_name = 'super_user_name'
  386. cls.super_user_pass = 'super_user_pass'
  387. create_default_roles()
  388. project_member = User.objects.create_user(username=cls.project_member_name,
  389. password=cls.project_member_pass)
  390. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  391. password=cls.non_project_member_pass)
  392. super_user = User.objects.create_superuser(username=cls.super_user_name,
  393. password=cls.super_user_pass,
  394. email='fizz@buzz.com')
  395. cls.main_project = mommy.make('TextClassificationProject', users=[project_member, super_user])
  396. doc1 = mommy.make('Document', project=cls.main_project)
  397. doc2 = mommy.make('Document', project=cls.main_project)
  398. mommy.make('Document', project=cls.main_project)
  399. cls.random_order_project = mommy.make('TextClassificationProject', users=[project_member, super_user],
  400. randomize_document_order=True)
  401. mommy.make('Document', 100, project=cls.random_order_project)
  402. sub_project = mommy.make('TextClassificationProject', users=[non_project_member])
  403. mommy.make('Document', project=sub_project)
  404. cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
  405. cls.random_order_project_url = reverse(viewname='doc_list', args=[cls.random_order_project.id])
  406. cls.data = {'text': 'example'}
  407. assign_user_to_role(project_member=project_member, project=cls.main_project,
  408. role_name=settings.ROLE_ANNOTATOR)
  409. assign_user_to_role(project_member=project_member, project=cls.random_order_project,
  410. role_name=settings.ROLE_ANNOTATOR)
  411. mommy.make('DocumentAnnotation', document=doc1, user=project_member)
  412. mommy.make('DocumentAnnotation', document=doc2, user=project_member)
  413. def _test_list(self, url, username, password, expected_num_results):
  414. self.client.login(username=username, password=password)
  415. response = self.client.get(url, format='json')
  416. self.assertEqual(response.status_code, status.HTTP_200_OK)
  417. self.assertEqual(len(response.json().get('results')), expected_num_results)
  418. def test_returns_docs_to_project_member(self):
  419. self._test_list(self.url,
  420. username=self.project_member_name,
  421. password=self.project_member_pass,
  422. expected_num_results=3)
  423. def test_returns_docs_to_project_member_filtered_to_active(self):
  424. self._test_list('{}?doc_annotations__isnull=true'.format(self.url),
  425. username=self.project_member_name,
  426. password=self.project_member_pass,
  427. expected_num_results=1)
  428. def test_returns_docs_to_project_member_filtered_to_completed(self):
  429. self._test_list('{}?doc_annotations__isnull=false'.format(self.url),
  430. username=self.project_member_name,
  431. password=self.project_member_pass,
  432. expected_num_results=2)
  433. def test_returns_docs_to_project_member_filtered_to_active_with_collaborative_annotation(self):
  434. self._test_list('{}?doc_annotations__isnull=true'.format(self.url),
  435. username=self.super_user_name,
  436. password=self.super_user_pass,
  437. expected_num_results=3)
  438. self._patch_project(self.main_project, 'collaborative_annotation', True)
  439. self._test_list('{}?doc_annotations__isnull=true'.format(self.url),
  440. username=self.super_user_name,
  441. password=self.super_user_pass,
  442. expected_num_results=1)
  443. def test_returns_docs_to_project_member_filtered_to_completed_with_collaborative_annotation(self):
  444. self._test_list('{}?doc_annotations__isnull=false'.format(self.url),
  445. username=self.super_user_name,
  446. password=self.super_user_pass,
  447. expected_num_results=0)
  448. self._patch_project(self.main_project, 'collaborative_annotation', True)
  449. self._test_list('{}?doc_annotations__isnull=false'.format(self.url),
  450. username=self.super_user_name,
  451. password=self.super_user_pass,
  452. expected_num_results=2)
  453. def test_returns_docs_in_consistent_order_for_all_users(self):
  454. self.client.login(username=self.project_member_name, password=self.project_member_pass)
  455. user1_documents = self.client.get(self.url, format='json').json().get('results')
  456. self.client.logout()
  457. self.client.login(username=self.super_user_name, password=self.super_user_pass)
  458. user2_documents = self.client.get(self.url, format='json').json().get('results')
  459. self.client.logout()
  460. self.assertEqual([doc['id'] for doc in user1_documents], [doc['id'] for doc in user2_documents])
  461. def test_can_return_docs_in_consistent_random_order(self):
  462. self.client.login(username=self.project_member_name, password=self.project_member_pass)
  463. user1_documents1 = self.client.get(self.random_order_project_url, format='json').json().get('results')
  464. user1_documents2 = self.client.get(self.random_order_project_url, format='json').json().get('results')
  465. self.client.logout()
  466. self.assertEqual(user1_documents1, user1_documents2)
  467. self.client.login(username=self.super_user_name, password=self.super_user_pass)
  468. user2_documents1 = self.client.get(self.random_order_project_url, format='json').json().get('results')
  469. user2_documents2 = self.client.get(self.random_order_project_url, format='json').json().get('results')
  470. self.client.logout()
  471. self.assertEqual(user2_documents1, user2_documents2)
  472. self.assertNotEqual(user1_documents1, user2_documents1)
  473. self.assertNotEqual(user1_documents2, user2_documents2)
  474. def test_do_not_return_docs_to_non_project_member(self):
  475. self.client.login(username=self.non_project_member_name,
  476. password=self.non_project_member_pass)
  477. response = self.client.get(self.url, format='json')
  478. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  479. def test_do_not_return_docs_of_other_projects(self):
  480. self._test_list(self.url,
  481. username=self.project_member_name,
  482. password=self.project_member_pass,
  483. expected_num_results=self.main_project.documents.count())
  484. def test_allows_superuser_to_create_doc(self):
  485. self.client.login(username=self.super_user_name,
  486. password=self.super_user_pass)
  487. response = self.client.post(self.url, format='json', data=self.data)
  488. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  489. def test_disallows_project_member_to_create_doc(self):
  490. self.client.login(username=self.project_member_name,
  491. password=self.project_member_pass)
  492. response = self.client.post(self.url, format='json', data=self.data)
  493. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  494. @classmethod
  495. def doCleanups(cls):
  496. remove_all_role_mappings()
  497. class TestDocumentDetailAPI(APITestCase):
  498. @classmethod
  499. def setUpTestData(cls):
  500. cls.project_member_name = 'project_member_name'
  501. cls.project_member_pass = 'project_member_pass'
  502. cls.non_project_member_name = 'non_project_member_name'
  503. cls.non_project_member_pass = 'non_project_member_pass'
  504. cls.super_user_name = 'super_user_name'
  505. cls.super_user_pass = 'super_user_pass'
  506. create_default_roles()
  507. project_member = User.objects.create_user(username=cls.project_member_name,
  508. password=cls.project_member_pass)
  509. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  510. password=cls.non_project_member_pass)
  511. # Todo: change super_user to project_admin.
  512. super_user = User.objects.create_superuser(username=cls.super_user_name,
  513. password=cls.super_user_pass,
  514. email='fizz@buzz.com')
  515. project = mommy.make('TextClassificationProject', users=[project_member, super_user])
  516. cls.doc = mommy.make('Document', project=project)
  517. cls.url = reverse(viewname='doc_detail', args=[project.id, cls.doc.id])
  518. cls.data = {'text': 'example'}
  519. assign_user_to_role(project_member=project_member, project=project,
  520. role_name=settings.ROLE_ANNOTATOR)
  521. def test_returns_doc_to_project_member(self):
  522. self.client.login(username=self.project_member_name,
  523. password=self.project_member_pass)
  524. response = self.client.get(self.url, format='json')
  525. self.assertEqual(response.data['id'], self.doc.id)
  526. def test_do_not_return_doc_to_non_project_member(self):
  527. self.client.login(username=self.non_project_member_name,
  528. password=self.non_project_member_pass)
  529. response = self.client.get(self.url, format='json')
  530. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  531. def test_allows_superuser_to_update_doc(self):
  532. self.client.login(username=self.super_user_name,
  533. password=self.super_user_pass)
  534. response = self.client.patch(self.url, format='json', data=self.data)
  535. self.assertEqual(response.data['text'], self.data['text'])
  536. def test_disallows_project_member_to_update_doc(self):
  537. self.client.login(username=self.project_member_name,
  538. password=self.project_member_pass)
  539. response = self.client.patch(self.url, format='json', data=self.data)
  540. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  541. def test_allows_superuser_to_delete_doc(self):
  542. self.client.login(username=self.super_user_name,
  543. password=self.super_user_pass)
  544. response = self.client.delete(self.url, format='json')
  545. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  546. def test_disallows_project_member_to_delete_doc(self):
  547. self.client.login(username=self.project_member_name,
  548. password=self.project_member_pass)
  549. response = self.client.delete(self.url, format='json')
  550. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  551. @classmethod
  552. def doCleanups(cls):
  553. remove_all_role_mappings()
  554. class TestApproveLabelsAPI(APITestCase):
  555. @classmethod
  556. def setUpTestData(cls):
  557. cls.annotator_name = 'annotator_name'
  558. cls.annotator_pass = 'annotator_pass'
  559. cls.approver_name = 'approver_name_name'
  560. cls.approver_pass = 'approver_pass'
  561. cls.project_admin_name = 'project_admin_name'
  562. cls.project_admin_pass = 'project_admin_pass'
  563. annotator = User.objects.create_user(username=cls.annotator_name,
  564. password=cls.annotator_pass)
  565. approver = User.objects.create_user(username=cls.approver_name,
  566. password=cls.approver_pass)
  567. project_admin = User.objects.create_user(username=cls.project_admin_name,
  568. password=cls.project_admin_pass)
  569. project = mommy.make('TextClassificationProject', users=[annotator, approver, project_admin])
  570. cls.doc = mommy.make('Document', project=project)
  571. cls.url = reverse(viewname='approve_labels', args=[project.id, cls.doc.id])
  572. create_default_roles()
  573. assign_user_to_role(project_member=annotator, project=project,
  574. role_name=settings.ROLE_ANNOTATOR)
  575. assign_user_to_role(project_member=approver, project=project,
  576. role_name=settings.ROLE_ANNOTATION_APPROVER)
  577. assign_user_to_role(project_member=project_admin, project=project,
  578. role_name=settings.ROLE_PROJECT_ADMIN)
  579. def test_allow_project_admin_to_approve_and_disapprove_labels(self):
  580. self.client.login(username=self.project_admin_name, password=self.project_admin_pass)
  581. response = self.client.post(self.url, format='json', data={'approved': True})
  582. self.assertEqual(response.data['annotation_approver'], self.project_admin_name)
  583. response = self.client.post(self.url, format='json', data={'approved': False})
  584. self.assertIsNone(response.data['annotation_approver'])
  585. def test_allow_approver_to_approve_and_disapprove_labels(self):
  586. self.client.login(username=self.approver_name, password=self.approver_pass)
  587. response = self.client.post(self.url, format='json', data={'approved': True})
  588. self.assertEqual(response.data['annotation_approver'], self.approver_name)
  589. response = self.client.post(self.url, format='json', data={'approved': False})
  590. self.assertIsNone(response.data['annotation_approver'])
  591. def test_disallows_non_annotation_approver_to_approve_and_disapprove_labels(self):
  592. self.client.login(username=self.annotator_name, password=self.annotator_pass)
  593. response = self.client.post(self.url, format='json', data={'approved': True})
  594. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  595. @classmethod
  596. def doCleanups(cls):
  597. remove_all_role_mappings()
  598. class TestAnnotationListAPI(APITestCase, TestUtilsMixin):
  599. @classmethod
  600. def setUpTestData(cls):
  601. cls.project_member_name = 'project_member_name'
  602. cls.project_member_pass = 'project_member_pass'
  603. cls.another_project_member_name = 'another_project_member_name'
  604. cls.another_project_member_pass = 'another_project_member_pass'
  605. cls.non_project_member_name = 'non_project_member_name'
  606. cls.non_project_member_pass = 'non_project_member_pass'
  607. create_default_roles()
  608. project_member = User.objects.create_user(username=cls.project_member_name,
  609. password=cls.project_member_pass)
  610. another_project_member = User.objects.create_user(username=cls.another_project_member_name,
  611. password=cls.another_project_member_pass)
  612. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  613. password=cls.non_project_member_pass)
  614. main_project = mommy.make('SequenceLabelingProject', users=[project_member, another_project_member])
  615. main_project_label = mommy.make('Label', project=main_project)
  616. main_project_doc = mommy.make('Document', project=main_project)
  617. mommy.make('SequenceAnnotation', document=main_project_doc, user=project_member)
  618. mommy.make('SequenceAnnotation', document=main_project_doc, user=another_project_member)
  619. sub_project = mommy.make('SequenceLabelingProject', users=[non_project_member])
  620. sub_project_doc = mommy.make('Document', project=sub_project)
  621. mommy.make('SequenceAnnotation', document=sub_project_doc)
  622. cls.classification_project = mommy.make('TextClassificationProject',
  623. users=[project_member, another_project_member])
  624. cls.classification_project_label_1 = mommy.make('Label', project=cls.classification_project)
  625. cls.classification_project_label_2 = mommy.make('Label', project=cls.classification_project)
  626. cls.classification_project_document = mommy.make('Document', project=cls.classification_project)
  627. cls.classification_project_url = reverse(
  628. viewname='annotation_list', args=[cls.classification_project.id, cls.classification_project_document.id])
  629. assign_user_to_role(project_member=project_member, project=cls.classification_project,
  630. role_name=settings.ROLE_ANNOTATOR)
  631. assign_user_to_role(project_member=another_project_member, project=cls.classification_project,
  632. role_name=settings.ROLE_ANNOTATOR)
  633. cls.url = reverse(viewname='annotation_list', args=[main_project.id, main_project_doc.id])
  634. cls.post_data = {'start_offset': 0, 'end_offset': 1, 'label': main_project_label.id}
  635. cls.num_entity_of_project_member = SequenceAnnotation.objects.filter(document=main_project_doc,
  636. user=project_member).count()
  637. cls.num_entity_of_another_project_member = SequenceAnnotation.objects.filter(
  638. document=main_project_doc,
  639. user=another_project_member).count()
  640. cls.main_project = main_project
  641. assign_user_to_role(project_member=project_member, project=main_project,
  642. role_name=settings.ROLE_ANNOTATOR)
  643. def test_returns_annotations_to_project_member(self):
  644. self.client.login(username=self.project_member_name,
  645. password=self.project_member_pass)
  646. response = self.client.get(self.url, format='json')
  647. self.assertEqual(response.status_code, status.HTTP_200_OK)
  648. def test_do_not_return_annotations_to_non_project_member(self):
  649. self.client.login(username=self.non_project_member_name,
  650. password=self.non_project_member_pass)
  651. response = self.client.get(self.url, format='json')
  652. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  653. def test_do_not_return_annotations_of_another_project_member(self):
  654. self.client.login(username=self.project_member_name,
  655. password=self.project_member_pass)
  656. response = self.client.get(self.url, format='json')
  657. self.assertEqual(len(response.data), self.num_entity_of_project_member)
  658. def test_returns_annotations_of_another_project_member_if_collaborative_project(self):
  659. self._patch_project(self.main_project, 'collaborative_annotation', True)
  660. self.client.login(username=self.project_member_name,
  661. password=self.project_member_pass)
  662. response = self.client.get(self.url, format='json')
  663. self.assertEqual(len(response.data),
  664. self.num_entity_of_project_member + self.num_entity_of_another_project_member)
  665. def test_allows_project_member_to_create_annotation(self):
  666. self.client.login(username=self.project_member_name,
  667. password=self.project_member_pass)
  668. response = self.client.post(self.url, format='json', data=self.post_data)
  669. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  670. def test_disallows_non_project_member_to_create_annotation(self):
  671. self.client.login(username=self.non_project_member_name,
  672. password=self.non_project_member_pass)
  673. response = self.client.post(self.url, format='json', data=self.post_data)
  674. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  675. def test_disallows_second_annotation_for_single_class_project(self):
  676. self._patch_project(self.classification_project, 'single_class_classification', True)
  677. self.client.login(username=self.project_member_name, password=self.project_member_pass)
  678. response = self.client.post(self.classification_project_url, format='json',
  679. data={'label': self.classification_project_label_1.id})
  680. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  681. response = self.client.post(self.classification_project_url, format='json',
  682. data={'label': self.classification_project_label_2.id})
  683. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  684. def test_disallows_second_annotation_for_single_class_shared_project(self):
  685. self._patch_project(self.classification_project, 'single_class_classification', True)
  686. self._patch_project(self.classification_project, 'collaborative_annotation', True)
  687. self.client.login(username=self.project_member_name, password=self.project_member_pass)
  688. response = self.client.post(self.classification_project_url, format='json',
  689. data={'label': self.classification_project_label_1.id})
  690. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  691. self.client.login(username=self.another_project_member_name, password=self.another_project_member_pass)
  692. response = self.client.post(self.classification_project_url, format='json',
  693. data={'label': self.classification_project_label_2.id})
  694. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  695. def _patch_project(self, project, attribute, value):
  696. old_value = getattr(project, attribute, None)
  697. setattr(project, attribute, value)
  698. project.save()
  699. def cleanup_project():
  700. setattr(project, attribute, old_value)
  701. project.save()
  702. self.addCleanup(cleanup_project)
  703. @classmethod
  704. def doCleanups(cls):
  705. remove_all_role_mappings()
  706. class TestAnnotationDetailAPI(APITestCase):
  707. @classmethod
  708. def setUpTestData(cls):
  709. cls.super_user_name = 'super_user_name'
  710. cls.super_user_pass = 'super_user_pass'
  711. cls.project_member_name = 'project_member_name'
  712. cls.project_member_pass = 'project_member_pass'
  713. cls.another_project_member_name = 'another_project_member_name'
  714. cls.another_project_member_pass = 'another_project_member_pass'
  715. cls.non_project_member_name = 'non_project_member_name'
  716. cls.non_project_member_pass = 'non_project_member_pass'
  717. # Todo: change super_user to project_admin.
  718. super_user = User.objects.create_superuser(username=cls.super_user_name,
  719. password=cls.super_user_pass,
  720. email='fizz@buzz.com')
  721. create_default_roles()
  722. project_member = User.objects.create_user(username=cls.project_member_name,
  723. password=cls.project_member_pass)
  724. another_project_member = User.objects.create_user(username=cls.another_project_member_name,
  725. password=cls.another_project_member_pass)
  726. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  727. password=cls.non_project_member_pass)
  728. main_project = mommy.make('SequenceLabelingProject',
  729. users=[super_user, project_member, another_project_member])
  730. main_project_doc = mommy.make('Document', project=main_project)
  731. main_project_entity = mommy.make('SequenceAnnotation',
  732. document=main_project_doc, user=project_member)
  733. another_entity = mommy.make('SequenceAnnotation',
  734. document=main_project_doc, user=another_project_member)
  735. shared_project = mommy.make('SequenceLabelingProject',
  736. collaborative_annotation=True,
  737. users=[project_member, another_project_member])
  738. shared_project_doc = mommy.make('Document', project=shared_project)
  739. shared_entity = mommy.make('SequenceAnnotation', document=shared_project_doc, user=another_project_member)
  740. cls.url = reverse(viewname='annotation_detail', args=[main_project.id,
  741. main_project_doc.id,
  742. main_project_entity.id])
  743. cls.another_url = reverse(viewname='annotation_detail', args=[main_project.id,
  744. main_project_doc.id,
  745. another_entity.id])
  746. cls.shared_url = reverse(viewname='annotation_detail', args=[shared_project.id,
  747. shared_project_doc.id,
  748. shared_entity.id])
  749. cls.post_data = {'start_offset': 0, 'end_offset': 10}
  750. assign_user_to_role(project_member=project_member, project=main_project, role_name=settings.ROLE_ANNOTATOR)
  751. assign_user_to_role(project_member=project_member, project=shared_project, role_name=settings.ROLE_ANNOTATOR)
  752. def test_returns_annotation_to_project_member(self):
  753. self.client.login(username=self.project_member_name,
  754. password=self.project_member_pass)
  755. response = self.client.get(self.url, format='json')
  756. self.assertEqual(response.status_code, status.HTTP_200_OK)
  757. def test_do_not_return_annotation_to_non_project_member(self):
  758. self.client.login(username=self.non_project_member_name,
  759. password=self.non_project_member_pass)
  760. response = self.client.get(self.url, format='json')
  761. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  762. def test_do_not_return_annotation_by_another_project_member(self):
  763. self.client.login(username=self.project_member_name,
  764. password=self.project_member_pass)
  765. response = self.client.get(self.another_url, format='json')
  766. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  767. def test_allows_project_member_to_update_annotation(self):
  768. self.client.login(username=self.project_member_name,
  769. password=self.project_member_pass)
  770. response = self.client.patch(self.url, format='json', data=self.post_data)
  771. self.assertEqual(response.status_code, status.HTTP_200_OK)
  772. def test_disallows_non_project_member_to_update_annotation(self):
  773. self.client.login(username=self.non_project_member_name,
  774. password=self.non_project_member_pass)
  775. response = self.client.patch(self.url, format='json', data=self.post_data)
  776. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  777. def test_disallows_project_member_to_update_annotation_of_another_member(self):
  778. self.client.login(username=self.project_member_name,
  779. password=self.project_member_pass)
  780. response = self.client.patch(self.another_url, format='json', data=self.post_data)
  781. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  782. def test_allows_superuser_to_delete_annotation_of_another_member(self):
  783. self.client.login(username=self.super_user_name,
  784. password=self.super_user_pass)
  785. response = self.client.delete(self.another_url, format='json', data=self.post_data)
  786. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  787. def test_allows_project_member_to_delete_annotation(self):
  788. self.client.login(username=self.project_member_name,
  789. password=self.project_member_pass)
  790. response = self.client.delete(self.url, format='json')
  791. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  792. def test_disallows_project_member_to_delete_annotation(self):
  793. self.client.login(username=self.non_project_member_name,
  794. password=self.non_project_member_pass)
  795. response = self.client.delete(self.url, format='json')
  796. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  797. def test_disallows_project_member_to_delete_annotation_of_another_member(self):
  798. self.client.login(username=self.project_member_name,
  799. password=self.project_member_pass)
  800. response = self.client.delete(self.another_url, format='json', data=self.post_data)
  801. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  802. def test_allow_member_to_update_others_annotation_in_shared_project(self):
  803. self.client.login(username=self.project_member_name,
  804. password=self.project_member_pass)
  805. response = self.client.patch(self.shared_url, format='json', data=self.post_data)
  806. self.assertEqual(response.status_code, status.HTTP_200_OK)
  807. def test_allow_member_to_delete_others_annotation_in_shared_project(self):
  808. self.client.login(username=self.project_member_name,
  809. password=self.project_member_pass)
  810. response = self.client.delete(self.shared_url, format='json')
  811. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  812. @classmethod
  813. def doCleanups(cls):
  814. remove_all_role_mappings()
  815. class TestCommentListAPI(APITestCase):
  816. @classmethod
  817. def setUpTestData(cls):
  818. cls.project_member_name = 'project_member_name'
  819. cls.project_member_pass = 'project_member_pass'
  820. cls.another_project_member_name = 'another_project_member_name'
  821. cls.another_project_member_pass = 'another_project_member_pass'
  822. cls.non_project_member_name = 'non_project_member_name'
  823. cls.non_project_member_pass = 'non_project_member_pass'
  824. create_default_roles()
  825. cls.project_member = User.objects.create_user(username=cls.project_member_name,
  826. password=cls.project_member_pass)
  827. another_project_member = User.objects.create_user(username=cls.another_project_member_name,
  828. password=cls.another_project_member_pass)
  829. User.objects.create_user(username=cls.non_project_member_name, password=cls.non_project_member_pass)
  830. main_project = mommy.make('SequenceLabelingProject', users=[cls.project_member, another_project_member])
  831. main_project_doc = mommy.make('Document', project=main_project)
  832. cls.comment = mommy.make('Comment', document=main_project_doc, text='comment 1', user=cls.project_member)
  833. mommy.make('Comment', document=main_project_doc, text='comment 2', user=cls.project_member)
  834. mommy.make('Comment', document=main_project_doc, text='comment 3', user=another_project_member)
  835. cls.url = reverse(viewname='comment_list', args=[main_project.id, main_project_doc.id])
  836. assign_user_to_role(project_member=cls.project_member, project=main_project,
  837. role_name=settings.ROLE_ANNOTATOR)
  838. assign_user_to_role(project_member=another_project_member, project=main_project,
  839. role_name=settings.ROLE_ANNOTATOR)
  840. def test_returns_own_comments_to_project_member(self):
  841. self.client.login(username=self.project_member_name,
  842. password=self.project_member_pass)
  843. response = self.client.get(self.url, format='json')
  844. self.assertEqual(response.status_code, status.HTTP_200_OK)
  845. self.assertEqual(len(response.data['results']), 2)
  846. self.client.login(username=self.another_project_member_name,
  847. password=self.another_project_member_pass)
  848. response = self.client.get(self.url, format='json')
  849. self.assertEqual(response.status_code, status.HTTP_200_OK)
  850. self.assertEqual(len(response.data['results']), 1)
  851. def test_does_not_return_comments_to_non_project_member(self):
  852. self.client.login(username=self.non_project_member_name,
  853. password=self.non_project_member_pass)
  854. response = self.client.get(self.url, format='json')
  855. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  856. def test_does_not_allow_deletion_by_non_project_member(self):
  857. self.client.login(username=self.non_project_member_name,
  858. password=self.non_project_member_pass)
  859. response = self.client.delete('{}/{}'.format(self.url, self.comment.id), format='json')
  860. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  861. def test_does_not_allow_deletion_of_non_owned_comment(self):
  862. self.client.login(username=self.another_project_member_name,
  863. password=self.another_project_member_pass)
  864. response = self.client.delete('{}/{}'.format(self.url, self.comment.id), format='json')
  865. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  866. def test_create_update_delete_comment(self):
  867. self.client.login(username=self.project_member_name,
  868. password=self.project_member_pass)
  869. response = self.client.post(self.url, format='json', data={'text': 'comment'})
  870. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  871. self.assertEqual(response.data['user'], self.project_member.id)
  872. self.assertEqual(response.data['text'], 'comment')
  873. url = '{}/{}'.format(self.url, response.data['id'])
  874. response = self.client.get(self.url, format='json')
  875. self.assertEqual(response.status_code, status.HTTP_200_OK)
  876. self.assertEqual(len(response.data['results']), 3)
  877. response = self.client.patch(url, format='json', data={'text': 'new comment'})
  878. self.assertEqual(response.status_code, status.HTTP_200_OK)
  879. self.assertEqual(response.data['text'], 'new comment')
  880. response = self.client.delete(url)
  881. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  882. response = self.client.get(self.url, format='json')
  883. self.assertEqual(response.status_code, status.HTTP_200_OK)
  884. self.assertEqual(len(response.data['results']), 2)
  885. @classmethod
  886. def doCleanups(cls):
  887. remove_all_role_mappings()
  888. class TestSearch(APITestCase):
  889. @classmethod
  890. def setUpTestData(cls):
  891. cls.project_member_name = 'project_member_name'
  892. cls.project_member_pass = 'project_member_pass'
  893. cls.non_project_member_name = 'non_project_member_name'
  894. cls.non_project_member_pass = 'non_project_member_pass'
  895. create_default_roles()
  896. project_member = User.objects.create_user(username=cls.project_member_name,
  897. password=cls.project_member_pass)
  898. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  899. password=cls.non_project_member_pass)
  900. cls.main_project = mommy.make('TextClassificationProject', users=[project_member])
  901. cls.search_term = 'example'
  902. doc1 = mommy.make('Document', text=cls.search_term, project=cls.main_project)
  903. doc2 = mommy.make('Document', text='Lorem', project=cls.main_project)
  904. label1 = mommy.make('Label', project=cls.main_project)
  905. label2 = mommy.make('Label', project=cls.main_project)
  906. mommy.make('SequenceAnnotation', document=doc1, user=project_member, label=label1)
  907. mommy.make('SequenceAnnotation', document=doc2, user=project_member, label=label2)
  908. sub_project = mommy.make('TextClassificationProject', users=[non_project_member])
  909. mommy.make('Document', text=cls.search_term, project=sub_project)
  910. cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
  911. cls.data = {'q': cls.search_term}
  912. assign_user_to_role(project_member=project_member, project=cls.main_project,
  913. role_name=settings.ROLE_ANNOTATOR)
  914. def test_can_filter_doc_by_term(self):
  915. self.client.login(username=self.project_member_name,
  916. password=self.project_member_pass)
  917. response = self.client.get(self.url, format='json', data=self.data)
  918. count = Document.objects.filter(text__contains=self.search_term,
  919. project=self.main_project).count()
  920. self.assertEqual(response.data['count'], count)
  921. def test_can_order_doc_by_created_at_ascending(self):
  922. params = {'ordering': 'created_at'}
  923. self.client.login(username=self.project_member_name,
  924. password=self.project_member_pass)
  925. response = self.client.get(self.url, format='json', data=params)
  926. docs = Document.objects.filter(project=self.main_project).order_by('created_at').values()
  927. for d1, d2 in zip(response.data['results'], docs):
  928. self.assertEqual(d1['id'], d2['id'])
  929. def test_can_order_doc_by_created_at_descending(self):
  930. params = {'ordering': '-created_at'}
  931. self.client.login(username=self.project_member_name,
  932. password=self.project_member_pass)
  933. response = self.client.get(self.url, format='json', data=params)
  934. docs = Document.objects.filter(project=self.main_project).order_by('-created_at').values()
  935. for d1, d2 in zip(response.data['results'], docs):
  936. self.assertEqual(d1['id'], d2['id'])
  937. def test_can_order_doc_by_annotation_updated_at_ascending(self):
  938. params = {'ordering': 'seq_annotations__updated_at'}
  939. self.client.login(username=self.project_member_name,
  940. password=self.project_member_pass)
  941. response = self.client.get(self.url, format='json', data=params)
  942. docs = Document.objects.filter(project=self.main_project).order_by('seq_annotations__updated_at').values()
  943. for d1, d2 in zip(response.data['results'], docs):
  944. self.assertEqual(d1['id'], d2['id'])
  945. def test_can_order_doc_by_annotation_updated_at_descending(self):
  946. params = {'ordering': '-seq_annotations__updated_at'}
  947. self.client.login(username=self.project_member_name,
  948. password=self.project_member_pass)
  949. response = self.client.get(self.url, format='json', data=params)
  950. docs = Document.objects.filter(project=self.main_project).order_by('-seq_annotations__updated_at').values()
  951. for d1, d2 in zip(response.data['results'], docs):
  952. self.assertEqual(d1['id'], d2['id'])
  953. @classmethod
  954. def doCleanups(cls):
  955. remove_all_role_mappings()
  956. class TestFilter(APITestCase):
  957. @classmethod
  958. def setUpTestData(cls):
  959. cls.project_member_name = 'project_member_name'
  960. cls.project_member_pass = 'project_member_pass'
  961. create_default_roles()
  962. project_member = User.objects.create_user(username=cls.project_member_name,
  963. password=cls.project_member_pass)
  964. cls.main_project = mommy.make('SequenceLabelingProject', users=[project_member])
  965. cls.label1 = mommy.make('Label', project=cls.main_project)
  966. cls.label2 = mommy.make('Label', project=cls.main_project)
  967. doc1 = mommy.make('Document', project=cls.main_project)
  968. doc2 = mommy.make('Document', project=cls.main_project)
  969. mommy.make('Document', project=cls.main_project)
  970. mommy.make('SequenceAnnotation', document=doc1, user=project_member, label=cls.label1)
  971. mommy.make('SequenceAnnotation', document=doc2, user=project_member, label=cls.label2)
  972. cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
  973. cls.params = {'seq_annotations__label__id': cls.label1.id}
  974. assign_user_to_role(project_member=project_member, project=cls.main_project,
  975. role_name=settings.ROLE_ANNOTATOR)
  976. def test_can_filter_by_label(self):
  977. self.client.login(username=self.project_member_name,
  978. password=self.project_member_pass)
  979. response = self.client.get(self.url, format='json', data=self.params)
  980. docs = Document.objects.filter(project=self.main_project,
  981. seq_annotations__label__id=self.label1.id).values()
  982. for d1, d2 in zip(response.data['results'], docs):
  983. self.assertEqual(d1['id'], d2['id'])
  984. def test_can_filter_doc_with_annotation(self):
  985. params = {'seq_annotations__isnull': False}
  986. self.client.login(username=self.project_member_name,
  987. password=self.project_member_pass)
  988. response = self.client.get(self.url, format='json', data=params)
  989. docs = Document.objects.filter(project=self.main_project, seq_annotations__isnull=False).values()
  990. self.assertEqual(response.data['count'], docs.count())
  991. for d1, d2 in zip(response.data['results'], docs):
  992. self.assertEqual(d1['id'], d2['id'])
  993. def test_can_filter_doc_without_anotation(self):
  994. params = {'seq_annotations__isnull': True}
  995. self.client.login(username=self.project_member_name,
  996. password=self.project_member_pass)
  997. response = self.client.get(self.url, format='json', data=params)
  998. docs = Document.objects.filter(project=self.main_project, seq_annotations__isnull=True).values()
  999. self.assertEqual(response.data['count'], docs.count())
  1000. for d1, d2 in zip(response.data['results'], docs):
  1001. self.assertEqual(d1['id'], d2['id'])
  1002. @classmethod
  1003. def doCleanups(cls):
  1004. remove_all_role_mappings()
  1005. class TestUploader(APITestCase):
  1006. @classmethod
  1007. def setUpTestData(cls):
  1008. cls.super_user_name = 'super_user_name'
  1009. cls.super_user_pass = 'super_user_pass'
  1010. # Todo: change super_user to project_admin.
  1011. create_default_roles()
  1012. super_user = User.objects.create_superuser(username=cls.super_user_name,
  1013. password=cls.super_user_pass,
  1014. email='fizz@buzz.com')
  1015. cls.classification_project = mommy.make('TextClassificationProject',
  1016. users=[super_user], project_type=DOCUMENT_CLASSIFICATION)
  1017. cls.labeling_project = mommy.make('SequenceLabelingProject',
  1018. users=[super_user], project_type=SEQUENCE_LABELING)
  1019. cls.seq2seq_project = mommy.make('Seq2seqProject', users=[super_user], project_type=SEQ2SEQ)
  1020. assign_user_to_role(project_member=super_user, project=cls.classification_project,
  1021. role_name=settings.ROLE_PROJECT_ADMIN)
  1022. assign_user_to_role(project_member=super_user, project=cls.labeling_project,
  1023. role_name=settings.ROLE_PROJECT_ADMIN)
  1024. assign_user_to_role(project_member=super_user, project=cls.seq2seq_project,
  1025. role_name=settings.ROLE_PROJECT_ADMIN)
  1026. def setUp(self):
  1027. self.client.login(username=self.super_user_name,
  1028. password=self.super_user_pass)
  1029. def upload_test_helper(self, project_id, filename, file_format, expected_status, **kwargs):
  1030. url = reverse(viewname='doc_uploader', args=[project_id])
  1031. with open(os.path.join(DATA_DIR, filename), 'rb') as f:
  1032. response = self.client.post(url, data={'file': f, 'format': file_format})
  1033. self.assertEqual(response.status_code, expected_status)
  1034. def label_test_helper(self, project_id, expected_labels, expected_label_keys):
  1035. url = reverse(viewname='label_list', args=[project_id])
  1036. expected_keys = {key for label in expected_labels for key in label}
  1037. response = self.client.get(url).json()
  1038. actual_labels = [{key: value for (key, value) in label.items() if key in expected_keys}
  1039. for label in response]
  1040. self.assertCountEqual(actual_labels, expected_labels)
  1041. for label in response:
  1042. for expected_label_key in expected_label_keys:
  1043. self.assertIsNotNone(label.get(expected_label_key))
  1044. def test_can_upload_conll_format_file(self):
  1045. self.upload_test_helper(project_id=self.labeling_project.id,
  1046. filename='labeling.conll',
  1047. file_format='conll',
  1048. expected_status=status.HTTP_201_CREATED)
  1049. def test_cannot_upload_wrong_conll_format_file(self):
  1050. self.upload_test_helper(project_id=self.labeling_project.id,
  1051. filename='labeling.invalid.conll',
  1052. file_format='conll',
  1053. expected_status=status.HTTP_400_BAD_REQUEST)
  1054. def test_can_upload_classification_csv(self):
  1055. self.upload_test_helper(project_id=self.classification_project.id,
  1056. filename='example.csv',
  1057. file_format='csv',
  1058. expected_status=status.HTTP_201_CREATED)
  1059. def test_can_upload_classification_csv_with_out_of_order_columns(self):
  1060. self.upload_test_helper(project_id=self.classification_project.id,
  1061. filename='example_out_of_order_columns.csv',
  1062. file_format='csv',
  1063. expected_status=status.HTTP_201_CREATED)
  1064. self.label_test_helper(
  1065. project_id=self.classification_project.id,
  1066. expected_labels=[
  1067. {'text': 'Positive'},
  1068. {'text': 'Negative'},
  1069. ],
  1070. expected_label_keys=[],
  1071. )
  1072. def test_can_upload_csv_with_non_utf8_encoding(self):
  1073. self.upload_test_helper(project_id=self.classification_project.id,
  1074. filename='example.utf16.csv',
  1075. file_format='csv',
  1076. expected_status=status.HTTP_201_CREATED)
  1077. def test_can_upload_seq2seq_csv(self):
  1078. self.upload_test_helper(project_id=self.seq2seq_project.id,
  1079. filename='example.csv',
  1080. file_format='csv',
  1081. expected_status=status.HTTP_201_CREATED)
  1082. def test_can_upload_single_column_csv(self):
  1083. self.upload_test_helper(project_id=self.seq2seq_project.id,
  1084. filename='example_one_column.csv',
  1085. file_format='csv',
  1086. expected_status=status.HTTP_201_CREATED)
  1087. def test_can_upload_csv_file_does_not_match_column_and_row(self):
  1088. self.upload_test_helper(project_id=self.classification_project.id,
  1089. filename='example_column_and_row_not_matching.csv',
  1090. file_format='csv',
  1091. expected_status=status.HTTP_201_CREATED)
  1092. def test_cannot_upload_csv_file_has_too_many_columns(self):
  1093. self.upload_test_helper(project_id=self.classification_project.id,
  1094. filename='example.invalid.2.csv',
  1095. file_format='csv',
  1096. expected_status=status.HTTP_400_BAD_REQUEST)
  1097. def test_can_upload_classification_excel(self):
  1098. self.upload_test_helper(project_id=self.classification_project.id,
  1099. filename='example.xlsx',
  1100. file_format='excel',
  1101. expected_status=status.HTTP_201_CREATED)
  1102. def test_can_upload_seq2seq_excel(self):
  1103. self.upload_test_helper(project_id=self.seq2seq_project.id,
  1104. filename='example.xlsx',
  1105. file_format='excel',
  1106. expected_status=status.HTTP_201_CREATED)
  1107. def test_can_upload_single_column_excel(self):
  1108. self.upload_test_helper(project_id=self.seq2seq_project.id,
  1109. filename='example_one_column.xlsx',
  1110. file_format='excel',
  1111. expected_status=status.HTTP_201_CREATED)
  1112. def test_can_upload_excel_file_does_not_match_column_and_row(self):
  1113. self.upload_test_helper(project_id=self.classification_project.id,
  1114. filename='example_column_and_row_not_matching.xlsx',
  1115. file_format='excel',
  1116. expected_status=status.HTTP_201_CREATED)
  1117. def test_cannot_upload_excel_file_has_too_many_columns(self):
  1118. self.upload_test_helper(project_id=self.classification_project.id,
  1119. filename='example.invalid.2.xlsx',
  1120. file_format='excel',
  1121. expected_status=status.HTTP_400_BAD_REQUEST)
  1122. @override_settings(IMPORT_BATCH_SIZE=1)
  1123. def test_can_upload_small_batch_size(self):
  1124. self.upload_test_helper(project_id=self.seq2seq_project.id,
  1125. filename='example_one_column_no_header.xlsx',
  1126. file_format='excel',
  1127. expected_status=status.HTTP_201_CREATED)
  1128. def test_can_upload_classification_jsonl(self):
  1129. self.upload_test_helper(project_id=self.classification_project.id,
  1130. filename='classification.jsonl',
  1131. file_format='json',
  1132. expected_status=status.HTTP_201_CREATED)
  1133. self.label_test_helper(
  1134. project_id=self.classification_project.id,
  1135. expected_labels=[
  1136. {'text': 'positive', 'suffix_key': 'p', 'prefix_key': None},
  1137. {'text': 'negative', 'suffix_key': 'n', 'prefix_key': None},
  1138. {'text': 'neutral', 'suffix_key': 'n', 'prefix_key': 'ctrl'},
  1139. ],
  1140. expected_label_keys=[
  1141. 'background_color',
  1142. 'text_color',
  1143. ])
  1144. def test_can_upload_labeling_jsonl(self):
  1145. self.upload_test_helper(project_id=self.labeling_project.id,
  1146. filename='labeling.jsonl',
  1147. file_format='json',
  1148. expected_status=status.HTTP_201_CREATED)
  1149. self.label_test_helper(
  1150. project_id=self.labeling_project.id,
  1151. expected_labels=[
  1152. {'text': 'LOC', 'suffix_key': 'l', 'prefix_key': None},
  1153. {'text': 'ORG', 'suffix_key': 'o', 'prefix_key': None},
  1154. {'text': 'PER', 'suffix_key': 'p', 'prefix_key': None},
  1155. ],
  1156. expected_label_keys=[
  1157. 'background_color',
  1158. 'text_color',
  1159. ])
  1160. def test_can_upload_seq2seq_jsonl(self):
  1161. self.upload_test_helper(project_id=self.seq2seq_project.id,
  1162. filename='seq2seq.jsonl',
  1163. file_format='json',
  1164. expected_status=status.HTTP_201_CREATED)
  1165. def test_can_upload_plain_text(self):
  1166. self.upload_test_helper(project_id=self.classification_project.id,
  1167. filename='example.txt',
  1168. file_format='plain',
  1169. expected_status=status.HTTP_201_CREATED)
  1170. def test_can_upload_data_without_label(self):
  1171. self.upload_test_helper(project_id=self.classification_project.id,
  1172. filename='example.jsonl',
  1173. file_format='json',
  1174. expected_status=status.HTTP_201_CREATED)
  1175. @classmethod
  1176. def doCleanups(cls):
  1177. remove_all_role_mappings()
  1178. @override_settings(CLOUD_BROWSER_APACHE_LIBCLOUD_PROVIDER='LOCAL')
  1179. @override_settings(CLOUD_BROWSER_APACHE_LIBCLOUD_ACCOUNT=os.path.dirname(DATA_DIR))
  1180. @override_settings(CLOUD_BROWSER_APACHE_LIBCLOUD_SECRET_KEY='not-used')
  1181. class TestCloudUploader(TestUploader):
  1182. def upload_test_helper(self, project_id, filename, file_format, expected_status, **kwargs):
  1183. query_params = {
  1184. 'project_id': project_id,
  1185. 'upload_format': file_format,
  1186. 'container': kwargs.pop('container', os.path.basename(DATA_DIR)),
  1187. 'object': filename,
  1188. }
  1189. query_params.update(kwargs)
  1190. response = self.client.get(reverse('cloud_uploader'), query_params)
  1191. self.assertEqual(response.status_code, expected_status)
  1192. def test_cannot_upload_with_missing_file(self):
  1193. self.upload_test_helper(project_id=self.classification_project.id,
  1194. filename='does-not-exist',
  1195. file_format='json',
  1196. expected_status=status.HTTP_400_BAD_REQUEST)
  1197. def test_cannot_upload_with_missing_container(self):
  1198. self.upload_test_helper(project_id=self.classification_project.id,
  1199. filename='example.jsonl',
  1200. container='does-not-exist',
  1201. file_format='json',
  1202. expected_status=status.HTTP_400_BAD_REQUEST)
  1203. def test_cannot_upload_with_missing_query_parameters(self):
  1204. response = self.client.get(reverse('cloud_uploader'), {'project_id': self.classification_project.id})
  1205. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  1206. def test_can_upload_with_redirect(self):
  1207. self.upload_test_helper(project_id=self.classification_project.id,
  1208. filename='example.jsonl',
  1209. next='http://somewhere',
  1210. file_format='json',
  1211. expected_status=status.HTTP_302_FOUND)
  1212. def test_can_upload_with_redirect_to_blank(self):
  1213. self.upload_test_helper(project_id=self.classification_project.id,
  1214. filename='example.jsonl',
  1215. next='about:blank',
  1216. file_format='json',
  1217. expected_status=status.HTTP_201_CREATED)
  1218. class TestFeatures(APITestCase):
  1219. @classmethod
  1220. def setUpTestData(cls):
  1221. cls.user_name = 'user_name'
  1222. cls.user_pass = 'user_pass'
  1223. create_default_roles()
  1224. cls.user = User.objects.create_user(username=cls.user_name, password=cls.user_pass, email='fizz@buzz.com')
  1225. def setUp(self):
  1226. self.client.login(username=self.user_name, password=self.user_pass)
  1227. @override_settings(CLOUD_BROWSER_APACHE_LIBCLOUD_PROVIDER=None)
  1228. def test_no_cloud_upload(self):
  1229. response = self.client.get(reverse('features'))
  1230. self.assertFalse(response.json().get('cloud_upload'))
  1231. @override_settings(IMPORT_BATCH_SIZE=2)
  1232. class TestParser(APITestCase):
  1233. def parser_helper(self, filename, parser, include_label=True):
  1234. with open(os.path.join(DATA_DIR, filename), mode='rb') as f:
  1235. result = list(parser.parse(f))
  1236. for data in result:
  1237. for r in data:
  1238. self.assertIn('text', r)
  1239. if include_label:
  1240. self.assertIn('labels', r)
  1241. return result
  1242. def test_give_valid_data_to_conll_parser(self):
  1243. self.parser_helper(filename='labeling.conll', parser=CoNLLParser())
  1244. def test_give_valid_data_to_conll_parser_with_trailing_newlines(self):
  1245. result = self.parser_helper(filename='labeling.trailing.conll', parser=CoNLLParser())
  1246. self.assertEqual(len(result), 1)
  1247. self.assertEqual(len(result[0]), 1)
  1248. def test_plain_parser(self):
  1249. self.parser_helper(filename='example.txt', parser=PlainTextParser(), include_label=False)
  1250. def test_give_invalid_data_to_conll_parser(self):
  1251. with self.assertRaises(FileParseException):
  1252. self.parser_helper(filename='labeling.invalid.conll',
  1253. parser=CoNLLParser())
  1254. def test_give_classification_data_to_csv_parser(self):
  1255. self.parser_helper(filename='example.csv', parser=CSVParser(), include_label=False)
  1256. def test_give_seq2seq_data_to_csv_parser(self):
  1257. self.parser_helper(filename='example.csv', parser=CSVParser(), include_label=False)
  1258. def test_give_classification_data_to_json_parser(self):
  1259. self.parser_helper(filename='classification.jsonl', parser=JSONParser())
  1260. def test_give_labeling_data_to_json_parser(self):
  1261. self.parser_helper(filename='labeling.jsonl', parser=JSONParser())
  1262. def test_give_seq2seq_data_to_json_parser(self):
  1263. self.parser_helper(filename='seq2seq.jsonl', parser=JSONParser())
  1264. def test_give_data_without_label_to_json_parser(self):
  1265. self.parser_helper(filename='example.jsonl', parser=JSONParser(), include_label=False)
  1266. def test_give_labeling_data_to_fasttext_parser(self):
  1267. self.parser_helper(filename='example_fasttext.txt', parser=FastTextParser())
  1268. def test_give_data_without_label_name_to_fasttext_parser(self):
  1269. with self.assertRaises(FileParseException):
  1270. self.parser_helper(filename='example_fasttext_label_tag_without_name.txt', parser=FastTextParser())
  1271. def test_give_data_without_text_to_fasttext_parser(self):
  1272. with self.assertRaises(FileParseException):
  1273. self.parser_helper(filename='example_fasttext_without_text.txt', parser=FastTextParser())
  1274. class TestDownloader(APITestCase):
  1275. @classmethod
  1276. def setUpTestData(cls):
  1277. cls.super_user_name = 'super_user_name'
  1278. cls.super_user_pass = 'super_user_pass'
  1279. # Todo: change super_user to project_admin.
  1280. create_default_roles()
  1281. super_user = User.objects.create_superuser(username=cls.super_user_name,
  1282. password=cls.super_user_pass,
  1283. email='fizz@buzz.com')
  1284. cls.classification_project = mommy.make('TextClassificationProject',
  1285. users=[super_user], project_type=DOCUMENT_CLASSIFICATION)
  1286. cls.labeling_project = mommy.make('SequenceLabelingProject',
  1287. users=[super_user], project_type=SEQUENCE_LABELING)
  1288. cls.seq2seq_project = mommy.make('Seq2seqProject', users=[super_user], project_type=SEQ2SEQ)
  1289. cls.speech2text_project = mommy.make('Speech2textProject', users=[super_user], project_type=SPEECH2TEXT)
  1290. cls.classification_url = reverse(viewname='doc_downloader', args=[cls.classification_project.id])
  1291. cls.labeling_url = reverse(viewname='doc_downloader', args=[cls.labeling_project.id])
  1292. cls.seq2seq_url = reverse(viewname='doc_downloader', args=[cls.seq2seq_project.id])
  1293. cls.speech2text_url = reverse(viewname='doc_downloader', args=[cls.speech2text_project.id])
  1294. def setUp(self):
  1295. self.client.login(username=self.super_user_name,
  1296. password=self.super_user_pass)
  1297. def download_test_helper(self, url, format, expected_status):
  1298. response = self.client.get(url, data={'q': format})
  1299. self.assertEqual(response.status_code, expected_status)
  1300. def test_cannot_download_conll_format_file(self):
  1301. self.download_test_helper(url=self.labeling_url,
  1302. format='conll',
  1303. expected_status=status.HTTP_400_BAD_REQUEST)
  1304. def test_can_download_classification_csv(self):
  1305. self.download_test_helper(url=self.classification_url,
  1306. format='csv',
  1307. expected_status=status.HTTP_200_OK)
  1308. def test_can_download_labeling_csv(self):
  1309. self.download_test_helper(url=self.labeling_url,
  1310. format='csv',
  1311. expected_status=status.HTTP_200_OK)
  1312. def test_can_download_seq2seq_csv(self):
  1313. self.download_test_helper(url=self.seq2seq_url,
  1314. format='csv',
  1315. expected_status=status.HTTP_200_OK)
  1316. def test_can_download_classification_jsonl(self):
  1317. self.download_test_helper(url=self.classification_url,
  1318. format='json',
  1319. expected_status=status.HTTP_200_OK)
  1320. def test_can_download_labeling_jsonl(self):
  1321. self.download_test_helper(url=self.labeling_url,
  1322. format='json',
  1323. expected_status=status.HTTP_200_OK)
  1324. def test_can_download_seq2seq_jsonl(self):
  1325. self.download_test_helper(url=self.seq2seq_url,
  1326. format='json',
  1327. expected_status=status.HTTP_200_OK)
  1328. def test_can_download_speech2text_jsonl(self):
  1329. self.download_test_helper(url=self.speech2text_url,
  1330. format='json',
  1331. expected_status=status.HTTP_200_OK)
  1332. def test_can_download_labelling_jsonl(self):
  1333. self.download_test_helper(url=self.labeling_url,
  1334. format='jsonl',
  1335. expected_status=status.HTTP_200_OK)
  1336. def test_can_download_plain_text(self):
  1337. self.download_test_helper(url=self.classification_url,
  1338. format='plain',
  1339. expected_status=status.HTTP_400_BAD_REQUEST)
  1340. def test_can_download_classification_fasttext(self):
  1341. self.download_test_helper(url=self.classification_url,
  1342. format='txt',
  1343. expected_status=status.HTTP_200_OK)
  1344. class TestStatisticsAPI(APITestCase, TestUtilsMixin):
  1345. @classmethod
  1346. def setUpTestData(cls):
  1347. cls.super_user_name = 'super_user_name'
  1348. cls.super_user_pass = 'super_user_pass'
  1349. cls.other_user_name = 'other_user_name'
  1350. cls.other_user_pass = 'other_user_pass'
  1351. create_default_roles()
  1352. # Todo: change super_user to project_admin.
  1353. super_user = User.objects.create_superuser(username=cls.super_user_name,
  1354. password=cls.super_user_pass,
  1355. email='fizz@buzz.com')
  1356. other_user = User.objects.create_user(username=cls.other_user_name,
  1357. password=cls.other_user_pass,
  1358. email='bar@buzz.com')
  1359. cls.project = mommy.make('TextClassificationProject', users=[super_user, other_user])
  1360. doc1 = mommy.make('Document', project=cls.project)
  1361. doc2 = mommy.make('Document', project=cls.project)
  1362. mommy.make('DocumentAnnotation', document=doc1, user=super_user)
  1363. mommy.make('DocumentAnnotation', document=doc2, user=other_user)
  1364. cls.url = reverse(viewname='statistics', args=[cls.project.id])
  1365. cls.doc = Document.objects.filter(project=cls.project)
  1366. assign_user_to_role(project_member=other_user, project=cls.project,
  1367. role_name=settings.ROLE_ANNOTATOR)
  1368. @classmethod
  1369. def doCleanups(cls):
  1370. remove_all_role_mappings()
  1371. def test_returns_exact_progress(self):
  1372. self.client.login(username=self.super_user_name,
  1373. password=self.super_user_pass)
  1374. response = self.client.get(self.url, format='json')
  1375. self.assertEqual(response.data['total'], 2)
  1376. self.assertEqual(response.data['remaining'], 1)
  1377. def test_returns_exact_progress_with_collaborative_annotation(self):
  1378. self._patch_project(self.project, 'collaborative_annotation', True)
  1379. self.client.login(username=self.other_user_name,
  1380. password=self.other_user_pass)
  1381. response = self.client.get(self.url, format='json')
  1382. self.assertEqual(response.data['total'], 2)
  1383. self.assertEqual(response.data['remaining'], 0)
  1384. def test_returns_user_count(self):
  1385. self.client.login(username=self.super_user_name,
  1386. password=self.super_user_pass)
  1387. response = self.client.get(self.url, format='json')
  1388. self.assertIn('label', response.data)
  1389. self.assertIsInstance(response.data['label'], dict)
  1390. def test_returns_label_count(self):
  1391. self.client.login(username=self.super_user_name,
  1392. password=self.super_user_pass)
  1393. response = self.client.get(self.url, format='json')
  1394. self.assertIn('user', response.data)
  1395. self.assertIsInstance(response.data['user'], dict)
  1396. def test_returns_partial_response(self):
  1397. self.client.login(username=self.super_user_name,
  1398. password=self.super_user_pass)
  1399. response = self.client.get(f'{self.url}?include=user', format='json')
  1400. self.assertEqual(list(response.data.keys()), ['user'])
  1401. class TestUserAPI(APITestCase):
  1402. @classmethod
  1403. def setUpTestData(cls):
  1404. cls.super_user_name = 'super_user_name'
  1405. cls.super_user_pass = 'super_user_pass'
  1406. create_default_roles()
  1407. User.objects.create_superuser(username=cls.super_user_name,
  1408. password=cls.super_user_pass,
  1409. email='fizz@buzz.com')
  1410. cls.url = reverse(viewname='user_list')
  1411. def test_returns_user_count(self):
  1412. self.client.login(username=self.super_user_name,
  1413. password=self.super_user_pass)
  1414. response = self.client.get(self.url, format='json')
  1415. self.assertEqual(1, len(response.data))
  1416. class TestRoleAPI(APITestCase):
  1417. @classmethod
  1418. def setUpTestData(cls):
  1419. cls.user_name = 'user_name'
  1420. cls.user_pass = 'user_pass'
  1421. cls.project_admin_name = 'project_admin_name'
  1422. cls.project_admin_pass = 'project_admin_pass'
  1423. create_default_roles()
  1424. cls.user = User.objects.create_user(username=cls.user_name,
  1425. password=cls.user_pass)
  1426. User.objects.create_superuser(username=cls.project_admin_name,
  1427. password=cls.project_admin_pass,
  1428. email='fizz@buzz.com')
  1429. cls.url = reverse(viewname='roles')
  1430. def test_cannot_create_multiple_roles_with_same_name(self):
  1431. self.client.login(username=self.project_admin_name,
  1432. password=self.project_admin_pass)
  1433. roles = [
  1434. {'name': 'examplerole', 'description': 'example'},
  1435. {'name': 'examplerole', 'description': 'example'}
  1436. ]
  1437. self.client.post(self.url, format='json', data=roles[0])
  1438. second_response = self.client.post(self.url, format='json', data=roles[1])
  1439. self.assertEqual(second_response.status_code, status.HTTP_400_BAD_REQUEST)
  1440. def test_nonadmin_cannot_create_role(self):
  1441. self.client.login(username=self.user_name,
  1442. password=self.user_pass)
  1443. data = {'name': 'testrole', 'description': 'example'}
  1444. response = self.client.post(self.url, format='json', data=data)
  1445. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  1446. def test_admin_can_create_role(self):
  1447. self.client.login(username=self.project_admin_name,
  1448. password=self.project_admin_pass)
  1449. data = {'name': 'testrole', 'description': 'example'}
  1450. response = self.client.post(self.url, format='json', data=data)
  1451. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  1452. def test_admin_can_get_roles(self):
  1453. self.client.login(username=self.project_admin_name,
  1454. password=self.project_admin_pass)
  1455. response = self.client.get(self.url, format='json')
  1456. self.assertEqual(response.status_code, status.HTTP_200_OK)
  1457. class TestRoleMappingListAPI(APITestCase):
  1458. @classmethod
  1459. def setUpTestData(cls):
  1460. cls.project_member_name = 'project_member_name'
  1461. cls.project_member_pass = 'project_member_pass'
  1462. cls.second_project_member_name = 'second_project_member_name'
  1463. cls.second_project_member_pass = 'second_project_member_pass'
  1464. cls.project_admin_name = 'project_admin_name'
  1465. cls.project_admin_pass = 'project_admin_pass'
  1466. create_default_roles()
  1467. project_member = User.objects.create_user(username=cls.project_member_name,
  1468. password=cls.project_member_pass)
  1469. cls.second_project_member = User.objects.create_user(username=cls.second_project_member_name,
  1470. password=cls.second_project_member_pass)
  1471. project_admin = User.objects.create_user(username=cls.project_admin_name,
  1472. password=cls.project_admin_pass)
  1473. cls.main_project = mommy.make('Project', users=[project_member, project_admin, cls.second_project_member])
  1474. cls.other_project = mommy.make('Project', users=[cls.second_project_member, project_admin])
  1475. cls.admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN)
  1476. cls.role = mommy.make('Role', name='otherrole')
  1477. mommy.make('RoleMapping', role=cls.admin_role, project=cls.main_project, user=project_admin)
  1478. cls.data = {'user': project_member.id, 'role': cls.admin_role.id, 'project': cls.main_project.id}
  1479. cls.other_url = reverse(viewname='rolemapping_list', args=[cls.other_project.id])
  1480. cls.url = reverse(viewname='rolemapping_list', args=[cls.main_project.id])
  1481. def test_returns_mappings_to_project_admin(self):
  1482. self.client.login(username=self.project_admin_name,
  1483. password=self.project_admin_pass)
  1484. response = self.client.get(self.url, format='json')
  1485. self.assertEqual(response.status_code, status.HTTP_200_OK)
  1486. def test_allows_superuser_to_create_mapping(self):
  1487. self.client.login(username=self.project_admin_name,
  1488. password=self.project_admin_pass)
  1489. response = self.client.post(self.url, format='json', data=self.data)
  1490. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  1491. def test_do_not_allow_nonadmin_to_create_mapping(self):
  1492. self.client.login(username=self.project_member_name,
  1493. password=self.project_member_pass)
  1494. response = self.client.post(self.url, format='json', data=self.data)
  1495. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  1496. def test_do_not_return_mappings_to_nonadmin(self):
  1497. self.client.login(username=self.project_member_name,
  1498. password=self.project_member_pass)
  1499. response = self.client.get(self.url, format='json')
  1500. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  1501. class TestRoleMappingDetailAPI(APITestCase):
  1502. @classmethod
  1503. def setUpTestData(cls):
  1504. cls.project_admin_name = 'project_admin_name'
  1505. cls.project_admin_pass = 'project_admin_pass'
  1506. cls.project_member_name = 'project_member_name'
  1507. cls.project_member_pass = 'project_member_pass'
  1508. cls.non_project_member_name = 'non_project_member_name'
  1509. cls.non_project_member_pass = 'non_project_member_pass'
  1510. create_default_roles()
  1511. project_admin = User.objects.create_user(username=cls.project_admin_name,
  1512. password=cls.project_admin_pass)
  1513. project_member = User.objects.create_user(username=cls.project_member_name,
  1514. password=cls.project_member_pass)
  1515. User.objects.create_user(username=cls.non_project_member_name, password=cls.non_project_member_pass)
  1516. project = mommy.make('Project', users=[project_admin, project_member])
  1517. admin_role = Role.objects.get(name=settings.ROLE_PROJECT_ADMIN)
  1518. annotator_role = Role.objects.get(name=settings.ROLE_ANNOTATOR)
  1519. cls.rolemapping = mommy.make('RoleMapping', role=admin_role, project=project, user=project_admin)
  1520. cls.url = reverse(viewname='rolemapping_detail', args=[project.id, cls.rolemapping.id])
  1521. cls.data = {'role': annotator_role.id }
  1522. def test_returns_rolemapping_to_project_member(self):
  1523. self.client.login(username=self.project_admin_name,
  1524. password=self.project_admin_pass)
  1525. response = self.client.get(self.url, format='json')
  1526. self.assertEqual(response.data['id'], self.rolemapping.id)
  1527. def test_do_not_return_mapping_to_non_project_member(self):
  1528. self.client.login(username=self.non_project_member_name,
  1529. password=self.non_project_member_pass)
  1530. response = self.client.get(self.url, format='json')
  1531. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  1532. def test_allows_admin_to_update_mapping(self):
  1533. self.client.login(username=self.project_admin_name,
  1534. password=self.project_admin_pass)
  1535. response = self.client.patch(self.url, format='json', data=self.data)
  1536. self.assertEqual(response.data['role'], self.data['role'])
  1537. def test_disallows_project_member_to_update_mapping(self):
  1538. self.client.login(username=self.project_member_name,
  1539. password=self.project_member_pass)
  1540. response = self.client.patch(self.url, format='json', data=self.data)
  1541. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  1542. def test_allows_admin_to_delete_mapping(self):
  1543. self.client.login(username=self.project_admin_name,
  1544. password=self.project_admin_pass)
  1545. response = self.client.delete(self.url, format='json')
  1546. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  1547. def test_disallows_project_member_to_delete_mapping(self):
  1548. self.client.login(username=self.project_member_name,
  1549. password=self.project_member_pass)
  1550. response = self.client.delete(self.url, format='json')
  1551. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)