244 lines
8.5 KiB
Python
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
|