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.

374 lines
12 KiB

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