From d3e7ed7f065b634721132ebdd10990df2455b471 Mon Sep 17 00:00:00 2001 From: sascha Date: Thu, 13 Aug 2026 12:58:56 +0200 Subject: [PATCH] feat: add voiceclone support (tests/test_app.py) --- tests/test_app.py | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/tests/test_app.py b/tests/test_app.py index e9960d4..3a3be69 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -45,6 +45,110 @@ def test_health_exposes_current_version(): assert response.json()["version"] == app.VERSION == "2.3.5" +def test_tts_generate_returns_cloned_wav(monkeypatch): + captured = {} + + class FakeResponse: + status_code = 200 + content = b"RIFF" + b"test-wave" + + class FakeClient: + def __init__(self, **_kwargs): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *_args): + return False + + async def post(self, url, json): + captured["url"] = url + captured["json"] = json + return FakeResponse() + + monkeypatch.setattr(app.httpx, "AsyncClient", FakeClient) + with TestClient(app.app) as client: + response = client.post( + "/tts/generate", + headers={"Authorization": "Bearer test-token"}, + json={"text": "Hallo Sascha", "voice": "deep_thought.mp3", "language": "de"}, + ) + assert response.status_code == 200 + assert response.headers["content-type"].startswith("audio/wav") + assert response.content.startswith(b"RIFF") + assert captured["json"]["voice_mode"] == "clone" + assert captured["json"]["reference_audio_filename"] == "deep_thought.mp3" + + +def test_tts_generate_validates_text_and_voice_before_backend(monkeypatch): + monkeypatch.setattr( + app.httpx, + "AsyncClient", + lambda **_kwargs: (_ for _ in ()).throw(AssertionError("backend must not be called")), + ) + with TestClient(app.app) as client: + empty = client.post( + "/tts/generate", + headers={"Authorization": "Bearer test-token"}, + json={"text": ""}, + ) + traversal = client.post( + "/tts/generate", + headers={"Authorization": "Bearer test-token"}, + json={"text": "Hallo", "voice": "../secret.wav"}, + ) + assert empty.status_code == 422 + assert traversal.status_code == 422 + + +def test_tts_generate_rejects_non_wav_backend_response(monkeypatch): + class FakeResponse: + status_code = 200 + content = b"not audio" + + class FakeClient: + def __init__(self, **_kwargs): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *_args): + return False + + async def post(self, _url, json): + return FakeResponse() + + monkeypatch.setattr(app.httpx, "AsyncClient", FakeClient) + with TestClient(app.app) as client: + response = client.post( + "/tts/generate", + headers={"Authorization": "Bearer test-token"}, + json={"text": "Hallo"}, + ) + assert response.status_code == 502 + assert "invalid audio" in response.text + + +def test_tts_bridge_deploy_installs_secrets_without_logging_them(monkeypatch): + calls = [] + monkeypatch.setattr(app, "BUTLER_TOKEN", "butler-secret") + monkeypatch.setattr(app, "_vault_cache", {}) + monkeypatch.setattr(app, "_ssh", lambda host, command, timeout=30: (calls.append((host, command, timeout)) or (0, "", ""))) + with TestClient(app.app) as client: + response = client.post( + "/tts/bridge/deploy", + headers={"Authorization": "Bearer butler-secret"}, + json={"rotate_client_token": True}, + ) + assert response.status_code == 200 + assert response.json()["listen"] == "0.0.0.0:8099" + assert len(response.json()["client_token"]) >= 32 + assert calls[0][0] == "sascha@10.5.85.5" + assert "butler-secret" not in calls[0][1] + + def test_sysctl_audit_reads_fixed_keys_from_inventory_host(monkeypatch): payload = { "live": {"net.ipv4.tcp_congestion_control": "bbr"},