| import json |
| import os |
| import tempfile |
| import unittest |
| from pathlib import Path |
| from unittest import mock |
|
|
| from approach.config import ( |
| OPENROUTER_BASE_URL, |
| get_model_profile, |
| sanitize_manifest, |
| validate_model_available, |
| ) |
| from approach.providers import EncodedImage, OpenAICompatibleChatClient, make_multimodal_content |
|
|
|
|
| class ConfigProviderTests(unittest.TestCase): |
| def test_default_profile_uses_openrouter(self): |
| profile = get_model_profile("default") |
| self.assertEqual(profile.provider, "openrouter") |
| self.assertEqual(profile.model, "openai/gpt-5.6-sol") |
| self.assertEqual(profile.base_url, OPENROUTER_BASE_URL) |
| self.assertEqual(profile.api_key_env, "OPENROUTER_API_KEY") |
|
|
| def test_aliases_keep_paper_profiles_available(self): |
| self.assertEqual(get_model_profile("gpt4v").model, "openai/gpt-4o-2024-08-06") |
| self.assertEqual(get_model_profile("claude35sonnet").model, "anthropic/claude-3.5-sonnet") |
| self.assertEqual(get_model_profile("gemini15pro").model, "google/gemini-pro-1.5") |
|
|
| def test_json_config_override_without_yaml_dependency(self): |
| with tempfile.TemporaryDirectory() as tmpdir: |
| path = Path(tmpdir) / "profiles.json" |
| path.write_text( |
| json.dumps( |
| { |
| "profiles": { |
| "default": { |
| "provider": "openrouter", |
| "model": "custom/model", |
| "base_url": OPENROUTER_BASE_URL, |
| "api_key_env": "CUSTOM_KEY", |
| } |
| } |
| } |
| ) |
| ) |
| profile = get_model_profile("default", str(path)) |
| self.assertEqual(profile.model, "custom/model") |
| self.assertEqual(profile.api_key_env, "CUSTOM_KEY") |
|
|
| def test_model_catalog_validation_is_dependency_injected(self): |
| profile = get_model_profile("default") |
| self.assertTrue(validate_model_available(profile, [{"id": "openai/gpt-5.6-sol"}])) |
| with self.assertRaises(ValueError): |
| validate_model_available(profile, [{"id": "other/model"}]) |
|
|
| def test_multimodal_content_supports_multiple_images(self): |
| content = make_multimodal_content( |
| "check", |
| ["aaa", EncodedImage(data="bbb", media_type="image/png")], |
| ) |
| self.assertEqual(content[0], {"type": "text", "text": "check"}) |
| self.assertEqual(len(content), 3) |
| self.assertEqual(content[1]["image_url"]["url"], "data:image/jpeg;base64,aaa") |
| self.assertEqual(content[2]["image_url"]["url"], "data:image/png;base64,bbb") |
|
|
| def test_client_posts_openai_compatible_payload(self): |
| profile = get_model_profile("default") |
| response = mock.Mock() |
| response.json.return_value = { |
| "model": profile.model, |
| "choices": [{"message": {"content": "{\"ok\": true}"}}], |
| } |
| post = mock.Mock(return_value=response) |
|
|
| with mock.patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-token"}): |
| client = OpenAICompatibleChatClient(profile, post=post) |
| payload = client.build_payload( |
| "verify", |
| ["image-a", "image-b"], |
| response_format={"type": "json_schema", "json_schema": {"name": "x"}}, |
| ) |
| result = client.complete_json( |
| "verify", |
| ["image-a", "image-b"], |
| response_format={"type": "json_schema", "json_schema": {"name": "x"}}, |
| ) |
|
|
| self.assertEqual(payload["model"], "openai/gpt-5.6-sol") |
| self.assertEqual(result, {"ok": True}) |
| self.assertEqual( |
| post.call_args.args[0], |
| "https://openrouter.ai/api/v1/chat/completions", |
| ) |
| self.assertEqual(post.call_args.kwargs["headers"]["Authorization"], "Bearer test-token") |
| self.assertIn("response_format", post.call_args.kwargs["json"]) |
|
|
| def test_client_supports_multi_turn_json_requests(self): |
| profile = get_model_profile("paper_claude") |
| response = mock.Mock() |
| response.json.return_value = { |
| "choices": [{"message": {"content": '{"objects": ["button"]}'}}] |
| } |
| post = mock.Mock(return_value=response) |
| messages = [ |
| {"role": "user", "content": "list objects"}, |
| {"role": "assistant", "content": '{"objects": ["button"]}'}, |
| {"role": "user", "content": "filter interactable objects"}, |
| ] |
|
|
| with mock.patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-token"}): |
| result = OpenAICompatibleChatClient(profile, post=post).complete_json_messages( |
| messages |
| ) |
|
|
| self.assertEqual(result, {"objects": ["button"]}) |
| self.assertEqual(post.call_args.kwargs["json"]["messages"], messages) |
|
|
| def test_manifest_never_records_api_key_value(self): |
| profile = get_model_profile("default") |
| manifest = sanitize_manifest( |
| profile, |
| {"model": "openai/gpt-5.6-sol"}, |
| {"model": "openai/gpt-5.6-sol", "usage": {"total_tokens": 12}}, |
| ) |
| self.assertEqual(manifest["api_key_env"], "OPENROUTER_API_KEY") |
| self.assertNotIn("sk-", json.dumps(manifest)) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|