test: update Phase 2 tests for ASR provider abstraction
Update TestTranscribeFull to use async/await and patch the moved OpenAI import (now in asr_providers.py). Set ASR_PROVIDER=dashscope in test fixtures to ensure tests don't pick up the real .env ASR_PROVIDER value. All 19 Phase 2 + 7 integration tests pass. Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
733824c177
commit
6928fff8ff
|
|
@ -27,6 +27,7 @@ def video_client(tmp_path, monkeypatch):
|
||||||
upload_dir.mkdir()
|
upload_dir.mkdir()
|
||||||
monkeypatch.setenv("VIDEO_UPLOAD_DIR", str(upload_dir))
|
monkeypatch.setenv("VIDEO_UPLOAD_DIR", str(upload_dir))
|
||||||
monkeypatch.setenv("MAX_VIDEO_SIZE_MB", "50")
|
monkeypatch.setenv("MAX_VIDEO_SIZE_MB", "50")
|
||||||
|
monkeypatch.setenv("ASR_PROVIDER", "dashscope")
|
||||||
monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test-key")
|
monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test-key")
|
||||||
|
|
||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
|
|
@ -49,7 +50,7 @@ def _upload_video(client, filename="test.mp4", content=b"\x00" * 1024):
|
||||||
class TestUploadTranscribeFlow:
|
class TestUploadTranscribeFlow:
|
||||||
"""Full upload → transcribe with mocked ASR and real file I/O."""
|
"""Full upload → transcribe with mocked ASR and real file I/O."""
|
||||||
|
|
||||||
@patch("app.services.asr_client.OpenAI")
|
@patch("app.services.asr_providers.OpenAI")
|
||||||
@patch("app.services.video_service.asyncio.create_subprocess_exec")
|
@patch("app.services.video_service.asyncio.create_subprocess_exec")
|
||||||
def test_upload_then_transcribe(self, mock_subprocess, mock_openai_cls, video_client):
|
def test_upload_then_transcribe(self, mock_subprocess, mock_openai_cls, video_client):
|
||||||
"""Upload video → extract audio (mocked ffmpeg) → transcribe (mocked ASR) → verify response."""
|
"""Upload video → extract audio (mocked ffmpeg) → transcribe (mocked ASR) → verify response."""
|
||||||
|
|
@ -93,7 +94,7 @@ class TestUploadTranscribeFlow:
|
||||||
wav_path = upload_dir / f"{video_id}_audio.wav"
|
wav_path = upload_dir / f"{video_id}_audio.wav"
|
||||||
assert not wav_path.exists(), "Temp WAV file should be cleaned up after transcription"
|
assert not wav_path.exists(), "Temp WAV file should be cleaned up after transcription"
|
||||||
|
|
||||||
@patch("app.services.asr_client.OpenAI")
|
@patch("app.services.asr_providers.OpenAI")
|
||||||
@patch("app.services.video_service.asyncio.create_subprocess_exec")
|
@patch("app.services.video_service.asyncio.create_subprocess_exec")
|
||||||
def test_upload_transcribe_custom_language(self, mock_subprocess, mock_openai_cls, video_client):
|
def test_upload_transcribe_custom_language(self, mock_subprocess, mock_openai_cls, video_client):
|
||||||
"""Transcribe with language=en should pass it through."""
|
"""Transcribe with language=en should pass it through."""
|
||||||
|
|
|
||||||
|
|
@ -123,10 +123,12 @@ class TestToTraditional:
|
||||||
|
|
||||||
|
|
||||||
class TestTranscribeFull:
|
class TestTranscribeFull:
|
||||||
def test_returns_traditional_chinese_text(self, monkeypatch):
|
@pytest.mark.asyncio
|
||||||
|
async def test_returns_traditional_chinese_text(self, monkeypatch):
|
||||||
from app.services.asr_client import ASRClient
|
from app.services.asr_client import ASRClient
|
||||||
|
|
||||||
settings = MagicMock()
|
settings = MagicMock()
|
||||||
|
settings.asr_provider = "dashscope"
|
||||||
settings.dashscope_api_key = "sk-test-key"
|
settings.dashscope_api_key = "sk-test-key"
|
||||||
settings.asr_model_name = "qwen3-asr-flash"
|
settings.asr_model_name = "qwen3-asr-flash"
|
||||||
|
|
||||||
|
|
@ -139,8 +141,8 @@ class TestTranscribeFull:
|
||||||
mock_openai_client = MagicMock()
|
mock_openai_client = MagicMock()
|
||||||
mock_openai_client.chat.completions.create.return_value = mock_resp
|
mock_openai_client.chat.completions.create.return_value = mock_resp
|
||||||
|
|
||||||
with patch("app.services.asr_client.OpenAI", return_value=mock_openai_client):
|
with patch("app.services.asr_providers.OpenAI", return_value=mock_openai_client):
|
||||||
result = client.transcribe_full(b"fake-audio-bytes", language="yue")
|
result = await client.transcribe_full(b"fake-audio-bytes", language="yue")
|
||||||
|
|
||||||
assert result == "測試結果"
|
assert result == "測試結果"
|
||||||
mock_openai_client.chat.completions.create.assert_called_once()
|
mock_openai_client.chat.completions.create.assert_called_once()
|
||||||
|
|
@ -148,10 +150,12 @@ class TestTranscribeFull:
|
||||||
assert call_kwargs.kwargs["model"] == "qwen3-asr-flash"
|
assert call_kwargs.kwargs["model"] == "qwen3-asr-flash"
|
||||||
assert call_kwargs.kwargs["extra_body"]["asr_options"]["language"] == "yue"
|
assert call_kwargs.kwargs["extra_body"]["asr_options"]["language"] == "yue"
|
||||||
|
|
||||||
def test_uses_correct_api_endpoint(self, monkeypatch):
|
@pytest.mark.asyncio
|
||||||
|
async def test_uses_correct_api_endpoint(self, monkeypatch):
|
||||||
from app.services.asr_client import ASRClient
|
from app.services.asr_client import ASRClient
|
||||||
|
|
||||||
settings = MagicMock()
|
settings = MagicMock()
|
||||||
|
settings.asr_provider = "dashscope"
|
||||||
settings.dashscope_api_key = "sk-test-key"
|
settings.dashscope_api_key = "sk-test-key"
|
||||||
settings.asr_model_name = "qwen3-asr-flash"
|
settings.asr_model_name = "qwen3-asr-flash"
|
||||||
|
|
||||||
|
|
@ -164,17 +168,19 @@ class TestTranscribeFull:
|
||||||
mock_openai_client = MagicMock()
|
mock_openai_client = MagicMock()
|
||||||
mock_openai_client.chat.completions.create.return_value = mock_resp
|
mock_openai_client.chat.completions.create.return_value = mock_resp
|
||||||
|
|
||||||
with patch("app.services.asr_client.OpenAI", return_value=mock_openai_client) as mock_openai_cls:
|
with patch("app.services.asr_providers.OpenAI", return_value=mock_openai_client) as mock_openai_cls:
|
||||||
client.transcribe_full(b"audio", language="yue")
|
await client.transcribe_full(b"audio", language="yue")
|
||||||
mock_openai_cls.assert_called_once_with(
|
mock_openai_cls.assert_called_once_with(
|
||||||
api_key="sk-test-key",
|
api_key="sk-test-key",
|
||||||
base_url="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
base_url="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_auto_language_omits_language_param(self, monkeypatch):
|
@pytest.mark.asyncio
|
||||||
|
async def test_auto_language_omits_language_param(self, monkeypatch):
|
||||||
from app.services.asr_client import ASRClient
|
from app.services.asr_client import ASRClient
|
||||||
|
|
||||||
settings = MagicMock()
|
settings = MagicMock()
|
||||||
|
settings.asr_provider = "dashscope"
|
||||||
settings.dashscope_api_key = "sk-test-key"
|
settings.dashscope_api_key = "sk-test-key"
|
||||||
settings.asr_model_name = "qwen3-asr-flash"
|
settings.asr_model_name = "qwen3-asr-flash"
|
||||||
|
|
||||||
|
|
@ -187,8 +193,8 @@ class TestTranscribeFull:
|
||||||
mock_openai_client = MagicMock()
|
mock_openai_client = MagicMock()
|
||||||
mock_openai_client.chat.completions.create.return_value = mock_resp
|
mock_openai_client.chat.completions.create.return_value = mock_resp
|
||||||
|
|
||||||
with patch("app.services.asr_client.OpenAI", return_value=mock_openai_client):
|
with patch("app.services.asr_providers.OpenAI", return_value=mock_openai_client):
|
||||||
client.transcribe_full(b"audio", language="auto")
|
await client.transcribe_full(b"audio", language="auto")
|
||||||
|
|
||||||
call_kwargs = mock_openai_client.chat.completions.create.call_args
|
call_kwargs = mock_openai_client.chat.completions.create.call_args
|
||||||
assert call_kwargs.kwargs.get("extra_body") is None
|
assert call_kwargs.kwargs.get("extra_body") is None
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ def video_client(tmp_path, monkeypatch):
|
||||||
upload_dir.mkdir()
|
upload_dir.mkdir()
|
||||||
monkeypatch.setenv("VIDEO_UPLOAD_DIR", str(upload_dir))
|
monkeypatch.setenv("VIDEO_UPLOAD_DIR", str(upload_dir))
|
||||||
monkeypatch.setenv("MAX_VIDEO_SIZE_MB", "50")
|
monkeypatch.setenv("MAX_VIDEO_SIZE_MB", "50")
|
||||||
|
monkeypatch.setenv("ASR_PROVIDER", "dashscope")
|
||||||
monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test-key")
|
monkeypatch.setenv("DASHSCOPE_API_KEY", "sk-test-key")
|
||||||
|
|
||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
|
|
@ -44,7 +45,7 @@ def _upload_video(client, filename="test.mp4", content=b"\x00" * 1024):
|
||||||
|
|
||||||
class TestTranscribeSuccess:
|
class TestTranscribeSuccess:
|
||||||
@patch("app.routers.video.VideoService.extract_audio")
|
@patch("app.routers.video.VideoService.extract_audio")
|
||||||
@patch("app.services.asr_client.OpenAI")
|
@patch("app.services.asr_providers.OpenAI")
|
||||||
def test_transcribe_returns_response(self, mock_openai_cls, mock_extract, video_client):
|
def test_transcribe_returns_response(self, mock_openai_cls, mock_extract, video_client):
|
||||||
"""POST transcribe should return FullTranscriptResponse."""
|
"""POST transcribe should return FullTranscriptResponse."""
|
||||||
client, upload_dir = video_client
|
client, upload_dir = video_client
|
||||||
|
|
@ -74,7 +75,7 @@ class TestTranscribeSuccess:
|
||||||
assert "測" in data["text"] or "試" in data["text"]
|
assert "測" in data["text"] or "試" in data["text"]
|
||||||
|
|
||||||
@patch("app.routers.video.VideoService.extract_audio")
|
@patch("app.routers.video.VideoService.extract_audio")
|
||||||
@patch("app.services.asr_client.OpenAI")
|
@patch("app.services.asr_providers.OpenAI")
|
||||||
def test_transcribe_custom_language(self, mock_openai_cls, mock_extract, video_client):
|
def test_transcribe_custom_language(self, mock_openai_cls, mock_extract, video_client):
|
||||||
"""POST transcribe with language param should pass it through."""
|
"""POST transcribe with language param should pass it through."""
|
||||||
client, upload_dir = video_client
|
client, upload_dir = video_client
|
||||||
|
|
@ -169,6 +170,7 @@ class TestTranscribeMissingApiKey:
|
||||||
upload_dir.mkdir()
|
upload_dir.mkdir()
|
||||||
monkeypatch.setenv("VIDEO_UPLOAD_DIR", str(upload_dir))
|
monkeypatch.setenv("VIDEO_UPLOAD_DIR", str(upload_dir))
|
||||||
monkeypatch.setenv("MAX_VIDEO_SIZE_MB", "50")
|
monkeypatch.setenv("MAX_VIDEO_SIZE_MB", "50")
|
||||||
|
monkeypatch.setenv("ASR_PROVIDER", "dashscope")
|
||||||
monkeypatch.setenv("DASHSCOPE_API_KEY", "")
|
monkeypatch.setenv("DASHSCOPE_API_KEY", "")
|
||||||
|
|
||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue