1. 新增内置TTS工具,支持OpenAI兼容语音合成服务 2. 新增ASR音频解析和视频解析处理器 3. 扩展文件解析支持范围,添加音视频格式支持 4. 新增ffmpeg音视频处理基础工具库 5. 补充相关单元测试用例
124 lines
4.6 KiB
Python
124 lines
4.6 KiB
Python
"""ASR 解析器单元测试。"""
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from yuxi.knowledge.parser.asr import ASRProcessor
|
|
from yuxi.knowledge.parser.base import DocumentProcessorException
|
|
|
|
|
|
@pytest.fixture
|
|
def asr():
|
|
return ASRProcessor()
|
|
|
|
|
|
class TestASRProcessorBasics:
|
|
def test_service_name(self, asr):
|
|
assert asr.get_service_name() == "asr"
|
|
|
|
def test_supported_extensions(self, asr):
|
|
exts = asr.get_supported_extensions()
|
|
assert ".mp3" in exts
|
|
assert ".wav" in exts
|
|
assert ".m4a" in exts
|
|
assert ".flac" in exts
|
|
|
|
def test_supports_file_type(self, asr):
|
|
assert asr.supports_file_type(".mp3")
|
|
assert asr.supports_file_type(".wav")
|
|
assert not asr.supports_file_type(".pdf")
|
|
|
|
|
|
class TestSegmentsToMarkdown:
|
|
def test_basic_formatting(self):
|
|
segments = [
|
|
{"start": 0, "end": 5, "text": "Hello world"},
|
|
{"start": 65, "end": 70, "text": "Second segment"},
|
|
]
|
|
result = ASRProcessor._segments_to_markdown(segments, "test.mp3")
|
|
assert "# 音频转写" in result
|
|
assert "> 来源: test.mp3" in result
|
|
assert "**[00:00]** Hello world" in result
|
|
assert "**[01:05]** Second segment" in result
|
|
|
|
def test_empty_segments(self):
|
|
result = ASRProcessor._segments_to_markdown([], "test.mp3")
|
|
assert "# 音频转写" in result
|
|
assert "> 来源: test.mp3" in result
|
|
|
|
def test_skip_empty_text(self):
|
|
segments = [
|
|
{"start": 0, "end": 5, "text": ""},
|
|
{"start": 5, "end": 10, "text": " "},
|
|
{"start": 10, "end": 15, "text": "valid"},
|
|
]
|
|
result = ASRProcessor._segments_to_markdown(segments, "test.mp3")
|
|
assert "valid" in result
|
|
assert result.count("**[") == 1
|
|
|
|
|
|
class TestProcessFile:
|
|
@patch("yuxi.knowledge.parser.asr.probe_audio_duration", return_value=60.0)
|
|
@patch("yuxi.knowledge.parser.asr.convert_to_wav")
|
|
@patch("yuxi.knowledge.parser.asr.ASRProcessor._transcribe")
|
|
def test_success(self, mock_transcribe, mock_convert, mock_duration, asr, tmp_path):
|
|
test_file = tmp_path / "test.mp3"
|
|
test_file.write_bytes(b"fake audio data")
|
|
mock_transcribe.return_value = [{"start": 0, "end": 5, "text": "Hello"}]
|
|
|
|
result = asr.process_file(str(test_file))
|
|
assert "# 音频转写" in result
|
|
assert "Hello" in result
|
|
|
|
@patch("yuxi.knowledge.parser.asr.probe_audio_duration", return_value=60.0)
|
|
def test_file_too_large(self, mock_duration, asr, tmp_path):
|
|
test_file = tmp_path / "big.mp3"
|
|
test_file.write_bytes(b"x" * (16 * 1024 * 1024 + 1))
|
|
|
|
with pytest.raises(DocumentProcessorException, match="大小"):
|
|
asr.process_file(str(test_file))
|
|
|
|
@patch("yuxi.knowledge.parser.asr.probe_audio_duration", return_value=1500.0)
|
|
def test_duration_too_long(self, mock_duration, asr, tmp_path):
|
|
test_file = tmp_path / "long.mp3"
|
|
test_file.write_bytes(b"fake audio")
|
|
|
|
with pytest.raises(DocumentProcessorException, match="时长"):
|
|
asr.process_file(str(test_file))
|
|
|
|
@patch("yuxi.knowledge.parser.asr.probe_audio_duration", return_value=60.0)
|
|
@patch("yuxi.knowledge.parser.asr.convert_to_wav")
|
|
def test_unsupported_asr_provider(self, mock_convert, mock_duration, asr, tmp_path):
|
|
test_file = tmp_path / "test.mp3"
|
|
test_file.write_bytes(b"fake audio")
|
|
|
|
with pytest.raises(DocumentProcessorException, match="不支持的 ASR 提供者"):
|
|
asr.process_file(str(test_file), {"asr_provider": "unknown"})
|
|
|
|
|
|
class TestCheckHealth:
|
|
@patch.dict(os.environ, {"ASR_PROVIDER": "whisper", "WHISPER_API_KEY": "test-key"})
|
|
def test_whisper_configured(self, asr):
|
|
result = asr.check_health()
|
|
assert result["status"] == "healthy"
|
|
|
|
@patch.dict(os.environ, {"ASR_PROVIDER": "whisper"}, clear=False)
|
|
def test_whisper_not_configured(self, asr):
|
|
with patch.dict(os.environ, {"WHISPER_API_KEY": ""}):
|
|
result = asr.check_health()
|
|
assert result["status"] == "unavailable"
|
|
|
|
@patch.dict(os.environ, {"ASR_PROVIDER": "funasr"})
|
|
@patch("yuxi.knowledge.parser.asr.httpx.Client")
|
|
def test_funasr_unreachable(self, mock_client_cls, asr):
|
|
mock_client = MagicMock()
|
|
mock_client.__enter__ = MagicMock(return_value=mock_client)
|
|
mock_client.__exit__ = MagicMock(return_value=False)
|
|
mock_client.get.side_effect = Exception("connection refused")
|
|
mock_client_cls.return_value = mock_client
|
|
|
|
result = asr.check_health()
|
|
assert result["status"] == "unhealthy"
|