"""Tests for scripts/import_unlinked_epub_chapters.py, a manual tool."""
import zipfile
import pytest
from bindery import config
from bindery.store import Store
from scripts import import_unlinked_epub_chapters as script
from scripts.import_unlinked_epub_chapters import (
ScriptError,
chapter_url,
main,
read_epub,
splice_order,
suggest_matches,
)
STORY_URL = "https://www.royalroad.com/fiction/1234"
CONTAINER_XML = """
{text}
" def make_opf(count: int, identifier: str) -> str: items = "\n".join( f'“I’m here.”
" ) z.writestr("OEBPS/text/two.xhtml", "Text
") chapters = read_epub(path) assert [c["title"] for c in chapters] == ["CHAPTER ONE: Arrival", "CHAPTER TWO"] assert "“I’m here.”
" in chapters[0]["html"] def test_read_epub_points_images_at_their_own_urls_and_drops_missing_ones(tmp_path): path = make_epub( tmp_path / "book.epub", [("Map", '

one
", "h1", "new", 0) db_store.add_chapter(story_id, f"{STORY_URL}/4", "Chapter 4", "four
", "h4", "new", 1) epub = make_epub( tmp_path / "book.epub", [ ("Cover", "cover"), ("Chapter 1", "one text"), ("Chapter 2", '
two text'),
("Chapter 3", "three text"),
("Chapter 4", "four text"),
("About the Author", "author"),
],
files={"OEBPS/map.png": b"png"},
)
return db_store, story_id, epub
def answer(monkeypatch, *answers):
"""Answer the prompts in order. A prompt that shows a default in [brackets] gets it for an empty answer."""
replies = iter(answers)
prompts = []
def fake_input(prompt=""):
prompts.append(prompt)
return next(replies)
monkeypatch.setattr("builtins.input", fake_input)
return prompts
def titles(db_store, story_id):
return [c["title"] for c in db_store.list_chapters(story_id)]
def test_main_fills_a_gap_with_the_suggested_matches(story, monkeypatch, capsys):
db_store, story_id, epub = story
# Before the gap: stored chapter 1. Then Enter takes each suggestion.
prompts = answer(monkeypatch, "1", "", "", "", "y")
main([STORY_URL, str(epub)])
# Each prompt says what it asks for, and shows the suggestion. Numbers start at 1.
assert prompts[:4] == [
"Stored chapter just BEFORE the gap (or 'start'): ",
'ePub chapter that matches "Chapter 1" [2]: ',
"Stored chapter just AFTER the gap (or 'end') [2]: ",
'ePub chapter that matches "Chapter 4" [5]: ',
]
assert titles(db_store, story_id) == ["Chapter 1", "Chapter 2", "Chapter 3", "Chapter 4"]
added = db_store.list_chapters(story_id)[1]
assert added["url"].startswith(f"{STORY_URL}#bindery-import-")
assert added["on_site"] == 0
assert db_store.list_revisions(added["id"])[0]["source"] == "epub-import"
html = db_store.revision_html(added["id"], 1)
(image,) = db_store.images(story_id)
assert f'src="{image["url"]}"' in html
assert 'The script will add these 2 chapters after "Chapter 1"' in capsys.readouterr().out
# A second run over the same gap finds the same chapters in the story, and adds nothing.
answer(monkeypatch, "1", "", "4", "")
main([STORY_URL, str(epub)])
assert len(db_store.list_chapters(story_id)) == 4
assert "2 ePub chapters of the gap are in the story already" in capsys.readouterr().out
def test_main_asks_again_for_a_bad_answer_and_can_show_the_whole_list(story, monkeypatch, capsys):
db_store, story_id, epub = story
monkeypatch.setattr(script, "WINDOW", 2)
answer(monkeypatch, "9", "list", "1", "", "1", "", "", "n")
main([STORY_URL, str(epub)])
out = capsys.readouterr().out
assert "Type a number from 1 to 2, or 'list' or 'start'." in out
# The AFTER question cannot go before the BEFORE chapter.
assert "Type a number from 2 to 2, or 'list' or 'end'." in out
assert titles(db_store, story_id) == ["Chapter 1", "Chapter 4"]
def test_main_asks_for_the_first_and_last_epub_chapters_at_the_start_and_end(story, monkeypatch):
db_store, story_id, epub = story
# The gap is at the start: add from ePub chapter 1 up to the match of stored Chapter 1.
prompts = answer(monkeypatch, "start", "1", "", "", "y")
main([STORY_URL, str(epub)])
assert prompts[1] == "FIRST ePub chapter to add: "
assert titles(db_store, story_id) == ["Cover", "Chapter 1", "Chapter 4"]
# The gap is after the last stored chapter, so the script does not ask for the chapter after it.
prompts = answer(monkeypatch, "3", "", "", "y")
main([STORY_URL, str(epub)])
assert prompts[2] == "LAST ePub chapter to add [6]: "
assert titles(db_store, story_id) == ["Cover", "Chapter 1", "Chapter 4", "About the Author"]
def test_main_changes_nothing_when_the_user_says_no(story, monkeypatch):
db_store, story_id, epub = story
answer(monkeypatch, "1", "", "", "", "n")
main([STORY_URL, str(epub)])
assert titles(db_store, story_id) == ["Chapter 1", "Chapter 4"]
assert db_store.images(story_id) == []
def test_remove_deletes_only_the_added_chapters_and_their_revisions(story, monkeypatch):
db_store, story_id, _ = story
added = db_store.add_chapter(story_id, f"{STORY_URL}#bindery-import-x", "Two", "2
", "h2", "epub-import", 1) db_store.add_revision(added, "2b
", "h2b", "recheck") answer(monkeypatch, "n") main([STORY_URL, "--remove"]) assert len(db_store.list_chapters(story_id)) == 3 answer(monkeypatch, "y") main([STORY_URL, "--remove"]) assert titles(db_store, story_id) == ["Chapter 1", "Chapter 4"] assert db_store.list_revisions(added) == []