Files
bindery/tests/test_build.py
T
tim 5f682d209b Remove small duplicates in the settings, the logins and the names
One function reads a true or false value. The download name uses the
file name cleaning of the ingest folder. The login code reads the login
mode from the configuration, and has one lockout message. The title
helper of FanFicFare is now unescape_title, because cwa.py has another
plain_title.
2026-10-03 08:09:41 +02:00

156 lines
6.4 KiB
Python

"""Build an epub from stored data without network access, then read it back."""
import base64
import datetime
import re
import zipfile
import pytest
from defusedxml import ElementTree
from bindery import config, fff
from bindery.rules import compile_rules, normalize_rules
URL = "https://www.royalroad.com/fiction/1234/test-story"
PNG = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
)
IMG_URL = "https://www.royalroadcdn.com/public/img.png"
COVER = {"url": "https://www.royalroadcdn.com/public/covers/1234.png", "ext": "png", "mime": "image/png", "data": PNG}
def metahtml(url=URL):
adapter = fff.get_adapter(url)
story = adapter.story
story.setMetadata("title", "Test Story")
story.addToList("author", "Writer")
story.addToList("authorId", "42")
story.setMetadata("description", "<p>About the story</p>")
story.setMetadata("datePublished", datetime.datetime(2024, 1, 2))
story.setMetadata("dateUpdated", datetime.datetime(2024, 3, 4))
story.addToList("genre", "Fantasy")
return story.dump_html_metadata()
def chapters():
base = "https://www.royalroad.com/fiction/1234/test-story/chapter/{0}/c{0}"
return [
{"url": base.format(10), "title": "One", "html": '<div><p class="rnd1">Zorian wakes.</p></div>'},
{"url": base.format(11), "title": "Two", "html": f'<div><p>Picture</p><img src="{IMG_URL}"/></div>'},
{"url": base.format(15), "title": "Three", "html": "<div><p>Zorian again.</p></div>"},
]
def test_build_and_read_back(tmp_path):
rules = compile_rules(normalize_rules([{"pattern": "Zorian", "replacement": "Z"}]))
out = tmp_path / "book.epub"
images = [{"url": IMG_URL, "ext": "png", "mime": "image/png", "data": PNG}]
meta = fff.build_epub(URL, metahtml(), chapters(), images, None, rules, {"title": "Kept Title"}, out)
assert meta["title"] == "Kept Title"
assert meta["authors"] == ["Writer"]
assert meta["pubdate"].startswith("2024-01-02")
with zipfile.ZipFile(out) as z:
names = z.namelist()
text = "".join(z.read(n).decode("utf-8", "ignore") for n in names if n.endswith(".xhtml"))
opf = z.read("content.opf").decode()
assert "Z wakes." in text and "Zorian" not in text
assert any(n.startswith("OEBPS/images/ffdl-") and n.endswith(".png") for n in names)
assert re.search(r"<dc:title[^>]*>Kept Title</dc:title>", opf)
contents = fff.read_epub(out)
assert [c["title"] for c in contents.chapters] == ["One", "Two", "Three"]
assert [c["url"] for c in contents.chapters] == [c["url"] for c in chapters()]
assert IMG_URL in contents.chapters[1]["html"]
assert contents.images and contents.images[0]["url"] == IMG_URL
def test_a_title_with_entities_builds_and_imports_in_fanficfare_form(tmp_path):
# FanFicFare's form, as the site titles in the store have it.
title = "Folly &amp; Magic &lt;1&gt;"
out = tmp_path / "book.epub"
fff.build_epub(URL, metahtml(), [dict(chapters()[0], title=title)], [], None, [], {}, out)
with zipfile.ZipFile(out) as z:
for name in z.namelist():
if name.endswith((".xhtml", ".ncx", ".opf")):
ElementTree.fromstring(z.read(name))
# An import stores the same title as a download, so the next update finds no rename.
assert fff.read_epub(out).chapters[0]["title"] == title
assert fff.unescape_title(title) == "Folly & Magic <1>"
assert fff.escape_title("Folly & Magic <1>") == title
@pytest.mark.parametrize(
("personal_ini", "expected"),
[
("", "coveronly"),
("[www.royalroad.com]\ninclude_images:false\n", "coveronly"),
("[defaults]\ninclude_images:true\n", "true"),
("[defaults]\ninclude_images:coveronly\n", "coveronly"),
],
)
def test_include_images_is_never_off(personal_ini, expected, tmp_path, monkeypatch):
path = tmp_path / "personal.ini"
path.write_text(personal_ini)
monkeypatch.setattr(config, "PERSONAL_INI", path)
assert fff.get_configuration(URL).getConfig("include_images") == expected
@pytest.mark.parametrize("personal_ini", ["", "[defaults]\ninclude_images:false\n"])
def test_the_cover_goes_in_the_epub_without_include_images(personal_ini, tmp_path, monkeypatch):
path = tmp_path / "personal.ini"
path.write_text(personal_ini)
monkeypatch.setattr(config, "PERSONAL_INI", path)
out = tmp_path / "book.epub"
fff.build_epub(URL, metahtml(), chapters(), [], COVER, [], {}, out)
with zipfile.ZipFile(out) as z:
names = z.namelist()
opf = z.read("content.opf").decode()
assert "OEBPS/cover.xhtml" in names
assert any(n.startswith("OEBPS/images/cover.") for n in names)
assert re.search(r'<meta name="cover" content="[^"]+"', opf)
def test_locked_fields_replace_site_metadata(tmp_path):
out = tmp_path / "book.epub"
overrides = {
"title": "Kept Title",
"authors": ["Pen Name", "Co Writer"],
"comments": "<p>Kept blurb</p>",
"series": ["Kept Saga", 3.0],
"pubdate": "2019-05-06T00:00:00+00:00",
"languages": "de",
"tags": ["mine", "reread"],
}
meta = fff.build_epub(URL, metahtml(), chapters(), [], None, [], overrides, out)
assert meta["authors"] == ["Pen Name", "Co Writer"]
assert meta["tags"] == ["mine", "reread"]
with zipfile.ZipFile(out) as z:
opf = z.read("content.opf").decode()
assert re.findall(r"<dc:creator[^>]*>([^<]*)</dc:creator>", opf) == ["Pen Name", "Co Writer"]
assert re.findall(r"<dc:subject>([^<]*)</dc:subject>", opf) == ["mine", "reread"]
assert 'name="calibre:series" content="Kept Saga"' in opf
assert 'name="calibre:series_index" content="3.00"' in opf
assert "<dc:language>de</dc:language>" in opf
assert "2019-05-06" in opf
assert "Kept blurb" in opf
def test_unlocked_fields_keep_site_metadata(tmp_path):
out = tmp_path / "book.epub"
meta = fff.build_epub(URL, metahtml(), chapters(), [], None, [], {}, out)
assert meta["title"] == "Test Story"
assert meta["authors"] == ["Writer"]
assert "Fantasy" in meta["tags"]
def test_supported_sites():
assert fff.is_supported_site("www.royalroad.com")
assert fff.is_supported_site("WWW.RoyalRoad.com ")
assert not fff.is_supported_site("example.com")
def test_normalize_url():
assert fff.normalize_url(URL) == ("https://www.royalroad.com/fiction/1234", "www.royalroad.com")
assert fff.normalize_url("https://example.com/nothing") is None