"""视频解析器单元测试。""" from unittest.mock import MagicMock, patch import pytest from yuxi.knowledge.parser.base import DocumentProcessorException from yuxi.knowledge.parser.ffmpeg import FFmpegError from yuxi.knowledge.parser.video import VideoProcessor @pytest.fixture def video(): return VideoProcessor() class TestVideoProcessorBasics: def test_service_name(self, video): assert video.get_service_name() == "video" def test_supported_extensions(self, video): exts = video.get_supported_extensions() assert ".mp4" in exts assert ".avi" in exts assert ".mov" in exts assert ".mkv" in exts def test_supports_file_type(self, video): assert video.supports_file_type(".mp4") assert not video.supports_file_type(".mp3") class TestProcessFile: @patch( "yuxi.knowledge.parser.video.VideoProcessor._process_keyframes", return_value="## 视频画面文字\n\n**[00:00]** text", ) @patch( "yuxi.knowledge.parser.video.VideoProcessor._process_audio_track", return_value="# 音频转写\n\n**[00:00]** hello", ) def test_success_both_tracks(self, mock_audio, mock_keyframes, video, tmp_path): test_file = tmp_path / "test.mp4" test_file.write_bytes(b"fake video data") result = video.process_file(str(test_file)) assert "音频转写" in result assert "视频画面文字" in result assert "---" in result @patch("yuxi.knowledge.parser.video.VideoProcessor._process_keyframes", return_value=None) @patch("yuxi.knowledge.parser.video.VideoProcessor._process_audio_track", return_value="# 音频转写") def test_audio_only(self, mock_audio, mock_keyframes, video, tmp_path): test_file = tmp_path / "test.mp4" test_file.write_bytes(b"fake video data") result = video.process_file(str(test_file)) assert "音频转写" in result @patch("yuxi.knowledge.parser.video.VideoProcessor._process_keyframes", return_value="## 视频画面文字") @patch("yuxi.knowledge.parser.video.VideoProcessor._process_audio_track", return_value=None) def test_keyframes_only(self, mock_audio, mock_keyframes, video, tmp_path): test_file = tmp_path / "test.mp4" test_file.write_bytes(b"fake video data") result = video.process_file(str(test_file)) assert "视频画面文字" in result @patch("yuxi.knowledge.parser.video.VideoProcessor._process_keyframes", return_value=None) @patch("yuxi.knowledge.parser.video.VideoProcessor._process_audio_track", return_value=None) def test_no_content_raises(self, mock_audio, mock_keyframes, video, tmp_path): test_file = tmp_path / "test.mp4" test_file.write_bytes(b"fake video data") with pytest.raises(DocumentProcessorException, match="未产生任何内容"): video.process_file(str(test_file)) def test_file_too_large(self, video, tmp_path): test_file = tmp_path / "big.mp4" test_file.write_bytes(b"x" * (16 * 1024 * 1024 + 1)) with pytest.raises(DocumentProcessorException, match="大小"): video.process_file(str(test_file)) class TestProcessAudioTrack: @patch("yuxi.knowledge.parser.factory.DocumentProcessorFactory") @patch("yuxi.knowledge.parser.video.extract_audio_from_video") def test_success(self, mock_extract, mock_factory, video): mock_factory.process_file.return_value = "# 音频转写" result = video._process_audio_track("video.mp4", {}) assert result == "# 音频转写" @patch("yuxi.knowledge.parser.video.extract_audio_from_video", side_effect=FFmpegError("no audio")) def test_no_audio_track_returns_none(self, mock_extract, video): result = video._process_audio_track("video.mp4", {}) assert result is None class TestProcessKeyframes: @patch("yuxi.knowledge.parser.video.extract_keyframes", return_value=[]) def test_no_frames(self, mock_extract, video): result = video._process_keyframes("video.mp4", {}) assert result is None def test_ocr_disabled(self, video): result = video._process_keyframes("video.mp4", {"ocr_engine": "disable"}) assert result is None @patch("yuxi.knowledge.parser.factory.DocumentProcessorFactory") @patch("yuxi.knowledge.parser.video.extract_keyframes") def test_with_frames(self, mock_extract, mock_factory, video, tmp_path): frame_path = str(tmp_path / "frame_0001.png") mock_extract.return_value = [(frame_path, 1.0)] mock_processor = MagicMock() mock_processor.process_file.return_value = "OCR text" mock_factory.get_processor.return_value = mock_processor result = video._process_keyframes("video.mp4", {}) assert result is not None assert "## 视频画面文字" in result assert "**[00:01]** OCR text" in result class TestCheckHealth: @patch("yuxi.knowledge.parser.ffmpeg.run_ffmpeg", side_effect=FFmpegError("not found")) def test_ffmpeg_unavailable(self, mock_ffmpeg, video): result = video.check_health() assert result["status"] == "unavailable"