General A/B testing.
[wolnelektury.git] / src / catalogue / tests / test_book_import.py
1 # This file is part of Wolnelektury, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from django.core.files.base import ContentFile
5 from catalogue.test_utils import *
6 from catalogue import models
7 from librarian import WLURI
8
9 from os import path, makedirs
10
11
12 class BookImportLogicTests(WLTestCase):
13
14     def setUp(self):
15         WLTestCase.setUp(self)
16         self.book_info = BookInfoStub(
17             url=WLURI.from_slug("default-book"),
18             about="http://wolnelektury.pl/example/URI/default_book",
19             title="Default Book",
20             author=PersonStub(("Jim",), "Lazy"),
21             kind="X-Kind",
22             genre="X-Genre",
23             epoch="X-Epoch",
24             language="pol",
25         )
26
27         self.expected_tags = [
28            ('author', 'jim-lazy'),
29            ('genre', 'x-genre'),
30            ('epoch', 'x-epoch'),
31            ('kind', 'x-kind'),
32         ]
33         self.expected_tags.sort()
34
35     def test_empty_book(self):
36         book_text = "<utwor />"
37         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
38         book.refresh_from_db()
39
40         self.assertEqual(book.title, "Default Book")
41         self.assertEqual(book.slug, "default-book")
42         self.assertTrue(book.parent is None)
43         self.assertFalse(book.has_html_file())
44
45         # no fragments generated
46         self.assertEqual(book.fragments.count(), 0)
47
48         # TODO: this should be filled out probably...
49         self.assertEqual(book.wiki_link, '')
50         self.assertEqual(book.gazeta_link, '')
51         self.assertEqual(book.description, '')
52
53         tags = [(tag.category, tag.slug) for tag in book.tags]
54         tags.sort()
55
56         self.assertEqual(tags, self.expected_tags)
57
58     def test_not_quite_empty_book(self):
59         """ Not empty, but without any real text.
60
61         Should work like any other non-empty book.
62         """
63
64         book_text = """<utwor>
65         <liryka_l>
66             <nazwa_utworu>Nic</nazwa_utworu>
67         </liryka_l></utwor>
68         """
69
70         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
71         book.refresh_from_db()
72         self.assertTrue(book.has_html_file())
73
74     def test_book_with_fragment(self):
75         book_text = """<utwor>
76         <opowiadanie>
77             <akap><begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" /></akap>
78         </opowiadanie></utwor>
79         """
80
81         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
82         book.refresh_from_db()
83         self.assertTrue(book.has_html_file())
84
85         self.assertEqual(book.fragments.count(), 1)
86         self.assertEqual(book.fragments.all()[0].text, '<p class="paragraph">Ala ma kota</p>\n')
87
88         self.assertTrue(('theme', 'love') in [(tag.category, tag.slug) for tag in book.fragments.all()[0].tags])
89
90     def test_book_with_empty_theme(self):
91         """ empty themes should be ignored """
92
93         book_text = """<utwor>
94         <opowiadanie>
95             <akap><begin id="m01" /><motyw id="m01"> , Love , , </motyw>Ala ma kota<end id="m01" /></akap>
96         </opowiadanie></utwor>
97         """
98
99         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
100         self.assertTrue([('theme', 'love')],
101                      book.fragments.all()[0].tags.filter(category='theme').values_list('category', 'slug'))
102
103     def test_book_with_no_theme(self):
104         """ fragments with no themes shouldn't be created at all """
105
106         book_text = """<utwor>
107         <opowiadanie>
108             <akap><begin id="m01" /><motyw id="m01"></motyw>Ala ma kota<end id="m01" /></akap>
109         </opowiadanie></utwor>
110         """
111
112         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
113         self.assertEqual(book.fragments.count(), 0)
114         self.assertEqual(book.tags.filter(category='theme').count(), 0)
115
116     def test_book_with_invalid_slug(self):
117         """ Book with invalid characters in slug shouldn't be imported """
118         self.book_info.url = WLURI.from_slug("default_book")
119         book_text = "<utwor />"
120         with self.assertRaises(ValueError):
121             models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
122
123     def test_book_replace_title(self):
124         book_text = """<utwor />"""
125         models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
126         self.book_info.title = "Extraordinary"
127         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
128
129         tags = [(tag.category, tag.slug) for tag in book.tags]
130         tags.sort()
131
132         self.assertEqual(tags, self.expected_tags)
133
134     def test_book_replace_author(self):
135         book_text = """<utwor />"""
136         models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
137         self.book_info.author = PersonStub(("Hans", "Christian"), "Andersen")
138         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info, overwrite=True)
139
140         tags = [(tag.category, tag.slug) for tag in book.tags]
141         tags.sort()
142
143         self.expected_tags.remove(('author', 'jim-lazy'))
144         self.expected_tags.append(('author', 'hans-christian-andersen'))
145         self.expected_tags.sort()
146
147         self.assertEqual(tags, self.expected_tags)
148
149         # the old tag shouldn't disappear
150         models.Tag.objects.get(slug="jim-lazy", category="author")
151
152     def test_book_remove_fragment(self):
153         book_text = """<utwor>
154         <opowiadanie>
155             <akap>
156                 <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
157                 <begin id="m02" /><motyw id="m02">Hatred</motyw>To kot Ali<end id="m02" />
158             </akap>
159         </opowiadanie></utwor>
160         """
161         book_text_after = """<utwor>
162         <opowiadanie>
163             <akap>
164                 <begin id="m01" /><motyw id="m01">Love</motyw>Ala ma kota<end id="m01" />
165                 To kot Ali
166             </akap>
167         </opowiadanie></utwor>
168         """
169
170         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
171         self.assertEqual(book.fragments.count(), 2)
172         book = models.Book.from_text_and_meta(ContentFile(book_text_after), self.book_info, overwrite=True)
173         self.assertEqual(book.fragments.count(), 1)
174
175     def test_multiple_tags(self):
176         book_text = """<utwor />"""
177         self.book_info.authors = self.book_info.author, PersonStub(("Joe",), "Dilligent"),
178         self.book_info.kinds = self.book_info.kind, 'Y-Kind',
179         self.book_info.genres = self.book_info.genre, 'Y-Genre',
180         self.book_info.epochs = self.book_info.epoch, 'Y-Epoch',
181
182         self.expected_tags.extend([
183            ('author', 'joe-dilligent'),
184            ('genre', 'y-genre'),
185            ('epoch', 'y-epoch'),
186            ('kind', 'y-kind'),
187         ])
188         self.expected_tags.sort()
189
190         book = models.Book.from_text_and_meta(ContentFile(book_text), self.book_info)
191         tags = [(tag.category, tag.slug) for tag in book.tags]
192         tags.sort()
193
194         self.assertEqual(tags, self.expected_tags)
195
196
197 class ChildImportTests(WLTestCase):
198
199     def setUp(self):
200         WLTestCase.setUp(self)
201         self.child_info = BookInfoStub(
202             genre='X-Genre',
203             epoch='X-Epoch',
204             kind='X-Kind',
205             author=PersonStub(("Joe",), "Doe"),
206             **info_args("Child")
207         )
208
209         self.parent_info = BookInfoStub(
210             genre='X-Genre',
211             epoch='X-Epoch',
212             kind='X-Kind',
213             author=PersonStub(("Jim",), "Lazy"),
214             parts=[self.child_info.url],
215             **info_args("Parent")
216         )
217
218     def test_child(self):
219         text = """<utwor />"""
220         child = models.Book.from_text_and_meta(ContentFile(text), self.child_info)
221         parent = models.Book.from_text_and_meta(ContentFile(text), self.parent_info)
222         author = parent.tags.get(category='author')
223         books = self.client.get(author.get_absolute_url()).context['object_list']
224         self.assertEqual(len(books), 1, "Only parent book should be visible on author's page")
225
226     def test_child_replace(self):
227         parent_text = """<utwor />"""
228         child_text = """<utwor>
229         <opowiadanie>
230             <akap><begin id="m01" /><motyw id="m01">Pies</motyw>Ala ma kota<end id="m01" /></akap>
231         </opowiadanie></utwor>
232         """
233         child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info)
234         parent = models.Book.from_text_and_meta(ContentFile(parent_text), self.parent_info)
235         child_text = """<utwor>
236         <opowiadanie>
237             <akap><begin id="m01" /><motyw id="m01">Kot</motyw>Ala ma kota<end id="m01" /></akap>
238         </opowiadanie></utwor>
239         """
240         child = models.Book.from_text_and_meta(ContentFile(child_text), self.child_info, overwrite=True)
241         themes = parent.related_themes()
242         self.assertEqual(['Kot'], [tag.name for tag in themes], 'wrong related theme list')
243
244
245 class TreeImportTest(WLTestCase):
246     def setUp(self):
247         WLTestCase.setUp(self)
248         self.child_info = BookInfoStub(
249             genre='X-Genre',
250             epoch='X-Epoch',
251             kind='X-Kind',
252             author=PersonStub(("Joe",), "Doe"),
253             **info_args("Child")
254         )
255         self.CHILD_TEXT = """<utwor>
256         <opowiadanie>
257             <akap><begin id="m01" /><motyw id="m01">Pies</motyw>
258                 Ala ma kota<end id="m01" /></akap>
259         </opowiadanie></utwor>
260         """
261         self.child = models.Book.from_text_and_meta(
262             ContentFile(self.CHILD_TEXT), self.child_info)
263
264         self.book_info = BookInfoStub(
265             genre='X-Genre',
266             epoch='X-Epoch',
267             kind='X-Kind',
268             author=PersonStub(("Joe",), "Doe"),
269             parts=[self.child_info.url],
270             **info_args("Book")
271         )
272         self.BOOK_TEXT = """<utwor />"""
273         self.book = models.Book.from_text_and_meta(
274             ContentFile(self.BOOK_TEXT), self.book_info)
275
276         self.parent_info = BookInfoStub(
277             genre='X-Genre',
278             epoch='X-Epoch',
279             kind='X-Kind',
280             author=PersonStub(("Jim",), "Lazy"),
281             parts=[self.book_info.url],
282             **info_args("Parent")
283         )
284         self.PARENT_TEXT = """<utwor />"""
285         self.parent = models.Book.from_text_and_meta(
286             ContentFile(self.PARENT_TEXT), self.parent_info)
287
288     def test_ok(self):
289         self.assertEqual(
290                 list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
291                 [self.parent],
292                 "There should be only parent on common tag page."
293             )
294         # pies = models.Tag.objects.get(slug='pies')
295         themes = self.parent.related_themes()
296         self.assertEqual(len(themes), 1, "There should be child theme in parent theme counter.")
297         # TODO: book_count is deprecated, update here.
298         # epoch = models.Tag.objects.get(slug='x-epoch')
299         # self.assertEqual(epoch.book_count, 1, "There should be only parent in common tag's counter.")
300
301     def test_child_republish(self):
302         child_text = """<utwor>
303         <opowiadanie>
304             <akap><begin id="m01" /><motyw id="m01">Pies, Kot</motyw>
305                 Ala ma kota<end id="m01" /></akap>
306         </opowiadanie></utwor>
307         """
308         models.Book.from_text_and_meta(
309             ContentFile(child_text), self.child_info, overwrite=True)
310         self.assertEqual(
311                 list(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
312                 [self.parent],
313                 "There should only be parent on common tag page."
314             )
315         # pies = models.Tag.objects.get(slug='pies')
316         # kot = models.Tag.objects.get(slug='kot')
317         self.assertEqual(len(self.parent.related_themes()), 2,
318                          "There should be child themes in parent theme counter.")
319         # TODO: book_count is deprecated, update here.
320         # epoch = models.Tag.objects.get(slug='x-epoch')
321         # self.assertEqual(epoch.book_count, 1, "There should only be parent in common tag's counter.")
322
323     def test_book_change_child(self):
324         second_child_info = BookInfoStub(
325             genre='X-Genre',
326             epoch='X-Epoch',
327             kind='Other-Kind',
328             author=PersonStub(("Joe",), "Doe"),
329             **info_args("Second Child")
330         )
331         second_child_text = """<utwor>
332         <opowiadanie>
333             <akap><begin id="m01" /><motyw id="m01">Kot</motyw>
334                 Ala ma kota<end id="m01" /></akap>
335         </opowiadanie></utwor>
336         """
337         # Import a second child.
338         second_child = models.Book.from_text_and_meta(
339             ContentFile(second_child_text), second_child_info)
340         # The book has only this new child now.
341         self.book_info.parts = [second_child_info.url]
342         self.book = models.Book.from_text_and_meta(
343             ContentFile(self.BOOK_TEXT), self.book_info, overwrite=True)
344
345         self.assertEqual(
346             set(self.client.get('/katalog/gatunek/x-genre/').context['object_list']),
347             {self.parent, self.child},
348             "There should be parent and old child on common tag page."
349         )
350         # kot = models.Tag.objects.get(slug='kot')
351         self.assertEqual(len(self.parent.related_themes()), 1,
352                          "There should only be new child themes in parent theme counter.")
353         # # book_count deprecated, update test.
354         # epoch = models.Tag.objects.get(slug='x-epoch')
355         # self.assertEqual(epoch.book_count, 2,
356         #                  "There should be parent and old child in common tag's counter.")
357         self.assertEqual(
358             list(self.client.get('/katalog/lektura/parent/motyw/kot/').context['fragments']),
359             [second_child.fragments.all()[0]],
360             "There should be new child's fragments on parent's theme page."
361         )
362         self.assertEqual(
363             list(self.client.get('/katalog/lektura/parent/motyw/pies/').context['fragments']),
364             [],
365             "There should be no old child's fragments on parent's theme page."
366         )
367
368
369 class MultilingualBookImportTest(WLTestCase):
370     def setUp(self):
371         WLTestCase.setUp(self)
372         common_uri = WLURI.from_slug('common-slug')
373
374         self.pol_info = BookInfoStub(
375             genre='X-Genre',
376             epoch='X-Epoch',
377             kind='X-Kind',
378             author=PersonStub(("Joe",), "Doe"),
379             variant_of=common_uri,
380             **info_args("Książka")
381         )
382
383         self.eng_info = BookInfoStub(
384             genre='X-Genre',
385             epoch='X-Epoch',
386             kind='X-Kind',
387             author=PersonStub(("Joe",), "Doe"),
388             variant_of=common_uri,
389             **info_args("A book", "eng")
390         )
391
392     def test_multilingual_import(self):
393         book_text = """<utwor><opowiadanie><akap>A</akap></opowiadanie></utwor>"""
394
395         models.Book.from_text_and_meta(ContentFile(book_text), self.pol_info)
396         models.Book.from_text_and_meta(ContentFile(book_text), self.eng_info)
397
398         self.assertEqual(
399             set([b.language for b in models.Book.objects.all()]),
400             {'pol', 'eng'},
401             'Books imported in wrong languages.'
402         )
403
404
405 class BookImportGenerateTest(WLTestCase):
406     def setUp(self):
407         WLTestCase.setUp(self)
408         xml = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml')
409         self.book = models.Book.from_xml_file(xml)
410
411     def test_gen_pdf(self):
412         self.book.pdf_file.build()
413         book = models.Book.objects.get(pk=self.book.pk)
414         self.assertTrue(path.exists(book.pdf_file.path))
415
416     def test_gen_pdf_parent(self):
417         """This book contains a child."""
418         xml = path.join(path.dirname(__file__), "files/fraszki.xml")
419         parent = models.Book.from_xml_file(xml)
420         parent.pdf_file.build()
421         parent = models.Book.objects.get(pk=parent.pk)
422         self.assertTrue(path.exists(parent.pdf_file.path))
423
424     def test_custom_pdf(self):
425         from catalogue.tasks import build_custom_pdf
426         out = 'test-custom.pdf'
427         absoulute_path = path.join(settings.MEDIA_ROOT, out)
428
429         if not path.exists(path.dirname(absoulute_path)):
430             makedirs(path.dirname(absoulute_path))
431
432         build_custom_pdf(self.book.id, customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out)
433         self.assertTrue(path.exists(absoulute_path))