231 lines
8.0 KiB
Python
231 lines
8.0 KiB
Python
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()
|