"""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"