Files
epubmaker/tests/test_writer.py
T

244 lines
8.5 KiB
Python

import os
import pytest
from unittest.mock import patch
from epubmaker.models import BuildContext, Book, Section, Image, Name
@pytest.fixture
def ctx(tmp_path):
context = BuildContext()
context.book.title = "Test Book"
context.book.uuid = "test-uuid-1234"
context.book.isbn = "978-0-000-00000-0"
context.book.publisher = "Test Publisher"
context.book.copyright = "2024 Test"
context.book.subjects = "Fiction"
context.book.source = "Original Source"
context.book.publish_date = "2024-1-1"
context.book.directory = str(tmp_path / "testbook")
context.book.filename = str(tmp_path / "source" / "book.html")
context.book.author.append(Name(first="Jane", last="Doe"))
return context
from epubmaker.writer import (
write_structure, write_misc, write_ncx, write_toc,
write_cover, write_contents, write_sections, write_illustrations,
)
# --- write_structure ---
def test_write_structure_creates_epub_directories(ctx):
write_structure(ctx, overwrite=False)
for subdir in ["META-INF", "OEBPS", "OEBPS/Images", "OEBPS/Styles", "OEBPS/Text"]:
assert os.path.isdir(os.path.join(ctx.book.directory, subdir))
def test_write_structure_exits_if_exists(ctx):
os.makedirs(ctx.book.directory)
with pytest.raises(SystemExit):
write_structure(ctx, overwrite=False)
def test_write_structure_overwrite_replaces_existing(ctx):
os.makedirs(ctx.book.directory)
sentinel = os.path.join(ctx.book.directory, "old_file.txt")
open(sentinel, "w").close()
write_structure(ctx, overwrite=True)
assert os.path.isdir(ctx.book.directory)
assert not os.path.exists(sentinel)
# --- write_misc ---
def test_write_misc_mimetype_content(ctx):
write_structure(ctx, overwrite=False)
write_misc(ctx)
with open(os.path.join(ctx.book.directory, "mimetype")) as f:
assert f.read() == "application/epub+zip"
def test_write_misc_container_xml_references_opf(ctx):
write_structure(ctx, overwrite=False)
write_misc(ctx)
with open(os.path.join(ctx.book.directory, "META-INF", "container.xml")) as f:
assert 'full-path="OEBPS/content.opf"' in f.read()
def test_write_misc_ibooks_display_options_created(ctx):
write_structure(ctx, overwrite=False)
write_misc(ctx)
path = os.path.join(ctx.book.directory, "META-INF", "com.apple.ibooks.display-options.xml")
assert os.path.isfile(path)
# --- write_ncx ---
def test_write_ncx_contains_title_and_uuid(ctx):
write_structure(ctx, overwrite=False)
write_ncx(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
content = f.read()
assert ctx.book.title in content
assert ctx.book.uuid in content
def test_write_ncx_includes_non_toc_sections(ctx):
ctx.book.sections.append(Section(type="text", name="chapter1", title="Chapter One"))
write_structure(ctx, overwrite=False)
write_ncx(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
content = f.read()
assert "chapter1" in content
assert "Chapter One" in content
def test_write_ncx_excludes_toc_type_sections(ctx):
ctx.book.sections.append(Section(type="toc", name="toc", title="Table of Contents"))
write_structure(ctx, overwrite=False)
write_ncx(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
content = f.read()
assert 'id ="toc"' not in content
def test_write_ncx_play_order_increments(ctx):
for i in range(3):
ctx.book.sections.append(Section(type="text", name=f"ch{i}", title=f"Chapter {i}"))
write_structure(ctx, overwrite=False)
write_ncx(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.ncx")) as f:
content = f.read()
assert 'playOrder="2"' in content
assert 'playOrder="3"' in content
assert 'playOrder="4"' in content
# --- write_toc ---
def test_write_toc_writes_section_body(ctx):
section = Section(body="<p>Contents here</p>\n")
write_structure(ctx, overwrite=False)
write_toc(ctx, section)
with open(os.path.join(ctx.book.directory, "OEBPS", "toc.xhtml")) as f:
content = f.read()
assert "<p>Contents here</p>" in content
assert "Table of Contents" in content
# --- write_cover ---
def test_write_cover_creates_xhtml_with_title(ctx, tmp_path):
source_images = tmp_path / "source" / "Images"
source_images.mkdir(parents=True)
(source_images / "cover.jpeg").write_bytes(b"fake")
write_structure(ctx, overwrite=False)
write_cover(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "Text", "cover.xhtml")) as f:
content = f.read()
assert ctx.book.title in content
assert 'src="../Images/cover.jpeg"' in content
def test_write_cover_copies_image_to_output(ctx, tmp_path):
source_images = tmp_path / "source" / "Images"
source_images.mkdir(parents=True)
(source_images / "cover.jpeg").write_bytes(b"image-data")
write_structure(ctx, overwrite=False)
write_cover(ctx)
assert os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Images", "cover.jpeg"))
# --- write_sections ---
def test_write_sections_creates_xhtml_per_section(ctx):
ctx.book.sections.append(Section(type="text", name="chapter1", title="Chapter One", body="<p>Content</p>\n"))
write_structure(ctx, overwrite=False)
write_sections(ctx)
path = os.path.join(ctx.book.directory, "OEBPS", "Text", "chapter1.xhtml")
assert os.path.isfile(path)
with open(path) as f:
content = f.read()
assert "Chapter One" in content
assert "<p>Content</p>" in content
def test_write_sections_routes_toc_to_toc_xhtml(ctx):
ctx.book.sections.append(Section(type="toc", name="toc", title="Table of Contents", body="<p>toc</p>\n"))
write_structure(ctx, overwrite=False)
write_sections(ctx)
assert os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "toc.xhtml"))
assert not os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Text", "toc.xhtml"))
def test_write_sections_multiple_sections(ctx):
for i in range(3):
ctx.book.sections.append(Section(type="text", name=f"ch{i}", title=f"Chapter {i}", body=f"<p>ch{i}</p>\n"))
write_structure(ctx, overwrite=False)
write_sections(ctx)
for i in range(3):
assert os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Text", f"ch{i}.xhtml"))
# --- write_illustrations ---
def test_write_illustrations_skips_when_no_images(ctx):
write_structure(ctx, overwrite=False)
write_illustrations(ctx)
assert not os.path.isfile(os.path.join(ctx.book.directory, "OEBPS", "Text", "illustrations.xhtml"))
def test_write_illustrations_creates_file_with_image_links(ctx):
ctx.book.images.append(Image(name="photo.jpg", caption="A scenic photo"))
write_structure(ctx, overwrite=False)
write_illustrations(ctx)
path = os.path.join(ctx.book.directory, "OEBPS", "Text", "illustrations.xhtml")
assert os.path.isfile(path)
with open(path) as f:
content = f.read()
assert "photo.jpg" in content
assert "A scenic photo" in content
# --- write_contents ---
def test_write_contents_creates_opf_with_metadata(ctx):
write_structure(ctx, overwrite=False)
write_contents(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
content = f.read()
assert ctx.book.title in content
assert ctx.book.publisher in content
assert ctx.book.isbn in content
def test_write_contents_includes_section_in_manifest(ctx):
ctx.book.sections.append(Section(type="text", name="chapter1", title="Chapter One"))
write_structure(ctx, overwrite=False)
write_contents(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
content = f.read()
assert "chapter1.xhtml" in content
def test_write_contents_includes_series_metadata(ctx):
ctx.book.series.name = "My Series"
ctx.book.series.number = "1"
write_structure(ctx, overwrite=False)
write_contents(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
content = f.read()
assert "My Series" in content
assert "calibre:series" in content
def test_write_contents_includes_illustrations_when_images_present(ctx):
ctx.book.images.append(Image(name="photo.jpg", type="jpeg", caption="caption"))
write_structure(ctx, overwrite=False)
with patch("epubmaker.writer.copy"):
write_contents(ctx)
with open(os.path.join(ctx.book.directory, "OEBPS", "content.opf")) as f:
content = f.read()
assert "illustrations.xhtml" in content