test_attachments_api.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import json
  2. import os
  3. from PIL import Image
  4. from django.conf import settings
  5. from django.urls import reverse
  6. from django.utils import six
  7. from django.utils.encoding import smart_str
  8. from misago.acl.models import Role
  9. from misago.acl.testutils import override_acl
  10. from misago.users.testutils import AuthenticatedUserTestCase
  11. from ..models import Attachment, AttachmentType
  12. TESTFILES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'testfiles')
  13. TEST_DOCUMENT_PATH = os.path.join(TESTFILES_DIR, 'document.pdf')
  14. TEST_LARGEPNG_PATH = os.path.join(TESTFILES_DIR, 'large.png')
  15. TEST_SMALLJPG_PATH = os.path.join(TESTFILES_DIR, 'small.jpg')
  16. TEST_ANIMATEDGIT_PATH = os.path.join(TESTFILES_DIR, 'animated.gif')
  17. TEST_CORRUPTEDIMG_PATH = os.path.join(TESTFILES_DIR, 'corrupted.gif')
  18. class AttachmentsApiTestCase(AuthenticatedUserTestCase):
  19. def setUp(self):
  20. super(AttachmentsApiTestCase, self).setUp()
  21. AttachmentType.objects.all().delete()
  22. self.api_link = reverse('misago:api:attachment-list')
  23. def override_acl(self, new_acl=None):
  24. if new_acl:
  25. acl = self.user.acl.copy()
  26. acl.update(new_acl)
  27. override_acl(self.user, acl)
  28. def test_anonymous(self):
  29. """user has to be authenticated to be able to upload files"""
  30. self.logout_user()
  31. response = self.client.post(self.api_link)
  32. self.assertEqual(response.status_code, 403)
  33. def test_no_permission(self):
  34. """user needs permission to upload files"""
  35. self.override_acl({
  36. 'max_attachment_size': 0
  37. })
  38. response = self.client.post(self.api_link)
  39. self.assertContains(response, "don't have permission to upload new files", status_code=403)
  40. def test_no_file_uploaded(self):
  41. """no file uploaded scenario is handled"""
  42. response = self.client.post(self.api_link)
  43. self.assertContains(response, "No file has been uploaded.", status_code=400)
  44. def test_invalid_extension(self):
  45. """uploaded file's extension is rejected as invalid"""
  46. AttachmentType.objects.create(
  47. name="Test extension",
  48. extensions='jpg,jpeg',
  49. mimetypes=None
  50. )
  51. with open(TEST_DOCUMENT_PATH, 'rb') as upload:
  52. response = self.client.post(self.api_link, data={
  53. 'upload': upload
  54. })
  55. self.assertContains(response, "You can't upload files of this type.", status_code=400)
  56. def test_invalid_mime(self):
  57. """uploaded file's mimetype is rejected as invalid"""
  58. AttachmentType.objects.create(
  59. name="Test extension",
  60. extensions='png',
  61. mimetypes='loremipsum'
  62. )
  63. with open(TEST_DOCUMENT_PATH, 'rb') as upload:
  64. response = self.client.post(self.api_link, data={
  65. 'upload': upload
  66. })
  67. self.assertContains(response, "You can't upload files of this type.", status_code=400)
  68. def test_no_perm_to_type(self):
  69. """user needs permission to upload files of this type"""
  70. attachment_type = AttachmentType.objects.create(
  71. name="Test extension",
  72. extensions='png',
  73. mimetypes='application/pdf'
  74. )
  75. user_roles = (r.pk for r in self.user.get_roles())
  76. attachment_type.limit_uploads_to.set(Role.objects.exclude(id__in=user_roles))
  77. with open(TEST_DOCUMENT_PATH, 'rb') as upload:
  78. response = self.client.post(self.api_link, data={
  79. 'upload': upload
  80. })
  81. self.assertContains(response, "You can't upload files of this type.", status_code=400)
  82. def test_type_is_locked(self):
  83. """new uploads for this filetype are locked"""
  84. attachment_type = AttachmentType.objects.create(
  85. name="Test extension",
  86. extensions='png',
  87. mimetypes='application/pdf',
  88. status=AttachmentType.LOCKED
  89. )
  90. with open(TEST_DOCUMENT_PATH, 'rb') as upload:
  91. response = self.client.post(self.api_link, data={
  92. 'upload': upload
  93. })
  94. self.assertContains(response, "You can't upload files of this type.", status_code=400)
  95. def test_type_is_disabled(self):
  96. """new uploads for this filetype are disabled"""
  97. attachment_type = AttachmentType.objects.create(
  98. name="Test extension",
  99. extensions='png',
  100. mimetypes='application/pdf',
  101. status=AttachmentType.DISABLED
  102. )
  103. with open(TEST_DOCUMENT_PATH, 'rb') as upload:
  104. response = self.client.post(self.api_link, data={
  105. 'upload': upload
  106. })
  107. self.assertContains(response, "You can't upload files of this type.", status_code=400)
  108. def test_upload_too_big_for_type(self):
  109. """too big uploads are rejected"""
  110. AttachmentType.objects.create(
  111. name="Test extension",
  112. extensions='png',
  113. mimetypes='image/png',
  114. size_limit=100
  115. )
  116. with open(TEST_LARGEPNG_PATH, 'rb') as upload:
  117. response = self.client.post(self.api_link, data={
  118. 'upload': upload
  119. })
  120. self.assertContains(response, "can't upload files of this type larger than", status_code=400)
  121. def test_upload_too_big_for_user(self):
  122. """too big uploads are rejected"""
  123. self.override_acl({
  124. 'max_attachment_size': 100
  125. })
  126. AttachmentType.objects.create(
  127. name="Test extension",
  128. extensions='png',
  129. mimetypes='image/png'
  130. )
  131. with open(TEST_LARGEPNG_PATH, 'rb') as upload:
  132. response = self.client.post(self.api_link, data={
  133. 'upload': upload
  134. })
  135. self.assertContains(response, "can't upload files larger than", status_code=400)
  136. def test_corrupted_image_upload(self):
  137. """corrupted image upload is handled"""
  138. attachment_type = AttachmentType.objects.create(
  139. name="Test extension",
  140. extensions='gif'
  141. )
  142. with open(TEST_CORRUPTEDIMG_PATH, 'rb') as upload:
  143. response = self.client.post(self.api_link, data={
  144. 'upload': upload
  145. })
  146. self.assertContains(response, "Uploaded image was corrupted or invalid.", status_code=400)
  147. def test_document_upload(self):
  148. """successful upload creates orphan attachment"""
  149. attachment_type = AttachmentType.objects.create(
  150. name="Test extension",
  151. extensions='pdf',
  152. mimetypes='application/pdf'
  153. )
  154. with open(TEST_DOCUMENT_PATH, 'rb') as upload:
  155. response = self.client.post(self.api_link, data={
  156. 'upload': upload
  157. })
  158. self.assertEqual(response.status_code, 200)
  159. response_json = json.loads(smart_str(response.content))
  160. attachment = Attachment.objects.get(id=response_json['id'])
  161. self.assertEqual(attachment.filename, 'document.pdf')
  162. self.assertTrue(attachment.is_file)
  163. self.assertFalse(attachment.is_image)
  164. self.assertIsNotNone(attachment.file)
  165. self.assertTrue(not attachment.image)
  166. self.assertTrue(not attachment.thumbnail)
  167. self.assertTrue(six.text_type(attachment.file).endswith('document.pdf'))
  168. self.assertIsNone(response_json['post'])
  169. self.assertEqual(response_json['uploader_name'], self.user.username)
  170. self.assertEqual(response_json['url']['index'], attachment.get_absolute_url())
  171. self.assertIsNone(response_json['url']['thumb'])
  172. self.assertEqual(response_json['url']['uploader'], self.user.get_absolute_url())
  173. # files associated with attachment are deleted on its deletion
  174. file_path = attachment.file.path
  175. self.assertTrue(os.path.exists(file_path))
  176. attachment.delete()
  177. self.assertFalse(os.path.exists(file_path))
  178. def test_small_image_upload(self):
  179. """successful small image upload creates orphan attachment without thumbnail"""
  180. attachment_type = AttachmentType.objects.create(
  181. name="Test extension",
  182. extensions='jpeg,jpg',
  183. mimetypes='image/jpeg'
  184. )
  185. with open(TEST_SMALLJPG_PATH, 'rb') as upload:
  186. response = self.client.post(self.api_link, data={
  187. 'upload': upload
  188. })
  189. self.assertEqual(response.status_code, 200)
  190. response_json = json.loads(smart_str(response.content))
  191. attachment = Attachment.objects.get(id=response_json['id'])
  192. self.assertEqual(attachment.filename, 'small.jpg')
  193. self.assertFalse(attachment.is_file)
  194. self.assertTrue(attachment.is_image)
  195. self.assertTrue(not attachment.file)
  196. self.assertIsNotNone(attachment.image)
  197. self.assertTrue(not attachment.thumbnail)
  198. self.assertTrue(six.text_type(attachment.image).endswith('small.jpg'))
  199. self.assertIsNone(response_json['post'])
  200. self.assertEqual(response_json['uploader_name'], self.user.username)
  201. self.assertEqual(response_json['url']['index'], attachment.get_absolute_url())
  202. self.assertIsNone(response_json['url']['thumb'])
  203. self.assertEqual(response_json['url']['uploader'], self.user.get_absolute_url())
  204. def test_large_image_upload(self):
  205. """successful large image upload creates orphan attachment with thumbnail"""
  206. self.override_acl({
  207. 'max_attachment_size': 10 * 1024
  208. })
  209. attachment_type = AttachmentType.objects.create(
  210. name="Test extension",
  211. extensions='png',
  212. mimetypes='image/png'
  213. )
  214. with open(TEST_LARGEPNG_PATH, 'rb') as upload:
  215. response = self.client.post(self.api_link, data={
  216. 'upload': upload
  217. })
  218. self.assertEqual(response.status_code, 200)
  219. response_json = json.loads(smart_str(response.content))
  220. attachment = Attachment.objects.get(id=response_json['id'])
  221. self.assertEqual(attachment.filename, 'large.png')
  222. self.assertFalse(attachment.is_file)
  223. self.assertTrue(attachment.is_image)
  224. self.assertTrue(not attachment.file)
  225. self.assertIsNotNone(attachment.image)
  226. self.assertIsNotNone(attachment.thumbnail)
  227. self.assertTrue(six.text_type(attachment.image).endswith('large.png'))
  228. self.assertTrue(six.text_type(attachment.thumbnail).endswith('large.png'))
  229. self.assertIsNone(response_json['post'])
  230. self.assertEqual(response_json['uploader_name'], self.user.username)
  231. self.assertEqual(response_json['url']['index'], attachment.get_absolute_url())
  232. self.assertEqual(response_json['url']['thumb'], attachment.get_thumbnail_url())
  233. self.assertEqual(response_json['url']['uploader'], self.user.get_absolute_url())
  234. # thumbnail was scaled down
  235. thumbnail = Image.open(attachment.thumbnail.path)
  236. self.assertEqual(thumbnail.size[0], settings.MISAGO_ATTACHMENT_IMAGE_SIZE_LIMIT[0])
  237. self.assertLess(thumbnail.size[1], settings.MISAGO_ATTACHMENT_IMAGE_SIZE_LIMIT[1])
  238. # files associated with attachment are deleted on its deletion
  239. image_path = attachment.image.path
  240. thumbnail_path = attachment.thumbnail.path
  241. self.assertTrue(os.path.exists(image_path))
  242. self.assertTrue(os.path.exists(thumbnail_path))
  243. attachment.delete()
  244. self.assertFalse(os.path.exists(image_path))
  245. self.assertFalse(os.path.exists(thumbnail_path))
  246. def test_animated_image_upload(self):
  247. """successful gif upload creates orphan attachment with thumbnail"""
  248. attachment_type = AttachmentType.objects.create(
  249. name="Test extension",
  250. extensions='gif',
  251. mimetypes='image/gif'
  252. )
  253. with open(TEST_ANIMATEDGIT_PATH, 'rb') as upload:
  254. response = self.client.post(self.api_link, data={
  255. 'upload': upload
  256. })
  257. self.assertEqual(response.status_code, 200)
  258. response_json = json.loads(smart_str(response.content))
  259. attachment = Attachment.objects.get(id=response_json['id'])
  260. self.assertEqual(attachment.filename, 'animated.gif')
  261. self.assertFalse(attachment.is_file)
  262. self.assertTrue(attachment.is_image)
  263. self.assertTrue(not attachment.file)
  264. self.assertIsNotNone(attachment.image)
  265. self.assertIsNotNone(attachment.thumbnail)
  266. self.assertTrue(six.text_type(attachment.image).endswith('animated.gif'))
  267. self.assertTrue(six.text_type(attachment.thumbnail).endswith('animated.gif'))
  268. self.assertIsNone(response_json['post'])
  269. self.assertEqual(response_json['uploader_name'], self.user.username)
  270. self.assertEqual(response_json['url']['index'], attachment.get_absolute_url())
  271. self.assertEqual(response_json['url']['thumb'], attachment.get_thumbnail_url())
  272. self.assertEqual(response_json['url']['uploader'], self.user.get_absolute_url())