from fastapi.testclient import TestClient import pytest # from main import app, ENGINE_REGISTRY # No longer needed, using fixtures import shutil import asyncio TEST_TEXT = "Dies ist ein NovaAi Test." # Ensure the engine being tested is in the default .env config ENGINE = "piper" MODEL = "de_DE-thorsten-high" FORMAT = "ogg" # === Happy Path Tests === @pytest.mark.asyncio async def test_tts_link_success(app_client): """Tests successful synthesis returning a URL.""" resp = app_client.post("/tts", json={ "text": TEST_TEXT, "engine": ENGINE, "model": MODEL, "format": FORMAT }) assert resp.status_code == 200 data = resp.json() assert "audio_url" in data # Also test the audio download endpoint audio_url = data['audio_url'] audio_resp = app_client.get(audio_url) assert audio_resp.status_code == 200 assert len(audio_resp.content) > 1000 @pytest.mark.asyncio async def test_tts_base64_success(app_client): """Tests successful synthesis returning base64 data.""" resp = app_client.post("/tts?as=true", json={ "text": TEST_TEXT, "engine": ENGINE, "model": MODEL, "format": FORMAT }) assert resp.status_code == 200 data = resp.json() assert "audio_base64" in data assert len(data["audio_base64"]) > 1000 @pytest.mark.asyncio async def test_health_and_engines_endpoints(app_client): """Tests the /health and /engines endpoints for correct responses.""" resp_engines = app_client.get("/engines") assert resp_engines.status_code == 200 engines = resp_engines.json() assert ENGINE in engines resp_health = app_client.get("/health") assert resp_health.status_code == 200 health = resp_health.json() assert health["status"][ENGINE] == "ok" # === Failure Path Tests === @pytest.mark.asyncio async def test_tts_fails_with_invalid_engine(app_client): """Tests that a request with a non-existent engine fails with HTTP 404.""" response = app_client.post("/tts", json={ "text": TEST_TEXT, "engine": "non_existent_engine", "model": MODEL, }) assert response.status_code == 404 assert "Engine 'non_existent_engine' not found" in response.json()["detail"] @pytest.mark.asyncio async def test_tts_fails_with_invalid_model(app_client): """Tests that a request with a non-existent model fails with HTTP 400.""" response = app_client.post("/tts", json={ "text": TEST_TEXT, "engine": ENGINE, "model": "non_existent_model", }) assert response.status_code == 400 assert "Model 'non_existent_model' not found" in response.json()["detail"] @pytest.mark.asyncio async def test_tts_fails_with_invalid_speaker(app_client, piper_engine): """Tests that a request with a non-existent speaker fails with HTTP 400.""" # This model has speakers, so requesting a non-existent one should fail. # We need to pick a model that is known to have multiple speakers # The default piper engine models should have this. model_with_speakers = "en_US-kristin-medium" # Example model with speakers # First, check if the model is actually available to test against if model_with_speakers not in piper_engine.list_models(): pytest.skip(f"Model '{model_with_speakers}' not available for '{ENGINE}' to test against.") response = app_client.post("/tts", json={ "text": "This is a test.", "engine": ENGINE, "model": model_with_speakers, "speaker": "non_existent_speaker" }) assert response.status_code == 400 assert "Speaker 'non_existent_speaker' not found" in response.json()["detail"] @pytest.mark.asyncio async def test_tts_fails_with_unavailable_engine(app_client, piper_engine, monkeypatch): """Tests that a request fails with HTTP 503 if an engine is unhealthy.""" # Simulate the piper executable being not found by monkeypatching the specific instance monkeypatch.setattr(piper_engine, "piper_executable", None) response = app_client.post("/tts", json={ "text": TEST_TEXT, "engine": ENGINE, "model": MODEL, }) assert response.status_code == 503 assert "is not available" in response.json()["detail"]