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="
Contents here
\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 "Contents here
" 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="Content
\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 "Content
" 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="toc
\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"ch{i}
\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