Do not make the ingest folder
A folder that Bindery made hid a missing volume mount. The books went to a folder inside the container, and CWA never saw them. A missing folder is now an error in the log at the start, and each upload fails with a message that names the mount.
This commit is contained in:
@@ -7,6 +7,7 @@ from pathlib import Path
|
||||
_tmp = Path(tempfile.mkdtemp(prefix="bindery-tests-"))
|
||||
for name, sub in [("CONFIG_DIR", "config"), ("INGEST_DIR", "ingest"), ("TMP_DIR", "tmp")]:
|
||||
os.environ[name] = str(_tmp / sub)
|
||||
(_tmp / "ingest").mkdir() # Bindery does not make the ingest folder
|
||||
os.environ["BINDERY_START_THREADS"] = "false"
|
||||
os.environ["UPDATE_INTERVAL_HOURS"] = "0"
|
||||
for name in (
|
||||
|
||||
@@ -279,3 +279,18 @@ def test_the_start_deletes_uploads_that_no_job_needs(builds, jobs, uploads, stor
|
||||
orchestrator.queue_epub_file(waiting, "story.epub")
|
||||
orchestrator.remove_stale_uploads()
|
||||
assert list(uploads.iterdir()) == [waiting]
|
||||
|
||||
|
||||
def test_a_missing_ingest_folder_fails_the_job_until_the_folder_exists(builds, jobs, monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(config, "INGEST_DIR", tmp_path / "not-mounted")
|
||||
story_id = linked_story(ONE)
|
||||
site(monkeypatch, ONE)
|
||||
monkeypatch.setattr(orchestrator.time, "sleep", Mock())
|
||||
jobs.add(Job(mode=DownloadMode.NEW, story_id=story_id))
|
||||
job = orchestrator._run_next_job()
|
||||
assert job.status == "error" and "does not exist. Check INGEST_DIR and the volume mount." in job.message
|
||||
assert story(story_id)["status"] == "error"
|
||||
assert not config.INGEST_DIR.exists()
|
||||
config.INGEST_DIR.mkdir()
|
||||
assert "library book 1" in update(story_id)
|
||||
assert builds.call_count == 1
|
||||
|
||||
@@ -250,6 +250,7 @@ def test_library_database_failure_keeps_the_http_lookup(library_db, monkeypatch,
|
||||
def test_database_book_id_reaches_the_epub_sidecar(client, library_db, monkeypatch, tmp_path, saved_id, saved_uuid):
|
||||
monkeypatch.setattr(config, "CALIBRE_DB_PATH", library_db)
|
||||
monkeypatch.setattr(config, "INGEST_DIR", tmp_path / "ingest")
|
||||
config.INGEST_DIR.mkdir(exist_ok=True)
|
||||
remote = CwaClient("")
|
||||
remote.session.get = Mock(side_effect=AssertionError("No HTTP lookup needed"))
|
||||
monkeypatch.setattr(orchestrator, "cwa", lambda: remote)
|
||||
|
||||
@@ -188,3 +188,16 @@ def test_prepare_warns_about_a_cwa_password_without_a_user_name(monkeypatch, tmp
|
||||
with caplog.at_level("WARNING"):
|
||||
config.prepare()
|
||||
assert ("CWA_PASSWORD is set" in caplog.text) is warns
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exists", [False, True])
|
||||
def test_prepare_does_not_make_the_ingest_folder(monkeypatch, tmp_path, caplog, exists):
|
||||
monkeypatch.setattr(config, "CWA_URL", "https://cwa.invalid")
|
||||
for name, folder in [("CONFIG_DIR", "config"), ("UPLOAD_DIR", "uploads"), ("TMP_DIR", "tmp"), ("INGEST_DIR", "in")]:
|
||||
monkeypatch.setattr(config, name, tmp_path / folder)
|
||||
monkeypatch.setattr(config, "PERSONAL_INI", tmp_path / "config" / "personal.ini")
|
||||
if exists:
|
||||
config.INGEST_DIR.mkdir()
|
||||
config.prepare()
|
||||
assert config.INGEST_DIR.exists() is exists
|
||||
assert ("does not exist. Check INGEST_DIR and the volume mount" in caplog.text) is not exists
|
||||
|
||||
+13
-1
@@ -9,7 +9,7 @@ from test_build import URL, chapters, metahtml
|
||||
from bindery import fff
|
||||
from bindery.cwa import BookFinder, CwaClient, CwaError, plain_title, search_text, title_rank
|
||||
from bindery.epub import epub_metadata, epub_urls
|
||||
from bindery.ingest import MAX_NAME_LENGTH, IngestFolder
|
||||
from bindery.ingest import MAX_NAME_LENGTH, IngestError, IngestFolder
|
||||
|
||||
STORY_URL = "https://www.royalroad.com/fiction/1234"
|
||||
|
||||
@@ -187,6 +187,7 @@ def test_epub_reader(story_epub, tmp_path):
|
||||
|
||||
def test_ingest_uses_part_names_and_unique_names(tmp_path):
|
||||
folder = IngestFolder(tmp_path / "ingest")
|
||||
folder.path.mkdir()
|
||||
epub = tmp_path / "x.epub"
|
||||
epub.write_bytes(b"PK")
|
||||
first = folder.ingest(epub, 'A: "Story" / Name')
|
||||
@@ -198,6 +199,7 @@ def test_ingest_uses_part_names_and_unique_names(tmp_path):
|
||||
|
||||
def test_send_update_writes_sidecar(tmp_path):
|
||||
folder = IngestFolder(tmp_path / "ingest")
|
||||
folder.path.mkdir()
|
||||
epub = tmp_path / "x.epub"
|
||||
epub.write_bytes(b"PK")
|
||||
path = folder.send_update(epub, 7, "T" * 300)
|
||||
@@ -206,3 +208,13 @@ def test_send_update_writes_sidecar(tmp_path):
|
||||
manifest = json.loads((folder.path / (path.name + ".cwa.json")).read_text())
|
||||
assert manifest == {"action": "add_format", "book_id": 7}
|
||||
assert path.read_bytes() == b"PK"
|
||||
|
||||
|
||||
def test_a_missing_ingest_folder_is_an_error_and_is_not_made(tmp_path, story_epub):
|
||||
source = tmp_path / "book.epub"
|
||||
source.write_bytes(story_epub)
|
||||
folder = IngestFolder(tmp_path / "missing" / "ingest")
|
||||
for send in (lambda: folder.ingest(source, "Title"), lambda: folder.send_update(source, 5, "Title")):
|
||||
with pytest.raises(IngestError, match="does not exist. Check INGEST_DIR and the volume mount."):
|
||||
send()
|
||||
assert not (tmp_path / "missing").exists()
|
||||
|
||||
@@ -35,6 +35,7 @@ def fake(story_epub, monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(config, "CWA_PASSWORD", "secret")
|
||||
monkeypatch.setattr(orchestrator, "store", Store(tmp_path / "bindery.db"))
|
||||
monkeypatch.setattr(config, "INGEST_DIR", tmp_path / "ingest")
|
||||
config.INGEST_DIR.mkdir()
|
||||
monkeypatch.setattr(orchestrator, "finder", orchestrator.BookFinder())
|
||||
yield server
|
||||
server.close()
|
||||
|
||||
@@ -21,6 +21,7 @@ from bindery.store import Store
|
||||
@pytest.fixture(autouse=True)
|
||||
def isolated(client, jobs, monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(config, "INGEST_DIR", tmp_path / "ingest")
|
||||
config.INGEST_DIR.mkdir()
|
||||
monkeypatch.setattr(config, "TMP_DIR", tmp_path / "tmp")
|
||||
config.TMP_DIR.mkdir()
|
||||
monkeypatch.setattr(config, "CWA_URL", "")
|
||||
@@ -151,7 +152,7 @@ def test_build_without_ingest_succeeds_and_keeps_the_book_local(monkeypatch, tmp
|
||||
assert story["status"] == "ok"
|
||||
assert story["ingested_at"] == ("2026-01-01" if existing else None)
|
||||
assert list(config.TMP_DIR.glob(f"story-{story_id}-*.epub"))
|
||||
assert not (tmp_path / "ingest").exists()
|
||||
assert not list((tmp_path / "ingest").iterdir())
|
||||
details = orchestrator.story_details(story_id)
|
||||
assert details["uploads_paused"] is True
|
||||
assert details["story"]["upload_held"] is True
|
||||
@@ -184,7 +185,7 @@ def test_linking_off_exports_books_and_respects_pause(monkeypatch, tmp_path, con
|
||||
assert orchestrator.store.get_setting(story_id, "upload_held") is None
|
||||
else:
|
||||
assert message.startswith("Not sent.")
|
||||
assert not config.INGEST_DIR.exists()
|
||||
assert not list(config.INGEST_DIR.iterdir())
|
||||
assert orchestrator.store.get_setting(story_id, "upload_held") is True
|
||||
|
||||
|
||||
@@ -221,7 +222,7 @@ def test_enabling_linking_restores_the_saved_approval_preference(library):
|
||||
assert orchestrator.get_settings()["hold_new_links"] is True
|
||||
assert orchestrator.story_details(story_id)["story"]["needs_approval"] is True
|
||||
assert "Approve link" in rebuild(story_id)
|
||||
assert not config.INGEST_DIR.exists()
|
||||
assert not list(config.INGEST_DIR.iterdir())
|
||||
|
||||
|
||||
def test_linking_off_exports_every_rebuild_without_an_id_or_approval():
|
||||
@@ -294,7 +295,7 @@ def test_paused_unlinked_update_does_not_raise_not_found(monkeypatch):
|
||||
story_id = add_story(ingested_at="2026-01-01")
|
||||
assert rebuild(story_id) == "Not sent. Turn off pause to send."
|
||||
assert orchestrator.store.get_story(story_id)["status"] == "ok"
|
||||
assert not config.INGEST_DIR.exists()
|
||||
assert not list(config.INGEST_DIR.iterdir())
|
||||
|
||||
|
||||
def test_enabling_linking_rebuilds_held_books_and_matches_again(monkeypatch, jobs, tmp_path):
|
||||
@@ -335,7 +336,7 @@ def test_upload_settings_matrix(client, link_books, hold_new_links, paused, save
|
||||
assert listed["upload_held"] is pending
|
||||
assert listed["approval_held"] is (link_books and hold_new_links and not paused)
|
||||
assert bool(orchestrator.store.get_setting(story_id, "upload_held")) is pending
|
||||
assert config.INGEST_DIR.exists() is (not pending)
|
||||
assert bool(list(config.INGEST_DIR.iterdir())) is (not pending)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("missing_ingest", [False, True])
|
||||
@@ -376,7 +377,7 @@ def test_resuming_uploads_does_not_bypass_approval(client, jobs, library):
|
||||
assert client.get("/api/stories").json[0]["approval_held"] is True
|
||||
assert "Approve link" in orchestrator._run_rebuild(jobs.get_next())
|
||||
assert orchestrator.store.get_setting(story_id, "upload_held") is True
|
||||
assert not config.INGEST_DIR.exists()
|
||||
assert not list(config.INGEST_DIR.iterdir())
|
||||
|
||||
|
||||
def test_missing_library_connection_is_not_an_approval_hold(client, monkeypatch):
|
||||
|
||||
Reference in New Issue
Block a user