import csv import io import tempfile import unittest from pathlib import Path from unittest import mock import apply_abs_mock_report as apply_report class ApplyAbsMockReportTests(unittest.TestCase): def write_report(self, path: Path, rows: list[dict[str, str]]) -> None: fieldnames = [ "verification_status", "verification_source", "verification_note", "status", "current_path", "audio_file_count", "sample_audio_file", "author", "author_confidence", "author_source", "series", "sequence", "publish_year", "title", "title_source", "narrator", "proposed_abs_path", "notes", ] with path.open("w", encoding="utf-8", newline="") as handle: writer = csv.DictWriter(handle, fieldnames=fieldnames, delimiter="\t") writer.writeheader() writer.writerows(rows) def create_book(self, root: Path, relative_path: str) -> Path: book_root = root / relative_path (book_root / "Disc 1").mkdir(parents=True) (book_root / "Disc 1" / "01.mp3").write_bytes(b"audio") (book_root / "cover.jpg").write_bytes(b"cover") return book_root def test_copy_mode_recreates_report_structure(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: tmp = Path(tmpdir) source_root = tmp / "source" destination_root = tmp / "destination" report_path = tmp / "report.tsv" book_root = self.create_book(source_root, "Old Author/Old Book") self.write_report( report_path, [ { "status": "ready", "current_path": str(book_root), "proposed_abs_path": "New Author/New Series/Vol. 01 - New Book", } ], ) exit_code = apply_report.main( [ "--report", str(report_path), "--destination-root", str(destination_root), "--mode", "copy", ] ) self.assertEqual(exit_code, 0) copied_root = destination_root / "New Author" / "New Series" / "Vol. 01 - New Book" self.assertTrue((copied_root / "Disc 1" / "01.mp3").exists()) self.assertTrue((copied_root / "cover.jpg").exists()) self.assertTrue((book_root / "Disc 1" / "01.mp3").exists()) def test_move_mode_removes_source_tree(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: tmp = Path(tmpdir) source_root = tmp / "source" destination_root = tmp / "destination" report_path = tmp / "report.tsv" book_root = self.create_book(source_root, "Old Author/Old Book") self.write_report( report_path, [ { "status": "ready", "current_path": str(book_root), "proposed_abs_path": "Author/Book", } ], ) exit_code = apply_report.main( [ "--report", str(report_path), "--destination-root", str(destination_root), "--mode", "move", ] ) self.assertEqual(exit_code, 0) moved_root = destination_root / "Author" / "Book" self.assertTrue((moved_root / "Disc 1" / "01.mp3").exists()) self.assertFalse(book_root.exists()) def test_dry_run_prints_plan_without_creating_destination(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: tmp = Path(tmpdir) source_root = tmp / "source" destination_root = tmp / "destination" report_path = tmp / "report.tsv" book_root = self.create_book(source_root, "Old Author/Old Book") self.write_report( report_path, [ { "status": "ready", "current_path": str(book_root), "proposed_abs_path": "Author/Book", } ], ) stdout = io.StringIO() with mock.patch("sys.stdout", stdout): exit_code = apply_report.main( [ "--report", str(report_path), "--destination-root", str(destination_root), "--dry-run", ] ) self.assertEqual(exit_code, 0) self.assertIn("plan\t", stdout.getvalue()) self.assertFalse(destination_root.exists()) def test_duplicate_targets_fail_validation(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: tmp = Path(tmpdir) source_root = tmp / "source" report_path = tmp / "report.tsv" first_book = self.create_book(source_root, "Author A/Book One") second_book = self.create_book(source_root, "Author B/Book Two") self.write_report( report_path, [ { "status": "ready", "current_path": str(first_book), "proposed_abs_path": "Author/Shared", }, { "status": "ready", "current_path": str(second_book), "proposed_abs_path": "Author/Shared", }, ], ) stderr = io.StringIO() with mock.patch("sys.stderr", stderr): exit_code = apply_report.main( [ "--report", str(report_path), "--destination-root", str(tmp / "destination"), ] ) self.assertEqual(exit_code, 1) self.assertIn("duplicate proposed_abs_path", stderr.getvalue()) def test_default_status_only_applies_ready_rows(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: tmp = Path(tmpdir) source_root = tmp / "source" destination_root = tmp / "destination" report_path = tmp / "report.tsv" ready_book = self.create_book(source_root, "Ready Author/Ready Book") review_book = self.create_book(source_root, "Review Author/Review Book") self.write_report( report_path, [ { "status": "ready", "current_path": str(ready_book), "proposed_abs_path": "Ready Author/Ready Book", }, { "status": "review", "current_path": str(review_book), "proposed_abs_path": "Review Author/Review Book", }, ], ) exit_code = apply_report.main( [ "--report", str(report_path), "--destination-root", str(destination_root), ] ) self.assertEqual(exit_code, 0) self.assertTrue((destination_root / "Ready Author" / "Ready Book").exists()) self.assertFalse((destination_root / "Review Author" / "Review Book").exists()) self.assertTrue(review_book.exists()) if __name__ == "__main__": unittest.main()