test_attachments_middleware.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. from unittest.mock import Mock
  2. from rest_framework import serializers
  3. from misago.acl import useracl
  4. from misago.acl.test import patch_user_acl
  5. from misago.categories.models import Category
  6. from misago.conf import settings
  7. from misago.conftest import get_cache_versions
  8. from misago.threads import testutils
  9. from misago.threads.api.postingendpoint import PostingEndpoint
  10. from misago.threads.api.postingendpoint.attachments import (
  11. AttachmentsMiddleware, validate_attachments_count)
  12. from misago.threads.models import Attachment, AttachmentType
  13. from misago.users.testutils import AuthenticatedUserTestCase
  14. cache_versions = get_cache_versions()
  15. def patch_attachments_acl(acl_patch=None):
  16. acl_patch = acl_patch or {}
  17. acl_patch.setdefault("max_attachment_size", 1024)
  18. return patch_user_acl(acl_patch)
  19. class AttachmentsMiddlewareTests(AuthenticatedUserTestCase):
  20. def setUp(self):
  21. super().setUp()
  22. self.category = Category.objects.get(slug='first-category')
  23. self.thread = testutils.post_thread(category=self.category)
  24. self.post = self.thread.first_post
  25. self.post.update_fields = []
  26. self.filetype = AttachmentType.objects.order_by('id').last()
  27. def mock_attachment(self, user=True, post=None):
  28. return Attachment.objects.create(
  29. secret=Attachment.generate_new_secret(),
  30. filetype=self.filetype,
  31. post=post,
  32. size=1000,
  33. uploader=self.user if user else None,
  34. uploader_name=self.user.username,
  35. uploader_slug=self.user.slug,
  36. filename='testfile_%s.zip' % (Attachment.objects.count() + 1),
  37. )
  38. def test_use_this_middleware(self):
  39. """use_this_middleware returns False if we can't upload attachments"""
  40. with patch_user_acl({'max_attachment_size': 0}):
  41. user_acl = useracl.get_user_acl(self.user, cache_versions)
  42. middleware = AttachmentsMiddleware(user=self.user, user_acl=user_acl)
  43. self.assertFalse(middleware.use_this_middleware())
  44. with patch_user_acl({'max_attachment_size': 1024}):
  45. user_acl = useracl.get_user_acl(self.user, cache_versions)
  46. middleware = AttachmentsMiddleware(user=self.user, user_acl=user_acl)
  47. self.assertTrue(middleware.use_this_middleware())
  48. @patch_attachments_acl()
  49. def test_middleware_is_optional(self):
  50. """middleware is optional"""
  51. INPUTS = [{}, {'attachments': []}]
  52. user_acl = useracl.get_user_acl(self.user, cache_versions)
  53. for test_input in INPUTS:
  54. middleware = AttachmentsMiddleware(
  55. request=Mock(data=test_input),
  56. mode=PostingEndpoint.START,
  57. user=self.user,
  58. user_acl=user_acl,
  59. post=self.post,
  60. )
  61. serializer = middleware.get_serializer()
  62. self.assertTrue(serializer.is_valid())
  63. @patch_attachments_acl()
  64. def test_middleware_validates_ids(self):
  65. """middleware validates attachments ids"""
  66. INPUTS = ['none', ['a', 'b', 123], range(settings.MISAGO_POST_ATTACHMENTS_LIMIT + 1)]
  67. user_acl = useracl.get_user_acl(self.user, cache_versions)
  68. for test_input in INPUTS:
  69. middleware = AttachmentsMiddleware(
  70. request=Mock(data={
  71. 'attachments': test_input
  72. }),
  73. mode=PostingEndpoint.START,
  74. user=self.user,
  75. user_acl=user_acl,
  76. post=self.post,
  77. )
  78. serializer = middleware.get_serializer()
  79. self.assertFalse(serializer.is_valid(), "%r shouldn't validate" % test_input)
  80. @patch_attachments_acl()
  81. def test_get_initial_attachments(self):
  82. """get_initial_attachments returns list of attachments already existing on post"""
  83. user_acl = useracl.get_user_acl(self.user, cache_versions)
  84. middleware = AttachmentsMiddleware(
  85. request=Mock(data={}),
  86. mode=PostingEndpoint.EDIT,
  87. user=self.user,
  88. user_acl=user_acl,
  89. post=self.post,
  90. )
  91. serializer = middleware.get_serializer()
  92. attachments = serializer.get_initial_attachments(
  93. middleware.mode, middleware.user, middleware.post
  94. )
  95. self.assertEqual(attachments, [])
  96. attachment = self.mock_attachment(post=self.post)
  97. attachments = serializer.get_initial_attachments(
  98. middleware.mode, middleware.user_acl, middleware.post
  99. )
  100. self.assertEqual(attachments, [attachment])
  101. @patch_attachments_acl()
  102. def test_get_new_attachments(self):
  103. """get_initial_attachments returns list of attachments already existing on post"""
  104. user_acl = useracl.get_user_acl(self.user, cache_versions)
  105. middleware = AttachmentsMiddleware(
  106. request=Mock(data={}),
  107. mode=PostingEndpoint.EDIT,
  108. user=self.user,
  109. user_acl=user_acl,
  110. post=self.post,
  111. )
  112. serializer = middleware.get_serializer()
  113. attachments = serializer.get_new_attachments(middleware.user, [1, 2, 3])
  114. self.assertEqual(attachments, [])
  115. attachment = self.mock_attachment()
  116. attachments = serializer.get_new_attachments(middleware.user, [attachment.pk])
  117. self.assertEqual(attachments, [attachment])
  118. # only own orphaned attachments may be assigned to posts
  119. other_user_attachment = self.mock_attachment(user=False)
  120. attachments = serializer.get_new_attachments(middleware.user, [other_user_attachment.pk])
  121. self.assertEqual(attachments, [])
  122. @patch_attachments_acl({'can_delete_other_users_attachments': False})
  123. def test_cant_delete_attachment(self):
  124. """middleware validates if we have permission to delete other users attachments"""
  125. attachment = self.mock_attachment(user=False, post=self.post)
  126. self.assertIsNone(attachment.uploader)
  127. user_acl = useracl.get_user_acl(self.user, cache_versions)
  128. serializer = AttachmentsMiddleware(
  129. request=Mock(data={
  130. 'attachments': []
  131. }),
  132. mode=PostingEndpoint.EDIT,
  133. user=self.user,
  134. user_acl=user_acl,
  135. post=self.post,
  136. ).get_serializer()
  137. self.assertFalse(serializer.is_valid())
  138. @patch_attachments_acl()
  139. def test_add_attachments(self):
  140. """middleware adds attachments to post"""
  141. attachments = [
  142. self.mock_attachment(),
  143. self.mock_attachment(),
  144. ]
  145. user_acl = useracl.get_user_acl(self.user, cache_versions)
  146. middleware = AttachmentsMiddleware(
  147. request=Mock(data={
  148. 'attachments': [a.pk for a in attachments]
  149. }),
  150. mode=PostingEndpoint.EDIT,
  151. user=self.user,
  152. user_acl=user_acl,
  153. post=self.post,
  154. )
  155. serializer = middleware.get_serializer()
  156. self.assertTrue(serializer.is_valid())
  157. middleware.save(serializer)
  158. # attachments were associated with post
  159. self.assertEqual(self.post.update_fields, ['attachments_cache'])
  160. self.assertEqual(self.post.attachment_set.count(), 2)
  161. attachments_filenames = list(reversed([a.filename for a in attachments]))
  162. self.assertEqual([a['filename'] for a in self.post.attachments_cache],
  163. attachments_filenames)
  164. @patch_attachments_acl()
  165. def test_remove_attachments(self):
  166. """middleware removes attachment from post and db"""
  167. attachments = [
  168. self.mock_attachment(post=self.post),
  169. self.mock_attachment(post=self.post),
  170. ]
  171. user_acl = useracl.get_user_acl(self.user, cache_versions)
  172. middleware = AttachmentsMiddleware(
  173. request=Mock(data={
  174. 'attachments': [attachments[0].pk]
  175. }),
  176. mode=PostingEndpoint.EDIT,
  177. user=self.user,
  178. user_acl=user_acl,
  179. post=self.post,
  180. )
  181. serializer = middleware.get_serializer()
  182. self.assertTrue(serializer.is_valid())
  183. middleware.save(serializer)
  184. # attachments were associated with post
  185. self.assertEqual(self.post.update_fields, ['attachments_cache'])
  186. self.assertEqual(self.post.attachment_set.count(), 1)
  187. self.assertEqual(Attachment.objects.count(), 1)
  188. attachments_filenames = [attachments[0].filename]
  189. self.assertEqual([a['filename'] for a in self.post.attachments_cache],
  190. attachments_filenames)
  191. @patch_attachments_acl()
  192. def test_steal_attachments(self):
  193. """middleware validates if attachments are already assigned to other posts"""
  194. other_post = testutils.reply_thread(self.thread)
  195. attachments = [
  196. self.mock_attachment(post=other_post),
  197. self.mock_attachment(),
  198. ]
  199. user_acl = useracl.get_user_acl(self.user, cache_versions)
  200. middleware = AttachmentsMiddleware(
  201. request=Mock(data={
  202. 'attachments': [attachments[0].pk, attachments[1].pk]
  203. }),
  204. mode=PostingEndpoint.EDIT,
  205. user=self.user,
  206. user_acl=user_acl,
  207. post=self.post,
  208. )
  209. serializer = middleware.get_serializer()
  210. self.assertTrue(serializer.is_valid())
  211. middleware.save(serializer)
  212. # only unassociated attachment was associated with post
  213. self.assertEqual(self.post.update_fields, ['attachments_cache'])
  214. self.assertEqual(self.post.attachment_set.count(), 1)
  215. self.assertEqual(Attachment.objects.get(pk=attachments[0].pk).post, other_post)
  216. self.assertEqual(Attachment.objects.get(pk=attachments[1].pk).post, self.post)
  217. @patch_attachments_acl()
  218. def test_edit_attachments(self):
  219. """middleware removes and adds attachments to post"""
  220. attachments = [
  221. self.mock_attachment(post=self.post),
  222. self.mock_attachment(post=self.post),
  223. self.mock_attachment(),
  224. ]
  225. user_acl = useracl.get_user_acl(self.user, cache_versions)
  226. middleware = AttachmentsMiddleware(
  227. request=Mock(data={
  228. 'attachments': [attachments[0].pk, attachments[2].pk]
  229. }),
  230. mode=PostingEndpoint.EDIT,
  231. user=self.user,
  232. user_acl=user_acl,
  233. post=self.post,
  234. )
  235. serializer = middleware.get_serializer()
  236. self.assertTrue(serializer.is_valid())
  237. middleware.save(serializer)
  238. # attachments were associated with post
  239. self.assertEqual(self.post.update_fields, ['attachments_cache'])
  240. self.assertEqual(self.post.attachment_set.count(), 2)
  241. attachments_filenames = [attachments[2].filename, attachments[0].filename]
  242. self.assertEqual([a['filename'] for a in self.post.attachments_cache],
  243. attachments_filenames)
  244. class ValidateAttachmentsCountTests(AuthenticatedUserTestCase):
  245. def test_validate_attachments_count(self):
  246. """too large count of attachments is rejected"""
  247. validate_attachments_count(range(settings.MISAGO_POST_ATTACHMENTS_LIMIT))
  248. with self.assertRaises(serializers.ValidationError):
  249. validate_attachments_count(range(settings.MISAGO_POST_ATTACHMENTS_LIMIT + 1))