feat: Add OpenAI-compatible TTS endpoint and engines
- Implements POST /v1/audio/speech endpoint (OpenAI API compatible). - Integrates Kokoro and XTTS engines (including dependencies and implementations). - Updates main application to register new engines and router. - Adds unit tests for OpenAI compatibility. - Updates requirements.txt for new engines.
This commit is contained in:
74
tests/test_openai_api.py
Normal file
74
tests/test_openai_api.py
Normal file
@ -0,0 +1,74 @@
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from app.routers.openai_compatible import OpenAISpeechRequest
|
||||
|
||||
def test_openai_speech_endpoint_success(app_client, app_instance, tmp_path):
|
||||
# 1. Setup Mock Engine
|
||||
mock_engine = MagicMock()
|
||||
# Mock synthesis to return a dummy file path
|
||||
dummy_file = tmp_path / "test_output.wav"
|
||||
dummy_file.write_bytes(b"fake audio data")
|
||||
mock_engine.synthesize = AsyncMock(return_value=str(dummy_file))
|
||||
|
||||
# Inject mock engine into registry
|
||||
app_instance.ENGINE_REGISTRY["mock-engine"] = mock_engine
|
||||
|
||||
# 2. Make Request
|
||||
payload = {
|
||||
"model": "mock-engine",
|
||||
"input": "Hello OpenAI",
|
||||
"voice": "default",
|
||||
"response_format": "wav"
|
||||
}
|
||||
response = app_client.post("/v1/audio/speech", json=payload)
|
||||
|
||||
# 3. Assertions
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "audio/wav"
|
||||
assert response.content == b"fake audio data"
|
||||
|
||||
# Verify engine call
|
||||
mock_engine.synthesize.assert_called_once_with(
|
||||
text="Hello OpenAI",
|
||||
speaker="default",
|
||||
model=None,
|
||||
fmt="wav"
|
||||
)
|
||||
|
||||
def test_openai_speech_endpoint_engine_not_found(app_client):
|
||||
payload = {
|
||||
"model": "non-existent-engine",
|
||||
"input": "Test",
|
||||
"voice": "default"
|
||||
}
|
||||
response = app_client.post("/v1/audio/speech", json=payload)
|
||||
assert response.status_code == 404
|
||||
assert "not found" in response.json()["detail"]
|
||||
|
||||
def test_openai_speech_endpoint_tts_1_mapping(app_client, app_instance, tmp_path):
|
||||
# Setup Mock Engine for 'kokoro' (simulating it's active)
|
||||
mock_engine = MagicMock()
|
||||
dummy_file = tmp_path / "tts1_output.mp3"
|
||||
dummy_file.write_bytes(b"mp3 data")
|
||||
mock_engine.synthesize = AsyncMock(return_value=str(dummy_file))
|
||||
|
||||
# Inject into registry
|
||||
app_instance.ENGINE_REGISTRY["kokoro"] = mock_engine
|
||||
|
||||
# Request 'tts-1' -> should map to 'kokoro'
|
||||
payload = {
|
||||
"model": "tts-1",
|
||||
"input": "Mapping test",
|
||||
"voice": "alloy",
|
||||
"response_format": "mp3"
|
||||
}
|
||||
response = app_client.post("/v1/audio/speech", json=payload)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "audio/mpeg"
|
||||
|
||||
mock_engine.synthesize.assert_called_once()
|
||||
# Check that speaker passed through
|
||||
args, kwargs = mock_engine.synthesize.call_args
|
||||
assert kwargs["speaker"] == "alloy"
|
||||
Reference in New Issue
Block a user