From: Radek Czajka Date: Fri, 29 Jul 2022 09:51:34 +0000 (+0200) Subject: Tests. X-Git-Url: https://git.mdrn.pl/redakcja.git/commitdiff_plain/81f5e7445d649ead05b4d3d0a15b742444cd8b06?ds=inline;hp=1986ab5301eff65a3071430ce52158f9104285d0 Tests. --- diff --git a/Makefile b/Makefile index 304f5db7..34f331ad 100644 --- 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 index 91ea44a2..00000000 --- a/src/alerts/views.py +++ /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 index 57325633..00000000 --- a/src/cover/management/commands/refresh_covers.py +++ /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() diff --git a/src/cover/models.py b/src/cover/models.py index 04c2a6bc..87ac0369 100644 --- a/src/cover/models.py +++ b/src/cover/models.py @@ -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]) diff --git a/src/cover/tests.py b/src/cover/tests.py index a852f25b..4755991e 100644 --- a/src/cover/tests.py +++ b/src/cover/tests.py @@ -1,17 +1,224 @@ # 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( + 'Chłopka (Baba ukraińska), Krzyżanowski, Konrad (1872-1922), domena publiczna', + book.materialize() + ) + + +class CoverPreviewTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.book = Book.create(slug='test', text='', 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 index 00000000..fd0394fb 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 index 00000000..acdba432 --- /dev/null +++ b/src/cover/tests/book.xml @@ -0,0 +1,16 @@ + + + + Tytuł utworu + Utworu, Autor + 2022-07-26 + Wolne Lektury + pol + slug + domena publiczna + + + + diff --git a/src/cover/utils.py b/src/cover/utils.py index fcda91fd..56fb24a7 100644 --- a/src/cover/utils.py +++ b/src/cover/utils.py @@ -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( diff --git a/src/cover/views.py b/src/cover/views.py index 660909d5..638f1c06 100644 --- a/src/cover/views.py +++ b/src/cover/views.py @@ -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 index 3f4a848e..00000000 --- a/src/documents/management/commands/fixdc.py +++ /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.' diff --git a/src/redakcja/settings/__init__.py b/src/redakcja/settings/__init__.py index 1325743e..3286f073 100644 --- a/src/redakcja/settings/__init__.py +++ b/src/redakcja/settings/__init__.py @@ -290,6 +290,9 @@ REST_FRAMEWORK = { } +TEST_INTEGRATION = False + + try: SENTRY_DSN except NameError: diff --git a/src/redakcja/settings/test.py b/src/redakcja/settings/test.py index 4a80142f..dba64b6b 100644 --- a/src/redakcja/settings/test.py +++ b/src/redakcja/settings/test.py @@ -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 index 00000000..90d243d3 --- /dev/null +++ b/src/redakcja/settings/test_full.py @@ -0,0 +1,5 @@ +from .test import * + +TEST_INTEGRATION = True + +MIGRATION_MODULES = {}