Mostly automatic: empty f-strings, unused imports, formatting. The manual half was duplicated import blocks stranded mid-file and `== None` assertions, which become `is None` here because they compare plain attributes -- unlike the identical rule in filters/book.py, where they build SQL. Two unused variables are the tests never checking the disk in either delete_files direction. Left in place under a noqa so the gap stays visible.
85 lines
2.6 KiB
Python
85 lines
2.6 KiB
Python
"""Tests for BookPathGenerator."""
|
|
|
|
from pathlib import Path
|
|
|
|
from chitai.services.filesystem_library import (
|
|
BookPathGenerator,
|
|
sanitize_path_component,
|
|
)
|
|
|
|
|
|
ROOT = Path("/library")
|
|
|
|
|
|
def path_for(**book) -> Path:
|
|
return BookPathGenerator(ROOT).generate_path(book)
|
|
|
|
|
|
def test_author_and_title() -> None:
|
|
assert path_for(title="Dune", authors=["Frank Herbert"]) == (
|
|
ROOT / "Frank Herbert" / "Dune"
|
|
)
|
|
|
|
|
|
def test_a_book_with_no_authors() -> None:
|
|
assert path_for(title="Beowulf", authors=[]) == ROOT / "Unknown" / "Beowulf"
|
|
|
|
|
|
def test_a_series_adds_a_level_and_pads_the_position() -> None:
|
|
assert (
|
|
path_for(
|
|
title="Persepolis Rising",
|
|
authors=["James S. A. Corey"],
|
|
series="The Expanse",
|
|
series_position="7",
|
|
)
|
|
== ROOT / "James S. A. Corey" / "The Expanse" / "07 - Persepolis Rising"
|
|
)
|
|
|
|
|
|
def test_a_slash_in_a_title_does_not_add_a_directory() -> None:
|
|
"""
|
|
The separators in the path come from the template, never from the metadata.
|
|
|
|
A title with a slash in it — "AC/DC", "Him/Her" — would otherwise put the book one
|
|
level below where `book.path` says it is, which is what deletes, moves and file
|
|
lookups all act on. Calibre keeps the real title in its database and strips this
|
|
from its own directory names, so an import is where they surface.
|
|
"""
|
|
generated = path_for(title="Back in Black: AC/DC", authors=["Murray Engleheart"])
|
|
|
|
assert generated == ROOT / "Murray Engleheart" / "Back in Black: AC_DC"
|
|
assert generated.relative_to(ROOT).parts == (
|
|
"Murray Engleheart",
|
|
"Back in Black: AC_DC",
|
|
)
|
|
|
|
|
|
def test_a_slash_in_an_author_or_series_is_handled_too() -> None:
|
|
assert path_for(title="Split", authors=["A/B Collective"]) == (
|
|
ROOT / "A_B Collective" / "Split"
|
|
)
|
|
assert (
|
|
path_for(
|
|
title="Volume One",
|
|
authors=["Someone"],
|
|
series="Either/Or",
|
|
series_position="1",
|
|
)
|
|
== ROOT / "Someone" / "Either_Or" / "01 - Volume One"
|
|
)
|
|
|
|
|
|
def test_control_characters_are_removed() -> None:
|
|
assert path_for(title="Line\nBreak", authors=["Someone"]) == (
|
|
ROOT / "Someone" / "Line_Break"
|
|
)
|
|
|
|
|
|
def test_sanitize_path_component() -> None:
|
|
assert sanitize_path_component("AC/DC") == "AC_DC"
|
|
assert sanitize_path_component("back\\slash") == "back_slash"
|
|
assert sanitize_path_component(" padded ") == "padded"
|
|
# Colons and other punctuation are legal in a path and are left alone.
|
|
assert sanitize_path_component("Title: Subtitle") == "Title: Subtitle"
|