Tests.
authorRadek Czajka <rczajka@rczajka.pl>
Fri, 29 Jul 2022 09:51:34 +0000 (11:51 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Fri, 29 Jul 2022 09:51:34 +0000 (11:51 +0200)
13 files changed:
Makefile
src/alerts/views.py [deleted file]
src/cover/management/commands/refresh_covers.py [deleted file]
src/cover/models.py
src/cover/tests.py
src/cover/tests/angelus-novus.jpeg [new file with mode: 0644]
src/cover/tests/book.xml [new file with mode: 0644]
src/cover/utils.py
src/cover/views.py
src/documents/management/commands/fixdc.py [deleted file]
src/redakcja/settings/__init__.py
src/redakcja/settings/test.py
src/redakcja/settings/test_full.py [new file with mode: 0644]

index 304f5db..34f331a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,13 @@ deploy: src/redakcja/localsettings.py
 .ONESHELL:
 test:
        cd src
-       coverage run --branch --source='.' ./manage.py test --settings=redakcja.settings.test; true
+       ./manage.py test --settings=redakcja.settings.test
+
+
+.ONESHELL:
+test_full:
+       cd src
+       coverage run --branch --source='.' ./manage.py test --settings=redakcja.settings.test_full; true
        coverage html -d ../htmlcov.new
        rm -rf ../htmlcov
        mv ../htmlcov.new ../htmlcov
diff --git a/src/alerts/views.py b/src/alerts/views.py
deleted file mode 100644 (file)
index 91ea44a..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.shortcuts import render
-
-# Create your views here.
diff --git a/src/cover/management/commands/refresh_covers.py b/src/cover/management/commands/refresh_covers.py
deleted file mode 100644 (file)
index 5732563..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-import urllib2 as urllib
-from django.core.files.base import ContentFile
-from django.core.management import BaseCommand
-
-from cover.models import Image
-from cover.utils import get_flickr_data, URLOpener, FlickrError
-
-
-class Command(BaseCommand):
-    def add_arguments(self, parser):
-        parser.add_argument('--from', dest='from_id', type=int, default=1)
-
-    def handle(self, *args, **options):
-        from_id = options.get('from_id', 1)
-        images = Image.objects.filter(id__gte=from_id).exclude(book=None).order_by('id')
-        images = images.filter(source_url__contains='flickr.com').exclude(download_url__endswith='_o.jpg')
-        for image in images:
-            print(image.id)
-            try:
-                flickr_data = get_flickr_data(image.source_url)
-                print(flickr_data)
-            except FlickrError as e:
-                print('Flickr analysis failed: %s' % e)
-            else:
-                flickr_url = flickr_data['download_url']
-                if flickr_url != image.download_url:
-                    same_url = Image.objects.filter(download_url=flickr_url)
-                    if same_url:
-                        print('Download url already present in image %s' % same_url.get().id)
-                        continue
-                try:
-                    t = URLOpener().open(flickr_url).read()
-                except urllib.URLError:
-                    print('Broken download url')
-                except IOError:
-                    print('Connection failed')
-                else:
-                    image.download_url = flickr_url
-                    image.file.save(image.file.name, ContentFile(t))
-                    image.save()
index 04c2a6b..87ac036 100644 (file)
@@ -73,7 +73,7 @@ class Image(models.Model):
             img,
             save=False
         )
-        super().save(**kwargs)
+        super().save(update_fields=['use_file'])
     
     def get_absolute_url(self):
         return reverse('cover_image', args=[self.id])
index a852f25..4755991 100644 (file)
 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from django.test import TestCase
+import re
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.test import TestCase, override_settings
+from unittest import skipUnless
+from unittest.mock import patch
+from documents.models import Book
 from cover.forms import ImportForm
+from cover.models import Image
 
 
+IMAGE_PATH = __file__.rsplit('/', 1)[0] + '/tests/angelus-novus.jpeg'
+
+with open(__file__.rsplit('/', 1)[0] + '/tests/book.xml') as f:
+    SAMPLE_XML = f.read()
+
+
+@skipUnless(settings.TEST_INTEGRATION, 'Skip integration tests')
 class FlickrTests(TestCase):
+    def assertEqualWithRe(self, dict1, dict2):
+        self.assertEqual(len(dict1), len(dict2))
+        for k, v in dict2.items():
+            if isinstance(v, re.Pattern):
+                self.assertRegex(dict1[k], v)
+            else:
+                self.assertEqual(dict1[k], v)
+
     def test_flickr(self):
