返回 last30days-skill
test_providers_v3.py
根目录 / tests / test_providers_v3.py
1 import json
2 import os
3 import unittest
4 from unittest import mock
5 from typing import get_args
6
7 from lib import env
8 from lib import providers
9
10
11 class ProvidersV3Tests(unittest.TestCase):
12 def test_auto_prefers_gemini_with_google_key(self):
13 runtime, client = providers.resolve_runtime(
14 {"GOOGLE_API_KEY": "test", "LAST30DAYS_REASONING_PROVIDER": "auto"},
15 depth="default",
16 )
17 self.assertEqual("gemini", runtime.reasoning_provider)
18 self.assertEqual("gemini", client.name)
19 self.assertTrue(runtime.planner_model.startswith("gemini-3.1-"))
20
21 def test_auto_falls_back_to_openai(self):
22 runtime, client = providers.resolve_runtime(
23 {
24 "OPENAI_API_KEY": "test-key",
25 "OPENAI_AUTH_STATUS": "ok",
26 "LAST30DAYS_REASONING_PROVIDER": "auto",
27 },
28 depth="default",
29 )
30 self.assertEqual("openai", runtime.reasoning_provider)
31
32 def test_auto_falls_back_to_xai(self):
33 runtime, client = providers.resolve_runtime(
34 {"XAI_API_KEY": "test-key", "LAST30DAYS_REASONING_PROVIDER": "auto"},
35 depth="default",
36 )
37 self.assertEqual("xai", runtime.reasoning_provider)
38
39 def test_auto_returns_local_runtime_when_no_keys(self):
40 runtime, client = providers.resolve_runtime(
41 {"LAST30DAYS_REASONING_PROVIDER": "auto"},
42 depth="default",
43 )
44 self.assertEqual("local", runtime.reasoning_provider)
45 self.assertEqual("deterministic", runtime.planner_model)
46 self.assertEqual("local-score", runtime.rerank_model)
47 self.assertIsNone(client)
48
49 def test_explicit_gemini_without_key_still_raises(self):
50 with self.assertRaises(RuntimeError):
51 providers.resolve_runtime(
52 {"LAST30DAYS_REASONING_PROVIDER": "gemini"},
53 depth="default",
54 )
55
56 def test_explicit_openai_without_key_still_raises(self):
57 with self.assertRaises(RuntimeError):
58 providers.resolve_runtime(
59 {"LAST30DAYS_REASONING_PROVIDER": "openai"},
60 depth="default",
61 )
62
63 def test_explicit_xai_without_key_still_raises(self):
64 with self.assertRaises(RuntimeError):
65 providers.resolve_runtime(
66 {"LAST30DAYS_REASONING_PROVIDER": "xai"},
67 depth="default",
68 )
69
70 def test_codex_auth_is_not_supported_as_openai_provider_auth(self):
71 self.assertNotIn("codex", get_args(env.AuthSource))
72 self.assertFalse(hasattr(env, "AUTH_SOURCE_CODEX"))
73
74 def test_openai_provider_has_no_chatgpt_backend_route(self):
75 self.assertFalse(hasattr(providers, "CODEX_RESPONSES_URL"))
76 with self.assertRaises(TypeError):
77 providers.OpenAIClient("token", "codex", "acct")
78
79
80 class TestExtractJson(unittest.TestCase):
81 def test_direct_json(self):
82 result = providers.extract_json('{"scores": [1, 2]}')
83 self.assertEqual(result, {"scores": [1, 2]})
84
85 def test_json_in_markdown_fences(self):
86 text = '```json\n{"scores": [1, 2]}\n```'
87 result = providers.extract_json(text)
88 self.assertEqual(result, {"scores": [1, 2]})
89
90 def test_json_with_surrounding_text(self):
91 text = 'Here is the result:\n{"scores": [1]}\nDone.'
92 result = providers.extract_json(text)
93 self.assertEqual(result, {"scores": [1]})
94
95 def test_empty_text_raises(self):
96 with self.assertRaises(ValueError):
97 providers.extract_json("")
98
99 def test_no_json_raises(self):
100 with self.assertRaises(json.JSONDecodeError):
101 providers.extract_json("no json here at all")
102
103
104 class TestExtractOpenAIText(unittest.TestCase):
105 def test_output_text_field(self):
106 self.assertEqual("hello", providers.extract_openai_text({"output_text": "hello"}))
107
108 def test_choices_message_content(self):
109 payload = {"choices": [{"message": {"content": "world"}}]}
110 self.assertEqual("world", providers.extract_openai_text(payload))
111
112 def test_output_list_text(self):
113 payload = {"output": [{"text": "foo"}]}
114 self.assertEqual("foo", providers.extract_openai_text(payload))
115
116 def test_output_content_output_text_type(self):
117 payload = {"output": [{"content": [{"type": "output_text", "text": "bar"}]}]}
118 self.assertEqual("bar", providers.extract_openai_text(payload))
119
120 def test_output_string_item(self):
121 payload = {"output": ["direct string"]}
122 self.assertEqual("direct string", providers.extract_openai_text(payload))
123
124 def test_empty_payload_returns_empty(self):
125 self.assertEqual("", providers.extract_openai_text({}))
126
127
128 class TestExtractGeminiText(unittest.TestCase):
129 def test_standard_response(self):
130 payload = {"candidates": [{"content": {"parts": [{"text": "gemini says"}]}}]}
131 self.assertEqual("gemini says", providers.extract_gemini_text(payload))
132
133 def test_empty_candidates(self):
134 self.assertEqual("", providers.extract_gemini_text({"candidates": []}))
135
136 def test_empty_payload(self):
137 self.assertEqual("", providers.extract_gemini_text({}))
138
139
140 if __name__ == "__main__":
141 unittest.main()
142
143
144 class ResolveEndpointTests(unittest.TestCase):
145 """``*_BASE_URL`` accepts an API root as well as a full endpoint URL."""
146
147 def _resolve(self, value, env_var="OPENAI_BASE_URL", default=None):
148 default = default or providers.OPENAI_RESPONSES_URL
149 patched = {} if value is None else {env_var: value}
150 with mock.patch.dict(os.environ, patched, clear=True):
151 return providers.resolve_endpoint(env_var, default)
152
153 def test_unset_uses_default_endpoint(self):
154 self.assertEqual(providers.OPENAI_RESPONSES_URL, self._resolve(None))
155
156 def test_blank_value_uses_default_endpoint(self):
157 self.assertEqual(providers.OPENAI_RESPONSES_URL, self._resolve(" "))
158
159 def test_api_root_gets_endpoint_path_appended(self):
160 self.assertEqual(
161 "https://example.test/v1/responses",
162 self._resolve("https://example.test/v1"),
163 )
164
165 def test_trailing_slash_is_normalised(self):
166 self.assertEqual(
167 "https://example.test/v1/responses",
168 self._resolve("https://example.test/v1/"),
169 )
170
171 def test_full_endpoint_url_is_preserved(self):
172 self.assertEqual(
173 "https://example.test/v1/responses",
174 self._resolve("https://example.test/v1/responses"),
175 )
176
177 def test_openrouter_uses_chat_completions_path(self):
178 self.assertEqual(
179 "https://example.test/api/v1/chat/completions",
180 self._resolve(
181 "https://example.test/api/v1",
182 env_var="OPENROUTER_BASE_URL",
183 default=providers.OPENROUTER_URL,
184 ),
185 )
186
187 def test_openrouter_full_endpoint_url_is_preserved(self):
188 self.assertEqual(
189 "https://example.test/api/v1/chat/completions",
190 self._resolve(
191 "https://example.test/api/v1/chat/completions",
192 env_var="OPENROUTER_BASE_URL",
193 default=providers.OPENROUTER_URL,
194 ),
195 )
196
196 lines PYTHON