92 lines
3.8 KiB
Python
92 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
import uuid
|
|
from unittest import mock
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
|
|
|
from narration_import import NarrationImportError, import_wav, remove_narration # noqa: E402
|
|
|
|
|
|
class NarrationImportTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
cache_root = Path(__file__).resolve().parents[1] / ".cache"
|
|
cache_root.mkdir(exist_ok=True)
|
|
self.directory = cache_root / f"narration-import-test-{uuid.uuid4().hex}"
|
|
self.directory.mkdir()
|
|
self.source = self.directory / "source.wav"
|
|
self.source.write_bytes(b"disposable wav data")
|
|
|
|
def tearDown(self) -> None:
|
|
shutil.rmtree(self.directory)
|
|
|
|
def test_success_uses_fixed_preset_and_replaces_only_after_temp_output(self) -> None:
|
|
destination = self.directory / "narration.mp3"
|
|
destination.write_bytes(b"old narration")
|
|
|
|
def successful_ffmpeg(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
Path(command[-1]).write_bytes(b"converted mp3")
|
|
return subprocess.CompletedProcess(command, 0, "", "")
|
|
|
|
with mock.patch("narration_import.subprocess.run", side_effect=successful_ffmpeg) as run:
|
|
result = import_wav(self.directory, self.source, ffmpeg="ffmpeg")
|
|
|
|
self.assertEqual(result, destination)
|
|
self.assertEqual(destination.read_bytes(), b"converted mp3")
|
|
command = run.call_args.args[0]
|
|
self.assertEqual(
|
|
command[1:-1],
|
|
["-y", "-i", str(self.source), "-vn", "-ar", "44100", "-ac", "1",
|
|
"-c:a", "libmp3lame", "-b:a", "96k"],
|
|
)
|
|
self.assertFalse(any(self.directory.glob(".narration-*.mp3")))
|
|
|
|
def test_failed_conversion_preserves_existing_narration(self) -> None:
|
|
destination = self.directory / "narration.mp3"
|
|
destination.write_bytes(b"old narration")
|
|
failed = subprocess.CompletedProcess(["ffmpeg"], 1, "", "bad WAV")
|
|
|
|
with mock.patch("narration_import.subprocess.run", return_value=failed):
|
|
with self.assertRaisesRegex(NarrationImportError, "bad WAV"):
|
|
import_wav(self.directory, self.source, ffmpeg="ffmpeg")
|
|
|
|
self.assertEqual(destination.read_bytes(), b"old narration")
|
|
self.assertFalse(any(self.directory.glob(".narration-*.mp3")))
|
|
|
|
def test_missing_ffmpeg_and_removal_are_safe(self) -> None:
|
|
with mock.patch("narration_import.shutil.which", return_value=None):
|
|
with self.assertRaisesRegex(NarrationImportError, "ffmpeg is required"):
|
|
import_wav(self.directory, self.source)
|
|
unrelated = self.directory / "other-attachment.txt"
|
|
unrelated.write_text("keep", encoding="utf-8")
|
|
narration = self.directory / "narration.mp3"
|
|
narration.write_bytes(b"narration")
|
|
self.assertTrue(remove_narration(self.directory))
|
|
self.assertFalse(narration.exists())
|
|
self.assertEqual(unrelated.read_text(encoding="utf-8"), "keep")
|
|
|
|
def test_repository_fallback_is_used_when_ffmpeg_is_not_on_path(self) -> None:
|
|
fallback = self.directory / "ffmpeg.exe"
|
|
fallback.write_bytes(b"placeholder executable")
|
|
|
|
def successful_ffmpeg(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
|
|
Path(command[-1]).write_bytes(b"converted mp3")
|
|
return subprocess.CompletedProcess(command, 0, "", "")
|
|
|
|
with mock.patch("narration_import.shutil.which", return_value=None), mock.patch(
|
|
"narration_import.subprocess.run", side_effect=successful_ffmpeg
|
|
) as run:
|
|
import_wav(self.directory, self.source, fallback_ffmpeg=fallback)
|
|
|
|
self.assertEqual(run.call_args.args[0][0], str(fallback))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|