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.

890 lines
47 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. import os
  2. from rest_framework import status
  3. from rest_framework.reverse import reverse
  4. from rest_framework.test import APITestCase
  5. from model_mommy import mommy
  6. from ..models import User, SequenceAnnotation, Document
  7. from ..models import DOCUMENT_CLASSIFICATION, SEQUENCE_LABELING, SEQ2SEQ
  8. from ..utils import PlainTextParser, CoNLLParser, JSONParser, CSVParser
  9. from ..exceptions import FileParseException
  10. DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
  11. class TestProjectListAPI(APITestCase):
  12. @classmethod
  13. def setUpTestData(cls):
  14. cls.main_project_member_name = 'project_member_name'
  15. cls.main_project_member_pass = 'project_member_pass'
  16. cls.sub_project_member_name = 'sub_project_member_name'
  17. cls.sub_project_member_pass = 'sub_project_member_pass'
  18. cls.super_user_name = 'super_user_name'
  19. cls.super_user_pass = 'super_user_pass'
  20. main_project_member = User.objects.create_user(username=cls.main_project_member_name,
  21. password=cls.main_project_member_pass)
  22. sub_project_member = User.objects.create_user(username=cls.sub_project_member_name,
  23. password=cls.sub_project_member_pass)
  24. # Todo: change super_user to project_admin.
  25. super_user = User.objects.create_superuser(username=cls.super_user_name,
  26. password=cls.super_user_pass,
  27. email='fizz@buzz.com')
  28. cls.main_project = mommy.make('server.TextClassificationProject', users=[main_project_member])
  29. cls.sub_project = mommy.make('server.TextClassificationProject', users=[sub_project_member])
  30. cls.url = reverse(viewname='project_list')
  31. cls.data = {'name': 'example', 'project_type': 'DocumentClassification',
  32. 'description': 'example', 'guideline': 'example',
  33. 'resourcetype': 'TextClassificationProject'}
  34. cls.num_project = main_project_member.projects.count()
  35. def test_returns_main_project_to_main_project_member(self):
  36. self.client.login(username=self.main_project_member_name,
  37. password=self.main_project_member_pass)
  38. response = self.client.get(self.url, format='json')
  39. project = response.data[0]
  40. num_project = len(response.data)
  41. self.assertEqual(num_project, self.num_project)
  42. self.assertEqual(project['id'], self.main_project.id)
  43. def test_do_not_return_main_project_to_sub_project_member(self):
  44. self.client.login(username=self.sub_project_member_name,
  45. password=self.sub_project_member_pass)
  46. response = self.client.get(self.url, format='json')
  47. project = response.data[0]
  48. num_project = len(response.data)
  49. self.assertEqual(num_project, self.num_project)
  50. self.assertNotEqual(project['id'], self.main_project.id)
  51. def test_allows_superuser_to_create_project(self):
  52. self.client.login(username=self.super_user_name,
  53. password=self.super_user_pass)
  54. response = self.client.post(self.url, format='json', data=self.data)
  55. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  56. def test_disallows_project_member_to_create_project(self):
  57. self.client.login(username=self.main_project_member_name,
  58. password=self.main_project_member_pass)
  59. response = self.client.post(self.url, format='json', data=self.data)
  60. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  61. class TestProjectDetailAPI(APITestCase):
  62. @classmethod
  63. def setUpTestData(cls):
  64. cls.project_member_name = 'project_member_name'
  65. cls.project_member_pass = 'project_member_pass'
  66. cls.non_project_member_name = 'non_project_member_name'
  67. cls.non_project_member_pass = 'non_project_member_pass'
  68. cls.super_user_name = 'super_user_name'
  69. cls.super_user_pass = 'super_user_pass'
  70. cls.project_member = User.objects.create_user(username=cls.project_member_name,
  71. password=cls.project_member_pass)
  72. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  73. password=cls.non_project_member_pass)
  74. # Todo: change super_user to project_admin.
  75. super_user = User.objects.create_superuser(username=cls.super_user_name,
  76. password=cls.super_user_pass,
  77. email='fizz@buzz.com')
  78. cls.main_project = mommy.make('server.TextClassificationProject', users=[cls.project_member, super_user])
  79. sub_project = mommy.make('server.TextClassificationProject', users=[non_project_member])
  80. cls.url = reverse(viewname='project_detail', args=[cls.main_project.id])
  81. cls.data = {'description': 'lorem'}
  82. def test_returns_main_project_detail_to_main_project_member(self):
  83. self.client.login(username=self.project_member_name,
  84. password=self.project_member_pass)
  85. response = self.client.get(self.url, format='json')
  86. self.assertEqual(response.data['id'], self.main_project.id)
  87. def test_do_not_return_main_project_to_sub_project_member(self):
  88. self.client.login(username=self.non_project_member_name,
  89. password=self.non_project_member_pass)
  90. response = self.client.get(self.url, format='json')
  91. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  92. def test_allows_superuser_to_update_project(self):
  93. self.client.login(username=self.super_user_name,
  94. password=self.super_user_pass)
  95. response = self.client.patch(self.url, format='json', data=self.data)
  96. self.assertEqual(response.data['description'], self.data['description'])
  97. def test_disallows_project_member_to_update_project(self):
  98. self.client.login(username=self.project_member_name,
  99. password=self.project_member_pass)
  100. response = self.client.patch(self.url, format='json', data=self.data)
  101. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  102. def test_allows_superuser_to_delete_project(self):
  103. self.client.login(username=self.super_user_name,
  104. password=self.super_user_pass)
  105. response = self.client.delete(self.url, format='json')
  106. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  107. def test_disallows_project_member_to_delete_project(self):
  108. self.client.login(username=self.project_member_name,
  109. password=self.project_member_pass)
  110. response = self.client.delete(self.url, format='json')
  111. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  112. class TestLabelListAPI(APITestCase):
  113. @classmethod
  114. def setUpTestData(cls):
  115. cls.project_member_name = 'project_member_name'
  116. cls.project_member_pass = 'project_member_pass'
  117. cls.non_project_member_name = 'non_project_member_name'
  118. cls.non_project_member_pass = 'non_project_member_pass'
  119. cls.super_user_name = 'super_user_name'
  120. cls.super_user_pass = 'super_user_pass'
  121. project_member = User.objects.create_user(username=cls.project_member_name,
  122. password=cls.project_member_pass)
  123. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  124. password=cls.non_project_member_pass)
  125. # Todo: change super_user to project_admin.
  126. super_user = User.objects.create_superuser(username=cls.super_user_name,
  127. password=cls.super_user_pass,
  128. email='fizz@buzz.com')
  129. cls.main_project = mommy.make('server.Project', users=[project_member, super_user])
  130. cls.main_project_label = mommy.make('server.Label', project=cls.main_project)
  131. sub_project = mommy.make('server.Project', users=[non_project_member])
  132. mommy.make('server.Label', project=sub_project)
  133. cls.url = reverse(viewname='label_list', args=[cls.main_project.id])
  134. cls.data = {'text': 'example'}
  135. def test_returns_labels_to_project_member(self):
  136. self.client.login(username=self.project_member_name,
  137. password=self.project_member_pass)
  138. response = self.client.get(self.url, format='json')
  139. self.assertEqual(response.status_code, status.HTTP_200_OK)
  140. def test_do_not_return_labels_to_non_project_member(self):
  141. self.client.login(username=self.non_project_member_name,
  142. password=self.non_project_member_pass)
  143. response = self.client.get(self.url, format='json')
  144. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  145. def test_do_not_return_labels_of_other_projects(self):
  146. self.client.login(username=self.project_member_name,
  147. password=self.project_member_pass)
  148. response = self.client.get(self.url, format='json')
  149. label = response.data[0]
  150. num_labels = len(response.data)
  151. self.assertEqual(num_labels, len(self.main_project.labels.all()))
  152. self.assertEqual(label['id'], self.main_project_label.id)
  153. def test_allows_superuser_to_create_label(self):
  154. self.client.login(username=self.super_user_name,
  155. password=self.super_user_pass)
  156. response = self.client.post(self.url, format='json', data=self.data)
  157. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  158. def test_disallows_project_member_to_create_label(self):
  159. self.client.login(username=self.project_member_name,
  160. password=self.project_member_pass)
  161. response = self.client.post(self.url, format='json', data=self.data)
  162. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  163. class TestLabelDetailAPI(APITestCase):
  164. @classmethod
  165. def setUpTestData(cls):
  166. cls.project_member_name = 'project_member_name'
  167. cls.project_member_pass = 'project_member_pass'
  168. cls.non_project_member_name = 'non_project_member_name'
  169. cls.non_project_member_pass = 'non_project_member_pass'
  170. cls.super_user_name = 'super_user_name'
  171. cls.super_user_pass = 'super_user_pass'
  172. project_member = User.objects.create_user(username=cls.project_member_name,
  173. password=cls.project_member_pass)
  174. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  175. password=cls.non_project_member_pass)
  176. # Todo: change super_user to project_admin.
  177. super_user = User.objects.create_superuser(username=cls.super_user_name,
  178. password=cls.super_user_pass,
  179. email='fizz@buzz.com')
  180. project = mommy.make('server.Project', users=[project_member, super_user])
  181. cls.label = mommy.make('server.Label', project=project)
  182. cls.url = reverse(viewname='label_detail', args=[project.id, cls.label.id])
  183. cls.data = {'text': 'example'}
  184. def test_returns_label_to_project_member(self):
  185. self.client.login(username=self.project_member_name,
  186. password=self.project_member_pass)
  187. response = self.client.get(self.url, format='json')
  188. self.assertEqual(response.data['id'], self.label.id)
  189. def test_do_not_return_label_to_non_project_member(self):
  190. self.client.login(username=self.non_project_member_name,
  191. password=self.non_project_member_pass)
  192. response = self.client.get(self.url, format='json')
  193. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  194. def test_allows_superuser_to_update_label(self):
  195. self.client.login(username=self.super_user_name,
  196. password=self.super_user_pass)
  197. response = self.client.patch(self.url, format='json', data=self.data)
  198. self.assertEqual(response.data['text'], self.data['text'])
  199. def test_disallows_project_member_to_update_label(self):
  200. self.client.login(username=self.project_member_name,
  201. password=self.project_member_pass)
  202. response = self.client.patch(self.url, format='json', data=self.data)
  203. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  204. def test_allows_superuser_to_delete_label(self):
  205. self.client.login(username=self.super_user_name,
  206. password=self.super_user_pass)
  207. response = self.client.delete(self.url, format='json')
  208. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  209. def test_disallows_project_member_to_delete_label(self):
  210. self.client.login(username=self.project_member_name,
  211. password=self.project_member_pass)
  212. response = self.client.delete(self.url, format='json')
  213. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  214. class TestDocumentListAPI(APITestCase):
  215. @classmethod
  216. def setUpTestData(cls):
  217. cls.project_member_name = 'project_member_name'
  218. cls.project_member_pass = 'project_member_pass'
  219. cls.non_project_member_name = 'non_project_member_name'
  220. cls.non_project_member_pass = 'non_project_member_pass'
  221. cls.super_user_name = 'super_user_name'
  222. cls.super_user_pass = 'super_user_pass'
  223. project_member = User.objects.create_user(username=cls.project_member_name,
  224. password=cls.project_member_pass)
  225. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  226. password=cls.non_project_member_pass)
  227. # Todo: change super_user to project_admin.
  228. super_user = User.objects.create_superuser(username=cls.super_user_name,
  229. password=cls.super_user_pass,
  230. email='fizz@buzz.com')
  231. cls.main_project = mommy.make('server.TextClassificationProject', users=[project_member, super_user])
  232. mommy.make('server.Document', project=cls.main_project)
  233. sub_project = mommy.make('server.TextClassificationProject', users=[non_project_member])
  234. mommy.make('server.Document', project=sub_project)
  235. cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
  236. cls.data = {'text': 'example'}
  237. def test_returns_docs_to_project_member(self):
  238. self.client.login(username=self.project_member_name,
  239. password=self.project_member_pass)
  240. response = self.client.get(self.url, format='json')
  241. self.assertEqual(response.status_code, status.HTTP_200_OK)
  242. def test_do_not_return_docs_to_non_project_member(self):
  243. self.client.login(username=self.non_project_member_name,
  244. password=self.non_project_member_pass)
  245. response = self.client.get(self.url, format='json')
  246. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  247. def test_do_not_return_docs_of_other_projects(self):
  248. self.client.login(username=self.project_member_name,
  249. password=self.project_member_pass)
  250. response = self.client.get(self.url, format='json')
  251. self.assertEqual(response.data['count'], self.main_project.documents.count())
  252. def test_allows_superuser_to_create_doc(self):
  253. self.client.login(username=self.super_user_name,
  254. password=self.super_user_pass)
  255. response = self.client.post(self.url, format='json', data=self.data)
  256. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  257. def test_disallows_project_member_to_create_doc(self):
  258. self.client.login(username=self.project_member_name,
  259. password=self.project_member_pass)
  260. response = self.client.post(self.url, format='json', data=self.data)
  261. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  262. class TestDocumentDetailAPI(APITestCase):
  263. @classmethod
  264. def setUpTestData(cls):
  265. cls.project_member_name = 'project_member_name'
  266. cls.project_member_pass = 'project_member_pass'
  267. cls.non_project_member_name = 'non_project_member_name'
  268. cls.non_project_member_pass = 'non_project_member_pass'
  269. cls.super_user_name = 'super_user_name'
  270. cls.super_user_pass = 'super_user_pass'
  271. project_member = User.objects.create_user(username=cls.project_member_name,
  272. password=cls.project_member_pass)
  273. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  274. password=cls.non_project_member_pass)
  275. # Todo: change super_user to project_admin.
  276. super_user = User.objects.create_superuser(username=cls.super_user_name,
  277. password=cls.super_user_pass,
  278. email='fizz@buzz.com')
  279. project = mommy.make('server.TextClassificationProject', users=[project_member, super_user])
  280. cls.doc = mommy.make('server.Document', project=project)
  281. cls.url = reverse(viewname='doc_detail', args=[project.id, cls.doc.id])
  282. cls.data = {'text': 'example'}
  283. def test_returns_doc_to_project_member(self):
  284. self.client.login(username=self.project_member_name,
  285. password=self.project_member_pass)
  286. response = self.client.get(self.url, format='json')
  287. self.assertEqual(response.data['id'], self.doc.id)
  288. def test_do_not_return_doc_to_non_project_member(self):
  289. self.client.login(username=self.non_project_member_name,
  290. password=self.non_project_member_pass)
  291. response = self.client.get(self.url, format='json')
  292. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  293. def test_allows_superuser_to_update_doc(self):
  294. self.client.login(username=self.super_user_name,
  295. password=self.super_user_pass)
  296. response = self.client.patch(self.url, format='json', data=self.data)
  297. self.assertEqual(response.data['text'], self.data['text'])
  298. def test_disallows_project_member_to_update_doc(self):
  299. self.client.login(username=self.project_member_name,
  300. password=self.project_member_pass)
  301. response = self.client.patch(self.url, format='json', data=self.data)
  302. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  303. def test_allows_superuser_to_delete_doc(self):
  304. self.client.login(username=self.super_user_name,
  305. password=self.super_user_pass)
  306. response = self.client.delete(self.url, format='json')
  307. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  308. def test_disallows_project_member_to_delete_doc(self):
  309. self.client.login(username=self.project_member_name,
  310. password=self.project_member_pass)
  311. response = self.client.delete(self.url, format='json')
  312. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  313. class TestAnnotationListAPI(APITestCase):
  314. @classmethod
  315. def setUpTestData(cls):
  316. cls.project_member_name = 'project_member_name'
  317. cls.project_member_pass = 'project_member_pass'
  318. cls.another_project_member_name = 'another_project_member_name'
  319. cls.another_project_member_pass = 'another_project_member_pass'
  320. cls.non_project_member_name = 'non_project_member_name'
  321. cls.non_project_member_pass = 'non_project_member_pass'
  322. project_member = User.objects.create_user(username=cls.project_member_name,
  323. password=cls.project_member_pass)
  324. another_project_member = User.objects.create_user(username=cls.another_project_member_name,
  325. password=cls.another_project_member_pass)
  326. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  327. password=cls.non_project_member_pass)
  328. main_project = mommy.make('server.SequenceLabelingProject', users=[project_member, another_project_member])
  329. main_project_label = mommy.make('server.Label', project=main_project)
  330. main_project_doc = mommy.make('server.Document', project=main_project)
  331. mommy.make('server.SequenceAnnotation', document=main_project_doc, user=project_member)
  332. mommy.make('server.SequenceAnnotation', document=main_project_doc, user=another_project_member)
  333. sub_project = mommy.make('server.SequenceLabelingProject', users=[non_project_member])
  334. sub_project_doc = mommy.make('server.Document', project=sub_project)
  335. mommy.make('server.SequenceAnnotation', document=sub_project_doc)
  336. cls.url = reverse(viewname='annotation_list', args=[main_project.id, main_project_doc.id])
  337. cls.post_data = {'start_offset': 0, 'end_offset': 1, 'label': main_project_label.id}
  338. cls.num_entity_of_project_member = SequenceAnnotation.objects.filter(document=main_project_doc,
  339. user=project_member).count()
  340. def test_returns_annotations_to_project_member(self):
  341. self.client.login(username=self.project_member_name,
  342. password=self.project_member_pass)
  343. response = self.client.get(self.url, format='json')
  344. self.assertEqual(response.status_code, status.HTTP_200_OK)
  345. def test_do_not_return_annotations_to_non_project_member(self):
  346. self.client.login(username=self.non_project_member_name,
  347. password=self.non_project_member_pass)
  348. response = self.client.get(self.url, format='json')
  349. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  350. def test_do_not_return_annotations_of_another_project_member(self):
  351. self.client.login(username=self.project_member_name,
  352. password=self.project_member_pass)
  353. response = self.client.get(self.url, format='json')
  354. self.assertEqual(len(response.data), self.num_entity_of_project_member)
  355. def test_allows_project_member_to_create_annotation(self):
  356. self.client.login(username=self.project_member_name,
  357. password=self.project_member_pass)
  358. response = self.client.post(self.url, format='json', data=self.post_data)
  359. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  360. def test_disallows_non_project_member_to_create_annotation(self):
  361. self.client.login(username=self.non_project_member_name,
  362. password=self.non_project_member_pass)
  363. response = self.client.post(self.url, format='json', data=self.post_data)
  364. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  365. class TestAnnotationDetailAPI(APITestCase):
  366. @classmethod
  367. def setUpTestData(cls):
  368. cls.project_member_name = 'project_member_name'
  369. cls.project_member_pass = 'project_member_pass'
  370. cls.another_project_member_name = 'another_project_member_name'
  371. cls.another_project_member_pass = 'another_project_member_pass'
  372. cls.non_project_member_name = 'non_project_member_name'
  373. cls.non_project_member_pass = 'non_project_member_pass'
  374. project_member = User.objects.create_user(username=cls.project_member_name,
  375. password=cls.project_member_pass)
  376. another_project_member = User.objects.create_user(username=cls.another_project_member_name,
  377. password=cls.another_project_member_pass)
  378. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  379. password=cls.non_project_member_pass)
  380. main_project = mommy.make('server.SequenceLabelingProject',
  381. users=[project_member, another_project_member])
  382. main_project_doc = mommy.make('server.Document', project=main_project)
  383. main_project_entity = mommy.make('server.SequenceAnnotation',
  384. document=main_project_doc, user=project_member)
  385. another_entity = mommy.make('server.SequenceAnnotation',
  386. document=main_project_doc, user=another_project_member)
  387. sub_project = mommy.make('server.SequenceLabelingProject', users=[non_project_member])
  388. sub_project_doc = mommy.make('server.Document', project=sub_project)
  389. mommy.make('server.SequenceAnnotation', document=sub_project_doc)
  390. cls.url = reverse(viewname='annotation_detail', args=[main_project.id,
  391. main_project_doc.id,
  392. main_project_entity.id])
  393. cls.another_url = reverse(viewname='annotation_detail', args=[main_project.id,
  394. main_project_doc.id,
  395. another_entity.id])
  396. cls.post_data = {'start_offset': 0, 'end_offset': 10}
  397. def test_returns_annotation_to_project_member(self):
  398. self.client.login(username=self.project_member_name,
  399. password=self.project_member_pass)
  400. response = self.client.get(self.url, format='json')
  401. self.assertEqual(response.status_code, status.HTTP_200_OK)
  402. def test_do_not_return_annotation_to_non_project_member(self):
  403. self.client.login(username=self.non_project_member_name,
  404. password=self.non_project_member_pass)
  405. response = self.client.get(self.url, format='json')
  406. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  407. def test_do_not_return_annotation_by_another_project_member(self):
  408. self.client.login(username=self.project_member_name,
  409. password=self.project_member_pass)
  410. response = self.client.get(self.another_url, format='json')
  411. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  412. def test_allows_project_member_to_update_annotation(self):
  413. self.client.login(username=self.project_member_name,
  414. password=self.project_member_pass)
  415. response = self.client.patch(self.url, format='json', data=self.post_data)
  416. self.assertEqual(response.status_code, status.HTTP_200_OK)
  417. def test_disallows_non_project_member_to_update_annotation(self):
  418. self.client.login(username=self.non_project_member_name,
  419. password=self.non_project_member_pass)
  420. response = self.client.patch(self.url, format='json', data=self.post_data)
  421. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  422. def test_disallows_project_member_to_update_annotation_of_another_member(self):
  423. self.client.login(username=self.project_member_name,
  424. password=self.project_member_pass)
  425. response = self.client.patch(self.another_url, format='json', data=self.post_data)
  426. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  427. def test_allows_project_member_to_delete_annotation(self):
  428. self.client.login(username=self.project_member_name,
  429. password=self.project_member_pass)
  430. response = self.client.delete(self.url, format='json')
  431. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  432. def test_disallows_project_member_to_delete_annotation(self):
  433. self.client.login(username=self.non_project_member_name,
  434. password=self.non_project_member_pass)
  435. response = self.client.delete(self.url, format='json')
  436. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  437. def test_disallows_project_member_to_delete_annotation_of_another_member(self):
  438. self.client.login(username=self.project_member_name,
  439. password=self.project_member_pass)
  440. response = self.client.delete(self.another_url, format='json', data=self.post_data)
  441. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  442. class TestSearch(APITestCase):
  443. @classmethod
  444. def setUpTestData(cls):
  445. cls.project_member_name = 'project_member_name'
  446. cls.project_member_pass = 'project_member_pass'
  447. cls.non_project_member_name = 'non_project_member_name'
  448. cls.non_project_member_pass = 'non_project_member_pass'
  449. project_member = User.objects.create_user(username=cls.project_member_name,
  450. password=cls.project_member_pass)
  451. non_project_member = User.objects.create_user(username=cls.non_project_member_name,
  452. password=cls.non_project_member_pass)
  453. cls.main_project = mommy.make('server.TextClassificationProject', users=[project_member])
  454. cls.search_term = 'example'
  455. doc1 = mommy.make('server.Document', text=cls.search_term, project=cls.main_project)
  456. doc2 = mommy.make('server.Document', text='Lorem', project=cls.main_project)
  457. label1 = mommy.make('server.Label', project=cls.main_project)
  458. label2 = mommy.make('server.Label', project=cls.main_project)
  459. mommy.make('server.SequenceAnnotation', document=doc1, user=project_member, label=label1)
  460. mommy.make('server.SequenceAnnotation', document=doc2, user=project_member, label=label2)
  461. sub_project = mommy.make('server.TextClassificationProject', users=[non_project_member])
  462. mommy.make('server.Document', text=cls.search_term, project=sub_project)
  463. cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
  464. cls.data = {'q': cls.search_term}
  465. def test_can_filter_doc_by_term(self):
  466. self.client.login(username=self.project_member_name,
  467. password=self.project_member_pass)
  468. response = self.client.get(self.url, format='json', data=self.data)
  469. count = Document.objects.filter(text__contains=self.search_term,
  470. project=self.main_project).count()
  471. self.assertEqual(response.data['count'], count)
  472. def test_can_order_doc_by_created_at_ascending(self):
  473. params = {'ordering': 'created_at'}
  474. self.client.login(username=self.project_member_name,
  475. password=self.project_member_pass)
  476. response = self.client.get(self.url, format='json', data=params)
  477. docs = Document.objects.filter(project=self.main_project).order_by('created_at').values()
  478. for d1, d2 in zip(response.data['results'], docs):
  479. self.assertEqual(d1['id'], d2['id'])
  480. def test_can_order_doc_by_created_at_descending(self):
  481. params = {'ordering': '-created_at'}
  482. self.client.login(username=self.project_member_name,
  483. password=self.project_member_pass)
  484. response = self.client.get(self.url, format='json', data=params)
  485. docs = Document.objects.filter(project=self.main_project).order_by('-created_at').values()
  486. for d1, d2 in zip(response.data['results'], docs):
  487. self.assertEqual(d1['id'], d2['id'])
  488. def test_can_order_doc_by_annotation_updated_at_ascending(self):
  489. params = {'ordering': 'seq_annotations__updated_at'}
  490. self.client.login(username=self.project_member_name,
  491. password=self.project_member_pass)
  492. response = self.client.get(self.url, format='json', data=params)
  493. docs = Document.objects.filter(project=self.main_project).order_by('seq_annotations__updated_at').values()
  494. for d1, d2 in zip(response.data['results'], docs):
  495. self.assertEqual(d1['id'], d2['id'])
  496. def test_can_order_doc_by_annotation_updated_at_descending(self):
  497. params = {'ordering': '-seq_annotations__updated_at'}
  498. self.client.login(username=self.project_member_name,
  499. password=self.project_member_pass)
  500. response = self.client.get(self.url, format='json', data=params)
  501. docs = Document.objects.filter(project=self.main_project).order_by('-seq_annotations__updated_at').values()
  502. for d1, d2 in zip(response.data['results'], docs):
  503. self.assertEqual(d1['id'], d2['id'])
  504. class TestFilter(APITestCase):
  505. @classmethod
  506. def setUpTestData(cls):
  507. cls.project_member_name = 'project_member_name'
  508. cls.project_member_pass = 'project_member_pass'
  509. project_member = User.objects.create_user(username=cls.project_member_name,
  510. password=cls.project_member_pass)
  511. cls.main_project = mommy.make('server.SequenceLabelingProject', users=[project_member])
  512. cls.label1 = mommy.make('server.Label', project=cls.main_project)
  513. cls.label2 = mommy.make('server.Label', project=cls.main_project)
  514. doc1 = mommy.make('server.Document', project=cls.main_project)
  515. doc2 = mommy.make('server.Document', project=cls.main_project)
  516. doc3 = mommy.make('server.Document', project=cls.main_project)
  517. mommy.make('server.SequenceAnnotation', document=doc1, user=project_member, label=cls.label1)
  518. mommy.make('server.SequenceAnnotation', document=doc2, user=project_member, label=cls.label2)
  519. cls.url = reverse(viewname='doc_list', args=[cls.main_project.id])
  520. cls.params = {'seq_annotations__label__id': cls.label1.id}
  521. def test_can_filter_by_label(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', data=self.params)
  525. docs = Document.objects.filter(project=self.main_project,
  526. seq_annotations__label__id=self.label1.id).values()
  527. for d1, d2 in zip(response.data['results'], docs):
  528. self.assertEqual(d1['id'], d2['id'])
  529. def test_can_filter_doc_with_annotation(self):
  530. params = {'seq_annotations__isnull': False}
  531. self.client.login(username=self.project_member_name,
  532. password=self.project_member_pass)
  533. response = self.client.get(self.url, format='json', data=params)
  534. docs = Document.objects.filter(project=self.main_project, seq_annotations__isnull=False).values()
  535. self.assertEqual(response.data['count'], docs.count())
  536. for d1, d2 in zip(response.data['results'], docs):
  537. self.assertEqual(d1['id'], d2['id'])
  538. def test_can_filter_doc_without_anotation(self):
  539. params = {'seq_annotations__isnull': True}
  540. self.client.login(username=self.project_member_name,
  541. password=self.project_member_pass)
  542. response = self.client.get(self.url, format='json', data=params)
  543. docs = Document.objects.filter(project=self.main_project, seq_annotations__isnull=True).values()
  544. self.assertEqual(response.data['count'], docs.count())
  545. for d1, d2 in zip(response.data['results'], docs):
  546. self.assertEqual(d1['id'], d2['id'])
  547. class TestUploader(APITestCase):
  548. @classmethod
  549. def setUpTestData(cls):
  550. cls.super_user_name = 'super_user_name'
  551. cls.super_user_pass = 'super_user_pass'
  552. # Todo: change super_user to project_admin.
  553. super_user = User.objects.create_superuser(username=cls.super_user_name,
  554. password=cls.super_user_pass,
  555. email='fizz@buzz.com')
  556. cls.classification_project = mommy.make('server.TextClassificationProject',
  557. users=[super_user], project_type=DOCUMENT_CLASSIFICATION)
  558. cls.labeling_project = mommy.make('server.SequenceLabelingProject',
  559. users=[super_user], project_type=SEQUENCE_LABELING)
  560. cls.seq2seq_project = mommy.make('server.Seq2seqProject', users=[super_user], project_type=SEQ2SEQ)
  561. cls.classification_url = reverse(viewname='doc_uploader', args=[cls.classification_project.id])
  562. cls.labeling_url = reverse(viewname='doc_uploader', args=[cls.labeling_project.id])
  563. cls.seq2seq_url = reverse(viewname='doc_uploader', args=[cls.seq2seq_project.id])
  564. def setUp(self):
  565. self.client.login(username=self.super_user_name,
  566. password=self.super_user_pass)
  567. def upload_test_helper(self, url, filename, format, expected_status):
  568. with open(os.path.join(DATA_DIR, filename)) as f:
  569. response = self.client.post(url, data={'file': f, 'format': format})
  570. self.assertEqual(response.status_code, expected_status)
  571. def test_can_upload_conll_format_file(self):
  572. self.upload_test_helper(url=self.labeling_url,
  573. filename='labeling.conll',
  574. format='conll',
  575. expected_status=status.HTTP_201_CREATED)
  576. def test_cannot_upload_wrong_conll_format_file(self):
  577. self.upload_test_helper(url=self.labeling_url,
  578. filename='labeling.invalid.conll',
  579. format='conll',
  580. expected_status=status.HTTP_400_BAD_REQUEST)
  581. def test_can_upload_classification_csv(self):
  582. self.upload_test_helper(url=self.classification_url,
  583. filename='example.csv',
  584. format='csv',
  585. expected_status=status.HTTP_201_CREATED)
  586. def test_can_upload_seq2seq_csv(self):
  587. self.upload_test_helper(url=self.seq2seq_url,
  588. filename='example.csv',
  589. format='csv',
  590. expected_status=status.HTTP_201_CREATED)
  591. def test_cannot_upload_csv_file_does_not_match_column_and_row(self):
  592. self.upload_test_helper(url=self.classification_url,
  593. filename='example.invalid.1.csv',
  594. format='csv',
  595. expected_status=status.HTTP_400_BAD_REQUEST)
  596. def test_cannot_upload_csv_file_has_too_many_columns(self):
  597. self.upload_test_helper(url=self.classification_url,
  598. filename='example.invalid.2.csv',
  599. format='csv',
  600. expected_status=status.HTTP_400_BAD_REQUEST)
  601. def test_can_upload_classification_jsonl(self):
  602. self.upload_test_helper(url=self.classification_url,
  603. filename='classification.jsonl',
  604. format='json',
  605. expected_status=status.HTTP_201_CREATED)
  606. def test_can_upload_labeling_jsonl(self):
  607. self.upload_test_helper(url=self.labeling_url,
  608. filename='labeling.jsonl',
  609. format='json',
  610. expected_status=status.HTTP_201_CREATED)
  611. def test_can_upload_seq2seq_jsonl(self):
  612. self.upload_test_helper(url=self.seq2seq_url,
  613. filename='seq2seq.jsonl',
  614. format='json',
  615. expected_status=status.HTTP_201_CREATED)
  616. def test_can_upload_plain_text(self):
  617. self.upload_test_helper(url=self.classification_url,
  618. filename='example.txt',
  619. format='plain',
  620. expected_status=status.HTTP_201_CREATED)
  621. def test_can_upload_data_without_label(self):
  622. self.upload_test_helper(url=self.classification_url,
  623. filename='example.jsonl',
  624. format='json',
  625. expected_status=status.HTTP_201_CREATED)
  626. class TestParser(APITestCase):
  627. def parser_helper(self, filename, parser, include_label=True):
  628. with open(os.path.join(DATA_DIR, filename), mode='rb') as f:
  629. result = parser.parse(f)
  630. for data in result:
  631. for r in data:
  632. self.assertIn('text', r)
  633. if include_label:
  634. self.assertIn('labels', r)
  635. def test_give_valid_data_to_conll_parser(self):
  636. self.parser_helper(filename='labeling.conll', parser=CoNLLParser())
  637. def test_plain_parser(self):
  638. self.parser_helper(filename='example.txt', parser=PlainTextParser(), include_label=False)
  639. def test_give_invalid_data_to_conll_parser(self):
  640. with self.assertRaises(FileParseException):
  641. self.parser_helper(filename='labeling.invalid.conll',
  642. parser=CoNLLParser())
  643. def test_give_classification_data_to_csv_parser(self):
  644. self.parser_helper(filename='example.csv', parser=CSVParser())
  645. def test_give_seq2seq_data_to_csv_parser(self):
  646. self.parser_helper(filename='example.csv', parser=CSVParser())
  647. def test_give_classification_data_to_json_parser(self):
  648. self.parser_helper(filename='classification.jsonl', parser=JSONParser())
  649. def test_give_labeling_data_to_json_parser(self):
  650. self.parser_helper(filename='labeling.jsonl', parser=JSONParser())
  651. def test_give_seq2seq_data_to_json_parser(self):
  652. self.parser_helper(filename='seq2seq.jsonl', parser=JSONParser())
  653. def test_give_data_without_label_to_json_parser(self):
  654. self.parser_helper(filename='example.jsonl', parser=JSONParser(), include_label=False)
  655. class TestDownloader(APITestCase):
  656. @classmethod
  657. def setUpTestData(cls):
  658. cls.super_user_name = 'super_user_name'
  659. cls.super_user_pass = 'super_user_pass'
  660. # Todo: change super_user to project_admin.
  661. super_user = User.objects.create_superuser(username=cls.super_user_name,
  662. password=cls.super_user_pass,
  663. email='fizz@buzz.com')
  664. cls.classification_project = mommy.make('server.TextClassificationProject',
  665. users=[super_user], project_type=DOCUMENT_CLASSIFICATION)
  666. cls.labeling_project = mommy.make('server.SequenceLabelingProject',
  667. users=[super_user], project_type=SEQUENCE_LABELING)
  668. cls.seq2seq_project = mommy.make('server.Seq2seqProject', users=[super_user], project_type=SEQ2SEQ)
  669. cls.classification_url = reverse(viewname='doc_downloader', args=[cls.classification_project.id])
  670. cls.labeling_url = reverse(viewname='doc_downloader', args=[cls.labeling_project.id])
  671. cls.seq2seq_url = reverse(viewname='doc_downloader', args=[cls.seq2seq_project.id])
  672. def setUp(self):
  673. self.client.login(username=self.super_user_name,
  674. password=self.super_user_pass)
  675. def download_test_helper(self, url, format, expected_status):
  676. response = self.client.get(url, data={'q': format})
  677. self.assertEqual(response.status_code, expected_status)
  678. def test_cannot_download_conll_format_file(self):
  679. self.download_test_helper(url=self.labeling_url,
  680. format='conll',
  681. expected_status=status.HTTP_400_BAD_REQUEST)
  682. def test_can_download_classification_csv(self):
  683. self.download_test_helper(url=self.classification_url,
  684. format='csv',
  685. expected_status=status.HTTP_200_OK)
  686. def test_can_download_labeling_csv(self):
  687. self.download_test_helper(url=self.labeling_url,
  688. format='csv',
  689. expected_status=status.HTTP_200_OK)
  690. def test_can_download_seq2seq_csv(self):
  691. self.download_test_helper(url=self.seq2seq_url,
  692. format='csv',
  693. expected_status=status.HTTP_200_OK)
  694. def test_can_download_classification_jsonl(self):
  695. self.download_test_helper(url=self.classification_url,
  696. format='json',
  697. expected_status=status.HTTP_200_OK)
  698. def test_can_download_labeling_jsonl(self):
  699. self.download_test_helper(url=self.labeling_url,
  700. format='json',
  701. expected_status=status.HTTP_200_OK)
  702. def test_can_download_seq2seq_jsonl(self):
  703. self.download_test_helper(url=self.seq2seq_url,
  704. format='json',
  705. expected_status=status.HTTP_200_OK)
  706. def test_can_download_plain_text(self):
  707. self.download_test_helper(url=self.classification_url,
  708. format='plain',
  709. expected_status=status.HTTP_400_BAD_REQUEST)
  710. class TestStatisticsAPI(APITestCase):
  711. @classmethod
  712. def setUpTestData(cls):
  713. cls.super_user_name = 'super_user_name'
  714. cls.super_user_pass = 'super_user_pass'
  715. # Todo: change super_user to project_admin.
  716. super_user = User.objects.create_superuser(username=cls.super_user_name,
  717. password=cls.super_user_pass,
  718. email='fizz@buzz.com')
  719. main_project = mommy.make('server.TextClassificationProject', users=[super_user])
  720. doc1 = mommy.make('server.Document', project=main_project)
  721. doc2 = mommy.make('server.Document', project=main_project)
  722. mommy.make('DocumentAnnotation', document=doc1)
  723. cls.url = reverse(viewname='statistics', args=[main_project.id])
  724. cls.doc = Document.objects.filter(project=main_project)
  725. def test_returns_exact_progress(self):
  726. self.client.login(username=self.super_user_name,
  727. password=self.super_user_pass)
  728. response = self.client.get(self.url, format='json')
  729. total = self.doc.count()
  730. remaining = self.doc.filter(doc_annotations__isnull=True).count()
  731. self.assertEqual(response.data['total'], total)
  732. self.assertEqual(response.data['remaining'], remaining)
  733. def test_returns_user_count(self):
  734. self.client.login(username=self.super_user_name,
  735. password=self.super_user_pass)
  736. response = self.client.get(self.url, format='json')
  737. self.assertIn('label', response.data)
  738. self.assertIsInstance(response.data['label'], dict)
  739. def test_returns_label_count(self):
  740. self.client.login(username=self.super_user_name,
  741. password=self.super_user_pass)
  742. response = self.client.get(self.url, format='json')
  743. self.assertIn('user', response.data)
  744. self.assertIsInstance(response.data['user'], dict)