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.
- roksta/__init__.cpython-311-darwin.so +0 -0
- roksta/ai/__init__.cpython-311-darwin.so +0 -0
- roksta/ai/call_ai.cpython-311-darwin.so +0 -0
- roksta/ai/gemini.cpython-311-darwin.so +0 -0
- roksta/ai/generic.cpython-311-darwin.so +0 -0
- roksta/ai/llm.cpython-311-darwin.so +0 -0
- roksta/ai/openai.cpython-311-darwin.so +0 -0
- roksta/ai/tools/__init__.cpython-311-darwin.so +0 -0
- roksta/ai/tools/delete_file.cpython-311-darwin.so +0 -0
- roksta/ai/tools/edit_file.cpython-311-darwin.so +0 -0
- roksta/ai/tools/final_response.cpython-311-darwin.so +0 -0
- roksta/ai/tools/get_file_summaries.cpython-311-darwin.so +0 -0
- roksta/ai/tools/read_file.cpython-311-darwin.so +0 -0
- roksta/ai/tools/regex_replace.cpython-311-darwin.so +0 -0
- roksta/ai/tools/shell_any.cpython-311-darwin.so +0 -0
- roksta/ai/tools/shell_limited.cpython-311-darwin.so +0 -0
- roksta/ai/tools/tool_defs.cpython-311-darwin.so +0 -0
- roksta/ai/tools/tool_utils.cpython-311-darwin.so +0 -0
- roksta/ai/tools/web_fetch.cpython-311-darwin.so +0 -0
- roksta/ai/tools/write_file.cpython-311-darwin.so +0 -0
- roksta/analytics.cpython-311-darwin.so +0 -0
- roksta/balance.cpython-311-darwin.so +0 -0
- roksta/build_project.cpython-311-darwin.so +0 -0
- roksta/chat_workflow.cpython-311-darwin.so +0 -0
- roksta/check_for_updates.cpython-311-darwin.so +0 -0
- roksta/check_subtask_sequence.cpython-311-darwin.so +0 -0
- roksta/checkpoints.cpython-311-darwin.so +0 -0
- roksta/clarify_goal.cpython-311-darwin.so +0 -0
- roksta/codebase_listing.cpython-311-darwin.so +0 -0
- roksta/command_handlers/__init__.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_activate_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_add_funds_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_auto_charge_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_auto_commit_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_building_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_chat_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_dev_rate_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_feedback_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_goal_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_help_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_init_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_linting_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_login_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_logout_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_payment_details_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_quit_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_redeem_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_request_activation_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_testing_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_usage_command.cpython-311-darwin.so +0 -0
- roksta/create_default_config.cpython-311-darwin.so +0 -0
- roksta/create_default_ignore_file.cpython-311-darwin.so +0 -0
- roksta/default_config.cpython-311-darwin.so +0 -0
- roksta/default_ignores.cpython-311-darwin.so +0 -0
- roksta/discover_test_command.cpython-311-darwin.so +0 -0
- roksta/enums.cpython-311-darwin.so +0 -0
- roksta/env.cpython-311-darwin.so +0 -0
- roksta/extended_text_area.cpython-311-darwin.so +0 -0
- roksta/firebase.cpython-311-darwin.so +0 -0
- roksta/firebase_auth_web.cpython-311-darwin.so +0 -0
- roksta/firebase_config.cpython-311-darwin.so +0 -0
- roksta/fix_tests.cpython-311-darwin.so +0 -0
- roksta/gen_codebase_summaries.cpython-311-darwin.so +0 -0
- roksta/gen_one_line_goal.cpython-311-darwin.so +0 -0
- roksta/gen_subtasks.cpython-311-darwin.so +0 -0
- roksta/get_codebase_structure.cpython-311-darwin.so +0 -0
- roksta/get_failing_tests.cpython-311-darwin.so +0 -0
- roksta/goal_workflow.cpython-311-darwin.so +0 -0
- roksta/init_codebase.cpython-311-darwin.so +0 -0
- roksta/lint_code.cpython-311-darwin.so +0 -0
- roksta/logger.cpython-311-darwin.so +0 -0
- roksta/main.cpython-311-darwin.so +0 -0
- roksta/make_issue.cpython-311-darwin.so +0 -0
- roksta/new_features.cpython-311-darwin.so +0 -0
- roksta/parse_directive_cli_tokens.cpython-311-darwin.so +0 -0
- roksta/parse_readme.cpython-311-darwin.so +0 -0
- roksta/propose_solution.cpython-311-darwin.so +0 -0
- roksta/response_formats.cpython-311-darwin.so +0 -0
- roksta/rewrite_goal.cpython-311-darwin.so +0 -0
- roksta/roksta.cpython-311-darwin.so +0 -0
- roksta/run_cli_goal.cpython-311-darwin.so +0 -0
- roksta/save_chat_transcript.cpython-311-darwin.so +0 -0
- roksta/select_files.cpython-311-darwin.so +0 -0
- roksta/tips.cpython-311-darwin.so +0 -0
- roksta/utils.cpython-311-darwin.so +0 -0
- roksta/write_code.cpython-311-darwin.so +0 -0
- roksta-0.3.8.dist-info/METADATA +471 -0
- roksta-0.3.8.dist-info/RECORD +91 -0
- {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/top_level.txt +0 -1
- roksta-0.3.2.dist-info/METADATA +0 -40
- roksta-0.3.2.dist-info/RECORD +0 -121
- tests/__init__.py +0 -2
- tests/conftest.py +0 -211
- tests/functions/__init__.py +0 -2
- tests/functions/api_v1_00/__init__.py +0 -2
- tests/functions/api_v1_00/test__analytics.py +0 -416
- tests/functions/api_v1_00/test__gemini_proxy.py +0 -352
- tests/functions/api_v1_00/test__generic_proxy.py +0 -428
- tests/functions/api_v1_00/test__get_payment_details.py +0 -356
- tests/functions/api_v1_00/test__openai_proxy.py +0 -449
- tests/functions/api_v1_00/test__redeem_credit_code.py +0 -167
- tests/functions/api_v1_00/test__sync_emails.py +0 -325
- tests/functions/api_v1_00/test__take_payment.py +0 -491
- tests/functions/api_v1_00/test__use_activation_code.py +0 -438
- tests/functions/api_v1_01/__init__.py +0 -2
- tests/functions/api_v1_01/test__analytics.py +0 -416
- tests/functions/api_v1_01/test__gemini_proxy.py +0 -352
- tests/functions/api_v1_01/test__generic_proxy.py +0 -428
- tests/functions/api_v1_01/test__get_payment_details.py +0 -356
- tests/functions/api_v1_01/test__openai_proxy.py +0 -449
- tests/functions/api_v1_01/test__redeem_credit_code.py +0 -167
- tests/functions/api_v1_01/test__sync_emails.py +0 -325
- tests/functions/api_v1_01/test__take_payment.py +0 -491
- tests/functions/api_v1_01/test__use_activation_code.py +0 -438
- tests/functions/api_v1_02/__init__.py +0 -2
- tests/functions/api_v1_02/test__analytics.py +0 -416
- tests/functions/api_v1_02/test__gemini_proxy.py +0 -352
- tests/functions/api_v1_02/test__generic_proxy.py +0 -428
- tests/functions/api_v1_02/test__get_payment_details.py +0 -356
- tests/functions/api_v1_02/test__openai_proxy.py +0 -449
- tests/functions/api_v1_02/test__redeem_credit_code.py +0 -167
- tests/functions/api_v1_02/test__sync_emails.py +0 -325
- tests/functions/api_v1_02/test__take_payment.py +0 -491
- tests/functions/api_v1_02/test__use_activation_code.py +0 -438
- tests/functions/api_v1_02/test_proxy_keyword_replacement.py +0 -557
- tests/functions/api_v1_02/test_replace_keywords.py +0 -74
- tests/functions/test_auth.py +0 -24
- tests/functions/test_main.py +0 -73
- tests/functions/test_utils.py +0 -484
- {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/WHEEL +0 -0
- {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
|