1from io import StringIO
2import os
3import platform
4import sys
5import shutil
6import unittest
7from PIL import Image
8
9import pytest
10from django.test import TestCase
11from django.test.utils import override_settings
12
13from sorl.thumbnail import default, delete, get_thumbnail
14from sorl.thumbnail.base import ThumbnailBackend
15from sorl.thumbnail.conf import settings
16from sorl.thumbnail.helpers import get_module_class
17from sorl.thumbnail.images import ImageFile
18from .utils import BaseTestCase, FakeFile, same_open_fd_count
19from .models import Item
20
21
22pytestmark = pytest.mark.django_db
23
24
25class BackendTest(BaseTestCase):
26    def test_delete(self):
27        im1 = Item.objects.get(image='100x100.jpg').image
28        im2 = Item.objects.get(image='500x500.jpg').image
29        default.kvstore.get_or_set(ImageFile(im1))
30
31        # exists in kvstore and in storage
32        self.assertTrue(bool(default.kvstore.get(ImageFile(im1))))
33        self.assertTrue(ImageFile(im1).exists())
34
35        # delete
36        delete(im1)
37        self.assertFalse(bool(default.kvstore.get(ImageFile(im1))))
38        self.assertFalse(ImageFile(im1).exists())
39
40        default.kvstore.get_or_set(ImageFile(im2))
41
42        # exists in kvstore and in storage
43        self.assertTrue(bool(default.kvstore.get(ImageFile(im2))))
44        self.assertTrue(ImageFile(im2).exists())
45
46        # delete
47        delete(im2, delete_file=False)
48        self.assertFalse(bool(default.kvstore.get(ImageFile(im2))))
49        self.assertTrue(ImageFile(im2).exists())
50
51
52@override_settings(THUMBNAIL_PRESERVE_FORMAT=True, THUMBNAIL_FORMAT='XXX')
53class PreserveFormatTest(TestCase):
54    def setUp(self):
55        self.backend = ThumbnailBackend()
56
57    def test_with_various_formats(self):
58        self.assertEqual(self.backend._get_format(FakeFile('foo.jpg')), 'JPEG')
59        self.assertEqual(self.backend._get_format(FakeFile('foo.jpeg')), 'JPEG')
60        self.assertEqual(self.backend._get_format(FakeFile('foo.png')), 'PNG')
61        self.assertEqual(self.backend._get_format(FakeFile('foo.gif')), 'GIF')
62
63    def test_double_extension(self):
64        self.assertEqual(self.backend._get_format(FakeFile('foo.ext.jpg')), 'JPEG')
65
66    def test_that_capitalization_doesnt_matter(self):
67        self.assertEqual(self.backend._get_format(FakeFile('foo.PNG')), 'PNG')
68        self.assertEqual(self.backend._get_format(FakeFile('foo.JPG')), 'JPEG')
69
70    def test_fallback_format(self):
71        self.assertEqual(self.backend._get_format(FakeFile('foo.txt')), 'XXX')
72
73    def test_with_nonascii(self):
74        self.assertEqual(self.backend._get_format(FakeFile('你好.jpg')), 'JPEG')
75
76    def test_image_remote_url(self):
77        self.assertEqual(self.backend._get_format(FakeFile('http://example.com/1.png')), 'PNG')
78
79
80@unittest.skipIf(platform.system() == "Windows", "Can't easily count descriptors on windows")
81class TestDescriptors(unittest.TestCase):
82    """Make sure we're not leaving open descriptors on file exceptions"""
83    ENGINE = None
84
85    def setUp(self):
86        self.ENGINE = get_module_class(settings.THUMBNAIL_ENGINE)()
87
88    def test_no_source_get_image(self):
89        """If source image does not exists, properly close all file descriptors"""
90        source = ImageFile('nonexistent.jpeg')
91
92        with same_open_fd_count(self):
93            with self.assertRaises(IOError):
94                self.ENGINE.get_image(source)
95
96    def test_is_valid_image(self):
97        with same_open_fd_count(self):
98            self.ENGINE.is_valid_image(b'invalidbinaryimage.jpg')
99
100    @unittest.skipIf('pgmagick_engine' in settings.THUMBNAIL_ENGINE and sys.version_info.major == 2,
101                     'No output has been received in the last 10 minutes,'
102                     'this potentially indicates something wrong with the build itself.')
103    def test_write(self):
104        with same_open_fd_count(self):
105            with self.assertRaises(Exception):
106                self.ENGINE.write(image=self.ENGINE.get_image(StringIO(b'xxx')),
107                                  options={'format': 'JPEG', 'quality': 90, 'image_info': {}},
108                                  thumbnail=ImageFile('whatever_thumb.jpg', default.storage))
109
110
111class ModelTestCase(BaseTestCase):
112    def test_field1(self):
113        self.KVSTORE.clear()
114        item = Item.objects.get(image='100x100.jpg')
115        im = ImageFile(item.image)
116        self.assertEqual(None, self.KVSTORE.get(im))
117        self.BACKEND.get_thumbnail(im, '27x27')
118        self.BACKEND.get_thumbnail(im, '81x81')
119        self.assertNotEqual(None, self.KVSTORE.get(im))
120        self.assertEqual(3, len(list(self.KVSTORE._find_keys(identity='image'))))
121        self.assertEqual(1, len(list(self.KVSTORE._find_keys(identity='thumbnails'))))
122
123
124class TestInputCase(unittest.TestCase):
125    def setUp(self):
126        if not os.path.exists(settings.MEDIA_ROOT):
127            os.makedirs(settings.MEDIA_ROOT)
128
129        self.name = 'åäö.jpg'
130
131        fn = os.path.join(settings.MEDIA_ROOT, self.name)
132        im = Image.new('L', (666, 666))
133        im.save(fn)
134
135    def test_nonascii(self):
136        # also test the get_thumbnail shortcut
137        th = get_thumbnail(self.name, '200x200')
138        self.assertEqual(th.url, '/media/test/cache/f5/26/f52608b56718f62abc45a90ff9459f2c.jpg')
139
140    def tearDown(self):
141        shutil.rmtree(settings.MEDIA_ROOT)
142