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.

298 lines
9.7 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. from rest_framework import status
  2. from rest_framework.reverse import reverse
  3. from api.models import (DOCUMENT_CLASSIFICATION, SEQ2SEQ, SEQUENCE_LABELING)
  4. from labels.models import Category, Span, TextLabel
  5. from api.tests.api.utils import (CRUDMixin, make_annotation, make_doc, make_label,
  6. make_user, prepare_project)
  7. class TestLabelList:
  8. model = Category
  9. task = DOCUMENT_CLASSIFICATION
  10. view_name = 'annotation_list'
  11. @classmethod
  12. def setUpTestData(cls):
  13. cls.project = prepare_project(task=cls.task)
  14. cls.non_member = make_user()
  15. doc = make_doc(cls.project.item)
  16. for member in cls.project.users:
  17. cls.make_annotation(doc, member)
  18. cls.url = reverse(viewname=cls.view_name, args=[cls.project.item.id, doc.id])
  19. @classmethod
  20. def make_annotation(cls, doc, member):
  21. make_annotation(cls.task, doc=doc, user=member)
  22. def test_allows_project_member_to_fetch_annotation(self):
  23. for member in self.project.users:
  24. response = self.assert_fetch(member, status.HTTP_200_OK)
  25. self.assertEqual(len(response.data), 1) # fetch only own annotation
  26. def test_denies_non_project_member_to_fetch_annotation(self):
  27. self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN)
  28. def test_denies_unauthenticated_user_to_fetch_annotation(self):
  29. self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN)
  30. def test_allows_project_member_to_bulk_delete_annotation(self):
  31. self.assert_delete(self.project.users[0], status.HTTP_204_NO_CONTENT)
  32. count = self.model.objects.count()
  33. self.assertEqual(count, 2) # delete only own annotation
  34. class TestCategoryList(TestLabelList, CRUDMixin):
  35. model = Category
  36. task = DOCUMENT_CLASSIFICATION
  37. view_name = 'category_list'
  38. class TestSpanList(TestLabelList, CRUDMixin):
  39. model = Span
  40. task = SEQUENCE_LABELING
  41. view_name = 'span_list'
  42. @classmethod
  43. def make_annotation(cls, doc, member):
  44. make_annotation(cls.task, doc=doc, user=member, start_offset=0, end_offset=1)
  45. class TestTextList(TestLabelList, CRUDMixin):
  46. model = TextLabel
  47. task = SEQ2SEQ
  48. view_name = 'text_list'
  49. class TestSharedLabelList:
  50. model = Category
  51. task = DOCUMENT_CLASSIFICATION
  52. view_name = 'annotation_list'
  53. @classmethod
  54. def setUpTestData(cls):
  55. cls.project = prepare_project(task=cls.task, collaborative_annotation=True)
  56. doc = make_doc(cls.project.item)
  57. for member in cls.project.users:
  58. cls.make_annotation(doc, member)
  59. cls.url = reverse(viewname=cls.view_name, args=[cls.project.item.id, doc.id])
  60. @classmethod
  61. def make_annotation(cls, doc, member):
  62. make_annotation(cls.task, doc=doc, user=member)
  63. def test_allows_project_member_to_fetch_all_annotation(self):
  64. for member in self.project.users:
  65. response = self.assert_fetch(member, status.HTTP_200_OK)
  66. self.assertEqual(len(response.data), 3)
  67. def test_allows_project_member_to_bulk_delete_annotation(self):
  68. self.assert_delete(self.project.users[0], status.HTTP_204_NO_CONTENT)
  69. count = self.model.objects.count()
  70. self.assertEqual(count, 0) # delete all annotation in the doc
  71. class TestSharedCategoryList(TestSharedLabelList, CRUDMixin):
  72. model = Category
  73. task = DOCUMENT_CLASSIFICATION
  74. view_name = 'category_list'
  75. class TestSharedSpanList(TestSharedLabelList, CRUDMixin):
  76. model = Span
  77. task = SEQUENCE_LABELING
  78. view_name = 'span_list'
  79. start_offset = 0
  80. @classmethod
  81. def make_annotation(cls, doc, member):
  82. make_annotation(
  83. cls.task,
  84. doc=doc,
  85. user=member,
  86. start_offset=cls.start_offset,
  87. end_offset=cls.start_offset + 1
  88. )
  89. cls.start_offset += 1
  90. class TestSharedTextList(TestSharedLabelList, CRUDMixin):
  91. model = TextLabel
  92. task = SEQ2SEQ
  93. view_name = 'text_list'
  94. class TestDataLabeling:
  95. task = DOCUMENT_CLASSIFICATION
  96. view_name = 'annotation_list'
  97. def setUp(self):
  98. self.project = prepare_project(task=self.task)
  99. self.non_member = make_user()
  100. doc = make_doc(self.project.item)
  101. self.data = self.create_data()
  102. self.url = reverse(viewname=self.view_name, args=[self.project.item.id, doc.id])
  103. def create_data(self):
  104. label = make_label(self.project.item)
  105. return {'label': label.id}
  106. def test_allows_project_member_to_annotate(self):
  107. for member in self.project.users:
  108. self.assert_create(member, status.HTTP_201_CREATED)
  109. def test_denies_non_project_member_to_annotate(self):
  110. self.assert_create(self.non_member, status.HTTP_403_FORBIDDEN)
  111. def test_denies_unauthenticated_user_to_annotate(self):
  112. self.assert_create(expected=status.HTTP_403_FORBIDDEN)
  113. class TestCategoryCreation(TestDataLabeling, CRUDMixin):
  114. view_name = 'category_list'
  115. class TestSpanCreation(TestDataLabeling, CRUDMixin):
  116. task = SEQUENCE_LABELING
  117. view_name = 'span_list'
  118. def create_data(self):
  119. label = make_label(self.project.item)
  120. return {'label': label.id, 'start_offset': 0, 'end_offset': 1}
  121. class TestTextLabelCreation(TestDataLabeling, CRUDMixin):
  122. task = SEQ2SEQ
  123. view_name = 'text_list'
  124. def create_data(self):
  125. return {'text': 'example'}
  126. class TestLabelDetail:
  127. task = SEQUENCE_LABELING
  128. view_name = 'annotation_detail'
  129. def setUp(self):
  130. self.project = prepare_project(task=self.task)
  131. self.non_member = make_user()
  132. doc = make_doc(self.project.item)
  133. label = make_label(self.project.item)
  134. annotation = self.create_annotation_data(doc=doc)
  135. self.data = {'label': label.id}
  136. self.url = reverse(viewname=self.view_name, args=[self.project.item.id, doc.id, annotation.id])
  137. def create_annotation_data(self, doc):
  138. return make_annotation(
  139. task=self.task,
  140. doc=doc,
  141. user=self.project.users[0],
  142. start_offset=0,
  143. end_offset=1
  144. )
  145. def test_allows_owner_to_get_annotation(self):
  146. self.assert_fetch(self.project.users[0], status.HTTP_200_OK)
  147. def test_denies_non_owner_to_get_annotation(self):
  148. for member in self.project.users[1:]:
  149. self.assert_fetch(member, status.HTTP_403_FORBIDDEN)
  150. def test_denies_non_project_member_to_get_annotation(self):
  151. self.assert_fetch(self.non_member, status.HTTP_403_FORBIDDEN)
  152. def test_denies_unauthenticated_user_to_get_annotation(self):
  153. self.assert_fetch(expected=status.HTTP_403_FORBIDDEN)
  154. def test_allows_owner_to_update_annotation(self):
  155. self.assert_update(self.project.users[0], status.HTTP_200_OK)
  156. def test_denies_non_owner_to_update_annotation(self):
  157. for member in self.project.users[1:]:
  158. self.assert_update(member, status.HTTP_403_FORBIDDEN)
  159. def test_denies_non_project_member_to_update_annotation(self):
  160. self.assert_update(self.non_member, status.HTTP_403_FORBIDDEN)
  161. def test_allows_owner_to_delete_annotation(self):
  162. self.assert_delete(self.project.users[0], status.HTTP_204_NO_CONTENT)
  163. def test_denies_non_owner_to_delete_annotation(self):
  164. for member in self.project.users[1:]:
  165. self.assert_delete(member, status.HTTP_403_FORBIDDEN)
  166. def test_denies_non_project_member_to_delete_annotation(self):
  167. self.assert_delete(self.non_member, status.HTTP_403_FORBIDDEN)
  168. class TestCategoryDetail(TestLabelDetail, CRUDMixin):
  169. task = DOCUMENT_CLASSIFICATION
  170. view_name = 'category_detail'
  171. def create_annotation_data(self, doc):
  172. return make_annotation(task=self.task, doc=doc, user=self.project.users[0])
  173. class TestSpanDetail(TestLabelDetail, CRUDMixin):
  174. task = SEQUENCE_LABELING
  175. view_name = 'span_detail'
  176. class TestTextDetail(TestLabelDetail, CRUDMixin):
  177. task = SEQ2SEQ
  178. view_name = 'text_detail'
  179. def setUp(self):
  180. super().setUp()
  181. self.data = {'text': 'changed'}
  182. def create_annotation_data(self, doc):
  183. return make_annotation(task=self.task, doc=doc, user=self.project.users[0])
  184. class TestSharedLabelDetail:
  185. task = DOCUMENT_CLASSIFICATION
  186. view_name = 'annotation_detail'
  187. def setUp(self):
  188. self.project = prepare_project(task=self.task, collaborative_annotation=True)
  189. doc = make_doc(self.project.item)
  190. annotation = self.make_annotation(doc, self.project.users[0])
  191. label = make_label(self.project.item)
  192. self.data = {'label': label.id}
  193. self.url = reverse(viewname=self.view_name, args=[self.project.item.id, doc.id, annotation.id])
  194. def make_annotation(self, doc, member):
  195. return make_annotation(self.task, doc=doc, user=member)
  196. def test_allows_any_member_to_get_annotation(self):
  197. for member in self.project.users:
  198. self.assert_fetch(member, status.HTTP_200_OK)
  199. def test_allows_any_member_to_update_annotation(self):
  200. for member in self.project.users:
  201. self.assert_update(member, status.HTTP_200_OK)
  202. def test_allows_any_member_to_delete_annotation(self):
  203. self.assert_delete(self.project.users[1], status.HTTP_204_NO_CONTENT)
  204. class TestSharedCategoryDetail(TestSharedLabelDetail, CRUDMixin):
  205. view_name = 'category_detail'
  206. class TestSharedSpanDetail(TestSharedLabelDetail, CRUDMixin):
  207. task = SEQUENCE_LABELING
  208. view_name = 'span_detail'
  209. def make_annotation(self, doc, member):
  210. return make_annotation(self.task, doc=doc, user=member, start_offset=0, end_offset=1)
  211. class TestSharedTextDetail(TestSharedLabelDetail, CRUDMixin):
  212. task = SEQ2SEQ
  213. view_name = 'text_detail'
  214. def setUp(self):
  215. super().setUp()
  216. self.data = {'text': 'changed'}