Files
labyricorn-site/tests/test_narration_import.py
Labyricorn 2fe6b689b0
Deploy production / deploy (push) Successful in 4s
Add blog and article narration import
2026-08-13 15:12:33 -07:00

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()