- pipeline/video.py 재작성: subprocess.run 제거, httpx로 Windows /encode_video 호출 - Windows 서버 다운 시 즉시 VideoGenerationError (NAS 로컬 폴백 X — 의도적 결정) - /app/data/* → /volume1/docker/webpage/data/* 경로 변환 (_container_to_nas) - 테스트는 respx mock 기반으로 교체 (6개)
133 lines
5.2 KiB
Python
133 lines
5.2 KiB
Python
import os
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
from app.pipeline import video, thumb, storage
|
|
|
|
import respx
|
|
import httpx
|
|
from httpx import Response
|
|
|
|
|
|
@pytest.fixture
|
|
def tmp_storage(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(storage, "VIDEO_DATA_DIR", str(tmp_path))
|
|
# 더미 입력 파일들
|
|
audio = tmp_path / "audio.mp3"
|
|
audio.write_bytes(b"\x00" * 100)
|
|
cover_dir = tmp_path / "50"
|
|
cover_dir.mkdir()
|
|
cover = cover_dir / "cover.jpg"
|
|
cover.write_bytes(b"\x00" * 100)
|
|
return tmp_path
|
|
|
|
|
|
@patch("subprocess.run")
|
|
def test_thumb_extracts_frame(mock_run, tmp_storage):
|
|
mock_run.return_value = MagicMock(returncode=0, stderr="")
|
|
video_path = tmp_storage / "60" / "video.mp4"
|
|
video_path.parent.mkdir()
|
|
video_path.write_bytes(b"\x00" * 100)
|
|
out = thumb.generate(pipeline_id=60, video_path=str(video_path),
|
|
track_title="Midnight Drive", overlay_text=False)
|
|
assert out["url"].endswith("/60/thumbnail.jpg")
|
|
args = mock_run.call_args[0][0]
|
|
assert args[0] == "ffmpeg"
|
|
|
|
|
|
@patch("subprocess.run")
|
|
def test_thumb_failure_raises(mock_run, tmp_storage):
|
|
mock_run.return_value = MagicMock(returncode=1, stderr="bad input")
|
|
video_path = tmp_storage / "61" / "video.mp4"
|
|
video_path.parent.mkdir()
|
|
video_path.write_bytes(b"\x00" * 100)
|
|
with pytest.raises(thumb.ThumbGenerationError):
|
|
thumb.generate(pipeline_id=61, video_path=str(video_path),
|
|
track_title="X", overlay_text=False)
|
|
|
|
|
|
# ===== Video tests (replacing the FFmpeg-based tests) =====
|
|
|
|
@pytest.fixture
|
|
def encoder_env(monkeypatch):
|
|
monkeypatch.setattr(video, "ENCODER_URL", "http://192.168.45.59:8765")
|
|
monkeypatch.setattr(video, "NAS_VIDEOS_ROOT", "/volume1/docker/webpage/data/videos")
|
|
monkeypatch.setattr(video, "NAS_MUSIC_ROOT", "/volume1/docker/webpage/data/music")
|
|
|
|
|
|
@respx.mock
|
|
def test_generate_video_calls_remote_encoder(encoder_env, tmp_path, monkeypatch):
|
|
monkeypatch.setattr(storage, "VIDEO_DATA_DIR", str(tmp_path))
|
|
respx.post("http://192.168.45.59:8765/encode_video").mock(
|
|
return_value=Response(200, json={
|
|
"ok": True, "duration_ms": 12000,
|
|
"output_path_nas": "/volume1/docker/webpage/data/videos/3/video.mp4",
|
|
"output_bytes": 28000000,
|
|
"encoder": "h264_nvenc", "preset": "p4",
|
|
})
|
|
)
|
|
out = video.generate(
|
|
pipeline_id=3,
|
|
audio_path="/app/data/1c695df3.mp3",
|
|
cover_path="/app/data/videos/3/cover.jpg",
|
|
genre="lo-fi", duration_sec=120, resolution="1920x1080",
|
|
style="visualizer",
|
|
)
|
|
assert out["url"].endswith("/3/video.mp4")
|
|
assert out["used_fallback"] is False
|
|
assert out["encode_duration_ms"] == 12000
|
|
|
|
|
|
@respx.mock
|
|
def test_generate_video_raises_on_connection_error(encoder_env, monkeypatch, tmp_path):
|
|
monkeypatch.setattr(storage, "VIDEO_DATA_DIR", str(tmp_path))
|
|
respx.post("http://192.168.45.59:8765/encode_video").mock(
|
|
side_effect=httpx.ConnectError("Connection refused")
|
|
)
|
|
with pytest.raises(video.VideoGenerationError) as exc:
|
|
video.generate(
|
|
pipeline_id=4,
|
|
audio_path="/app/data/x.mp3", cover_path="/app/data/videos/4/cover.jpg",
|
|
genre="lo-fi", duration_sec=120, resolution="1920x1080",
|
|
)
|
|
assert "연결 실패" in str(exc.value) or "Connection" in str(exc.value)
|
|
|
|
|
|
@respx.mock
|
|
def test_generate_video_raises_on_500(encoder_env, monkeypatch, tmp_path):
|
|
monkeypatch.setattr(storage, "VIDEO_DATA_DIR", str(tmp_path))
|
|
respx.post("http://192.168.45.59:8765/encode_video").mock(
|
|
return_value=Response(500, json={"detail": {"ok": False, "stage": "ffmpeg", "error": "bad codec"}})
|
|
)
|
|
with pytest.raises(video.VideoGenerationError) as exc:
|
|
video.generate(
|
|
pipeline_id=5,
|
|
audio_path="/app/data/x.mp3", cover_path="/app/data/videos/5/cover.jpg",
|
|
genre="lo-fi", duration_sec=120, resolution="1920x1080",
|
|
)
|
|
assert "Windows 인코더 오류" in str(exc.value)
|
|
assert "ffmpeg" in str(exc.value)
|
|
|
|
|
|
def test_generate_video_no_url_configured(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(storage, "VIDEO_DATA_DIR", str(tmp_path))
|
|
monkeypatch.setattr(video, "ENCODER_URL", "")
|
|
with pytest.raises(video.VideoGenerationError) as exc:
|
|
video.generate(
|
|
pipeline_id=6,
|
|
audio_path="/app/data/x.mp3", cover_path="/app/data/videos/6/cover.jpg",
|
|
genre="lo-fi", duration_sec=120, resolution="1920x1080",
|
|
)
|
|
assert "WINDOWS_VIDEO_ENCODER_URL" in str(exc.value)
|
|
|
|
|
|
def test_container_to_nas_videos_path(monkeypatch):
|
|
monkeypatch.setattr(video, "NAS_VIDEOS_ROOT", "/volume1/docker/webpage/data/videos")
|
|
monkeypatch.setattr(video, "NAS_MUSIC_ROOT", "/volume1/docker/webpage/data/music")
|
|
assert video._container_to_nas("/app/data/videos/3/cover.jpg") == "/volume1/docker/webpage/data/videos/3/cover.jpg"
|
|
|
|
|
|
def test_container_to_nas_music_path(monkeypatch):
|
|
monkeypatch.setattr(video, "NAS_VIDEOS_ROOT", "/volume1/docker/webpage/data/videos")
|
|
monkeypatch.setattr(video, "NAS_MUSIC_ROOT", "/volume1/docker/webpage/data/music")
|
|
assert video._container_to_nas("/app/data/abc.mp3") == "/volume1/docker/webpage/data/music/abc.mp3"
|