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.

368 lines
12 KiB

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