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,352 +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 _gemini_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
- 'firebase_admin',
23
- 'firebase_admin.firestore',
24
- 'utils',
25
- 'auth',
26
- 'google',
27
- 'google.genai',
28
- 'google.genai.types',
29
- ]
30
- for name in _names_to_fake:
31
- _orig_sys_modules[name] = sys.modules.get(name)
32
-
33
- # Fake firebase_functions.https_fn.Response to capture returned data
34
- firebase_functions = types.ModuleType('firebase_functions')
35
-
36
- class FakeResponse:
37
- def __init__(self, response=None, mimetype=None, status=200, **kwargs):
38
- # Mirror the small subset of the interface tests expect
39
- self.status_code = status
40
- if isinstance(response, (dict, list)):
41
- self._body_text = json.dumps(response)
42
- else:
43
- self._body_text = '' if response is None else response
44
- self.headers = kwargs.get('headers', {})
45
-
46
- def get_data(self, as_text=False):
47
- if as_text:
48
- return self._body_text
49
- return self._body_text.encode('utf-8')
50
-
51
- firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
52
- sys.modules['firebase_functions'] = firebase_functions
53
-
54
- # Fake firebase_admin and its firestore submodule to satisfy optional imports in the proxy
55
- firebase_admin = types.ModuleType('firebase_admin')
56
- firestore_mod = types.ModuleType('firebase_admin.firestore')
57
-
58
- # Minimal Client type for annotations and a no-op client() factory
59
- class _DummyClient: # pragma: no cover - just a stub for import-time type hints
60
- pass
61
-
62
- def _dummy_client():
63
- # Return a simple object; logic using it is patched in tests
64
- return types.SimpleNamespace()
65
-
66
- # Decorator stub used by perform_atomic_debit; never actually invoked in tests
67
- def _transactional(fn=None):
68
- def wrapper(*args, **kwargs):
69
- return fn(*args, **kwargs)
70
- return wrapper
71
-
72
- # Populate stubs on the firestore module
73
- setattr(firestore_mod, 'Client', _DummyClient)
74
- setattr(firestore_mod, 'client', _dummy_client)
75
- setattr(firestore_mod, 'transactional', _transactional)
76
- setattr(firestore_mod, 'SERVER_TIMESTAMP', object())
77
-
78
- # Attach firestore submodule to firebase_admin package and register in sys.modules
79
- setattr(firebase_admin, 'firestore', firestore_mod)
80
- sys.modules['firebase_admin'] = firebase_admin
81
- sys.modules['firebase_admin.firestore'] = firestore_mod
82
-
83
- # Fake utils module (provides functions imported by _gemini_proxy)
84
- utils_mod = types.ModuleType('utils')
85
-
86
-
87
- def _fake_create_json_response(success: bool, payload: any, status_code: int):
88
- response_body = {"success": success, "payload": payload}
89
- return firebase_functions.https_fn.Response(response=json.dumps(response_body), status=status_code, headers={'Content-Type': 'application/json'})
90
-
91
-
92
- def _fake_get_api_key(llm_family=None):
93
- return 'DUMMY_GEMINI_KEY'
94
-
95
-
96
- def _fake_verify_firebase_token(req):
97
- # Default: no-op (successful)
98
- return {}
99
-
100
-
101
- utils_mod.create_json_response = _fake_create_json_response
102
- utils_mod.get_api_key = _fake_get_api_key
103
- utils_mod.verify_firebase_token = _fake_verify_firebase_token
104
- sys.modules['utils'] = utils_mod
105
-
106
- # Fake auth module with validate_auth_key
107
- auth_mod = types.ModuleType('auth')
108
-
109
-
110
- def _fake_validate_auth_key(val: str) -> bool:
111
- return True
112
-
113
-
114
- auth_mod.validate_auth_key = _fake_validate_auth_key
115
- sys.modules['auth'] = auth_mod
116
-
117
- # Fake google.genai and types modules
118
- google_mod = types.ModuleType('google')
119
- genai_mod = types.ModuleType('google.genai')
120
- types_mod = types.ModuleType('google.genai.types')
121
-
122
- class DummyGenerateContentConfig:
123
- def __init__(self, **kwargs):
124
- self.kwargs = kwargs
125
-
126
- class DummyContent:
127
- def __init__(self, **kwargs):
128
- self.kwargs = kwargs
129
-
130
- types_mod.GenerateContentConfig = DummyGenerateContentConfig
131
- types_mod.Content = DummyContent
132
-
133
- # Provide a dummy Client implementation; tests will patch this when needed
134
- class DummyClient:
135
- def __init__(self, api_key):
136
- self.api_key = api_key
137
- class Models:
138
- def generate_content(self, **params):
139
- raise NotImplementedError("generate_content not implemented for dummy client")
140
- self.models = Models()
141
-
142
- genai_mod.Client = DummyClient
143
- # Also expose types on genai module (in case of attribute access)
144
- genai_mod.types = types_mod
145
-
146
- google_mod.genai = genai_mod
147
- sys.modules['google'] = google_mod
148
- sys.modules['google.genai'] = genai_mod
149
- sys.modules['google.genai.types'] = types_mod
150
-
151
- # Import the module under test after preparing the fake imports
152
- # Resolve the actual repository 'functions' directory (tests files live under tests/, so climb up to repo root)
153
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
154
- functions_root = os.path.join(repo_root, 'functions')
155
- module_path = os.path.join(functions_root, 'api_v1_00', '_gemini_proxy.py')
156
- spec = importlib.util.spec_from_file_location('api_v1_00._gemini_proxy', module_path)
157
- _gemini = importlib.util.module_from_spec(spec)
158
- spec.loader.exec_module(_gemini)
159
-
160
- # Restore original sys.modules mappings to avoid side-effects for other tests
161
- for name, orig in _orig_sys_modules.items():
162
- if orig is None:
163
- try:
164
- del sys.modules[name]
165
- except KeyError:
166
- pass
167
- else:
168
- sys.modules[name] = orig
169
-
170
- # Helper request stub used in tests
171
- class DummyRequest:
172
- def __init__(self, headers=None, method='POST', json_data=None, raise_on_get_json=False):
173
- self.headers = headers or {}
174
- self.method = method
175
- self._json_data = json_data
176
- self._raise = raise_on_get_json
177
-
178
- def get_json(self, silent=False):
179
- if self._raise:
180
- raise Exception('Malformed JSON')
181
- return self._json_data
182
-
183
-
184
- def _parse_response(resp):
185
- data = resp.get_data(as_text=True)
186
- return json.loads(data)
187
-
188
-
189
- # -----------------------------
190
- # Tests
191
- # -----------------------------
192
-
193
-
194
- def test_verify_firebase_token_failure_returns_401():
195
- req = DummyRequest(headers={_gemini.AUTH_HEADER_NAME: 'ok'}, method='POST')
196
- with patch.object(_gemini, 'verify_firebase_token', side_effect=Exception('invalid token')):
197
- resp = _gemini._gemini_proxy(req)
198
-
199
- assert resp.status_code == 401
200
- payload = _parse_response(resp)
201
- assert payload['success'] is False
202
- assert 'Unauthorized' in payload['payload']
203
-
204
-
205
- def test_missing_auth_header_returns_401():
206
- # no app auth header
207
- req = DummyRequest(headers={}, method='POST', json_data={'model': 'm', 'contents': 'c', 'config': {}})
208
- with patch.object(_gemini, 'validate_auth_key', return_value=True), patch.object(_gemini, 'verify_firebase_token', return_value={}):
209
- resp = _gemini._gemini_proxy(req)
210
-
211
- assert resp.status_code == 401
212
- payload = _parse_response(resp)
213
- assert payload['success'] is False
214
- assert 'Missing app authentication key' in payload['payload']
215
-
216
-
217
- def test_invalid_auth_key_returns_403():
218
- headers = {_gemini.AUTH_HEADER_NAME: 'bad'}
219
- req = DummyRequest(headers=headers, method='POST', json_data={'model': 'm', 'contents': 'c', 'config': {}})
220
- with patch.object(_gemini, 'validate_auth_key', return_value=False), patch.object(_gemini, 'verify_firebase_token', return_value={}):
221
- resp = _gemini._gemini_proxy(req)
222
-
223
- assert resp.status_code == 403
224
- payload = _parse_response(resp)
225
- assert payload['success'] is False
226
- assert 'Invalid app authentication key' in payload['payload']
227
-
228
-
229
- def test_non_post_method_returns_405():
230
- headers = {_gemini.AUTH_HEADER_NAME: 'ok'}
231
- req = DummyRequest(headers=headers, method='GET')
232
- with patch.object(_gemini, 'validate_auth_key', return_value=True), \
233
- patch.object(_gemini, 'verify_firebase_token', return_value={}), \
234
- patch.object(_gemini, 'ensure_balance_positive', return_value=(True, 100.0)):
235
- resp = _gemini._gemini_proxy(req)
236
-
237
- assert resp.status_code == 405
238
- payload = _parse_response(resp)
239
- assert payload['success'] is False
240
- assert 'POST method required' in payload['payload']
241
-
242
-
243
- def test_malformed_json_returns_400():
244
- headers = {_gemini.AUTH_HEADER_NAME: 'ok'}
245
- req = DummyRequest(headers=headers, method='POST', raise_on_get_json=True)
246
- with patch.object(_gemini, 'validate_auth_key', return_value=True), \
247
- patch.object(_gemini, 'verify_firebase_token', return_value={}), \
248
- patch.object(_gemini, 'ensure_balance_positive', return_value=(True, 100.0)):
249
- resp = _gemini._gemini_proxy(req)
250
-
251
- assert resp.status_code == 400
252
- payload = _parse_response(resp)
253
- assert payload['success'] is False
254
- assert 'Invalid JSON payload' in payload['payload']
255
-
256
-
257
- def test_rehydrate_params_error_returns_400():
258
- headers = {_gemini.AUTH_HEADER_NAME: 'ok'}
259
- # missing 'model' to cause KeyError inside rehydrate_params
260
- req = DummyRequest(headers=headers, method='POST', json_data={'contents': 'x', 'config': {}})
261
- with patch.object(_gemini, 'validate_auth_key', return_value=True), \
262
- patch.object(_gemini, 'verify_firebase_token', return_value={}), \
263
- patch.object(_gemini, 'ensure_balance_positive', return_value=(True, 100.0)):
264
- resp = _gemini._gemini_proxy(req)
265
-
266
- assert resp.status_code == 400
267
- payload = _parse_response(resp)
268
- assert payload['success'] is False
269
- assert 'Error hydrating params' in payload['payload']
270
-
271
-
272
- def test_get_api_key_failure_returns_500():
273
- headers = {_gemini.AUTH_HEADER_NAME: 'ok'}
274
- req = DummyRequest(headers=headers, method='POST', json_data={'model': 'g', 'contents': 'hi', 'config': {}})
275
- with patch.object(_gemini, 'validate_auth_key', return_value=True), \
276
- patch.object(_gemini, 'verify_firebase_token', return_value={}), \
277
- patch.object(_gemini, 'ensure_balance_positive', return_value=(True, 100.0)), \
278
- patch.object(_gemini, 'get_api_key', side_effect=Exception('boom')):
279
- resp = _gemini._gemini_proxy(req)
280
-
281
- assert resp.status_code == 500
282
- payload = _parse_response(resp)
283
- assert payload['success'] is False
284
- assert 'Could not retrieve API key' in payload['payload']
285
-
286
-
287
- def test_successful_flow_calls_genai_and_returns_payload():
288
- headers = {_gemini.AUTH_HEADER_NAME: 'ok'}
289
- req_payload = {'model': 'gem-model', 'contents': 'hello', 'config': {}}
290
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
291
-
292
- class FakeClient:
293
- def __init__(self, api_key):
294
- self.api_key = api_key
295
- self.models = self
296
-
297
- def generate_content(self, **params):
298
- class FakeResp:
299
- def __init__(self):
300
- # include usage metadata expected by get_usage()
301
- self.usage_metadata = types.SimpleNamespace(prompt_token_count=1, total_token_count=2)
302
-
303
- def to_json_dict(self_inner):
304
- return { 'result': 'ok', 'received_model': params.get('model') }
305
- return FakeResp()
306
-
307
- with patch.object(_gemini, 'validate_auth_key', return_value=True), \
308
- patch.object(_gemini, 'verify_firebase_token', return_value={}), \
309
- patch.object(_gemini, 'ensure_balance_positive', return_value=(True, 100.0)), \
310
- patch.object(_gemini, 'bill_with_retry', return_value=("ok", 99.5)), \
311
- patch.object(_gemini, 'get_api_key', return_value='GEMINI-KEY'), \
312
- patch.object(_gemini.genai, 'Client', FakeClient):
313
- resp = _gemini._gemini_proxy(req)
314
-
315
- assert resp.status_code == 200
316
- payload = _parse_response(resp)
317
- assert payload['success'] is True
318
- assert isinstance(payload['payload'], dict)
319
- assert payload['payload']['result'] == 'ok'
320
- assert payload['payload']['received_model'] == 'gem-model'
321
-
322
-
323
- def test_genai_exception_with_status_code_is_returned_as_error_status():
324
- headers = {_gemini.AUTH_HEADER_NAME: 'ok'}
325
- req_payload = {'model': 'g', 'contents': 'hey', 'config': {}}
326
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
327
-
328
- class CustomErr(Exception):
329
- pass
330
-
331
- err = CustomErr('rate limited')
332
- err.status_code = 502
333
-
334
- class ErrClient:
335
- def __init__(self, api_key):
336
- self.api_key = api_key
337
- self.models = self
338
-
339
- def generate_content(self, **params):
340
- raise err
341
-
342
- with patch.object(_gemini, 'validate_auth_key', return_value=True), \
343
- patch.object(_gemini, 'verify_firebase_token', return_value={}), \
344
- patch.object(_gemini, 'ensure_balance_positive', return_value=(True, 100.0)), \
345
- patch.object(_gemini, 'get_api_key', return_value='GEMINI-KEY'), \
346
- patch.object(_gemini.genai, 'Client', ErrClient):
347
- resp = _gemini._gemini_proxy(req)
348
-
349
- assert resp.status_code == 502
350
- payload = _parse_response(resp)
351
- assert payload['success'] is False
352
- assert 'Internal Server Error' in payload['payload']