-        form = ImportForm({"source_url": "https://www.flickr.com/photos/rczajka/6941928577/in/photostream"})
+        form = ImportForm({
+            "source_url": "https://www.flickr.com/photos/rczajka/6941928577/in/photostream"
+        })
+        self.assertTrue(form.is_valid())
+        self.assertEqualWithRe(
+            form.cleaned_data,
+            {
+                'source_url': "https://www.flickr.com/photos/rczajka/6941928577/",
+                'author': "Radek Czajka@Flickr",
+                'title': "Pirate Stańczyk",
+                'license_name': "CC BY 2.0",
+                'license_url': "https://creativecommons.org/licenses/by/2.0/",
+                'download_url': re.compile(r'\.staticflickr\.com'),
+            }
+        )
+
+    def test_wikimedia_fal(self):
+        form = ImportForm({
+            "source_url": "https://commons.wikimedia.org/wiki/File:Valdai_IverskyMon_asv2018_img47.jpg"
+        })
+        self.assertTrue(form.is_valid())
+        self.assertEqual(
+            form.cleaned_data,
+            {
+                'title': 'Valdai IverskyMon asv2018 img47',
+                'author': 'A.Savin',
+                'source_url': 'https://commons.wikimedia.org/wiki/File:Valdai_IverskyMon_asv2018_img47.jpg',
+                'download_url': 'https://upload.wikimedia.org/wikipedia/commons/4/43/Valdai_IverskyMon_asv2018_img47.jpg',
+                'license_url': 'http://artlibre.org/licence/lal/en',
+                'license_name': 'FAL'
+            }
+        )
+
+    def test_wikimedia_public_domain(self):
+        form = ImportForm({
+            "source_url": 'https://commons.wikimedia.org/wiki/File:Pymonenko_A_boy_in_a_straw_hat.jpg'
+        })
         self.assertTrue(form.is_valid())
