Remove unused dependency on nose. Move to default tests discovery.
authorRadek Czajka <rczajka@rczajka.pl>
Sat, 15 Dec 2018 21:01:12 +0000 (22:01 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Wed, 19 Dec 2018 20:42:27 +0000 (21:42 +0100)
29 files changed:
.gitignore
Makefile [new file with mode: 0644]
README.md
requirements/requirements-test.txt
src/catalogue/tests/__init__.py
src/catalogue/tests/book_import.py [deleted file]
src/catalogue/tests/bookmedia.py [deleted file]
src/catalogue/tests/cover.py [deleted file]
src/catalogue/tests/files/fraszka-do-anusie.xml [changed mode: 0755->0644]
src/catalogue/tests/files/fraszki.xml [changed mode: 0755->0644]
src/catalogue/tests/tags.py [deleted file]
src/catalogue/tests/templatetags.py [deleted file]
src/catalogue/tests/test_book_import.py [new file with mode: 0644]
src/catalogue/tests/test_bookmedia.py [new file with mode: 0644]
src/catalogue/tests/test_cover.py [new file with mode: 0644]
src/catalogue/tests/test_tags.py [new file with mode: 0644]
src/catalogue/tests/test_templatetags.py [new file with mode: 0644]
src/catalogue/tests/test_visit.py [new file with mode: 0644]
src/catalogue/tests/visit.py [deleted file]
src/oai/tests/__init__.py
src/oai/tests/files/antygona.xml [changed mode: 0755->0644]
src/oai/tests/files/lubie-kiedy-kobieta.xml [changed mode: 0755->0644]
src/oai/tests/oaipmhapi.py [deleted file]
src/oai/tests/test_oaipmhapi.py [new file with mode: 0644]
src/opds/tests/__init__.py [changed mode: 0755->0644]
src/opds/tests/test_opds.py [new file with mode: 0644]
src/picture/tests/__init__.py
src/picture/tests/picture_import.py [deleted file]
src/picture/tests/test_picture_import.py [new file with mode: 0644]

index ed1bedc..a1c1ad1 100644 (file)
@@ -21,6 +21,7 @@ src/wolnelektury/static/scss/*.css
 coverage.xml
 pip-log.txt
 nosetests.xml
+/htmlcov
 
 # Mac OS X garbage
 .DS_Store
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..5ef3ba7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+.ONESHELL:
+test:
+       cd src
+       coverage run --branch --source='.' ./manage.py test; true
+       rm -rf ../htmlcov
+       coverage html -d ../htmlcov.new
+       rm -rf ../htmlcov
+       mv ../htmlcov.new ../htmlcov
+       coverage report
+       rm .coverage
index 340ae04..5b39ff9 100644 (file)
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ How to deploy (development version)
 1. Checkout the source code from Github and enter the directory
 2. Install libraries (we recommend using pip):
 
-    pip install -r requirements.txt
+    pip install -r requirements/requirements.txt
     git submodule update --init
 
 3. Setup your local configuration based on settings.py. You need to generate a new SECRET_KEY, database stuff and domain related stuff.
index 87aa0fe..794632d 100644 (file)
@@ -1,6 +1,4 @@
 -i https://py.mdrn.pl:8443/simple/
 
-nose>=1.3.7
-django-nose>=1.4.2,<1.5
-nosexcover
+coverage
 mock
index 4eb8e8d..e69de29 100644 (file)
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from catalogue.tests.book_import import *
-from catalogue.tests.bookmedia import *
-from catalogue.tests.cover import *
-from catalogue.tests.tags import *
-from catalogue.tests.templatetags import *
-from .visit import *
diff --git a/src/catalogue/tests/book_import.py b/src/catalogue/tests/book_import.py
deleted file mode 100644 (file)
index a445268..0000000
+++ /dev/null
@@ -1,432 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from django.core.files.base import ContentFile
-from catalogue.test_utils import *
-from catalogue import models
-from librarian import WLURI
-
-from nose.tools import raises
-from os import path, makedirs
-
-
-class BookImportLogicTests(WLTestCase):
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        self.book_info = BookInfoStub(
-            url=WLURI.from_slug(u"default-book"),
-            about=u"http://wolnelektury.pl/example/URI/default_book",
-            title=u"Default Book",
-            author=PersonStub(("Jim",), "Lazy"),
-            kind="X-Kind",
-            genre="X-Genre",
-            epoch="X-Epoch",
-            language=u"pol",
-        )
-
-        self.expected_tags = [
-           ('author', 'jim-lazy'),
-           ('genre', 'x-genre'),
-           ('epoch', 'x-epoch'),
-           ('kind', 'x-kind'),
-        ]
-        self.expected_tags.sort()
-
-    def test_empty_book(self):
-        book_text = "<utwor />"
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-
-        self.assertEqual(book.title, "Default Book")
-        self.assertEqual(book.slug, "default-book")
-        self.assert_(book.parent is None)
-        self.assertFalse(book.has_html_file())
-
-        # no fragments generated
-        self.assertEqual(book.fragments.count(), 0)
-
-        # TODO: this should be filled out probably...
-        self.assertEqual(book.wiki_link, '')
-        self.assertEqual(book.gazeta_link, '')
-        self.assertEqual(book.description, '')
-
-        tags = [(tag.category, tag.slug) for tag in book.tags]
-        tags.sort()
-
-        self.assertEqual(tags, self.expected_tags)
-
-    def test_not_quite_empty_book(self):
-        """ Not empty, but without any real text.
-
-        Should work like any other non-empty book.
-        """
-
-        book_text = """<utwor>
-        <liryka_l>
-            <nazwa_utworu>Nic</nazwa_utworu>
-        </liryka_l></utwor>
-        """
-
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        self.assertTrue(book.has_html_file())
-
-    def test_book_with_fragment(self):
-        book_text = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        self.assertTrue(book.has_html_file())
-
-        self.assertEqual(book.fragments.count(), 1)
-        self.assertEqual(book.fragments.all()[0].text, u'<p class="paragraph">Ala ma kota</p>\n')
-
-        self.assert_(('theme', 'love') in [(tag.category, tag.slug) for tag in book.fragments.all()[0].tags])
-
-    def test_book_with_empty_theme(self):
-        """ empty themes should be ignored """
-
-        book_text = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01"> , Love , , </motyw>Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        self.assert_([('theme', 'love')],
-                     book.fragments.all()[0].tags.filter(category='theme').values_list('category', 'slug'))
-
-    def test_book_with_no_theme(self):
-        """ fragments with no themes shouldn't be created at all """
-
-        book_text = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01"></motyw>Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        self.assertEqual(book.fragments.count(), 0)
-        self.assertEqual(book.tags.filter(category='theme').count(), 0)
-
-    @raises(ValueError)
-    def test_book_with_invalid_slug(self):
-        """ Book with invalid characters in slug shouldn't be imported """
-        self.book_info.url = WLURI.from_slug(u"default_book")
-        book_text = "<utwor />"
-        models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-
-    def test_book_replace_title(self):
-        book_text = """<utwor />"""
-        models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        self.book_info.title = u"Extraordinary"
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
-
-        tags = [(tag.category, tag.slug) for tag in book.tags]
-        tags.sort()
-
-        self.assertEqual(tags, self.expected_tags)
-
-    def test_book_replace_author(self):
-        book_text = """<utwor />"""
-        models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        self.book_info.author = PersonStub(("Hans", "Christian"), "Andersen")
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
-
-        tags = [(tag.category, tag.slug) for tag in book.tags]
-        tags.sort()
-
-        self.expected_tags.remove(('author', 'jim-lazy'))
-        self.expected_tags.append(('author', 'hans-christian-andersen'))
-        self.expected_tags.sort()
-
-        self.assertEqual(tags, self.expected_tags)
-
-        # the old tag shouldn't disappear
-        models.Tag.objects.get(slug="jim-lazy", category="author")
-
-    def test_book_remove_fragment(self):
-        book_text = """<utwor>
-        <opowiadanie>
-            <akap>
-                <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
-                <begin id="m02" /><motyw id="m02">Hatred</motyw>To kot Ali<end id="m02" />
-            </akap>
-        </opowiadanie></utwor>
-        """
-        book_text_after = """<utwor>
-        <opowiadanie>
-            <akap>
-                <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
-                To kot Ali
-            </akap>
-        </opowiadanie></utwor>
-        """
-
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        self.assertEqual(book.fragments.count(), 2)
-        book = models.Book.from_text_and_meta(ContentFile(book_text_after), self.book_info, overwrite=True)
-        self.assertEqual(book.fragments.count(), 1)
-
-    def test_multiple_tags(self):
-        book_text = """<utwor />"""
-        self.book_info.authors = self.book_info.author, PersonStub(("Joe",), "Dilligent"),
-        self.book_info.kinds = self.book_info.kind, 'Y-Kind',
-        self.book_info.genres = self.book_info.genre, 'Y-Genre',
-        self.book_info.epochs = self.book_info.epoch, 'Y-Epoch',
-
-        self.expected_tags.extend([
-           ('author', 'joe-dilligent'),
-           ('genre', 'y-genre'),
-           ('epoch', 'y-epoch'),
-           ('kind', 'y-kind'),
-        ])
-        self.expected_tags.sort()
-
-        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
-        tags = [(tag.category, tag.slug) for tag in book.tags]
-        tags.sort()
-
-        self.assertEqual(tags, self.expected_tags)
-
-
-class ChildImportTests(WLTestCase):
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        self.child_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Joe",), "Doe"),
-            **info_args("Child")
-        )
-
-        self.parent_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Jim",), "Lazy"),
-            parts=[self.child_info.url],
-            **info_args("Parent")
-        )
-
-    def test_child(self):
-        text = """<utwor />"""
-        child = models.Book.from_text_and_meta(ContentFile(text), self.child_info)
-        parent = models.Book.from_text_and_meta(ContentFile(text), self.parent_info)
-        author = parent.tags.get(category='author')
-        books = self.client.get(author.get_absolute_url()).context['object_list']
-        self.assertEqual(len(books), 1, "Only parent book should be visible on author's page")
-
-    def test_child_replace(self):
-        parent_text = """<utwor />"""
-        child_text = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01">Pies</motyw>Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-        child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info)
-        parent = models.Book.from_text_and_meta(ContentFile(parent_text), self.parent_info)
-        child_text = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01">Kot</motyw>Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-        child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info, overwrite=True)
-        themes = parent.related_themes()
-        self.assertEqual(['Kot'], [tag.name for tag in themes], 'wrong related theme list')
-
-
-class TreeImportTest(WLTestCase):
-    def setUp(self):
-        WLTestCase.setUp(self)
-        self.child_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Joe",), "Doe"),
-            **info_args("Child")
-        )
-        self.CHILD_TEXT = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01">Pies</motyw>
-                Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-        self.child = models.Book.from_text_and_meta(
-            ContentFile(self.CHILD_TEXT), self.child_info)
-
-        self.book_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Joe",), "Doe"),
-            parts=[self.child_info.url],
-            **info_args("Book")
-        )
-        self.BOOK_TEXT = """<utwor />"""
-        self.book = models.Book.from_text_and_meta(
-            ContentFile(self.BOOK_TEXT), self.book_info)
-
-        self.parent_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Jim",), "Lazy"),
-            parts=[self.book_info.url],
-            **info_args("Parent")
-        )
-        self.PARENT_TEXT = """<utwor />"""
-        self.parent = models.Book.from_text_and_meta(
-            ContentFile(self.PARENT_TEXT), self.parent_info)
-
-    def test_ok(self):
-        self.assertEqual(
-                list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
-                [self.parent],
-                u"There should be only parent on common tag page."
-            )
-        # pies = models.Tag.objects.get(slug='pies')
-        themes = self.parent.related_themes()
-        self.assertEqual(len(themes), 1, u"There should be child theme in parent theme counter.")
-        # TODO: book_count is deprecated, update here.
-        # epoch = models.Tag.objects.get(slug='x-epoch')
-        # self.assertEqual(epoch.book_count, 1, u"There should be only parent in common tag's counter.")
-
-    def test_child_republish(self):
-        child_text = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01">Pies, Kot</motyw>
-                Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-        models.Book.from_text_and_meta(
-            ContentFile(child_text), self.child_info, overwrite=True)
-        self.assertEqual(
-                list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
-                [self.parent],
-                u"There should only be parent on common tag page."
-            )
-        # pies = models.Tag.objects.get(slug='pies')
-        # kot = models.Tag.objects.get(slug='kot')
-        self.assertEqual(len(self.parent.related_themes()), 2,
-                         u"There should be child themes in parent theme counter.")
-        # TODO: book_count is deprecated, update here.
-        # epoch = models.Tag.objects.get(slug='x-epoch')
-        # self.assertEqual(epoch.book_count, 1, u"There should only be parent in common tag's counter.")
-
-    def test_book_change_child(self):
-        second_child_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='Other-Kind',
-            author=PersonStub(("Joe",), "Doe"),
-            **info_args("Second Child")
-        )
-        second_child_text = """<utwor>
-        <opowiadanie>
-            <akap><begin id="m01" /><motyw id="m01">Kot</motyw>
-                Ala ma kota<end id="m01" /></akap>
-        </opowiadanie></utwor>
-        """
-        # Import a second child.
-        second_child = models.Book.from_text_and_meta(
-            ContentFile(second_child_text), second_child_info)
-        # The book has only this new child now.
-        self.book_info.parts = [second_child_info.url]
-        self.book = models.Book.from_text_and_meta(
-            ContentFile(self.BOOK_TEXT), self.book_info, overwrite=True)
-
-        self.assertEqual(
-            set(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
-            {self.parent, self.child},
-            u"There should be parent and old child on common tag page."
-        )
-        # kot = models.Tag.objects.get(slug='kot')
-        self.assertEqual(len(self.parent.related_themes()), 1,
-                         u"There should only be new child themes in parent theme counter.")
-        # # book_count deprecated, update test.
-        # epoch = models.Tag.objects.get(slug='x-epoch')
-        # self.assertEqual(epoch.book_count, 2,
-        #                  u"There should be parent and old child in common tag's counter.")
-        self.assertEqual(
-            list(self.client.get('/katalog/lektura/parent/motyw/kot/').context['fragments']),
-            [second_child.fragments.all()[0]],
-            u"There should be new child's fragments on parent's theme page."
-        )
-        self.assertEqual(
-            list(self.client.get('/katalog/lektura/parent/motyw/pies/').context['fragments']),
-            [],
-            u"There should be no old child's fragments on parent's theme page."
-        )
-
-
-class MultilingualBookImportTest(WLTestCase):
-    def setUp(self):
-        WLTestCase.setUp(self)
-        common_uri = WLURI.from_slug('common-slug')
-
-        self.pol_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Joe",), "Doe"),
-            variant_of=common_uri,
-            **info_args(u"Książka")
-        )
-
-        self.eng_info = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Joe",), "Doe"),
-            variant_of=common_uri,
-            **info_args("A book", "eng")
-        )
-
-    def test_multilingual_import(self):
-        book_text = """<utwor><opowiadanie><akap>A</akap></opowiadanie></utwor>"""
-
-        models.Book.from_text_and_meta(ContentFile(book_text), self.pol_info)
-        models.Book.from_text_and_meta(ContentFile(book_text), self.eng_info)
-
-        self.assertEqual(
-            set([b.language for b in models.Book.objects.all()]),
-            {'pol', 'eng'},
-            'Books imported in wrong languages.'
-        )
-
-
-class BookImportGenerateTest(WLTestCase):
-    def setUp(self):
-        WLTestCase.setUp(self)
-        xml = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml')
-        self.book = models.Book.from_xml_file(xml)
-
-    def test_gen_pdf(self):
-        self.book.pdf_file.build()
-        book = models.Book.objects.get(pk=self.book.pk)
-        self.assertTrue(path.exists(book.pdf_file.path))
-
-    def test_gen_pdf_parent(self):
-        """This book contains a child."""
-        xml = path.join(path.dirname(__file__), "files/fraszki.xml")
-        parent = models.Book.from_xml_file(xml)
-        parent.pdf_file.build()
-        parent = models.Book.objects.get(pk=parent.pk)
-        self.assertTrue(path.exists(parent.pdf_file.path))
-
-    def test_custom_pdf(self):
-        from catalogue.tasks import build_custom_pdf
-        out = 'test-custom.pdf'
-        absoulute_path = path.join(settings.MEDIA_ROOT, out)
-
-        if not path.exists(path.dirname(absoulute_path)):
-            makedirs(path.dirname(absoulute_path))
-
-        build_custom_pdf(self.book.id, customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out)
-        self.assertTrue(path.exists(absoulute_path))
diff --git a/src/catalogue/tests/bookmedia.py b/src/catalogue/tests/bookmedia.py
deleted file mode 100644 (file)
index 263e48d..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from os.path import basename, exists
-from unittest import skip
-
-from django.core.files.base import ContentFile
-
-from catalogue.test_utils import *
-from catalogue import models, utils
-
-
-class BookMediaTests(WLTestCase):
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        self.file = ContentFile('X')
-        self.file2 = ContentFile('Y')
-        self.book = models.Book.objects.create(slug='test-book', title='Test')
-
-    def set_title(self, title):
-        self.book.title = title
-        self.book.save()
-
-    def test_diacritics(self):
-        bm = models.BookMedia(book=self.book, type="ogg", name=u"Zażółć gęślą jaźń")
-        self.set_title(bm.name)
-        bm.file.save(None, self.file)
-        self.assertEqual(basename(bm.file.name), 'zazolc-gesla-jazn.ogg')
-
-    def test_long_name(self):
-        bm = models.BookMedia(
-            book=self.book, type="ogg",
-            name="Some very very very very very very very very very very very very very very very very long file name")
-        self.set_title(bm.name)
-        bm.file.save(bm.name, self.file)
-
-        # reload to see what was really saved
-        bm = models.BookMedia.objects.get(pk=bm.pk)
-        self.assertEqual(bm.file.size, 1)
-
-    def test_overwrite(self):
-        """
-            File gets overwritten with same filename on update.
-        """
-
-        bm = models.BookMedia(book=self.book, type='ogg', name="Some media")
-        self.set_title(bm.name)
-        bm.file.save(None, self.file)
-        bm.file.save(None, self.file2)
-
-        self.assertEqual(bm.file.read(), 'Y')
-        self.assertEqual(basename(bm.file.name), 'some-media.ogg')
-
-    @skip('broken, but is it needed?')
-    def test_no_clobber(self):
-        """
-            File save doesn't clobber some other media with similar name.
-        """
-
-        bm = models.BookMedia(book=self.book, type='ogg', name=u"Tytul")
-        self.set_title(bm.name)
-        bm.file.save(None, self.file)
-        bm2 = models.BookMedia(book=self.book, type='ogg', name=u"Tytuł")
-        self.set_title(bm2.name)
-        bm2.file.save(None, self.file2)
-        self.assertEqual(basename(bm.file.name), 'tytul.ogg')
-        self.assertNotEqual(basename(bm2.file.name), 'tytul.ogg')
-        self.assertEqual(bm.file.read(), 'X')
-        self.assertEqual(bm2.file.read(), 'Y')
-
-    def test_change_name(self):
-        """
-            File name reflects name change.
-        """
-
-        bm = models.BookMedia(book=self.book, type='ogg', name="Title")
-        self.set_title(bm.name)
-        bm.file.save(None, self.file)
-        self.set_title("Other Title")
-        bm.save()
-        self.assertEqual(basename(bm.file.name), 'other-title.ogg')
-        self.assertEqual(bm.file.read(), 'X')
-
-    @skip('broken, but is it needed?')
-    def test_change_name_no_clobber(self):
-        """
-            File name after change won't clobber some other file
-            with similar name.
-        """
-
-        bm = models.BookMedia(book=self.book, type='ogg', name="Title")
-        self.set_title(bm.name)
-        bm.file.save(None, self.file)
-        bm2 = models.BookMedia(book=self.book, type='ogg', name="Other title")
-        self.set_title(bm2.name)
-        bm2.file.save(None, self.file2)
-        self.set_title("Title")
-        bm2.save()
-        self.assertNotEqual(basename(bm2.file.name), 'title.ogg')
-        self.assertEqual(bm.file.read(), 'X')
-        self.assertEqual(bm2.file.read(), 'Y')
-
-    def test_zip_audiobooks(self):
-        paths = [
-            (None, join(dirname(__file__), "files/fraszka-do-anusie.xml")),
-            (None, join(dirname(__file__), "files/fraszki.xml")),
-            ]
-
-        url = utils.create_zip(paths, 'test-zip-slug')
-        self.assertEqual("zip/test-zip-slug.zip", url)
-        self.assertTrue(exists(join(settings.MEDIA_ROOT, url)))
-
-        utils.remove_zip('test-zip-slug')
-        self.assertFalse(exists(join(settings.MEDIA_ROOT, url)))
-
-    def test_remove_zip_on_media_change(self):
-        bm = models.BookMedia(book=self.book, type='ogg', name="Title")
-        self.set_title(bm.name)
-        bm.file.save(None, self.file)
-        bm.save()
-
-        zip_url = self.book.zip_audiobooks('ogg')
-        self.assertEqual('zip/'+self.book.slug+'_ogg.zip', zip_url)
-        self.assertTrue(exists(join(settings.MEDIA_ROOT, zip_url)))
-
-        bm2 = models.BookMedia(book=self.book, type='ogg', name="Other title")
-        self.set_title(bm2.name)
-        bm2.file.save(None, self.file2)
-        self.set_title("Title")
-        bm2.save()
-        # was the audiobook zip deleted?
-        self.assertFalse(exists(join(settings.MEDIA_ROOT, zip_url)))
diff --git a/src/catalogue/tests/cover.py b/src/catalogue/tests/cover.py
deleted file mode 100755 (executable)
index 8c5d047..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from django.core.files.base import ContentFile
-from catalogue.test_utils import BookInfoStub, PersonStub, info_args, WLTestCase
-from catalogue.models import Book
-from mock import patch
-
-
-class CoverTests(WLTestCase):
-    """Checks in parent_cover_changed is properly called."""
-    def setUp(self):
-        WLTestCase.setUp(self)
-        self.TEXT = """<utwor />"""
-        self.child = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Joe",), "Doe"),
-            **info_args("Child")
-        )
-
-        self.parent = BookInfoStub(
-            genre='X-Genre',
-            epoch='X-Epoch',
-            kind='X-Kind',
-            author=PersonStub(("Jim",), "Lazy"),
-            cover_url="http://example.com/cover.jpg",
-            parts=[self.child.url],
-            **info_args("Parent")
-        )
-
-    @patch.object(Book, 'parent_cover_changed', autospec=True)
-    def test_simple_import(self, parent_cover_changed):
-        child = Book.from_text_and_meta(ContentFile(self.TEXT), self.child)
-        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent)
-        parent_cover_changed.assert_called_with(child)
-
-        # Now reimport parent.
-        parent_cover_changed.reset_mock()
-        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
-        self.assertEqual(parent_cover_changed.call_count, 0)
-
-        # Now change cover in parent.
-        parent_cover_changed.reset_mock()
-        self.parent.cover_url = "http://example.com/other-cover.jpg"
-        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
-        parent_cover_changed.assert_called_with(child)
-
-    @patch.object(Book, 'parent_cover_changed', autospec=True)
-    def test_change_cover(self, parent_cover_changed):
-        child = Book.from_text_and_meta(ContentFile(self.TEXT), self.child)
-        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent)
-        parent_cover_changed.assert_called_with(child)
-
-    @patch.object(Book, 'parent_cover_changed', autospec=True)
-    def test_new_child(self, parent_cover_changed):
-        # Add parent without child first.
-        parts, self.parent.parts = self.parent.parts, []
-        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent)
-
-        # Now import child and reimport parent.
-        child = Book.from_text_and_meta(ContentFile(self.TEXT), self.child)
-        self.parent.parts = parts
-        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
-        parent_cover_changed.assert_called_with(child)
-
-        # Now remove the child.
-        parent_cover_changed.reset_mock()
-        self.parent.parts = []
-        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
-        parent_cover_changed.assert_called_with(child)
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
diff --git a/src/catalogue/tests/tags.py b/src/catalogue/tests/tags.py
deleted file mode 100644 (file)
index d5aa72c..0000000
+++ /dev/null
@@ -1,284 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from unittest import skip
-
-from django.core.files.base import ContentFile
-from django.test import Client
-from catalogue import models
-from catalogue.test_utils import *
-
-
-class BooksByTagTests(WLTestCase):
-    """ tests the /katalog/category/tag page for found books """
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        author = PersonStub(("Common",), "Man")
-
-        # grandchild
-        self.gchild_info = BookInfoStub(genre='Genre', epoch='Epoch', kind='Kind', author=author,
-                                        **info_args("GChild"))
-        # child
-        self.child_info = BookInfoStub(genre='Genre', epoch='Epoch', kind='Other Kind', author=author,
-                                       parts=[self.gchild_info.url],
-                                       **info_args("Child"))
-        # parent
-        self.parent_info = BookInfoStub(genre='Genre', epoch='Epoch', kind='Kind', author=author,
-                                        parts=[self.child_info.url],
-                                        **info_args("Parent"))
-
-        self.book_file = ContentFile('<utwor />')
-
-    def test_nonexistent_tag(self):
-        """ Looking for a non-existent tag should yield 404 """
-        self.assertEqual(404, self.client.get('/katalog/autor/czeslaw-milosz/').status_code)
-
-    def test_book_tag(self):
-        """ Looking for a book tag isn't permitted """
-        models.Book.from_text_and_meta(self.book_file, self.gchild_info)
-        self.assertEqual(404, self.client.get('/katalog/gchild/').status_code)
-
-    def test_tag_empty(self):
-        """ Tag with no books should return no books """
-        models.Book.from_text_and_meta(self.book_file, self.gchild_info)
-        models.Tag.objects.create(name='Empty tag', slug='empty', category='author')
-
-        context = self.client.get('/katalog/autor/empty/').context
-        self.assertEqual(0, len(context['object_list']))
-
-    def test_tag_eliminate(self):
-        """ Filtering by tag should only yield top-level qualifying books. """
-        for info in self.gchild_info, self.child_info, self.parent_info:
-            models.Book.from_text_and_meta(self.book_file, info)
-
-        # all three qualify
-        context = self.client.get('/katalog/gatunek/genre/').context
-        self.assertEqual([book.title for book in context['object_list']],
-                         ['Parent'])
-
-        # parent and gchild qualify, child doesn't
-        context = self.client.get('/katalog/rodzaj/kind/').context
-        self.assertEqual([book.title for book in context['object_list']],
-                         ['Parent'])
-
-        # Filtering by child's tag should yield the child
-        context = self.client.get('/katalog/rodzaj/other-kind/').context
-        self.assertEqual([book.title for book in context['object_list']],
-                         ['Child'])
-
-
-class TagRelatedTagsTests(WLTestCase):
-    """ tests the /katalog/category/tag/ page for related tags """
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        self.client = Client()
-        author = PersonStub(("Common",), "Man")
-
-        gchild_info = BookInfoStub(author=author, genre="GchildGenre", epoch='Epoch', kind="Kind",
-                                   **info_args(u"GChild"))
-        child1_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind",
-                                   parts=[gchild_info.url],
-                                   **info_args(u"Child1"))
-        child2_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind",
-                                   **info_args(u"Child2"))
-        parent_info = BookInfoStub(author=author, genre="Genre", epoch='Epoch', kind="Kind",
-                                   parts=[child1_info.url, child2_info.url],
-                                   **info_args(u"Parent"))
-
-        for info in gchild_info, child1_info, child2_info, parent_info:
-            book_text = """<utwor><opowiadanie><akap>
-                <begin id="m01" />
-                    <motyw id="m01">Theme, %sTheme</motyw>
-                    Ala ma kota
-                <end id="m01" />
-                </akap></opowiadanie></utwor>
-                """ % info.title.encode('utf-8')
-            book = models.Book.from_text_and_meta(ContentFile(book_text), info)
-            book.save()
-
-        tag_empty = models.Tag(name='Empty tag', slug='empty', category='author')
-        tag_empty.save()
-
-    def test_empty(self):
-        """ empty tag should have no related tags """
-
-        cats = self.client.get('/katalog/autor/empty/').context['categories']
-        self.assertEqual({k: v for (k, v) in cats.items() if v}, {}, 'tags related to empty tag')
-
-    def test_has_related(self):
-        """ related own and descendants' tags should be generated """
-
-        cats = self.client.get('/katalog/rodzaj/kind/').context['categories']
-        self.assertTrue('Common Man' in [tag.name for tag in cats['author']],
-                        'missing `author` related tag')
-        self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']],
-                        'missing `epoch` related tag')
-        self.assertFalse(cats.get("kind", False),
-                         "There should be no child-only related `kind` tags")
-        self.assertTrue("Genre" in [tag.name for tag in cats['genre']],
-                        'missing `genre` related tag')
-        self.assertFalse("ChildGenre" in [tag.name for tag in cats['genre']],
-                         "There should be no child-only related `genre` tags")
-        self.assertTrue("GchildGenre" in [tag.name for tag in cats['genre']],
-                        "missing grandchild's related tag")
-        self.assertTrue('Theme' in [tag.name for tag in cats['theme']],
-                        "missing related theme")
-        self.assertFalse('Child1Theme' in [tag.name for tag in cats['theme']],
-                         "There should be no child-only related `theme` tags")
-        self.assertTrue('GChildTheme' in [tag.name for tag in cats['theme']],
-                        "missing grandchild's related theme")
-
-    def test_related_differ(self):
-        """ related tags shouldn't include filtering tags """
-
-        response = self.client.get('/katalog/rodzaj/kind/')
-        cats = response.context['categories']
-        self.assertFalse(cats.get('kind', False),
-                         'filtering tag wrongly included in related')
-        cats = self.client.get('/katalog/motyw/theme/').context['categories']
-        self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
-                         'filtering theme wrongly included in related')
-
-    def test_parent_tag_once(self):
-        """ if parent and descendants have a common tag, count it only once """
-
-        cats = self.client.get('/katalog/rodzaj/kind/').context['categories']
-        self.assertEqual([(tag.name, tag.count) for tag in cats['epoch']],
-                         [('Epoch', 1)],
-                         'wrong related tag epoch tag on tag page')
-
-    def test_siblings_tags_count(self):
-        """ if children have tags and parent hasn't, count the children """
-
-        cats = self.client.get('/katalog/epoka/epoch/').context['categories']
-        self.assertTrue(
-            ('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']],
-            'wrong related kind tags on tag page, got: ' +
-            unicode([(tag.name, tag.count) for tag in cats['kind']]))
-
-        # all occurencies of theme should be counted
-        self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']],
-                        'wrong related theme count')
-
-    def test_query_child_tag(self):
-        """
-        If child and parent have a common tag, but parent isn't included
-        in the result, child should still count.
-        """
-        cats = self.client.get('/katalog/gatunek/childgenre/').context['categories']
-        self.assertTrue(('Epoch', 2) in [(tag.name, tag.count) for tag in cats['epoch']],
-                        'wrong related kind tags on tag page, got: ' +
-                        unicode([(tag.name, tag.count) for tag in cats['epoch']]))
-
-
-class CleanTagRelationTests(WLTestCase):
-    """ tests for tag relations cleaning after deleting things """
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        author = PersonStub(("Common",), "Man")
-
-        book_info = BookInfoStub(author=author, genre="G", epoch='E', kind="K", **info_args(u"Book"))
-        book_text = """<utwor><opowiadanie><akap>
-            <begin id="m01" /><motyw id="m01">Theme</motyw>Ala ma kota
-            <end id="m01" />
-            </akap></opowiadanie></utwor>
-            """
-        self.book = models.Book.from_text_and_meta(ContentFile(book_text), book_info)
-
-    @skip('Not implemented and not priority')
-    def test_delete_objects(self):
-        """ there should be no related tags left after deleting some objects """
-
-        models.Book.objects.all().delete()
-        cats = self.client.get('/katalog/rodzaj/k/').context['categories']
-        self.assertEqual({k: v for (k, v) in cats.items() if v}, {})
-        self.assertEqual(models.Fragment.objects.all().count(), 0,
-                         "orphaned fragments left")
-        self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
-                         "orphaned TagRelation objects left")
-
-    def test_deleted_tag(self):
-        """ there should be no tag relations left after deleting tags """
-
-        models.Tag.objects.all().delete()
-        self.assertEqual(len(self.book.related_themes()), 0)
-        self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
-                         "orphaned TagRelation objects left")
-
-
-class TestIdenticalTag(WLTestCase):
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        author = PersonStub((), "Tag")
-
-        self.book_info = BookInfoStub(author=author, genre="tag", epoch='tag', kind="tag", **info_args(u"tag"))
-        self.book_text = """<utwor>
-            <opowiadanie>
-            <akap>
-                <begin id="m01" /><motyw id="m01">tag</motyw>Ala ma kota<end id="m01" />
-            </akap>
-            </opowiadanie>
-            </utwor>
-        """
-
-    def test_book_tags(self):
-        """ there should be all related tags in relevant categories """
-        book = models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
-
-        related_themes = book.related_themes()
-        for category in 'author', 'kind', 'genre', 'epoch':
-            self.assertTrue('tag' in book.tags.filter(category=category).values_list('slug', flat=True),
-                            'missing related tag for %s' % category)
-        self.assertTrue('tag' in [tag.slug for tag in related_themes])
-
-    def test_qualified_url(self):
-        models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
-        categories = {'author': 'autor', 'theme': 'motyw', 'epoch': 'epoka', 'kind': 'rodzaj', 'genre': 'gatunek'}
-        for cat, localcat in categories.iteritems():
-            context = self.client.get('/katalog/%s/tag/' % localcat).context
-            self.assertEqual(1, len(context['object_list']))
-            self.assertNotEqual({}, context['categories'])
-            self.assertFalse(context['categories'].get(cat, False))
-
-
-class BookTagsTests(WLTestCase):
-    """ tests the /katalog/lektura/book/ page for related tags """
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        author1 = PersonStub(("Common",), "Man")
-        author2 = PersonStub(("Jim",), "Lazy")
-
-        child_info = BookInfoStub(authors=(author1, author2), genre="ChildGenre", epoch='Epoch', kind="ChildKind",
-                                  **info_args(u"Child"))
-        parent_info = BookInfoStub(author=author1, genre="Genre", epoch='Epoch', kind="Kind",
-                                   parts=[child_info.url],
-                                   **info_args(u"Parent"))
-
-        for info in child_info, parent_info:
-            book_text = """<utwor><opowiadanie><akap>
-                <begin id="m01" />
-                    <motyw id="m01">Theme, %sTheme</motyw>
-                    Ala ma kota
-                <end id="m01" />
-                </akap></opowiadanie></utwor>
-                """ % info.title.encode('utf-8')
-            models.Book.from_text_and_meta(ContentFile(book_text), info)
-
-    def test_book_tags(self):
-        """ book should have own tags and whole tree's themes """
-
-        book = models.Book.objects.get(slug='parent')
-        related_themes = book.related_themes()
-
-        self.assertEqual([t.slug for t in book.authors()],
-                         ['common-man'])
-        self.assertEqual([t.slug for t in book.tags.filter(category='kind')],
-                         ['kind'])
-        self.assertEqual([(tag.name, tag.count) for tag in related_themes],
-                         [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
diff --git a/src/catalogue/tests/templatetags.py b/src/catalogue/tests/templatetags.py
deleted file mode 100644 (file)
index c69472f..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from catalogue import models
-from catalogue.templatetags import catalogue_tags
-from catalogue.test_utils import *
-from django.core.files.base import ContentFile
-
-
-class BookDescTests(WLTestCase):
-    """ tests book_title template tag """
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        authors = PersonStub(("Common",), "Man"), PersonStub(("Jane",), "Doe")
-
-        child_info = BookInfoStub(authors=authors, genre="Genre", epoch='Epoch', kind="Kind",
-                                  **info_args(u"Child"))
-        parent_info = BookInfoStub(authors=authors, genre="Genre", epoch='Epoch', kind="Kind",
-                                   parts=[child_info.url],
-                                   **info_args(u"Parent"))
-
-        self.child = models.Book.from_text_and_meta(ContentFile('<utwor/>'), child_info)
-        models.Book.from_text_and_meta(ContentFile('<utwor/>'), parent_info)
-        self.child = models.Book.objects.get(pk=self.child.pk)
-
-    def test_book_desc(self):
-        """ book description should return authors, ancestors, book """
-        self.assertEqual(catalogue_tags.book_title(self.child), 'Jane Doe, Common Man, Parent, Child')
diff --git a/src/catalogue/tests/test_book_import.py b/src/catalogue/tests/test_book_import.py
new file mode 100644 (file)
index 0000000..da5e586
--- /dev/null
@@ -0,0 +1,431 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.core.files.base import ContentFile
+from catalogue.test_utils import *
+from catalogue import models
+from librarian import WLURI
+
+from os import path, makedirs
+
+
+class BookImportLogicTests(WLTestCase):
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        self.book_info = BookInfoStub(
+            url=WLURI.from_slug(u"default-book"),
+            about=u"http://wolnelektury.pl/example/URI/default_book",
+            title=u"Default Book",
+            author=PersonStub(("Jim",), "Lazy"),
+            kind="X-Kind",
+            genre="X-Genre",
+            epoch="X-Epoch",
+            language=u"pol",
+        )
+
+        self.expected_tags = [
+           ('author', 'jim-lazy'),
+           ('genre', 'x-genre'),
+           ('epoch', 'x-epoch'),
+           ('kind', 'x-kind'),
+        ]
+        self.expected_tags.sort()
+
+    def test_empty_book(self):
+        book_text = "<utwor />"
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+
+        self.assertEqual(book.title, "Default Book")
+        self.assertEqual(book.slug, "default-book")
+        self.assert_(book.parent is None)
+        self.assertFalse(book.has_html_file())
+
+        # no fragments generated
+        self.assertEqual(book.fragments.count(), 0)
+
+        # TODO: this should be filled out probably...
+        self.assertEqual(book.wiki_link, '')
+        self.assertEqual(book.gazeta_link, '')
+        self.assertEqual(book.description, '')
+
+        tags = [(tag.category, tag.slug) for tag in book.tags]
+        tags.sort()
+
+        self.assertEqual(tags, self.expected_tags)
+
+    def test_not_quite_empty_book(self):
+        """ Not empty, but without any real text.
+
+        Should work like any other non-empty book.
+        """
+
+        book_text = """<utwor>
+        <liryka_l>
+            <nazwa_utworu>Nic</nazwa_utworu>
+        </liryka_l></utwor>
+        """
+
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        self.assertTrue(book.has_html_file())
+
+    def test_book_with_fragment(self):
+        book_text = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        self.assertTrue(book.has_html_file())
+
+        self.assertEqual(book.fragments.count(), 1)
+        self.assertEqual(book.fragments.all()[0].text, u'<p class="paragraph">Ala ma kota</p>\n')
+
+        self.assert_(('theme', 'love') in [(tag.category, tag.slug) for tag in book.fragments.all()[0].tags])
+
+    def test_book_with_empty_theme(self):
+        """ empty themes should be ignored """
+
+        book_text = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01"> , Love , , </motyw>Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        self.assert_([('theme', 'love')],
+                     book.fragments.all()[0].tags.filter(category='theme').values_list('category', 'slug'))
+
+    def test_book_with_no_theme(self):
+        """ fragments with no themes shouldn't be created at all """
+
+        book_text = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01"></motyw>Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        self.assertEqual(book.fragments.count(), 0)
+        self.assertEqual(book.tags.filter(category='theme').count(), 0)
+
+    def test_book_with_invalid_slug(self):
+        """ Book with invalid characters in slug shouldn't be imported """
+        self.book_info.url = WLURI.from_slug(u"default_book")
+        book_text = "<utwor />"
+        with self.assertRaises(ValueError):
+            models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+
+    def test_book_replace_title(self):
+        book_text = """<utwor />"""
+        models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        self.book_info.title = u"Extraordinary"
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
+
+        tags = [(tag.category, tag.slug) for tag in book.tags]
+        tags.sort()
+
+        self.assertEqual(tags, self.expected_tags)
+
+    def test_book_replace_author(self):
+        book_text = """<utwor />"""
+        models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        self.book_info.author = PersonStub(("Hans", "Christian"), "Andersen")
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
+
+        tags = [(tag.category, tag.slug) for tag in book.tags]
+        tags.sort()
+
+        self.expected_tags.remove(('author', 'jim-lazy'))
+        self.expected_tags.append(('author', 'hans-christian-andersen'))
+        self.expected_tags.sort()
+
+        self.assertEqual(tags, self.expected_tags)
+
+        # the old tag shouldn't disappear
+        models.Tag.objects.get(slug="jim-lazy", category="author")
+
+    def test_book_remove_fragment(self):
+        book_text = """<utwor>
+        <opowiadanie>
+            <akap>
+                <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
+                <begin id="m02" /><motyw id="m02">Hatred</motyw>To kot Ali<end id="m02" />
+            </akap>
+        </opowiadanie></utwor>
+        """
+        book_text_after = """<utwor>
+        <opowiadanie>
+            <akap>
+                <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
+                To kot Ali
+            </akap>
+        </opowiadanie></utwor>
+        """
+
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        self.assertEqual(book.fragments.count(), 2)
+        book = models.Book.from_text_and_meta(ContentFile(book_text_after), self.book_info, overwrite=True)
+        self.assertEqual(book.fragments.count(), 1)
+
+    def test_multiple_tags(self):
+        book_text = """<utwor />"""
+        self.book_info.authors = self.book_info.author, PersonStub(("Joe",), "Dilligent"),
+        self.book_info.kinds = self.book_info.kind, 'Y-Kind',
+        self.book_info.genres = self.book_info.genre, 'Y-Genre',
+        self.book_info.epochs = self.book_info.epoch, 'Y-Epoch',
+
+        self.expected_tags.extend([
+           ('author', 'joe-dilligent'),
+           ('genre', 'y-genre'),
+           ('epoch', 'y-epoch'),
+           ('kind', 'y-kind'),
+        ])
+        self.expected_tags.sort()
+
+        book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
+        tags = [(tag.category, tag.slug) for tag in book.tags]
+        tags.sort()
+
+        self.assertEqual(tags, self.expected_tags)
+
+
+class ChildImportTests(WLTestCase):
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        self.child_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            **info_args("Child")
+        )
+
+        self.parent_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Jim",), "Lazy"),
+            parts=[self.child_info.url],
+            **info_args("Parent")
+        )
+
+    def test_child(self):
+        text = """<utwor />"""
+        child = models.Book.from_text_and_meta(ContentFile(text), self.child_info)
+        parent = models.Book.from_text_and_meta(ContentFile(text), self.parent_info)
+        author = parent.tags.get(category='author')
+        books = self.client.get(author.get_absolute_url()).context['object_list']
+        self.assertEqual(len(books), 1, "Only parent book should be visible on author's page")
+
+    def test_child_replace(self):
+        parent_text = """<utwor />"""
+        child_text = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01">Pies</motyw>Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+        child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info)
+        parent = models.Book.from_text_and_meta(ContentFile(parent_text), self.parent_info)
+        child_text = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01">Kot</motyw>Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+        child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info, overwrite=True)
+        themes = parent.related_themes()
+        self.assertEqual(['Kot'], [tag.name for tag in themes], 'wrong related theme list')
+
+
+class TreeImportTest(WLTestCase):
+    def setUp(self):
+        WLTestCase.setUp(self)
+        self.child_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            **info_args("Child")
+        )
+        self.CHILD_TEXT = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01">Pies</motyw>
+                Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+        self.child = models.Book.from_text_and_meta(
+            ContentFile(self.CHILD_TEXT), self.child_info)
+
+        self.book_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            parts=[self.child_info.url],
+            **info_args("Book")
+        )
+        self.BOOK_TEXT = """<utwor />"""
+        self.book = models.Book.from_text_and_meta(
+            ContentFile(self.BOOK_TEXT), self.book_info)
+
+        self.parent_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Jim",), "Lazy"),
+            parts=[self.book_info.url],
+            **info_args("Parent")
+        )
+        self.PARENT_TEXT = """<utwor />"""
+        self.parent = models.Book.from_text_and_meta(
+            ContentFile(self.PARENT_TEXT), self.parent_info)
+
+    def test_ok(self):
+        self.assertEqual(
+                list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
+                [self.parent],
+                u"There should be only parent on common tag page."
+            )
+        # pies = models.Tag.objects.get(slug='pies')
+        themes = self.parent.related_themes()
+        self.assertEqual(len(themes), 1, u"There should be child theme in parent theme counter.")
+        # TODO: book_count is deprecated, update here.
+        # epoch = models.Tag.objects.get(slug='x-epoch')
+        # self.assertEqual(epoch.book_count, 1, u"There should be only parent in common tag's counter.")
+
+    def test_child_republish(self):
+        child_text = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01">Pies, Kot</motyw>
+                Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+        models.Book.from_text_and_meta(
+            ContentFile(child_text), self.child_info, overwrite=True)
+        self.assertEqual(
+                list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
+                [self.parent],
+                u"There should only be parent on common tag page."
+            )
+        # pies = models.Tag.objects.get(slug='pies')
+        # kot = models.Tag.objects.get(slug='kot')
+        self.assertEqual(len(self.parent.related_themes()), 2,
+                         u"There should be child themes in parent theme counter.")
+        # TODO: book_count is deprecated, update here.
+        # epoch = models.Tag.objects.get(slug='x-epoch')
+        # self.assertEqual(epoch.book_count, 1, u"There should only be parent in common tag's counter.")
+
+    def test_book_change_child(self):
+        second_child_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='Other-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            **info_args("Second Child")
+        )
+        second_child_text = """<utwor>
+        <opowiadanie>
+            <akap><begin id="m01" /><motyw id="m01">Kot</motyw>
+                Ala ma kota<end id="m01" /></akap>
+        </opowiadanie></utwor>
+        """
+        # Import a second child.
+        second_child = models.Book.from_text_and_meta(
+            ContentFile(second_child_text), second_child_info)
+        # The book has only this new child now.
+        self.book_info.parts = [second_child_info.url]
+        self.book = models.Book.from_text_and_meta(
+            ContentFile(self.BOOK_TEXT), self.book_info, overwrite=True)
+
+        self.assertEqual(
+            set(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
+            {self.parent, self.child},
+            u"There should be parent and old child on common tag page."
+        )
+        # kot = models.Tag.objects.get(slug='kot')
+        self.assertEqual(len(self.parent.related_themes()), 1,
+                         u"There should only be new child themes in parent theme counter.")
+        # # book_count deprecated, update test.
+        # epoch = models.Tag.objects.get(slug='x-epoch')
+        # self.assertEqual(epoch.book_count, 2,
+        #                  u"There should be parent and old child in common tag's counter.")
+        self.assertEqual(
+            list(self.client.get('/katalog/lektura/parent/motyw/kot/').context['fragments']),
+            [second_child.fragments.all()[0]],
+            u"There should be new child's fragments on parent's theme page."
+        )
+        self.assertEqual(
+            list(self.client.get('/katalog/lektura/parent/motyw/pies/').context['fragments']),
+            [],
+            u"There should be no old child's fragments on parent's theme page."
+        )
+
+
+class MultilingualBookImportTest(WLTestCase):
+    def setUp(self):
+        WLTestCase.setUp(self)
+        common_uri = WLURI.from_slug('common-slug')
+
+        self.pol_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            variant_of=common_uri,
+            **info_args(u"Książka")
+        )
+
+        self.eng_info = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            variant_of=common_uri,
+            **info_args("A book", "eng")
+        )
+
+    def test_multilingual_import(self):
+        book_text = """<utwor><opowiadanie><akap>A</akap></opowiadanie></utwor>"""
+
+        models.Book.from_text_and_meta(ContentFile(book_text), self.pol_info)
+        models.Book.from_text_and_meta(ContentFile(book_text), self.eng_info)
+
+        self.assertEqual(
+            set([b.language for b in models.Book.objects.all()]),
+            {'pol', 'eng'},
+            'Books imported in wrong languages.'
+        )
+
+
+class BookImportGenerateTest(WLTestCase):
+    def setUp(self):
+        WLTestCase.setUp(self)
+        xml = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml')
+        self.book = models.Book.from_xml_file(xml)
+
+    def test_gen_pdf(self):
+        self.book.pdf_file.build()
+        book = models.Book.objects.get(pk=self.book.pk)
+        self.assertTrue(path.exists(book.pdf_file.path))
+
+    def test_gen_pdf_parent(self):
+        """This book contains a child."""
+        xml = path.join(path.dirname(__file__), "files/fraszki.xml")
+        parent = models.Book.from_xml_file(xml)
+        parent.pdf_file.build()
+        parent = models.Book.objects.get(pk=parent.pk)
+        self.assertTrue(path.exists(parent.pdf_file.path))
+
+    def test_custom_pdf(self):
+        from catalogue.tasks import build_custom_pdf
+        out = 'test-custom.pdf'
+        absoulute_path = path.join(settings.MEDIA_ROOT, out)
+
+        if not path.exists(path.dirname(absoulute_path)):
+            makedirs(path.dirname(absoulute_path))
+
+        build_custom_pdf(self.book.id, customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out)
+        self.assertTrue(path.exists(absoulute_path))
diff --git a/src/catalogue/tests/test_bookmedia.py b/src/catalogue/tests/test_bookmedia.py
new file mode 100644 (file)
index 0000000..263e48d
--- /dev/null
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from os.path import basename, exists
+from unittest import skip
+
+from django.core.files.base import ContentFile
+
+from catalogue.test_utils import *
+from catalogue import models, utils
+
+
+class BookMediaTests(WLTestCase):
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        self.file = ContentFile('X')
+        self.file2 = ContentFile('Y')
+        self.book = models.Book.objects.create(slug='test-book', title='Test')
+
+    def set_title(self, title):
+        self.book.title = title
+        self.book.save()
+
+    def test_diacritics(self):
+        bm = models.BookMedia(book=self.book, type="ogg", name=u"Zażółć gęślą jaźń")
+        self.set_title(bm.name)
+        bm.file.save(None, self.file)
+        self.assertEqual(basename(bm.file.name), 'zazolc-gesla-jazn.ogg')
+
+    def test_long_name(self):
+        bm = models.BookMedia(
+            book=self.book, type="ogg",
+            name="Some very very very very very very very very very very very very very very very very long file name")
+        self.set_title(bm.name)
+        bm.file.save(bm.name, self.file)
+
+        # reload to see what was really saved
+        bm = models.BookMedia.objects.get(pk=bm.pk)
+        self.assertEqual(bm.file.size, 1)
+
+    def test_overwrite(self):
+        """
+            File gets overwritten with same filename on update.
+        """
+
+        bm = models.BookMedia(book=self.book, type='ogg', name="Some media")
+        self.set_title(bm.name)
+        bm.file.save(None, self.file)
+        bm.file.save(None, self.file2)
+
+        self.assertEqual(bm.file.read(), 'Y')
+        self.assertEqual(basename(bm.file.name), 'some-media.ogg')
+
+    @skip('broken, but is it needed?')
+    def test_no_clobber(self):
+        """
+            File save doesn't clobber some other media with similar name.
+        """
+
+        bm = models.BookMedia(book=self.book, type='ogg', name=u"Tytul")
+        self.set_title(bm.name)
+        bm.file.save(None, self.file)
+        bm2 = models.BookMedia(book=self.book, type='ogg', name=u"Tytuł")
+        self.set_title(bm2.name)
+        bm2.file.save(None, self.file2)
+        self.assertEqual(basename(bm.file.name), 'tytul.ogg')
+        self.assertNotEqual(basename(bm2.file.name), 'tytul.ogg')
+        self.assertEqual(bm.file.read(), 'X')
+        self.assertEqual(bm2.file.read(), 'Y')
+
+    def test_change_name(self):
+        """
+            File name reflects name change.
+        """
+
+        bm = models.BookMedia(book=self.book, type='ogg', name="Title")
+        self.set_title(bm.name)
+        bm.file.save(None, self.file)
+        self.set_title("Other Title")
+        bm.save()
+        self.assertEqual(basename(bm.file.name), 'other-title.ogg')
+        self.assertEqual(bm.file.read(), 'X')
+
+    @skip('broken, but is it needed?')
+    def test_change_name_no_clobber(self):
+        """
+            File name after change won't clobber some other file
+            with similar name.
+        """
+
+        bm = models.BookMedia(book=self.book, type='ogg', name="Title")
+        self.set_title(bm.name)
+        bm.file.save(None, self.file)
+        bm2 = models.BookMedia(book=self.book, type='ogg', name="Other title")
+        self.set_title(bm2.name)
+        bm2.file.save(None, self.file2)
+        self.set_title("Title")
+        bm2.save()
+        self.assertNotEqual(basename(bm2.file.name), 'title.ogg')
+        self.assertEqual(bm.file.read(), 'X')
+        self.assertEqual(bm2.file.read(), 'Y')
+
+    def test_zip_audiobooks(self):
+        paths = [
+            (None, join(dirname(__file__), "files/fraszka-do-anusie.xml")),
+            (None, join(dirname(__file__), "files/fraszki.xml")),
+            ]
+
+        url = utils.create_zip(paths, 'test-zip-slug')
+        self.assertEqual("zip/test-zip-slug.zip", url)
+        self.assertTrue(exists(join(settings.MEDIA_ROOT, url)))
+
+        utils.remove_zip('test-zip-slug')
+        self.assertFalse(exists(join(settings.MEDIA_ROOT, url)))
+
+    def test_remove_zip_on_media_change(self):
+        bm = models.BookMedia(book=self.book, type='ogg', name="Title")
+        self.set_title(bm.name)
+        bm.file.save(None, self.file)
+        bm.save()
+
+        zip_url = self.book.zip_audiobooks('ogg')
+        self.assertEqual('zip/'+self.book.slug+'_ogg.zip', zip_url)
+        self.assertTrue(exists(join(settings.MEDIA_ROOT, zip_url)))
+
+        bm2 = models.BookMedia(book=self.book, type='ogg', name="Other title")
+        self.set_title(bm2.name)
+        bm2.file.save(None, self.file2)
+        self.set_title("Title")
+        bm2.save()
+        # was the audiobook zip deleted?
+        self.assertFalse(exists(join(settings.MEDIA_ROOT, zip_url)))
diff --git a/src/catalogue/tests/test_cover.py b/src/catalogue/tests/test_cover.py
new file mode 100644 (file)
index 0000000..8c5d047
--- /dev/null
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from django.core.files.base import ContentFile
+from catalogue.test_utils import BookInfoStub, PersonStub, info_args, WLTestCase
+from catalogue.models import Book
+from mock import patch
+
+
+class CoverTests(WLTestCase):
+    """Checks in parent_cover_changed is properly called."""
+    def setUp(self):
+        WLTestCase.setUp(self)
+        self.TEXT = """<utwor />"""
+        self.child = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Joe",), "Doe"),
+            **info_args("Child")
+        )
+
+        self.parent = BookInfoStub(
+            genre='X-Genre',
+            epoch='X-Epoch',
+            kind='X-Kind',
+            author=PersonStub(("Jim",), "Lazy"),
+            cover_url="http://example.com/cover.jpg",
+            parts=[self.child.url],
+            **info_args("Parent")
+        )
+
+    @patch.object(Book, 'parent_cover_changed', autospec=True)
+    def test_simple_import(self, parent_cover_changed):
+        child = Book.from_text_and_meta(ContentFile(self.TEXT), self.child)
+        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent)
+        parent_cover_changed.assert_called_with(child)
+
+        # Now reimport parent.
+        parent_cover_changed.reset_mock()
+        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
+        self.assertEqual(parent_cover_changed.call_count, 0)
+
+        # Now change cover in parent.
+        parent_cover_changed.reset_mock()
+        self.parent.cover_url = "http://example.com/other-cover.jpg"
+        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
+        parent_cover_changed.assert_called_with(child)
+
+    @patch.object(Book, 'parent_cover_changed', autospec=True)
+    def test_change_cover(self, parent_cover_changed):
+        child = Book.from_text_and_meta(ContentFile(self.TEXT), self.child)
+        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent)
+        parent_cover_changed.assert_called_with(child)
+
+    @patch.object(Book, 'parent_cover_changed', autospec=True)
+    def test_new_child(self, parent_cover_changed):
+        # Add parent without child first.
+        parts, self.parent.parts = self.parent.parts, []
+        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent)
+
+        # Now import child and reimport parent.
+        child = Book.from_text_and_meta(ContentFile(self.TEXT), self.child)
+        self.parent.parts = parts
+        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
+        parent_cover_changed.assert_called_with(child)
+
+        # Now remove the child.
+        parent_cover_changed.reset_mock()
+        self.parent.parts = []
+        parent = Book.from_text_and_meta(ContentFile(self.TEXT), self.parent, overwrite=True)
+        parent_cover_changed.assert_called_with(child)
diff --git a/src/catalogue/tests/test_tags.py b/src/catalogue/tests/test_tags.py
new file mode 100644 (file)
index 0000000..d5aa72c
--- /dev/null
@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from unittest import skip
+
+from django.core.files.base import ContentFile
+from django.test import Client
+from catalogue import models
+from catalogue.test_utils import *
+
+
+class BooksByTagTests(WLTestCase):
+    """ tests the /katalog/category/tag page for found books """
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        author = PersonStub(("Common",), "Man")
+
+        # grandchild
+        self.gchild_info = BookInfoStub(genre='Genre', epoch='Epoch', kind='Kind', author=author,
+                                        **info_args("GChild"))
+        # child
+        self.child_info = BookInfoStub(genre='Genre', epoch='Epoch', kind='Other Kind', author=author,
+                                       parts=[self.gchild_info.url],
+                                       **info_args("Child"))
+        # parent
+        self.parent_info = BookInfoStub(genre='Genre', epoch='Epoch', kind='Kind', author=author,
+                                        parts=[self.child_info.url],
+                                        **info_args("Parent"))
+
+        self.book_file = ContentFile('<utwor />')
+
+    def test_nonexistent_tag(self):
+        """ Looking for a non-existent tag should yield 404 """
+        self.assertEqual(404, self.client.get('/katalog/autor/czeslaw-milosz/').status_code)
+
+    def test_book_tag(self):
+        """ Looking for a book tag isn't permitted """
+        models.Book.from_text_and_meta(self.book_file, self.gchild_info)
+        self.assertEqual(404, self.client.get('/katalog/gchild/').status_code)
+
+    def test_tag_empty(self):
+        """ Tag with no books should return no books """
+        models.Book.from_text_and_meta(self.book_file, self.gchild_info)
+        models.Tag.objects.create(name='Empty tag', slug='empty', category='author')
+
+        context = self.client.get('/katalog/autor/empty/').context
+        self.assertEqual(0, len(context['object_list']))
+
+    def test_tag_eliminate(self):
+        """ Filtering by tag should only yield top-level qualifying books. """
+        for info in self.gchild_info, self.child_info, self.parent_info:
+            models.Book.from_text_and_meta(self.book_file, info)
+
+        # all three qualify
+        context = self.client.get('/katalog/gatunek/genre/').context
+        self.assertEqual([book.title for book in context['object_list']],
+                         ['Parent'])
+
+        # parent and gchild qualify, child doesn't
+        context = self.client.get('/katalog/rodzaj/kind/').context
+        self.assertEqual([book.title for book in context['object_list']],
+                         ['Parent'])
+
+        # Filtering by child's tag should yield the child
+        context = self.client.get('/katalog/rodzaj/other-kind/').context
+        self.assertEqual([book.title for book in context['object_list']],
+                         ['Child'])
+
+
+class TagRelatedTagsTests(WLTestCase):
+    """ tests the /katalog/category/tag/ page for related tags """
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        self.client = Client()
+        author = PersonStub(("Common",), "Man")
+
+        gchild_info = BookInfoStub(author=author, genre="GchildGenre", epoch='Epoch', kind="Kind",
+                                   **info_args(u"GChild"))
+        child1_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind",
+                                   parts=[gchild_info.url],
+                                   **info_args(u"Child1"))
+        child2_info = BookInfoStub(author=author, genre="ChildGenre", epoch='Epoch', kind="ChildKind",
+                                   **info_args(u"Child2"))
+        parent_info = BookInfoStub(author=author, genre="Genre", epoch='Epoch', kind="Kind",
+                                   parts=[child1_info.url, child2_info.url],
+                                   **info_args(u"Parent"))
+
+        for info in gchild_info, child1_info, child2_info, parent_info:
+            book_text = """<utwor><opowiadanie><akap>
+                <begin id="m01" />
+                    <motyw id="m01">Theme, %sTheme</motyw>
+                    Ala ma kota
+                <end id="m01" />
+                </akap></opowiadanie></utwor>
+                """ % info.title.encode('utf-8')
+            book = models.Book.from_text_and_meta(ContentFile(book_text), info)
+            book.save()
+
+        tag_empty = models.Tag(name='Empty tag', slug='empty', category='author')
+        tag_empty.save()
+
+    def test_empty(self):
+        """ empty tag should have no related tags """
+
+        cats = self.client.get('/katalog/autor/empty/').context['categories']
+        self.assertEqual({k: v for (k, v) in cats.items() if v}, {}, 'tags related to empty tag')
+
+    def test_has_related(self):
+        """ related own and descendants' tags should be generated """
+
+        cats = self.client.get('/katalog/rodzaj/kind/').context['categories']
+        self.assertTrue('Common Man' in [tag.name for tag in cats['author']],
+                        'missing `author` related tag')
+        self.assertTrue('Epoch' in [tag.name for tag in cats['epoch']],
+                        'missing `epoch` related tag')
+        self.assertFalse(cats.get("kind", False),
+                         "There should be no child-only related `kind` tags")
+        self.assertTrue("Genre" in [tag.name for tag in cats['genre']],
+                        'missing `genre` related tag')
+        self.assertFalse("ChildGenre" in [tag.name for tag in cats['genre']],
+                         "There should be no child-only related `genre` tags")
+        self.assertTrue("GchildGenre" in [tag.name for tag in cats['genre']],
+                        "missing grandchild's related tag")
+        self.assertTrue('Theme' in [tag.name for tag in cats['theme']],
+                        "missing related theme")
+        self.assertFalse('Child1Theme' in [tag.name for tag in cats['theme']],
+                         "There should be no child-only related `theme` tags")
+        self.assertTrue('GChildTheme' in [tag.name for tag in cats['theme']],
+                        "missing grandchild's related theme")
+
+    def test_related_differ(self):
+        """ related tags shouldn't include filtering tags """
+
+        response = self.client.get('/katalog/rodzaj/kind/')
+        cats = response.context['categories']
+        self.assertFalse(cats.get('kind', False),
+                         'filtering tag wrongly included in related')
+        cats = self.client.get('/katalog/motyw/theme/').context['categories']
+        self.assertFalse('Theme' in [tag.name for tag in cats['theme']],
+                         'filtering theme wrongly included in related')
+
+    def test_parent_tag_once(self):
+        """ if parent and descendants have a common tag, count it only once """
+
+        cats = self.client.get('/katalog/rodzaj/kind/').context['categories']
+        self.assertEqual([(tag.name, tag.count) for tag in cats['epoch']],
+                         [('Epoch', 1)],
+                         'wrong related tag epoch tag on tag page')
+
+    def test_siblings_tags_count(self):
+        """ if children have tags and parent hasn't, count the children """
+
+        cats = self.client.get('/katalog/epoka/epoch/').context['categories']
+        self.assertTrue(
+            ('ChildKind', 2) in [(tag.name, tag.count) for tag in cats['kind']],
+            'wrong related kind tags on tag page, got: ' +
+            unicode([(tag.name, tag.count) for tag in cats['kind']]))
+
+        # all occurencies of theme should be counted
+        self.assertTrue(('Theme', 4) in [(tag.name, tag.count) for tag in cats['theme']],
+                        'wrong related theme count')
+
+    def test_query_child_tag(self):
+        """
+        If child and parent have a common tag, but parent isn't included
+        in the result, child should still count.
+        """
+        cats = self.client.get('/katalog/gatunek/childgenre/').context['categories']
+        self.assertTrue(('Epoch', 2) in [(tag.name, tag.count) for tag in cats['epoch']],
+                        'wrong related kind tags on tag page, got: ' +
+                        unicode([(tag.name, tag.count) for tag in cats['epoch']]))
+
+
+class CleanTagRelationTests(WLTestCase):
+    """ tests for tag relations cleaning after deleting things """
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        author = PersonStub(("Common",), "Man")
+
+        book_info = BookInfoStub(author=author, genre="G", epoch='E', kind="K", **info_args(u"Book"))
+        book_text = """<utwor><opowiadanie><akap>
+            <begin id="m01" /><motyw id="m01">Theme</motyw>Ala ma kota
+            <end id="m01" />
+            </akap></opowiadanie></utwor>
+            """
+        self.book = models.Book.from_text_and_meta(ContentFile(book_text), book_info)
+
+    @skip('Not implemented and not priority')
+    def test_delete_objects(self):
+        """ there should be no related tags left after deleting some objects """
+
+        models.Book.objects.all().delete()
+        cats = self.client.get('/katalog/rodzaj/k/').context['categories']
+        self.assertEqual({k: v for (k, v) in cats.items() if v}, {})
+        self.assertEqual(models.Fragment.objects.all().count(), 0,
+                         "orphaned fragments left")
+        self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
+                         "orphaned TagRelation objects left")
+
+    def test_deleted_tag(self):
+        """ there should be no tag relations left after deleting tags """
+
+        models.Tag.objects.all().delete()
+        self.assertEqual(len(self.book.related_themes()), 0)
+        self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0,
+                         "orphaned TagRelation objects left")
+
+
+class TestIdenticalTag(WLTestCase):
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        author = PersonStub((), "Tag")
+
+        self.book_info = BookInfoStub(author=author, genre="tag", epoch='tag', kind="tag", **info_args(u"tag"))
+        self.book_text = """<utwor>
+            <opowiadanie>
+            <akap>
+                <begin id="m01" /><motyw id="m01">tag</motyw>Ala ma kota<end id="m01" />
+            </akap>
+            </opowiadanie>
+            </utwor>
+        """
+
+    def test_book_tags(self):
+        """ there should be all related tags in relevant categories """
+        book = models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
+
+        related_themes = book.related_themes()
+        for category in 'author', 'kind', 'genre', 'epoch':
+            self.assertTrue('tag' in book.tags.filter(category=category).values_list('slug', flat=True),
+                            'missing related tag for %s' % category)
+        self.assertTrue('tag' in [tag.slug for tag in related_themes])
+
+    def test_qualified_url(self):
+        models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info)
+        categories = {'author': 'autor', 'theme': 'motyw', 'epoch': 'epoka', 'kind': 'rodzaj', 'genre': 'gatunek'}
+        for cat, localcat in categories.iteritems():
+            context = self.client.get('/katalog/%s/tag/' % localcat).context
+            self.assertEqual(1, len(context['object_list']))
+            self.assertNotEqual({}, context['categories'])
+            self.assertFalse(context['categories'].get(cat, False))
+
+
+class BookTagsTests(WLTestCase):
+    """ tests the /katalog/lektura/book/ page for related tags """
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        author1 = PersonStub(("Common",), "Man")
+        author2 = PersonStub(("Jim",), "Lazy")
+
+        child_info = BookInfoStub(authors=(author1, author2), genre="ChildGenre", epoch='Epoch', kind="ChildKind",
+                                  **info_args(u"Child"))
+        parent_info = BookInfoStub(author=author1, genre="Genre", epoch='Epoch', kind="Kind",
+                                   parts=[child_info.url],
+                                   **info_args(u"Parent"))
+
+        for info in child_info, parent_info:
+            book_text = """<utwor><opowiadanie><akap>
+                <begin id="m01" />
+                    <motyw id="m01">Theme, %sTheme</motyw>
+                    Ala ma kota
+                <end id="m01" />
+                </akap></opowiadanie></utwor>
+                """ % info.title.encode('utf-8')
+            models.Book.from_text_and_meta(ContentFile(book_text), info)
+
+    def test_book_tags(self):
+        """ book should have own tags and whole tree's themes """
+
+        book = models.Book.objects.get(slug='parent')
+        related_themes = book.related_themes()
+
+        self.assertEqual([t.slug for t in book.authors()],
+                         ['common-man'])
+        self.assertEqual([t.slug for t in book.tags.filter(category='kind')],
+                         ['kind'])
+        self.assertEqual([(tag.name, tag.count) for tag in related_themes],
+                         [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)])
diff --git a/src/catalogue/tests/test_templatetags.py b/src/catalogue/tests/test_templatetags.py
new file mode 100644 (file)
index 0000000..c69472f
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from catalogue import models
+from catalogue.templatetags import catalogue_tags
+from catalogue.test_utils import *
+from django.core.files.base import ContentFile
+
+
+class BookDescTests(WLTestCase):
+    """ tests book_title template tag """
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        authors = PersonStub(("Common",), "Man"), PersonStub(("Jane",), "Doe")
+
+        child_info = BookInfoStub(authors=authors, genre="Genre", epoch='Epoch', kind="Kind",
+                                  **info_args(u"Child"))
+        parent_info = BookInfoStub(authors=authors, genre="Genre", epoch='Epoch', kind="Kind",
+                                   parts=[child_info.url],
+                                   **info_args(u"Parent"))
+
+        self.child = models.Book.from_text_and_meta(ContentFile('<utwor/>'), child_info)
+        models.Book.from_text_and_meta(ContentFile('<utwor/>'), parent_info)
+        self.child = models.Book.objects.get(pk=self.child.pk)
+
+    def test_book_desc(self):
+        """ book description should return authors, ancestors, book """
+        self.assertEqual(catalogue_tags.book_title(self.child), 'Jane Doe, Common Man, Parent, Child')
diff --git a/src/catalogue/tests/test_visit.py b/src/catalogue/tests/test_visit.py
new file mode 100644 (file)
index 0000000..5e640ad
--- /dev/null
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from catalogue import models
+from catalogue.test_utils import BookInfoStub, PersonStub, WLTestCase, info_args
+from django.core.files.base import ContentFile
+
+
+class VisitTest(WLTestCase):
+    """Simply create some objects and visit some views."""
+
+    def setUp(self):
+        WLTestCase.setUp(self)
+        author = PersonStub(("Jane",), "Doe")
+        book_info = BookInfoStub(author=author, genre="Genre",
+            epoch='Epoch', kind="Kind", **info_args(u"A book"))
+        self.book = models.Book.from_text_and_meta(ContentFile('''
+            <utwor>
+            <opowiadanie>
+                <akap>
+                    <begin id="b1" />
+                    <motyw id="m1">Theme</motyw>
+                    Test
+                    <end id="e1" />
+                </akap>
+            </opowiadanie>
+            </utwor>
+            '''), book_info)
+        self.collection = models.Collection.objects.create(
+            title='Biblioteczka Boya', slug='boy', book_slugs='a-book')
+
+    def test_visit_urls(self):
+        """ book description should return authors, ancestors, book """
+        url_map = {
+            200: [
+                '',
+                'lektury/',
+                'lektury/boy/',
+                'nowe/',
+                'lektura/a-book/',
+                'lektura/a-book.html',
+                'lektura/a-book/motyw/theme/',
+                'motyw/theme/',
+                'autor/jane-doe/',
+                # 'autor/jane-doe/gatunek/genre/',
+                # 'autor/jane-doe/gatunek/genre/motyw/theme/',
+                'b/%d/mini.pl.html' % self.book.pk,
+                'b/%d/mini_nolink.pl.html' % self.book.pk,
+                'b/%d/short.pl.html' % self.book.pk,
+                'b/%d/wide.pl.html' % self.book.pk,
+                'f/%d/promo.pl.html' % self.book.fragments.all()[0].pk,
+                'f/%d/short.pl.html' % self.book.fragments.all()[0].pk,
+                ],
+            404: [
+                'lektury/nonexistent/',  # Nonexistent Collection.
+                'lektura/nonexistent/',  # Nonexistent Book.
+                'lektura/nonexistent.html',  # Nonexistent Book's HTML.
+                'lektura/nonexistent/motyw/theme/',  # Nonexistent Book's theme.
+                'lektura/a-book/motyw/nonexistent/',  # Nonexistent theme in a Book.
+                'autor/nonexistent/',  # Nonexistent author.
+                'motyw/nonexistent/',  # Nonexistent theme.
+                'zh.json',  # Nonexistent language.
+                'b/%d/mini.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'b/%d/mini_nolink.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'b/%d/short.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'b/%d/wide.pl.html' % (self.book.pk + 100),  # Nonexistent book.
+                'f/%d/promo.pl.html' % (self.book.fragments.all()[0].pk + 100),  # Nonexistent fragment.
+                'f/%d/short.pl.html' % (self.book.fragments.all()[0].pk + 100),  # Nonexistent fragment.
+                ]
+            }
+        prefix = '/katalog/'
+        for expected_status, urls in url_map.items():
+            for url in urls:
+                print(url)
+                status = self.client.get(prefix + url).status_code
+                self.assertEqual(
+                    status, expected_status,
+                    "Wrong status code for '%s'. Expected %d, got %d." % (prefix + url, expected_status, status))
diff --git a/src/catalogue/tests/visit.py b/src/catalogue/tests/visit.py
deleted file mode 100644 (file)
index 5e640ad..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from catalogue import models
-from catalogue.test_utils import BookInfoStub, PersonStub, WLTestCase, info_args
-from django.core.files.base import ContentFile
-
-
-class VisitTest(WLTestCase):
-    """Simply create some objects and visit some views."""
-
-    def setUp(self):
-        WLTestCase.setUp(self)
-        author = PersonStub(("Jane",), "Doe")
-        book_info = BookInfoStub(author=author, genre="Genre",
-            epoch='Epoch', kind="Kind", **info_args(u"A book"))
-        self.book = models.Book.from_text_and_meta(ContentFile('''
-            <utwor>
-            <opowiadanie>
-                <akap>
-                    <begin id="b1" />
-                    <motyw id="m1">Theme</motyw>
-                    Test
-                    <end id="e1" />
-                </akap>
-            </opowiadanie>
-            </utwor>
-            '''), book_info)
-        self.collection = models.Collection.objects.create(
-            title='Biblioteczka Boya', slug='boy', book_slugs='a-book')
-
-    def test_visit_urls(self):
-        """ book description should return authors, ancestors, book """
-        url_map = {
-            200: [
-                '',
-                'lektury/',
-                'lektury/boy/',
-                'nowe/',
-                'lektura/a-book/',
-                'lektura/a-book.html',
-                'lektura/a-book/motyw/theme/',
-                'motyw/theme/',
-                'autor/jane-doe/',
-                # 'autor/jane-doe/gatunek/genre/',
-                # 'autor/jane-doe/gatunek/genre/motyw/theme/',
-                'b/%d/mini.pl.html' % self.book.pk,
-                'b/%d/mini_nolink.pl.html' % self.book.pk,
-                'b/%d/short.pl.html' % self.book.pk,
-                'b/%d/wide.pl.html' % self.book.pk,
-                'f/%d/promo.pl.html' % self.book.fragments.all()[0].pk,
-                'f/%d/short.pl.html' % self.book.fragments.all()[0].pk,
-                ],
-            404: [
-                'lektury/nonexistent/',  # Nonexistent Collection.
-                'lektura/nonexistent/',  # Nonexistent Book.
-                'lektura/nonexistent.html',  # Nonexistent Book's HTML.
-                'lektura/nonexistent/motyw/theme/',  # Nonexistent Book's theme.
-                'lektura/a-book/motyw/nonexistent/',  # Nonexistent theme in a Book.
-                'autor/nonexistent/',  # Nonexistent author.
-                'motyw/nonexistent/',  # Nonexistent theme.
-                'zh.json',  # Nonexistent language.
-                'b/%d/mini.pl.html' % (self.book.pk + 100),  # Nonexistent book.
-                'b/%d/mini_nolink.pl.html' % (self.book.pk + 100),  # Nonexistent book.
-                'b/%d/short.pl.html' % (self.book.pk + 100),  # Nonexistent book.
-                'b/%d/wide.pl.html' % (self.book.pk + 100),  # Nonexistent book.
-                'f/%d/promo.pl.html' % (self.book.fragments.all()[0].pk + 100),  # Nonexistent fragment.
-                'f/%d/short.pl.html' % (self.book.fragments.all()[0].pk + 100),  # Nonexistent fragment.
-                ]
-            }
-        prefix = '/katalog/'
-        for expected_status, urls in url_map.items():
-            for url in urls:
-                print(url)
-                status = self.client.get(prefix + url).status_code
-                self.assertEqual(
-                    status, expected_status,
-                    "Wrong status code for '%s'. Expected %d, got %d." % (prefix + url, expected_status, status))
index 561ef60..e69de29 100644 (file)
@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from oai.tests.oaipmhapi import *
old mode 100755 (executable)
new mode 100644 (file)
old mode 100755 (executable)
new mode 100644 (file)
diff --git a/src/oai/tests/oaipmhapi.py b/src/oai/tests/oaipmhapi.py
deleted file mode 100644 (file)
index 938624b..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from catalogue.test_utils import WLTestCase
-from catalogue import models
-from oai.handlers import *
-from oaipmh.server import *
-from os import path
-from oaipmh.metadata import MetadataRegistry
-
-
-class BookMetadataTest(WLTestCase):
-    def setUp(self):
-        super(BookMetadataTest, self).setUp()
-        xml = path.join(path.dirname(__file__), 'files/lubie-kiedy-kobieta.xml')
-        self.book = models.Book.from_xml_file(xml)
-
-        xml = path.join(path.dirname(__file__), 'files/antygona.xml')
-        self.book2 = models.Book.from_xml_file(xml)
-
-        mr = MetadataRegistry()
-        self.catalogue = Catalogue(mr)
-
-        mr.registerWriter('oai_dc', oai_dc_writer)
-        nsmap = {'oai_dc': NS_OAIDC, 'dc': NS_DC, 'xsi': NS_XSI}
-        self.xml = XMLTreeServer(self.catalogue, mr, nsmap)
-
-    # def test_get_record(self):
-    #     self.xml.getRecord(identifier='lubie-kiedy-kobieta', metadataPrefix='oai_dc')
-    #     self.xml.listRecords(metadataPrefix='oai_dc')
-    #
-    # def test_selecting(self):
-    #     records, token = self.catalogue.listRecords(**{'set': 'epoch:starozytnosc'})
diff --git a/src/oai/tests/test_oaipmhapi.py b/src/oai/tests/test_oaipmhapi.py
new file mode 100644 (file)
index 0000000..938624b
--- /dev/null
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from catalogue.test_utils import WLTestCase
+from catalogue import models
+from oai.handlers import *
+from oaipmh.server import *
+from os import path
+from oaipmh.metadata import MetadataRegistry
+
+
+class BookMetadataTest(WLTestCase):
+    def setUp(self):
+        super(BookMetadataTest, self).setUp()
+        xml = path.join(path.dirname(__file__), 'files/lubie-kiedy-kobieta.xml')
+        self.book = models.Book.from_xml_file(xml)
+
+        xml = path.join(path.dirname(__file__), 'files/antygona.xml')
+        self.book2 = models.Book.from_xml_file(xml)
+
+        mr = MetadataRegistry()
+        self.catalogue = Catalogue(mr)
+
+        mr.registerWriter('oai_dc', oai_dc_writer)
+        nsmap = {'oai_dc': NS_OAIDC, 'dc': NS_DC, 'xsi': NS_XSI}
+        self.xml = XMLTreeServer(self.catalogue, mr, nsmap)
+
+    # def test_get_record(self):
+    #     self.xml.getRecord(identifier='lubie-kiedy-kobieta', metadataPrefix='oai_dc')
+    #     self.xml.listRecords(metadataPrefix='oai_dc')
+    #
+    # def test_selecting(self):
+    #     records, token = self.catalogue.listRecords(**{'set': 'epoch:starozytnosc'})
old mode 100755 (executable)
new mode 100644 (file)
index 339bfaa..e69de29
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from unittest import skipIf
-from lxml import etree
-from django.conf import settings
-import catalogue
-from catalogue.test_utils import WLTestCase, get_fixture
-from catalogue.models import Book
-from librarian import WLURI, XMLNamespace
-from search.index import Index
-
-AtomNS = XMLNamespace("http://www.w3.org/2005/Atom")
-
-
-@skipIf(getattr(settings, 'NO_SEARCH_INDEX', False), u'Requires search server and NO_SEARCH_INDEX=False.')
-class OpdsSearchTests(WLTestCase):
-    """Tests search feed in OPDS.."""
-    def setUp(self):
-        WLTestCase.setUp(self)
-        index = Index()
-        index.index.delete_all()
-        index.index.commit()
-
-        self.do_doktora = Book.from_xml_file(
-            get_fixture('do-doktora.xml'))
-        self.do_anusie = Book.from_xml_file(
-            get_fixture('fraszka-do-anusie.xml', catalogue))
-
-    def assert_finds(self, query, books):
-        """Takes a query and tests against books expected to be found."""
-        tree = etree.fromstring(
-            self.client.get('/opds/search/?%s' % query).content)
-        elem_ids = tree.findall('.//%s/%s' % (AtomNS('entry'), AtomNS('id')))
-        slugs = [WLURI(elem.text).slug for elem in elem_ids]
-        self.assertEqual(set(slugs), set(b.slug for b in books), u"OPDS search '%s' failed." % query)
-
-    def test_opds_search_simple(self):
-        """Do a simple q= test, also emulate dumb OPDS clients."""
-        both = {self.do_doktora, self.do_anusie}
-        self.assert_finds('q=fraszka', both)
-        self.assert_finds('q=fraszka&author={opds:author}', both)
-
-    def test_opds_search_title(self):
-        """Search by title."""
-        both = {self.do_doktora, self.do_anusie}
-        self.assert_finds('title=fraszka', both)
-        self.assert_finds('title=fraszka', both)
-        self.assert_finds('q=title:doktora', [self.do_doktora])
-
-    def test_opds_search_author(self):
-        """Search by author."""
-        self.assert_finds('q=fraszka&author=Kochanowski', [self.do_doktora])
-        self.assert_finds('q=fraszka+author:Kochanowski', [self.do_doktora])
-        self.assert_finds('q=Kochanowski', [self.do_doktora])
-
-    def test_opds_search_translator(self):
-        """Search by translator."""
-        self.assert_finds('q=fraszka&translator=Fikcyjny', [self.do_doktora])
-        self.assert_finds('q=fraszka+translator:Fikcyjny', [self.do_doktora])
-        self.assert_finds('q=Fikcyjny', [self.do_doktora])
diff --git a/src/opds/tests/test_opds.py b/src/opds/tests/test_opds.py
new file mode 100644 (file)
index 0000000..339bfaa
--- /dev/null
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from unittest import skipIf
+from lxml import etree
+from django.conf import settings
+import catalogue
+from catalogue.test_utils import WLTestCase, get_fixture
+from catalogue.models import Book
+from librarian import WLURI, XMLNamespace
+from search.index import Index
+
+AtomNS = XMLNamespace("http://www.w3.org/2005/Atom")
+
+
+@skipIf(getattr(settings, 'NO_SEARCH_INDEX', False), u'Requires search server and NO_SEARCH_INDEX=False.')
+class OpdsSearchTests(WLTestCase):
+    """Tests search feed in OPDS.."""
+    def setUp(self):
+        WLTestCase.setUp(self)
+        index = Index()
+        index.index.delete_all()
+        index.index.commit()
+
+        self.do_doktora = Book.from_xml_file(
+            get_fixture('do-doktora.xml'))
+        self.do_anusie = Book.from_xml_file(
+            get_fixture('fraszka-do-anusie.xml', catalogue))
+
+    def assert_finds(self, query, books):
+        """Takes a query and tests against books expected to be found."""
+        tree = etree.fromstring(
+            self.client.get('/opds/search/?%s' % query).content)
+        elem_ids = tree.findall('.//%s/%s' % (AtomNS('entry'), AtomNS('id')))
+        slugs = [WLURI(elem.text).slug for elem in elem_ids]
+        self.assertEqual(set(slugs), set(b.slug for b in books), u"OPDS search '%s' failed." % query)
+
+    def test_opds_search_simple(self):
+        """Do a simple q= test, also emulate dumb OPDS clients."""
+        both = {self.do_doktora, self.do_anusie}
+        self.assert_finds('q=fraszka', both)
+        self.assert_finds('q=fraszka&author={opds:author}', both)
+
+    def test_opds_search_title(self):
+        """Search by title."""
+        both = {self.do_doktora, self.do_anusie}
+        self.assert_finds('title=fraszka', both)
+        self.assert_finds('title=fraszka', both)
+        self.assert_finds('q=title:doktora', [self.do_doktora])
+
+    def test_opds_search_author(self):
+        """Search by author."""
+        self.assert_finds('q=fraszka&author=Kochanowski', [self.do_doktora])
+        self.assert_finds('q=fraszka+author:Kochanowski', [self.do_doktora])
+        self.assert_finds('q=Kochanowski', [self.do_doktora])
+
+    def test_opds_search_translator(self):
+        """Search by translator."""
+        self.assert_finds('q=fraszka&translator=Fikcyjny', [self.do_doktora])
+        self.assert_finds('q=fraszka+translator:Fikcyjny', [self.do_doktora])
+        self.assert_finds('q=Fikcyjny', [self.do_doktora])
index b2e50b0..e69de29 100644 (file)
@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from picture.tests.picture_import import *
diff --git a/src/picture/tests/picture_import.py b/src/picture/tests/picture_import.py
deleted file mode 100644 (file)
index 022b33c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-# -*- coding: utf-8 -*-
-# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
-# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
-#
-from __future__ import with_statement
-
-from os import path
-from picture.models import Picture
-from catalogue.test_utils import WLTestCase
-
-
-class PictureTest(WLTestCase):
-
-    def test_import(self):
-        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"))
-
-        themes = set()
-        for area in picture.areas.all():
-            themes.update([
-                (tag.category, tag.name)
-                for tag in area.tags if tag.category in (u'theme', u'thing')])
-        assert themes == {(u'theme', u'nieporządek'), (u'thing', u'Kosmos')}, \
-            'Bad themes on Picture areas: %s' % themes
-
-        pic_themes = set([tag.name for tag in picture.tags if tag.category in ('theme', 'thing')])
-        assert not pic_themes, 'Unwanted themes set on Pictures: %s' % pic_themes
-
-        picture.delete()
-
-    def test_import_with_explicit_image(self):
-        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"),
-                                        path.join(path.dirname(__file__), "files/kandinsky-composition-viii.png"))
-
-        picture.delete()
-
-    def test_import_2(self):
-        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"),
-                                        path.join(path.dirname(__file__), "files/kandinsky-composition-viii.png"),
-                                        overwrite=True)
-        cats = set([t.category for t in picture.tags])
-        assert 'epoch' in cats
-        assert 'kind' in cats
diff --git a/src/picture/tests/test_picture_import.py b/src/picture/tests/test_picture_import.py
new file mode 100644 (file)
index 0000000..022b33c
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+from __future__ import with_statement
+
+from os import path
+from picture.models import Picture
+from catalogue.test_utils import WLTestCase
+
+
+class PictureTest(WLTestCase):
+
+    def test_import(self):
+        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"))
+
+        themes = set()
+        for area in picture.areas.all():
+            themes.update([
+                (tag.category, tag.name)
+                for tag in area.tags if tag.category in (u'theme', u'thing')])
+        assert themes == {(u'theme', u'nieporządek'), (u'thing', u'Kosmos')}, \
+            'Bad themes on Picture areas: %s' % themes
+
+        pic_themes = set([tag.name for tag in picture.tags if tag.category in ('theme', 'thing')])
+        assert not pic_themes, 'Unwanted themes set on Pictures: %s' % pic_themes
+
+        picture.delete()
+
+    def test_import_with_explicit_image(self):
+        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"),
+                                        path.join(path.dirname(__file__), "files/kandinsky-composition-viii.png"))
+
+        picture.delete()
+
+    def test_import_2(self):
+        picture = Picture.from_xml_file(path.join(path.dirname(__file__), "files/kandinsky-composition-viii.xml"),
+                                        path.join(path.dirname(__file__), "files/kandinsky-composition-viii.png"),
+                                        overwrite=True)
+        cats = set([t.category for t in picture.tags])
+        assert 'epoch' in cats
+        assert 'kind' in cats