Merge remote-tracking branch 'origin/main' into codex/repair-pr-458

# Conflicts:
#	tools/graphics/image_selector.py
This commit is contained in:
calesthio
2026-08-13 10:38:22 -07:00
17 changed files with 1202 additions and 34 deletions
+103
View File
@@ -0,0 +1,103 @@
from __future__ import annotations
from unittest.mock import MagicMock, patch
from tools.audio.fal_elevenlabs_music import FalElevenLabsMusic
from tools.base_tool import ToolStatus
from tools.tool_registry import ToolRegistry
def _response(*, json_data=None, content=b""):
response = MagicMock()
response.json.return_value = json_data
response.content = content
response.raise_for_status.return_value = None
return response
def test_contract_and_rounded_cost(monkeypatch):
tool = FalElevenLabsMusic()
monkeypatch.setenv("FAL_KEY", "test-key")
assert tool.get_status() == ToolStatus.AVAILABLE
assert tool.estimate_cost({"duration_seconds": 20}) == 0.80
assert tool.estimate_cost({"duration_seconds": 61}) == 1.60
assert tool.get_info()["capability"] == "music_generation"
assert tool.get_info()["provider"] == "fal.ai"
def test_registry_discovers_provider(monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
registry = ToolRegistry()
registry.discover()
tool = registry.get("fal_elevenlabs_music")
assert tool is not None
assert tool.get_status() == ToolStatus.AVAILABLE
def test_execute_submits_once_and_downloads_audio(tmp_path, monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
output_path = tmp_path / "music.mp3"
tool = FalElevenLabsMusic()
tool._POLL_INTERVAL_SECONDS = 0
post_response = _response(
json_data={
"status_url": "https://queue.example/status",
"response_url": "https://queue.example/result",
}
)
status_response = _response(json_data={"status": "COMPLETED"})
result_response = _response(
json_data={"audio": {"url": "https://media.example/music.mp3"}}
)
audio_response = _response(content=b"fake-mp3")
with (
patch("requests.post", return_value=post_response) as mock_post,
patch(
"requests.get",
side_effect=[status_response, result_response, audio_response],
) as mock_get,
):
result = tool.execute(
{
"prompt": "gentle felt piano",
"duration_seconds": 20,
"force_instrumental": True,
"output_path": str(output_path),
}
)
assert result.success is True
assert result.cost_usd == 0.80
assert result.model == "fal-ai/elevenlabs/music"
assert output_path.read_bytes() == b"fake-mp3"
assert mock_post.call_count == 1
assert mock_post.call_args.kwargs["json"]["music_length_ms"] == 20000
assert mock_post.call_args.kwargs["json"]["force_instrumental"] is True
assert mock_get.call_count == 3
def test_execute_rejects_missing_duration_without_request(monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
with patch("requests.post") as mock_post:
result = FalElevenLabsMusic().execute({"prompt": "gentle piano"})
assert result.success is False
assert result.error == "duration_seconds is required"
mock_post.assert_not_called()
def test_error_redacts_fal_key(monkeypatch):
secret = "test-production-shaped-fal-key"
monkeypatch.setenv("FAL_KEY", secret)
with patch("requests.post", side_effect=RuntimeError(f"request used {secret}")):
result = FalElevenLabsMusic().execute(
{"prompt": "gentle piano", "duration_seconds": 20}
)
assert result.success is False
assert secret not in result.error
assert "[REDACTED]" in result.error
+132
View File
@@ -0,0 +1,132 @@
from __future__ import annotations
from unittest.mock import MagicMock, patch
from tools.audio.fal_elevenlabs_tts import FalElevenLabsTTS
from tools.base_tool import ToolStatus
from tools.tool_registry import ToolRegistry
def _response(*, json_data=None, content=b""):
response = MagicMock()
response.json.return_value = json_data
response.content = content
response.raise_for_status.return_value = None
return response
def test_contract_models_and_cost(monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
tool = FalElevenLabsTTS()
assert tool.get_status() == ToolStatus.AVAILABLE
assert tool.get_info()["capability"] == "tts"
assert tool.get_info()["provider"] == "fal.ai"
assert tool.estimate_cost({"text": "a" * 1000, "model_id": "eleven-v3"}) == 0.1
assert tool.estimate_cost({"text": "a" * 1000, "model_id": "multilingual-v2"}) == 0.1
assert tool.estimate_cost({"text": "a" * 1000, "model_id": "turbo-v2.5"}) == 0.05
def test_registry_discovers_fal_tts(monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
registry = ToolRegistry()
registry.discover()
tool = registry.get("fal_elevenlabs_tts")
assert tool is not None
assert tool.get_status() == ToolStatus.AVAILABLE
def test_tts_selector_routes_to_fal_provider(monkeypatch):
from tools.audio.tts_selector import TTSSelector
from tools.base_tool import ToolResult
monkeypatch.setenv("FAL_KEY", "test-key")
tool = FalElevenLabsTTS()
selector = TTSSelector()
monkeypatch.setattr(selector, "_providers", lambda: [tool])
monkeypatch.setattr(
selector,
"_select_best_tool",
lambda _inputs, _candidates, _context: (tool, None),
)
monkeypatch.setattr(
tool,
"execute",
lambda inputs: ToolResult(success=True, data={"received": inputs}),
)
result = selector.execute(
{"text": "hello", "preferred_provider": "fal.ai", "voice_id": "Rachel"}
)
assert result.success
assert result.data["selected_tool"] == "fal_elevenlabs_tts"
assert result.data["selected_provider"] == "fal.ai"
def test_execute_submits_once_and_downloads_audio(tmp_path, monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
output_path = tmp_path / "speech.mp3"
tool = FalElevenLabsTTS()
tool._POLL_INTERVAL_SECONDS = 0
post_response = _response(
json_data={
"status_url": "https://queue.example/status",
"response_url": "https://queue.example/result",
}
)
status_response = _response(json_data={"status": "COMPLETED"})
result_response = _response(
json_data={"audio": {"url": "https://media.example/speech.mp3"}}
)
audio_response = _response(content=b"fake-mp3")
with (
patch("requests.post", return_value=post_response) as mock_post,
patch(
"requests.get",
side_effect=[status_response, result_response, audio_response],
) as mock_get,
):
result = tool.execute(
{
"text": "A calm, measured test.",
"voice_id": "Rachel",
"model_id": "eleven-v3",
"stability": 0.65,
"language_code": "en",
"output_path": str(output_path),
}
)
assert result.success is True
assert result.model == "fal-ai/elevenlabs/tts/eleven-v3"
assert output_path.read_bytes() == b"fake-mp3"
assert mock_post.call_count == 1
assert mock_post.call_args.args[0].endswith("/fal-ai/elevenlabs/tts/eleven-v3")
assert mock_post.call_args.kwargs["json"]["voice"] == "Rachel"
assert mock_post.call_args.kwargs["json"]["stability"] == 0.65
assert mock_get.call_count == 3
def test_invalid_model_does_not_submit(monkeypatch):
monkeypatch.setenv("FAL_KEY", "test-key")
with patch("requests.post") as mock_post:
result = FalElevenLabsTTS().execute(
{"text": "hello", "model_id": "not-a-model"}
)
assert result.success is False
assert "model_id must be one of" in result.error
mock_post.assert_not_called()
def test_error_redacts_fal_key(monkeypatch):
secret = "test-production-shaped-fal-key"
monkeypatch.setenv("FAL_KEY", secret)
with patch("requests.post", side_effect=RuntimeError(f"request used {secret}")):
result = FalElevenLabsTTS().execute({"text": "hello"})
assert result.success is False
assert secret not in result.error
assert "[REDACTED]" in result.error
@@ -0,0 +1,156 @@
"""Tests for the Gemini image backend in google_imagen.
Models named `gemini-*` (e.g. gemini-2.5-flash-image) are not served by the
Imagen `:predict` endpoint — they generate images through generate_content
with an image_config. This backend matters on Vertex projects that have no
Imagen catalog access, where it is the only working Google image path.
"""
import sys
import types as pytypes
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
class _FakeInline:
def __init__(self, data: bytes):
self.data = data
class _FakePart:
def __init__(self, data: bytes):
self.inline_data = _FakeInline(data)
class _FakeContent:
def __init__(self, parts):
self.parts = parts
class _FakeCandidate:
def __init__(self, parts):
self.content = _FakeContent(parts)
class _FakeResponse:
def __init__(self, parts):
self.candidates = [_FakeCandidate(parts)]
@pytest.fixture
def imagen_tool(monkeypatch):
monkeypatch.setenv("GOOGLE_API_KEY", "test-key")
calls: list[dict] = []
class _FakeModels:
def generate_content(self, model=None, contents=None, config=None):
calls.append({"model": model, "contents": contents, "config": config})
return _FakeResponse([_FakePart(b"GEMINI_IMG")])
class _FakeClient:
models = _FakeModels()
import tools.google_credentials as gc
monkeypatch.setattr(
gc, "get_genai_client", lambda http_options=None, location=None: _FakeClient()
)
from tools.graphics.google_imagen import GoogleImagen
return GoogleImagen(), calls
def test_gemini_model_routes_to_generate_content(imagen_tool, tmp_path):
tool, calls = imagen_tool
out = tmp_path / "img.png"
result = tool.execute(
{
"prompt": "a flower",
"model": "gemini-2.5-flash-image",
"aspect_ratio": "16:9",
"output_path": str(out),
}
)
assert result.success
assert result.data["model"] == "gemini-2.5-flash-image"
assert out.read_bytes() == b"GEMINI_IMG"
assert len(calls) == 1
assert calls[0]["model"] == "gemini-2.5-flash-image"
# Aspect ratio must reach the API through image_config, not be dropped.
assert calls[0]["config"].image_config.aspect_ratio == "16:9"
def test_image_selector_maps_model_name_to_google_model(
imagen_tool, monkeypatch, tmp_path
):
"""The governed selector must be able to reach the Gemini backend."""
from tools.graphics.image_selector import ImageSelector
tool, calls = imagen_tool
selector = ImageSelector()
monkeypatch.setattr(selector, "_providers", lambda: [tool])
result = selector.execute(
{
"prompt": "a flower",
"preferred_provider": "google_imagen",
"model_name": "gemini-2.5-flash-image",
"output_path": str(tmp_path / "selected.png"),
}
)
assert result.success, result.error
assert calls[0]["model"] == "gemini-2.5-flash-image"
assert result.data["selected_tool"] == "google_imagen"
assert result.data["model"] == "gemini-2.5-flash-image"
def test_gemini_cost_estimate_is_per_image():
from tools.graphics.google_imagen import GoogleImagen
tool = GoogleImagen()
assert tool.estimate_cost(
{"model": "gemini-2.5-flash-image", "number_of_images": 2}
) == pytest.approx(0.039 * 2)
def test_text_only_response_is_a_clear_error(monkeypatch, tmp_path):
monkeypatch.setenv("GOOGLE_API_KEY", "test-key")
class _TextPart:
inline_data = None
class _FakeModels:
def generate_content(self, model=None, contents=None, config=None):
return _FakeResponse([_TextPart()])
class _FakeClient:
models = _FakeModels()
import tools.google_credentials as gc
monkeypatch.setattr(
gc, "get_genai_client", lambda http_options=None, location=None: _FakeClient()
)
from tools.graphics.google_imagen import GoogleImagen
result = GoogleImagen().execute(
{
"prompt": "a flower",
"model": "gemini-2.5-flash-image",
"output_path": str(tmp_path / "img.png"),
}
)
assert not result.success
assert "No image data" in result.error
+36
View File
@@ -0,0 +1,36 @@
from tools.audio.google_tts import GoogleTTS
from tools.google_credentials import has_google_credentials
def test_tts_only_key_does_not_enable_shared_google_providers(monkeypatch):
monkeypatch.setenv("GOOGLE_TTS_API_KEY", "test-tts-only-key")
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
monkeypatch.delenv("GOOGLE_APPLICATION_CREDENTIALS", raising=False)
assert GoogleTTS().get_status().value == "available"
assert has_google_credentials() is False
def test_tts_key_uses_header_and_is_redacted_from_errors(monkeypatch, tmp_path):
import requests
secret = "test-production-shaped-tts-key"
monkeypatch.setenv("GOOGLE_TTS_API_KEY", secret)
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
monkeypatch.delenv("GOOGLE_APPLICATION_CREDENTIALS", raising=False)
def fail_request(url, **kwargs):
assert kwargs["headers"]["x-goog-api-key"] == secret
assert "params" not in kwargs
raise requests.HTTPError(f"403 for {url}?key={secret}")
monkeypatch.setattr(requests, "post", fail_request)
result = GoogleTTS().execute(
{"text": "safe test sentence", "output_path": str(tmp_path / "speech.mp3")}
)
assert result.success is False
assert secret not in result.error
assert "[REDACTED]" in result.error