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.

1067 lines
55 KiB

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