-        self.assertEqual(form.cleaned_data['source_url'], "https://www.flickr.com/photos/rczajka/6941928577/")
-        self.assertEqual(form.cleaned_data['author'], "Radek Czajka@Flickr")
-        self.assertEqual(form.cleaned_data['title'], u"Pirate Stańczyk")
-        self.assertEqual(form.cleaned_data['license_name'], "CC BY 2.0")
-        self.assertEqual(form.cleaned_data['license_url'], "https://creativecommons.org/licenses/by/2.0/")
-        self.assertTrue('.staticflickr.com' in form.cleaned_data['download_url'])
+        self.assertEqual(
+            form.cleaned_data,
+            {
+                'title': 'Chłopiec w słomkowym kapeluszu',
+                'author': 'Mykola Pymonenko',
+                'source_url': 'https://commons.wikimedia.org/wiki/File:Pymonenko_A_boy_in_a_straw_hat.jpg',
+                'download_url': 'https://upload.wikimedia.org/wikipedia/commons/9/9b/Pymonenko_A_boy_in_a_straw_hat.jpg',
+                'license_url': 'https://pl.wikipedia.org/wiki/Domena_publiczna',
+                'license_name': 'domena publiczna'
+            }
+        )
+        
+    def test_mnw(self):
+        form = ImportForm({
+            "source_url": 'https://cyfrowe.mnw.art.pl/pl/katalog/511078'
+        })
+        self.assertTrue(form.is_valid())
+        self.assertEqualWithRe(
+            form.cleaned_data,
+            {
+                'title': 'Chłopka (Baba ukraińska)',
+                'author': 'Krzyżanowski, Konrad (1872-1922)',
+                'source_url': 'https://cyfrowe.mnw.art.pl/pl/katalog/511078',
+                'download_url': re.compile(r'https://cyfrowe-cdn\.mnw\.art\.pl/.*\.jpg'),
+                'license_url': 'https://pl.wikipedia.org/wiki/Domena_publiczna',
+                'license_name': 'domena publiczna'
+            }
+        )
+
+    def test_quick_import(self):
+        user = User.objects.create(username='test', is_superuser=True)
+        self.client.force_login(user)
+
+        book = Book.create(slug='test', text=SAMPLE_XML, creator=user)
+        
+        self.client.post(
+            '/cover/quick-import/1/',
+            {
+                'url': 'https://cyfrowe.mnw.art.pl/pl/katalog/511078'
+            }
+        )
+
+        self.assertEqual(Image.objects.all().count(), 1)
+        self.assertEqual(book[0].history().count(), 2)
+        self.assertIn(
+            '<dc:relation.coverImage.attribution>Chłopka (Baba ukraińska), Krzyżanowski, Konrad (1872-1922), domena publiczna</dc:relation.coverImage.attribution>',
+            book.materialize()
+        )
+
+
+class CoverPreviewTest(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.book = Book.create(slug='test', text='<utwor/>', creator=None)
+
+    def test_preview_from_bad_xml(self):
+        response = self.client.post('/cover/preview/', data={"xml": ''})
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.content, b'/media/static/img/sample_cover.png')
+
+    @patch('cover.views.make_cover')
+    def test_preview_from_minimal_xml(self, make_cover):
+        response = self.client.post('/cover/preview/', data={"xml": SAMPLE_XML})
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(b'/media/dynamic/cover/preview/', response.content)
+
+    def test_bad_book(self):
+        response = self.client.get('/cover/preview/test/')
+        self.assertEqual(response.status_code, 302)
+
+    @patch('cover.views.make_cover')
+    def test_good_book(self, make_cover):
+        self.book[0].commit(text=SAMPLE_XML)
+
+        response = self.client.get('/cover/preview/test/1/3/')
+        self.assertEqual(response.status_code, 404)
+
+        response = self.client.get('/cover/preview/test/1/')
+        self.assertEqual(response.status_code, 200)
+        self.assertNotIn('Content-Disposition', response)
+
+        response = self.client.get('/cover/preview/test/1/2/?download&width=100')
+        self.assertEqual(response.status_code, 200)
+        self.assertIn('attachment', response['Content-Disposition'])
+
+
+class TestAddCover(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.user = User.objects.create(username='test', is_superuser=True)
+
+    def test_add_image(self):
+        self.client.force_login(self.user)
+
+        response = self.client.get('/cover/add_image/')
+        self.assertEqual(response.status_code, 200)
+        
+        with open(IMAGE_PATH, 'rb') as image_file:
+            response = self.client.post(
+                '/cover/add_image/',
+                data={
+                    'title': 'Angelus Novus',
+                    'author': 'Paul Klee',
+                    'license_name': 'domena publiczna',
+                    'license_url': '',
+                    'file': image_file,
+                }
+            )
+        self.assertEqual(Image.objects.all().count(), 1)
+
+class TestCover(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.user = User.objects.create(username='test', is_superuser=True)
+        with open(IMAGE_PATH, 'rb') as f:
+            cls.img = Image.objects.create(
+                title='Angelus Novus',
+                author='Paul Klee',
+                license_name='domena publiczna',
+                license_url='',
+                file=SimpleUploadedFile(
+                    'angelus-novus.jpg',
+                    f.read(),
+                    content_type='image/jpeg'
+                )
+            )
+
+    def test_image_list(self):
+        response = self.client.get('/cover/image/')
+        self.assertEqual(len(response.context['object_list']), 1)
+        
+    def test_image(self):
+        response = self.client.get('/cover/image/1/')
+        self.assertEqual(response.context['object'].title, 'Angelus Novus')
+
+    def test_edit_image(self):
+        self.client.force_login(self.user)
+        response = self.client.post('/cover/image/1/', {
+            'author': 'author',
+            'title': 'changed title',
+            'license_name': 'domena',
+            'cut_top': 1,
+            'cut_bottom': 1,
+            'cut_left': 1,
+            'cut_right': 1,
+        })
+
+        response = self.client.get('/cover/image/1/')
+        self.assertEqual(response.context['object'].title, 'changed title')
+        
+        
+    def test_image_file(self):
+        response = self.client.get('/cover/image/1/file/')
+        self.assertRegex(response['Location'], r'^/media/dynamic/cover/image/')
diff --git a/src/cover/tests/angelus-novus.jpeg b/src/cover/tests/angelus-novus.jpeg
new file mode 100644 (file)
index 0000000..fd0394f
Binary files /dev/null and b/src/cover/tests/angelus-novus.jpeg differ
diff --git a/src/cover/tests/book.xml b/src/cover/tests/book.xml
new file mode 100644 (file)
index 0000000..acdba43
--- /dev/null
@@ -0,0 +1,16 @@
+<utwor>
+  <rdf:RDF
+      xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+      xmlns:dc="http://purl.org/dc/elements/1.1/">
+    <rdf:Description rdf:about="">
+      <dc:title>Tytuł utworu</dc:title>
+      <dc:creator>Utworu, Autor</dc:creator>
+      <dc:date>2022-07-26</dc:date>
+      <dc:publisher>Wolne Lektury</dc:publisher>
+      <dc:language>pol</dc:language>
+      <dc:identifier.url>slug</dc:identifier.url>
+      <dc:rights>domena publiczna</dc:rights>
+    </rdf:Description>
+  </rdf:RDF>
+</utwor>
+
index fcda91f..56fb24a 100644 (file)
@@ -76,14 +76,6 @@ def get_flickr_data(url):
 
 
 def get_wikimedia_data(url):
-    """
-    >>> get_wikimedia_data('https://commons.wikimedia.org/wiki/File:Valdai_IverskyMon_asv2018_img47.jpg')
-    {'title': 'Valdai IverskyMon asv2018 img47', 'author': 'A.Savin', 'source_url': 'https://commons.wikimedia.org/wiki/File:Valdai_IverskyMon_asv2018_img47.jpg', 'download_url': 'https://upload.wikimedia.org/wikipedia/commons/4/43/Valdai_IverskyMon_asv2018_img47.jpg', 'license_url': 'http://artlibre.org/licence/lal/en', 'license_name': 'FAL'}
-
-    >>> get_wikimedia_data('https://commons.wikimedia.org/wiki/File:Pymonenko_A_boy_in_a_straw_hat.jpg')
-    {'title': 'Chłopiec w słomkowym kapeluszu', 'author': 'Mykola Pymonenko', 'source_url': 'https://commons.wikimedia.org/wiki/File:Pymonenko_A_boy_in_a_straw_hat.jpg', 'download_url': 'https://upload.wikimedia.org/wikipedia/commons/9/9b/Pymonenko_A_boy_in_a_straw_hat.jpg', 'license_url': 'https://pl.wikipedia.org/wiki/Domena_publiczna', 'license_name': 'domena publiczna'}
-
-    """
     file_name = url.rsplit('/', 1)[-1].rsplit(':', 1)[-1]
     d = json.loads(URLOpener().open('https://commons.wikimedia.org/w/api.php?action=query&titles=File:{}&prop=imageinfo&iiprop=url|user|extmetadata&iimetadataversion=latest&format=json'.format(file_name)).read().decode('utf-8'))
 
@@ -118,10 +110,6 @@ def get_wikimedia_data(url):
 
 
 def get_mnw_data(url):
-    """
-    >>> get_mnw_data('https://cyfrowe.mnw.art.pl/pl/katalog/511078')
-    {'title': 'Chłopka (Baba ukraińska)', 'author': 'Krzyżanowski, Konrad (1872-1922)', 'source_url': 'https://cyfrowe.mnw.art.pl/pl/katalog/511078', 'download_url': 'https://cyfrowe-cdn.mnw.art.pl/upload/multimedia/a0/68/a0681c60f203d907d9c45050d245c921.jpg', 'license_url': 'https://pl.wikipedia.org/wiki/Domena_publiczna', 'license_name': 'domena publiczna'}
-    """
     nr = url.rsplit('/', 1)[-1]
     d = list(
         csv.DictReader(
index 660909d..638f1c0 100644 (file)
@@ -1,7 +1,10 @@
 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
+from hashlib import sha1
+from os import makedirs
 import os.path
+import PIL.Image
 from django.conf import settings
 from django.contrib.auth.decorators import permission_required
 from django.http import HttpResponse, HttpResponseRedirect, Http404
@@ -10,6 +13,8 @@ from django.views.decorators.csrf import csrf_exempt
 from django.views.decorators.http import require_POST
 from lxml import etree
 from librarian import RDFNS, DCNS
+from librarian.cover import make_cover
+from librarian.dcparser import BookInfo
 from documents.helpers import active_tab
 from documents.models import Book, Chunk
 from cover.models import Image
@@ -26,10 +31,6 @@ def preview(request, book, chunk=None, rev=None):
     If chunk and rev number are given, use version from given revision.
     If rev is not given, use publishable version.
     """
-    from PIL import Image
-    from librarian.cover import make_cover
-    from librarian.dcparser import BookInfo
-
     chunk = Chunk.get(book, chunk)
     if rev is not None:
         try:
@@ -44,7 +45,8 @@ def preview(request, book, chunk=None, rev=None):
 
     try:
         info = BookInfo.from_bytes(xml)
-    except:
+    except Exception as e:
+        print(e)
         return HttpResponseRedirect(os.path.join(settings.STATIC_URL, "img/sample_cover.png"))
     width = request.GET.get('width')
     width = int(width) if width else None
@@ -70,17 +72,11 @@ def preview(request, book, chunk=None, rev=None):
 @csrf_exempt
 @require_POST
 def preview_from_xml(request):
-    from hashlib import sha1
-    from PIL import Image
-    from os import makedirs
-    from lxml import etree
-    from librarian.cover import make_cover
-    from librarian.dcparser import BookInfo
-
     xml = request.POST['xml']
     try:
         info = BookInfo.from_bytes(xml.encode('utf-8'))
-    except:
+    except Exception as e:
+        print(e)
         return HttpResponse(os.path.join(settings.STATIC_URL, "img/sample_cover.png"))
     coverid = sha1(etree.tostring(info.to_etree())).hexdigest()
     cover = make_cover(info)
@@ -91,7 +87,7 @@ def preview_from_xml(request):
     except OSError:
         pass
     fname = os.path.join(cover_dir, "%s.%s" % (coverid, cover.ext()))
-    img = cover.image().resize(PREVIEW_SIZE, Image.ANTIALIAS)
+    img = cover.image().resize(PREVIEW_SIZE, PIL.Image.ANTIALIAS)
     img.save(os.path.join(settings.MEDIA_ROOT, fname))
     return HttpResponse(os.path.join(settings.MEDIA_URL, fname))
 
diff --git a/src/documents/management/commands/fixdc.py b/src/documents/management/commands/fixdc.py
deleted file mode 100644 (file)
index 3f4a848..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-# This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from librarian import RDFNS, WLURI, ValidationError
-from librarian.dcparser import BookInfo
-from documents.management import XmlUpdater
-from documents.management.commands import XmlUpdaterCommand
-
-
-class FixDC(XmlUpdater):
-    commit_desc = "auto-fixing DC"
-    retain_publishable = True
-    only_first_chunk = True
-
-    def fix_wluri(elem, change, verbose):
-        try:
-            WLURI.strict(elem.text)
-        except ValidationError:
-            correct_field = str(WLURI.from_slug(
-                                WLURI(elem.text.strip()).slug))
-            try:
-                WLURI.strict(correct_field)
-            except ValidationError:
-                # Can't make a valid WLURI out of it, leave as is.
-                return False
-            if verbose:
-                print("Changing %s from %s to %s" % (
-                        elem.tag, elem.text, correct_field
-                    ))
-            elem.text = correct_field
-            return True
-    for field in BookInfo.FIELDS:
-        if field.validator == WLURI:
-            XmlUpdater.fixes_elements('.//' + field.uri)(fix_wluri)
-
-    @XmlUpdater.fixes_elements(".//" + RDFNS("Description"))
-    def fix_rdfabout(elem, change, verbose):
-        correct_about = change.tree.book.correct_about()
-        attr_name = RDFNS("about")
-        current_about = elem.get(attr_name)
-        if current_about != correct_about:
-            if verbose:
-                print("Changing rdf:about from %s to %s" % (
-                        current_about, correct_about
-                    ))
-            elem.set(attr_name, correct_about)
-            return True
-
-
-class Command(XmlUpdaterCommand):
-    updater = FixDC
-    help = 'Fixes obvious errors in DC: rdf:about and WLURI format.'
index 1325743..3286f07 100644 (file)
@@ -290,6 +290,9 @@ REST_FRAMEWORK = {
 }
 
 
+TEST_INTEGRATION = False
+
+
 try:
     SENTRY_DSN
 except NameError:
index 4a80142..dba64b6 100644 (file)
@@ -26,3 +26,16 @@ SECRET_KEY = "not-so-secret"
 
 
 LITERARY_DIRECTOR_USERNAME = 'Kaowiec'
+
+MIN_COVER_SIZE = (1, 1)
+
+
+class DisableMigrations(object):
+
+    def __contains__(self, item):
+        return True
+
+    def __getitem__(self, item):
+        return None
+
+MIGRATION_MODULES = DisableMigrations()
diff --git a/src/redakcja/settings/test_full.py b/src/redakcja/settings/test_full.py
new file mode 100644 (file)
index 0000000..90d243d
--- /dev/null
@@ -0,0 +1,5 @@
+from .test import *
+
+TEST_INTEGRATION = True
+
+MIGRATION_MODULES = {}