- Add POST /api/tts endpoint with full request validation - Validate text, language code, speed range, and roles list - Integrate TTSService to generate multi-voice audiobooks - Fix Kokoro pipeline generator handling to properly collect audio - Stream audio response with correct Content-Type (audio/wav) - Add Content-Disposition header with filename (audiobook.wav) - Handle errors gracefully with appropriate HTTP status codes - Add comprehensive test suite for endpoint validation Requirements: 6.1, 6.7, 6.8, 9.5
175 lines
5.6 KiB
Python
175 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Test script for audio generation endpoint."""
|
|
import urllib.request
|
|
import json
|
|
import sys
|
|
|
|
BASE_URL = 'http://localhost:8001'
|
|
|
|
def test_generation_endpoint_validation():
|
|
"""Test validation of generation endpoint."""
|
|
print("Testing audio generation endpoint validation...")
|
|
|
|
# Test 1: Missing text field
|
|
print("\n1. Testing empty text validation...")
|
|
try:
|
|
data = json.dumps({
|
|
"text": "",
|
|
"lang": "a",
|
|
"roles": [{"id": "Narrator", "voice": "af_bella"}],
|
|
"speed": 1.0
|
|
}).encode('utf-8')
|
|
|
|
req = urllib.request.Request(
|
|
f'{BASE_URL}/api/tts',
|
|
data=data,
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
urllib.request.urlopen(req)
|
|
print("✗ Should have rejected empty text")
|
|
return False
|
|
except urllib.error.HTTPError as e:
|
|
if e.code == 400:
|
|
error_data = json.loads(e.read().decode())
|
|
print(f"✓ Correctly rejected empty text: {error_data['message']}")
|
|
else:
|
|
print(f"✗ Unexpected error code: {e.code}")
|
|
return False
|
|
|
|
# Test 2: Invalid language code
|
|
print("\n2. Testing invalid language code...")
|
|
try:
|
|
data = json.dumps({
|
|
"text": "Hello world",
|
|
"lang": "invalid",
|
|
"roles": [{"id": "Narrator", "voice": "af_bella"}],
|
|
"speed": 1.0
|
|
}).encode('utf-8')
|
|
|
|
req = urllib.request.Request(
|
|
f'{BASE_URL}/api/tts',
|
|
data=data,
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
urllib.request.urlopen(req)
|
|
print("✗ Should have rejected invalid language code")
|
|
return False
|
|
except urllib.error.HTTPError as e:
|
|
if e.code == 400:
|
|
error_data = json.loads(e.read().decode())
|
|
print(f"✓ Correctly rejected invalid language: {error_data['message']}")
|
|
else:
|
|
print(f"✗ Unexpected error code: {e.code}")
|
|
return False
|
|
|
|
# Test 3: Invalid speed value
|
|
print("\n3. Testing invalid speed value...")
|
|
try:
|
|
data = json.dumps({
|
|
"text": "Hello world",
|
|
"lang": "a",
|
|
"roles": [{"id": "Narrator", "voice": "af_bella"}],
|
|
"speed": 2.0 # Out of range
|
|
}).encode('utf-8')
|
|
|
|
req = urllib.request.Request(
|
|
f'{BASE_URL}/api/tts',
|
|
data=data,
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
urllib.request.urlopen(req)
|
|
print("✗ Should have rejected invalid speed")
|
|
return False
|
|
except urllib.error.HTTPError as e:
|
|
if e.code == 400:
|
|
error_data = json.loads(e.read().decode())
|
|
print(f"✓ Correctly rejected invalid speed: {error_data['message']}")
|
|
else:
|
|
print(f"✗ Unexpected error code: {e.code}")
|
|
return False
|
|
|
|
# Test 4: Empty roles list
|
|
print("\n4. Testing empty roles list...")
|
|
try:
|
|
data = json.dumps({
|
|
"text": "Hello world",
|
|
"lang": "a",
|
|
"roles": [],
|
|
"speed": 1.0
|
|
}).encode('utf-8')
|
|
|
|
req = urllib.request.Request(
|
|
f'{BASE_URL}/api/tts',
|
|
data=data,
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
urllib.request.urlopen(req)
|
|
print("✗ Should have rejected empty roles list")
|
|
return False
|
|
except urllib.error.HTTPError as e:
|
|
if e.code == 400:
|
|
error_data = json.loads(e.read().decode())
|
|
print(f"✓ Correctly rejected empty roles: {error_data['message']}")
|
|
else:
|
|
print(f"✗ Unexpected error code: {e.code}")
|
|
return False
|
|
|
|
# Test 5: Valid request with simple text
|
|
print("\n5. Testing valid request...")
|
|
try:
|
|
data = json.dumps({
|
|
"text": "Hello, this is a test.",
|
|
"lang": "a",
|
|
"roles": [{"id": "Narrator", "voice": "af_bella"}],
|
|
"speed": 1.0
|
|
}).encode('utf-8')
|
|
|
|
req = urllib.request.Request(
|
|
f'{BASE_URL}/api/tts',
|
|
data=data,
|
|
headers={'Content-Type': 'application/json'}
|
|
)
|
|
response = urllib.request.urlopen(req)
|
|
|
|
# Check Content-Type header
|
|
content_type = response.headers.get('Content-Type')
|
|
if content_type != 'audio/wav':
|
|
print(f"✗ Wrong Content-Type: {content_type}")
|
|
return False
|
|
print(f"✓ Correct Content-Type: {content_type}")
|
|
|
|
# Check Content-Disposition header
|
|
content_disposition = response.headers.get('Content-Disposition')
|
|
if 'audiobook.wav' not in content_disposition:
|
|
print(f"✗ Wrong Content-Disposition: {content_disposition}")
|
|
return False
|
|
print(f"✓ Correct Content-Disposition: {content_disposition}")
|
|
|
|
# Check that we got audio data
|
|
audio_data = response.read()
|
|
if len(audio_data) == 0:
|
|
print("✗ No audio data received")
|
|
return False
|
|
print(f"✓ Received audio data: {len(audio_data)} bytes")
|
|
|
|
except Exception as e:
|
|
print(f"✗ Valid request failed: {e}")
|
|
return False
|
|
|
|
return True
|
|
|
|
if __name__ == '__main__':
|
|
print("=" * 60)
|
|
print("Audio Generation Endpoint Tests")
|
|
print("=" * 60)
|
|
|
|
success = test_generation_endpoint_validation()
|
|
|
|
print("\n" + "=" * 60)
|
|
if success:
|
|
print("✓ All tests passed!")
|
|
sys.exit(0)
|
|
else:
|
|
print("✗ Some tests failed")
|
|
sys.exit(1)
|