roksta 0.3.2__cp311-cp311-macosx_10_9_universal2.whl → 0.3.8__cp311-cp311-macosx_10_9_universal2.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. roksta/__init__.cpython-311-darwin.so +0 -0
  2. roksta/ai/__init__.cpython-311-darwin.so +0 -0
  3. roksta/ai/call_ai.cpython-311-darwin.so +0 -0
  4. roksta/ai/gemini.cpython-311-darwin.so +0 -0
  5. roksta/ai/generic.cpython-311-darwin.so +0 -0
  6. roksta/ai/llm.cpython-311-darwin.so +0 -0
  7. roksta/ai/openai.cpython-311-darwin.so +0 -0
  8. roksta/ai/tools/__init__.cpython-311-darwin.so +0 -0
  9. roksta/ai/tools/delete_file.cpython-311-darwin.so +0 -0
  10. roksta/ai/tools/edit_file.cpython-311-darwin.so +0 -0
  11. roksta/ai/tools/final_response.cpython-311-darwin.so +0 -0
  12. roksta/ai/tools/get_file_summaries.cpython-311-darwin.so +0 -0
  13. roksta/ai/tools/read_file.cpython-311-darwin.so +0 -0
  14. roksta/ai/tools/regex_replace.cpython-311-darwin.so +0 -0
  15. roksta/ai/tools/shell_any.cpython-311-darwin.so +0 -0
  16. roksta/ai/tools/shell_limited.cpython-311-darwin.so +0 -0
  17. roksta/ai/tools/tool_defs.cpython-311-darwin.so +0 -0
  18. roksta/ai/tools/tool_utils.cpython-311-darwin.so +0 -0
  19. roksta/ai/tools/web_fetch.cpython-311-darwin.so +0 -0
  20. roksta/ai/tools/write_file.cpython-311-darwin.so +0 -0
  21. roksta/analytics.cpython-311-darwin.so +0 -0
  22. roksta/balance.cpython-311-darwin.so +0 -0
  23. roksta/build_project.cpython-311-darwin.so +0 -0
  24. roksta/chat_workflow.cpython-311-darwin.so +0 -0
  25. roksta/check_for_updates.cpython-311-darwin.so +0 -0
  26. roksta/check_subtask_sequence.cpython-311-darwin.so +0 -0
  27. roksta/checkpoints.cpython-311-darwin.so +0 -0
  28. roksta/clarify_goal.cpython-311-darwin.so +0 -0
  29. roksta/codebase_listing.cpython-311-darwin.so +0 -0
  30. roksta/command_handlers/__init__.cpython-311-darwin.so +0 -0
  31. roksta/command_handlers/handle_activate_command.cpython-311-darwin.so +0 -0
  32. roksta/command_handlers/handle_add_funds_command.cpython-311-darwin.so +0 -0
  33. roksta/command_handlers/handle_auto_charge_command.cpython-311-darwin.so +0 -0
  34. roksta/command_handlers/handle_auto_commit_command.cpython-311-darwin.so +0 -0
  35. roksta/command_handlers/handle_building_command.cpython-311-darwin.so +0 -0
  36. roksta/command_handlers/handle_chat_command.cpython-311-darwin.so +0 -0
  37. roksta/command_handlers/handle_dev_rate_command.cpython-311-darwin.so +0 -0
  38. roksta/command_handlers/handle_feedback_command.cpython-311-darwin.so +0 -0
  39. roksta/command_handlers/handle_goal_command.cpython-311-darwin.so +0 -0
  40. roksta/command_handlers/handle_help_command.cpython-311-darwin.so +0 -0
  41. roksta/command_handlers/handle_init_command.cpython-311-darwin.so +0 -0
  42. roksta/command_handlers/handle_linting_command.cpython-311-darwin.so +0 -0
  43. roksta/command_handlers/handle_login_command.cpython-311-darwin.so +0 -0
  44. roksta/command_handlers/handle_logout_command.cpython-311-darwin.so +0 -0
  45. roksta/command_handlers/handle_payment_details_command.cpython-311-darwin.so +0 -0
  46. roksta/command_handlers/handle_quit_command.cpython-311-darwin.so +0 -0
  47. roksta/command_handlers/handle_redeem_command.cpython-311-darwin.so +0 -0
  48. roksta/command_handlers/handle_request_activation_command.cpython-311-darwin.so +0 -0
  49. roksta/command_handlers/handle_testing_command.cpython-311-darwin.so +0 -0
  50. roksta/command_handlers/handle_usage_command.cpython-311-darwin.so +0 -0
  51. roksta/create_default_config.cpython-311-darwin.so +0 -0
  52. roksta/create_default_ignore_file.cpython-311-darwin.so +0 -0
  53. roksta/default_config.cpython-311-darwin.so +0 -0
  54. roksta/default_ignores.cpython-311-darwin.so +0 -0
  55. roksta/discover_test_command.cpython-311-darwin.so +0 -0
  56. roksta/enums.cpython-311-darwin.so +0 -0
  57. roksta/env.cpython-311-darwin.so +0 -0
  58. roksta/extended_text_area.cpython-311-darwin.so +0 -0
  59. roksta/firebase.cpython-311-darwin.so +0 -0
  60. roksta/firebase_auth_web.cpython-311-darwin.so +0 -0
  61. roksta/firebase_config.cpython-311-darwin.so +0 -0
  62. roksta/fix_tests.cpython-311-darwin.so +0 -0
  63. roksta/gen_codebase_summaries.cpython-311-darwin.so +0 -0
  64. roksta/gen_one_line_goal.cpython-311-darwin.so +0 -0
  65. roksta/gen_subtasks.cpython-311-darwin.so +0 -0
  66. roksta/get_codebase_structure.cpython-311-darwin.so +0 -0
  67. roksta/get_failing_tests.cpython-311-darwin.so +0 -0
  68. roksta/goal_workflow.cpython-311-darwin.so +0 -0
  69. roksta/init_codebase.cpython-311-darwin.so +0 -0
  70. roksta/lint_code.cpython-311-darwin.so +0 -0
  71. roksta/logger.cpython-311-darwin.so +0 -0
  72. roksta/main.cpython-311-darwin.so +0 -0
  73. roksta/make_issue.cpython-311-darwin.so +0 -0
  74. roksta/new_features.cpython-311-darwin.so +0 -0
  75. roksta/parse_directive_cli_tokens.cpython-311-darwin.so +0 -0
  76. roksta/parse_readme.cpython-311-darwin.so +0 -0
  77. roksta/propose_solution.cpython-311-darwin.so +0 -0
  78. roksta/response_formats.cpython-311-darwin.so +0 -0
  79. roksta/rewrite_goal.cpython-311-darwin.so +0 -0
  80. roksta/roksta.cpython-311-darwin.so +0 -0
  81. roksta/run_cli_goal.cpython-311-darwin.so +0 -0
  82. roksta/save_chat_transcript.cpython-311-darwin.so +0 -0
  83. roksta/select_files.cpython-311-darwin.so +0 -0
  84. roksta/tips.cpython-311-darwin.so +0 -0
  85. roksta/utils.cpython-311-darwin.so +0 -0
  86. roksta/write_code.cpython-311-darwin.so +0 -0
  87. roksta-0.3.8.dist-info/METADATA +471 -0
  88. roksta-0.3.8.dist-info/RECORD +91 -0
  89. {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/top_level.txt +0 -1
  90. roksta-0.3.2.dist-info/METADATA +0 -40
  91. roksta-0.3.2.dist-info/RECORD +0 -121
  92. tests/__init__.py +0 -2
  93. tests/conftest.py +0 -211
  94. tests/functions/__init__.py +0 -2
  95. tests/functions/api_v1_00/__init__.py +0 -2
  96. tests/functions/api_v1_00/test__analytics.py +0 -416
  97. tests/functions/api_v1_00/test__gemini_proxy.py +0 -352
  98. tests/functions/api_v1_00/test__generic_proxy.py +0 -428
  99. tests/functions/api_v1_00/test__get_payment_details.py +0 -356
  100. tests/functions/api_v1_00/test__openai_proxy.py +0 -449
  101. tests/functions/api_v1_00/test__redeem_credit_code.py +0 -167
  102. tests/functions/api_v1_00/test__sync_emails.py +0 -325
  103. tests/functions/api_v1_00/test__take_payment.py +0 -491
  104. tests/functions/api_v1_00/test__use_activation_code.py +0 -438
  105. tests/functions/api_v1_01/__init__.py +0 -2
  106. tests/functions/api_v1_01/test__analytics.py +0 -416
  107. tests/functions/api_v1_01/test__gemini_proxy.py +0 -352
  108. tests/functions/api_v1_01/test__generic_proxy.py +0 -428
  109. tests/functions/api_v1_01/test__get_payment_details.py +0 -356
  110. tests/functions/api_v1_01/test__openai_proxy.py +0 -449
  111. tests/functions/api_v1_01/test__redeem_credit_code.py +0 -167
  112. tests/functions/api_v1_01/test__sync_emails.py +0 -325
  113. tests/functions/api_v1_01/test__take_payment.py +0 -491
  114. tests/functions/api_v1_01/test__use_activation_code.py +0 -438
  115. tests/functions/api_v1_02/__init__.py +0 -2
  116. tests/functions/api_v1_02/test__analytics.py +0 -416
  117. tests/functions/api_v1_02/test__gemini_proxy.py +0 -352
  118. tests/functions/api_v1_02/test__generic_proxy.py +0 -428
  119. tests/functions/api_v1_02/test__get_payment_details.py +0 -356
  120. tests/functions/api_v1_02/test__openai_proxy.py +0 -449
  121. tests/functions/api_v1_02/test__redeem_credit_code.py +0 -167
  122. tests/functions/api_v1_02/test__sync_emails.py +0 -325
  123. tests/functions/api_v1_02/test__take_payment.py +0 -491
  124. tests/functions/api_v1_02/test__use_activation_code.py +0 -438
  125. tests/functions/api_v1_02/test_proxy_keyword_replacement.py +0 -557
  126. tests/functions/api_v1_02/test_replace_keywords.py +0 -74
  127. tests/functions/test_auth.py +0 -24
  128. tests/functions/test_main.py +0 -73
  129. tests/functions/test_utils.py +0 -484
  130. {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/WHEEL +0 -0
  131. {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/entry_points.txt +0 -0
@@ -1,428 +0,0 @@
1
- import os
2
- import sys
3
- import json
4
- import types
5
- import importlib
6
- from unittest.mock import patch
7
-
8
-
9
- # Ensure the functions/ directory is importable as a top-level module location
10
- PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
11
- FUNCTIONS_DIR = os.path.join(PROJECT_ROOT, 'functions')
12
- if FUNCTIONS_DIR not in sys.path:
13
- sys.path.insert(0, FUNCTIONS_DIR)
14
-
15
- # Prepare lightweight fake modules to satisfy imports inside _generic_proxy
16
- # We'll temporarily inject these into sys.modules while importing the module
17
-
18
- # Save any originals so we can restore them after import
19
- _orig_sys_modules = {}
20
- _names_to_fake = [
21
- 'firebase_functions',
22
- 'utils',
23
- 'auth',
24
- 'openai',
25
- ]
26
- for name in _names_to_fake:
27
- _orig_sys_modules[name] = sys.modules.get(name)
28
-
29
- # Fake firebase_functions.https_fn.Response to capture returned data
30
- firebase_functions = types.ModuleType('firebase_functions')
31
-
32
- class FakeResponse:
33
- def __init__(self, response=None, mimetype=None, status=200, **kwargs):
34
- # Mirror the small subset of the interface tests expect
35
- self.status_code = status
36
- if isinstance(response, (dict, list)):
37
- self._body_text = json.dumps(response)
38
- else:
39
- self._body_text = '' if response is None else response
40
- self.headers = kwargs.get('headers', {})
41
-
42
- def get_data(self, as_text=False):
43
- if as_text:
44
- return self._body_text
45
- return self._body_text.encode('utf-8')
46
-
47
- firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
48
- sys.modules['firebase_functions'] = firebase_functions
49
-
50
- # Fake utils module (provides functions imported by _generic_proxy)
51
- utils_mod = types.ModuleType('utils')
52
-
53
-
54
- def _fake_create_json_response(success: bool, payload: any, status_code: int):
55
- response_body = {"success": success, "payload": payload}
56
- return firebase_functions.https_fn.Response(response=json.dumps(response_body), status=status_code, headers={'Content-Type': 'application/json'})
57
-
58
-
59
- def _fake_get_api_key(llm_family=None):
60
- return 'DUMMY_KEY'
61
-
62
-
63
- def _fake_verify_firebase_token(req):
64
- # Default: no-op (successful)
65
- return {}
66
-
67
-
68
- utils_mod.create_json_response = _fake_create_json_response
69
- utils_mod.get_api_key = _fake_get_api_key
70
- utils_mod.verify_firebase_token = _fake_verify_firebase_token
71
- sys.modules['utils'] = utils_mod
72
-
73
- # Fake auth module with validate_auth_key
74
- auth_mod = types.ModuleType('auth')
75
-
76
-
77
- def _fake_validate_auth_key(val: str) -> bool:
78
- return True
79
-
80
-
81
- auth_mod.validate_auth_key = _fake_validate_auth_key
82
- sys.modules['auth'] = auth_mod
83
-
84
- # Fake openai module with APIError and a default OpenAI class
85
- openai_mod = types.ModuleType('openai')
86
-
87
- class APIError(Exception):
88
- pass
89
-
90
-
91
- class DummyOpenAI:
92
- def __init__(self, base_url=None, api_key=None, timeout=None):
93
- self.base_url = base_url
94
- self.api_key = api_key
95
- self.timeout = timeout
96
- class Completions:
97
- def create(self, **params):
98
- raise NotImplementedError("create not implemented for dummy client")
99
- def parse(self, **params):
100
- raise NotImplementedError("parse not implemented for dummy client")
101
- class Chat:
102
- def __init__(self):
103
- self.completions = Completions()
104
- self.chat = Chat()
105
-
106
- openai_mod.OpenAI = DummyOpenAI
107
- openai_mod.APIError = APIError
108
- sys.modules['openai'] = openai_mod
109
-
110
- # Import the module under test after preparing the fake imports
111
- #_generic = importlib.import_module('_generic_proxy')
112
-
113
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
114
- functions_root = os.path.join(repo_root, 'functions')
115
- module_path = os.path.join(functions_root, 'api_v1_00', '_generic_proxy.py')
116
- spec = importlib.util.spec_from_file_location('api_v1_00._generic_proxy', module_path)
117
- _generic = importlib.util.module_from_spec(spec)
118
- spec.loader.exec_module(_generic)
119
-
120
- # Patch Firestore client and billing functions to avoid external dependencies during tests
121
- # Provide a dummy Firestore client and make balance checks/billing no-ops.
122
- _generic.firestore = types.SimpleNamespace(client=lambda: object())
123
-
124
- def _fake_ensure_balance_positive(db, user_id):
125
- return True, 100.0
126
-
127
- def _fake_extract_usage(llm_family=None, call_type=None, model_id=None, request_payload=None, response_payload=None):
128
- # Default to zero-usage unless present in the payload
129
- usage = response_payload.get('usage', {}) if isinstance(response_payload, dict) else {}
130
- in_tokens = usage.get('prompt_tokens', 0) or usage.get('input_tokens', 0) or 0
131
- out_tokens = usage.get('completion_tokens', 0) or usage.get('output_tokens', 0) or 0
132
- return {"input_tokens": in_tokens, "output_tokens": out_tokens}
133
-
134
- def _fake_calculate_cost(model_id=None, input_tokens=0, output_tokens=0):
135
- return 0.0
136
-
137
- def _fake_bill_with_retry(db=None, user_id=None, model_id=None, usage=None, cost=None, reason=None):
138
- return "ok", 100.0
139
-
140
- _generic.ensure_balance_positive = _fake_ensure_balance_positive
141
- _generic.extract_usage = _fake_extract_usage
142
- # Compatibility shim: some versions of the module call get_usage(response) instead of extract_usage(...)
143
- _generic.get_usage = lambda response: _fake_extract_usage(response_payload=response)
144
- _generic.calculate_cost = _fake_calculate_cost
145
- _generic.bill_with_retry = _fake_bill_with_retry
146
-
147
- # Restore original sys.modules mappings to avoid side-effects for other tests
148
- for name, orig in _orig_sys_modules.items():
149
- if orig is None:
150
- try:
151
- del sys.modules[name]
152
- except KeyError:
153
- pass
154
- else:
155
- sys.modules[name] = orig
156
-
157
-
158
- # Helper request stub used in tests
159
- class DummyRequest:
160
- def __init__(self, headers=None, method='POST', json_data=None, raise_on_get_json=False):
161
- self.headers = headers or {}
162
- self.method = method
163
- self._json_data = json_data
164
- self._raise = raise_on_get_json
165
-
166
- def get_json(self, silent=False):
167
- if self._raise:
168
- raise Exception('Malformed JSON')
169
- return self._json_data
170
-
171
-
172
- def _parse_response(resp):
173
- data = resp.get_data(as_text=True)
174
- return json.loads(data)
175
-
176
-
177
- # -----------------------------
178
- # Tests
179
- # -----------------------------
180
-
181
-
182
- def test_verify_firebase_token_failure_returns_401():
183
- req = DummyRequest(headers={_generic.AUTH_HEADER_NAME: 'ok'}, method='POST')
184
- with patch.object(_generic, 'verify_firebase_token', side_effect=Exception('invalid token')):
185
- resp = _generic._generic_proxy(req)
186
-
187
- assert resp.status_code == 401
188
- payload = _parse_response(resp)
189
- assert payload['success'] is False
190
- assert 'Unauthorized' in payload['payload']
191
-
192
-
193
- def test_missing_auth_header_returns_401():
194
- req = DummyRequest(headers={}, method='POST', json_data={'llm_family': 'openai', 'call_type': 'create', 'call_params': {}})
195
- with patch.object(_generic, 'validate_auth_key', return_value=True), patch.object(_generic, 'verify_firebase_token', return_value={}):
196
- resp = _generic._generic_proxy(req)
197
-
198
- assert resp.status_code == 401
199
- payload = _parse_response(resp)
200
- assert payload['success'] is False
201
- assert 'Missing app authentication key' in payload['payload']
202
-
203
-
204
- def test_invalid_auth_key_returns_403():
205
- headers = {_generic.AUTH_HEADER_NAME: 'bad'}
206
- req = DummyRequest(headers=headers, method='POST', json_data={'llm_family': 'openai', 'call_type': 'create', 'call_params': {}})
207
- with patch.object(_generic, 'validate_auth_key', return_value=False), patch.object(_generic, 'verify_firebase_token', return_value={}):
208
- resp = _generic._generic_proxy(req)
209
-
210
- assert resp.status_code == 403
211
- payload = _parse_response(resp)
212
- assert payload['success'] is False
213
- assert 'Invalid app authentication key' in payload['payload']
214
-
215
-
216
- def test_non_post_method_returns_405():
217
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
218
- req = DummyRequest(headers=headers, method='GET')
219
- with patch.object(_generic, 'validate_auth_key', return_value=True), patch.object(_generic, 'verify_firebase_token', return_value={}):
220
- resp = _generic._generic_proxy(req)
221
-
222
- assert resp.status_code == 405
223
- payload = _parse_response(resp)
224
- assert payload['success'] is False
225
- assert 'POST method required' in payload['payload']
226
-
227
-
228
- def test_malformed_json_returns_400():
229
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
230
- req = DummyRequest(headers=headers, method='POST', raise_on_get_json=True)
231
- with patch.object(_generic, 'validate_auth_key', return_value=True), patch.object(_generic, 'verify_firebase_token', return_value={}):
232
- resp = _generic._generic_proxy(req)
233
-
234
- assert resp.status_code == 400
235
- payload = _parse_response(resp)
236
- assert payload['success'] is False
237
- assert 'Invalid JSON payload' in payload['payload']
238
-
239
-
240
- def test_missing_llm_family_returns_400():
241
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
242
- req = DummyRequest(headers=headers, method='POST', json_data={'call_type': 'create', 'call_params': {}})
243
- with patch.object(_generic, 'validate_auth_key', return_value=True), patch.object(_generic, 'verify_firebase_token', return_value={}):
244
- resp = _generic._generic_proxy(req)
245
-
246
- assert resp.status_code == 400
247
- payload = _parse_response(resp)
248
- assert payload['success'] is False
249
- assert "Missing 'llm_family'" in payload['payload']
250
-
251
-
252
- def test_invalid_llm_family_returns_400():
253
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
254
- req = DummyRequest(headers=headers, method='POST', json_data={'llm_family': 'notreal', 'call_type': 'create', 'call_params': {}})
255
- with patch.object(_generic, 'validate_auth_key', return_value=True), patch.object(_generic, 'verify_firebase_token', return_value={}):
256
- resp = _generic._generic_proxy(req)
257
-
258
- assert resp.status_code == 400
259
- payload = _parse_response(resp)
260
- assert payload['success'] is False
261
- assert 'Invalid LLM family' in payload['payload']
262
-
263
-
264
- def test_missing_or_invalid_call_type_or_call_params_returns_400():
265
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
266
- # Use an empty dict for call_params so the proxy can safely access call_params.get()
267
- req = DummyRequest(headers=headers, method='POST', json_data={'llm_family': 'openai', 'call_type': None, 'call_params': {}})
268
- with patch.object(_generic, 'validate_auth_key', return_value=True), patch.object(_generic, 'verify_firebase_token', return_value={}):
269
- resp = _generic._generic_proxy(req)
270
-
271
- assert resp.status_code == 400
272
- payload = _parse_response(resp)
273
- assert payload['success'] is False
274
- assert 'Missing or invalid required fields' in payload['payload']
275
-
276
-
277
- def test_invalid_call_type_returns_400():
278
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
279
- req = DummyRequest(headers=headers, method='POST', json_data={'llm_family': 'openai', 'call_type': 'bad', 'call_params': {}})
280
- with patch.object(_generic, 'validate_auth_key', return_value=True), patch.object(_generic, 'verify_firebase_token', return_value={}):
281
- resp = _generic._generic_proxy(req)
282
-
283
- assert resp.status_code == 400
284
- payload = _parse_response(resp)
285
- assert payload['success'] is False
286
- assert "Invalid 'call_type'" in payload['payload']
287
-
288
-
289
- def test_get_api_key_failure_returns_500():
290
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
291
- req = DummyRequest(headers=headers, method='POST', json_data={'llm_family': 'openai', 'call_type': 'create', 'call_params': {'model': 'm'}})
292
- with patch.object(_generic, 'validate_auth_key', return_value=True), \
293
- patch.object(_generic, 'verify_firebase_token', return_value={}), \
294
- patch.object(_generic, 'get_api_key', side_effect=Exception('boom')):
295
- resp = _generic._generic_proxy(req)
296
-
297
- assert resp.status_code == 500
298
- payload = _parse_response(resp)
299
- assert payload['success'] is False
300
- assert 'Could not retrieve API key' in payload['payload']
301
-
302
-
303
- def test_parse_without_response_format_returns_400():
304
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
305
- req = DummyRequest(headers=headers, method='POST', json_data={'llm_family': 'openai', 'call_type': 'parse', 'call_params': {'model': 'm'}})
306
-
307
- class NoopOpenAI:
308
- def __init__(self, api_key=None, base_url=None, timeout=None):
309
- class Completions:
310
- def create(self, **p):
311
- return None
312
- def parse(self, **p):
313
- return None
314
- class Chat:
315
- def __init__(self):
316
- self.completions = Completions()
317
- self.chat = Chat()
318
-
319
- with patch.object(_generic, 'validate_auth_key', return_value=True), \
320
- patch.object(_generic, 'verify_firebase_token', return_value={}), \
321
- patch.object(_generic, 'get_api_key', return_value='KEY'), \
322
- patch.object(_generic.openai, 'OpenAI', NoopOpenAI):
323
- resp = _generic._generic_proxy(req)
324
-
325
- assert resp.status_code == 400
326
- payload = _parse_response(resp)
327
- assert payload['success'] is False
328
- assert "Missing 'response_format' for parse call" in payload['payload']
329
-
330
-
331
- def test_successful_create_calls_openai_and_returns_payload():
332
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
333
- req_payload = {'llm_family': 'openai', 'call_type': 'create', 'call_params': {'model': 'gem-model', 'input': 'hello'}}
334
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
335
-
336
- class FakeClient:
337
- def __init__(self, api_key, base_url=None, timeout=None):
338
- self.api_key = api_key
339
- class Completions:
340
- def create(self_inner, **params):
341
- class FakeResp:
342
- def model_dump(self_inner, mode='json'):
343
- return {'result': 'ok', 'received_model': params.get('model')}
344
- return FakeResp()
345
- class Chat:
346
- def __init__(self):
347
- self.completions = Completions()
348
- self.chat = Chat()
349
-
350
- with patch.object(_generic, 'validate_auth_key', return_value=True), \
351
- patch.object(_generic, 'verify_firebase_token', return_value={}), \
352
- patch.object(_generic, 'get_api_key', return_value='GEMINI-KEY'), \
353
- patch.object(_generic.openai, 'OpenAI', FakeClient):
354
- resp = _generic._generic_proxy(req)
355
-
356
- assert resp.status_code == 200
357
- payload = _parse_response(resp)
358
- assert payload['success'] is True
359
- assert isinstance(payload['payload'], dict)
360
- assert payload['payload']['result'] == 'ok'
361
- assert payload['payload']['received_model'] == 'gem-model'
362
-
363
-
364
- def test_openai_api_error_with_status_code_is_returned_as_error_status():
365
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
366
- req_payload = {'llm_family': 'openai', 'call_type': 'create', 'call_params': {'model': 'g', 'input': 'hey'}}
367
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
368
-
369
- # Use the module's openai.APIError so the proxy's except block catches it
370
- err = _generic.openai.APIError('rate limited')
371
- err.status_code = 502
372
-
373
- class ErrClient:
374
- def __init__(self, api_key=None, base_url=None, timeout=None):
375
- class Completions:
376
- def create(self_inner, **params):
377
- raise err
378
- class Chat:
379
- def __init__(self):
380
- self.completions = Completions()
381
- self.chat = Chat()
382
-
383
- with patch.object(_generic, 'validate_auth_key', return_value=True), \
384
- patch.object(_generic, 'verify_firebase_token', return_value={}), \
385
- patch.object(_generic, 'get_api_key', return_value='GEMINI-KEY'), \
386
- patch.object(_generic.openai, 'OpenAI', ErrClient):
387
- resp = _generic._generic_proxy(req)
388
-
389
- assert resp.status_code == 502
390
- payload = _parse_response(resp)
391
- assert payload['success'] is False
392
- assert 'OpenAI API Error' in payload['payload']
393
-
394
-
395
- def test_parse_replaces_response_format_name_with_model_and_calls_openai():
396
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
397
- req_payload = {'llm_family': 'openai', 'call_type': 'parse', 'call_params': {'model': 'gem-model', 'input': 'hello', 'response_format': 'FileSummaryModel'}}
398
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
399
-
400
- class FakeClient2:
401
- def __init__(self, api_key, base_url=None, timeout=None):
402
- class Completions:
403
- def parse(self_inner, **params):
404
- class FakeResp:
405
- def model_dump(self_inner, mode='json'):
406
- return {
407
- 'result': 'ok',
408
- 'received_model': params.get('model'),
409
- 'is_response_format_string': isinstance(params.get('response_format'), str)
410
- }
411
- return FakeResp()
412
- class Chat:
413
- def __init__(self):
414
- self.completions = Completions()
415
- self.chat = Chat()
416
-
417
- with patch.object(_generic, 'validate_auth_key', return_value=True), \
418
- patch.object(_generic, 'verify_firebase_token', return_value={}), \
419
- patch.object(_generic, 'get_api_key', return_value='GEMINI-KEY'), \
420
- patch.object(_generic.openai, 'OpenAI', FakeClient2):
421
- resp = _generic._generic_proxy(req)
422
-
423
- assert resp.status_code == 200
424
- payload = _parse_response(resp)
425
- assert payload['success'] is True
426
- assert payload['payload']['result'] == 'ok'
427
- # The proxy should have replaced the response_format string with the model (i.e., not a string)
428
- assert payload['payload']['is_response_format_string'] is False