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,557 +0,0 @@
1
- import os
2
- import sys
3
- import json
4
- import types
5
- import importlib
6
- import importlib.util
7
- from unittest.mock import patch
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
-
16
- # Helper: Fake firebase_functions.Response used to capture returned data
17
- class FakeResponse:
18
- def __init__(self, response=None, mimetype=None, status=200, **kwargs):
19
- self.status_code = status
20
- if isinstance(response, (dict, list)):
21
- self._body_text = json.dumps(response)
22
- else:
23
- self._body_text = '' if response is None else response
24
- self.headers = kwargs.get('headers', {})
25
-
26
- def get_data(self, as_text=False):
27
- if as_text:
28
- return self._body_text
29
- return self._body_text.encode('utf-8')
30
-
31
-
32
- class DummyRequest:
33
- def __init__(self, headers=None, method='POST', json_data=None, raise_on_get_json=False):
34
- self.headers = headers or {}
35
- self.method = method
36
- self._json_data = json_data
37
- self._raise = raise_on_get_json
38
-
39
- def get_json(self, silent=False):
40
- if self._raise:
41
- raise Exception('Malformed JSON')
42
- return self._json_data
43
-
44
-
45
- def _parse_response(resp):
46
- data = resp.get_data(as_text=True)
47
- return json.loads(data)
48
-
49
-
50
- # -----------------------------
51
- # Tests for OpenAI proxy (v1_02)
52
- # -----------------------------
53
-
54
-
55
- def test_openai_create_replacement_applied_to_input():
56
- # Prepare fake modules to satisfy imports inside _openai_proxy
57
- _orig_sys_modules = {}
58
- names_to_fake = ['firebase_functions', 'utils', 'auth', 'openai', 'firebase_admin', 'billing']
59
- for name in names_to_fake:
60
- _orig_sys_modules[name] = sys.modules.get(name)
61
-
62
- # Fake firebase_functions
63
- firebase_functions = types.ModuleType('firebase_functions')
64
- firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
65
- sys.modules['firebase_functions'] = firebase_functions
66
-
67
- # Fake utils (create_json_response etc.)
68
- utils_mod = types.ModuleType('utils')
69
-
70
- def _fake_create_json_response(success: bool, payload: any, status_code: int):
71
- response_body = {"success": success, "payload": payload}
72
- return FakeResponse(response=json.dumps(response_body), status=status_code, headers={'Content-Type': 'application/json'})
73
-
74
- def _fake_get_api_key(llm_family=None):
75
- return 'DUMMY_KEY'
76
-
77
- def _fake_verify_firebase_token(req):
78
- return {}
79
-
80
- def _fake_get_request_json(req, strict=False):
81
- return req.get_json(silent=not strict)
82
-
83
- utils_mod.create_json_response = _fake_create_json_response
84
- utils_mod.get_api_key = _fake_get_api_key
85
- utils_mod.verify_firebase_token = _fake_verify_firebase_token
86
- utils_mod.get_request_json = _fake_get_request_json
87
- sys.modules['utils'] = utils_mod
88
-
89
- # Fake auth
90
- auth_mod = types.ModuleType('auth')
91
- auth_mod.validate_auth_key = lambda v: True
92
- sys.modules['auth'] = auth_mod
93
-
94
- # Fake openai module - will be patched into module under test
95
- openai_mod = types.ModuleType('openai')
96
- class DummyOpenAI:
97
- def __init__(self, api_key=None, timeout=None):
98
- self.api_key = api_key
99
- self.timeout = timeout
100
- self.responses = self
101
- def create(self, **params):
102
- raise NotImplementedError()
103
- def parse(self, **params):
104
- raise NotImplementedError()
105
- openai_mod.OpenAI = DummyOpenAI
106
- openai_mod.APIError = Exception
107
- sys.modules['openai'] = openai_mod
108
-
109
- # Fake billing
110
- billing_mod = types.ModuleType('billing')
111
- billing_mod.ensure_balance_positive = lambda db, user_id: (True, 100.0)
112
- billing_mod.calculate_cost = lambda model_id, in_t, out_t: 0.0
113
- billing_mod.bill_with_retry = lambda db, user_id, model_id, usage, cost, reason='usage': ("ok", 100.0)
114
- sys.modules['billing'] = billing_mod
115
-
116
- # Fake firebase_admin
117
- firebase_admin_mod = types.ModuleType('firebase_admin')
118
- firebase_admin_mod.firestore = types.SimpleNamespace(client=lambda: types.SimpleNamespace(), Client=type('Client', (), {}))
119
- sys.modules['firebase_admin'] = firebase_admin_mod
120
-
121
- # Import the package and module under test
122
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
123
- functions_root = os.path.join(repo_root, 'functions')
124
- pkg_init = os.path.join(functions_root, 'api_v1_02', '__init__.py')
125
- spec_pkg = importlib.util.spec_from_file_location('api_v1_02', pkg_init)
126
- pkg = importlib.util.module_from_spec(spec_pkg)
127
- spec_pkg.loader.exec_module(pkg)
128
- sys.modules['api_v1_02'] = pkg
129
-
130
- module_path = os.path.join(functions_root, 'api_v1_02', '_openai_proxy.py')
131
- spec = importlib.util.spec_from_file_location('api_v1_02._openai_proxy', module_path)
132
- _openai = importlib.util.module_from_spec(spec)
133
- spec.loader.exec_module(_openai)
134
-
135
- # Cleanup sys.modules fakes after import
136
- for name, orig in _orig_sys_modules.items():
137
- if orig is None:
138
- try:
139
- del sys.modules[name]
140
- except KeyError:
141
- pass
142
- else:
143
- sys.modules[name] = orig
144
-
145
- # Prepare request with keyword in input
146
- headers = {_openai.AUTH_HEADER_NAME: 'ok'}
147
- req_payload = {'call_type': 'create', 'call_params': {'model': 'gem-model', 'input': 'Hello ==SELECT_FILE_INSTRUCTIONS== world'}}
148
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
149
-
150
- # Fake client to capture params passed to create
151
- class FakeClient:
152
- last_create_params = None
153
- def __init__(self, api_key, timeout=None):
154
- self.api_key = api_key
155
- self.timeout = timeout
156
- self.responses = self
157
- def create(self, **params):
158
- FakeClient.last_create_params = params
159
- class FakeResp:
160
- def model_dump(self_inner, mode='json'):
161
- return {'result': 'ok', 'received_model': params.get('model'), 'usage': {'input_tokens': 1, 'output_tokens': 2}}
162
- return FakeResp()
163
-
164
- with patch.object(_openai, 'validate_auth_key', return_value=True), \
165
- patch.object(_openai, 'verify_firebase_token', return_value={}), \
166
- patch.object(_openai, 'get_api_key', return_value='OPENAI-KEY'), \
167
- patch.object(_openai.openai, 'OpenAI', FakeClient):
168
- resp = _openai._openai_proxy(req)
169
-
170
- payload = _parse_response(resp)
171
- assert resp.status_code == 200
172
- assert payload['success'] is True
173
- assert FakeClient.last_create_params is not None
174
- inp = str(FakeClient.last_create_params.get('input', ''))
175
- assert ('Some instructions for selecting files' in inp) or ('files in my codebase' in inp)
176
-
177
-
178
- def test_openai_parse_replacement_applied_to_input():
179
- # Similar setup as create test
180
- _orig_sys_modules = {}
181
- names_to_fake = ['firebase_functions', 'utils', 'auth', 'openai', 'firebase_admin', 'billing']
182
- for name in names_to_fake:
183
- _orig_sys_modules[name] = sys.modules.get(name)
184
-
185
- firebase_functions = types.ModuleType('firebase_functions')
186
- firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
187
- sys.modules['firebase_functions'] = firebase_functions
188
-
189
- utils_mod = types.ModuleType('utils')
190
- def _fake_create_json_response(success: bool, payload: any, status_code: int):
191
- response_body = {"success": success, "payload": payload}
192
- return FakeResponse(response=json.dumps(response_body), status=status_code, headers={'Content-Type': 'application/json'})
193
- def _fake_get_api_key(llm_family=None):
194
- return 'DUMMY_KEY'
195
- def _fake_verify_firebase_token(req):
196
- return {}
197
- def _fake_get_request_json(req, strict=False):
198
- return req.get_json(silent=not strict)
199
- utils_mod.create_json_response = _fake_create_json_response
200
- utils_mod.get_api_key = _fake_get_api_key
201
- utils_mod.verify_firebase_token = _fake_verify_firebase_token
202
- utils_mod.get_request_json = _fake_get_request_json
203
- sys.modules['utils'] = utils_mod
204
-
205
- auth_mod = types.ModuleType('auth')
206
- auth_mod.validate_auth_key = lambda v: True
207
- sys.modules['auth'] = auth_mod
208
-
209
- openai_mod = types.ModuleType('openai')
210
- class DummyOpenAI:
211
- def __init__(self, api_key=None, timeout=None):
212
- self.api_key = api_key
213
- self.timeout = timeout
214
- self.responses = self
215
- def create(self, **params):
216
- raise NotImplementedError()
217
- def parse(self, **params):
218
- raise NotImplementedError()
219
- openai_mod.OpenAI = DummyOpenAI
220
- openai_mod.APIError = Exception
221
- sys.modules['openai'] = openai_mod
222
-
223
- billing_mod = types.ModuleType('billing')
224
- billing_mod.ensure_balance_positive = lambda db, user_id: (True, 100.0)
225
- billing_mod.calculate_cost = lambda model_id, in_t, out_t: 0.0
226
- billing_mod.bill_with_retry = lambda db, user_id, model_id, usage, cost, reason='usage': ("ok", 100.0)
227
- sys.modules['billing'] = billing_mod
228
-
229
- firebase_admin_mod = types.ModuleType('firebase_admin')
230
- firebase_admin_mod.firestore = types.SimpleNamespace(client=lambda: types.SimpleNamespace(), Client=type('Client', (), {}))
231
- sys.modules['firebase_admin'] = firebase_admin_mod
232
-
233
- # Import package and module
234
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
235
- functions_root = os.path.join(repo_root, 'functions')
236
- pkg_init = os.path.join(functions_root, 'api_v1_02', '__init__.py')
237
- spec_pkg = importlib.util.spec_from_file_location('api_v1_02', pkg_init)
238
- pkg = importlib.util.module_from_spec(spec_pkg)
239
- spec_pkg.loader.exec_module(pkg)
240
- sys.modules['api_v1_02'] = pkg
241
-
242
- module_path = os.path.join(functions_root, 'api_v1_02', '_openai_proxy.py')
243
- spec = importlib.util.spec_from_file_location('api_v1_02._openai_proxy', module_path)
244
- _openai = importlib.util.module_from_spec(spec)
245
- spec.loader.exec_module(_openai)
246
-
247
- for name, orig in _orig_sys_modules.items():
248
- if orig is None:
249
- try:
250
- del sys.modules[name]
251
- except KeyError:
252
- pass
253
- else:
254
- sys.modules[name] = orig
255
-
256
- headers = {_openai.AUTH_HEADER_NAME: 'ok'}
257
- req_payload = {'call_type': 'parse', 'call_params': {'model': 'gem-model', 'input': 'Hello ==SELECT_FILE_INSTRUCTIONS==', 'text_format': 'FileSummaryModel'}}
258
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
259
-
260
- class FakeClient2:
261
- last_parse_params = None
262
- def __init__(self, api_key, timeout=None):
263
- self.api_key = api_key
264
- self.timeout = timeout
265
- self.responses = self
266
- def parse(self, **params):
267
- FakeClient2.last_parse_params = params
268
- class FakeOutputParsed:
269
- def __init__(self, data):
270
- self._data = data
271
- def dict(self):
272
- return self._data
273
- class FakeResp:
274
- def __init__(self, data):
275
- self.output_parsed = FakeOutputParsed(data)
276
- def model_dump(self_inner, mode='json'):
277
- return {'usage': {'input_tokens': 1, 'output_tokens': 2}}
278
- parsed = {'result': 'ok', 'received_model': params.get('model')}
279
- return FakeResp(parsed)
280
-
281
- with patch.object(_openai, 'validate_auth_key', return_value=True), \
282
- patch.object(_openai, 'verify_firebase_token', return_value={}), \
283
- patch.object(_openai, 'get_api_key', return_value='OPENAI-KEY'), \
284
- patch.object(_openai.openai, 'OpenAI', FakeClient2):
285
- resp = _openai._openai_proxy(req)
286
-
287
- payload = _parse_response(resp)
288
- assert resp.status_code == 200
289
- assert payload['success'] is True
290
- assert FakeClient2.last_parse_params is not None
291
- inp = str(FakeClient2.last_parse_params.get('input', ''))
292
- assert ('Some instructions for selecting files' in inp) or ('files in my codebase' in inp)
293
-
294
-
295
- # -----------------------------
296
- # Tests for Generic proxy (v1_02)
297
- # -----------------------------
298
-
299
-
300
- def test_generic_create_replacement_applied_to_input():
301
- _orig_sys_modules = {}
302
- names_to_fake = ['firebase_functions', 'utils', 'auth', 'openai', 'firebase_admin', 'billing']
303
- for name in names_to_fake:
304
- _orig_sys_modules[name] = sys.modules.get(name)
305
-
306
- firebase_functions = types.ModuleType('firebase_functions')
307
- firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
308
- sys.modules['firebase_functions'] = firebase_functions
309
-
310
- utils_mod = types.ModuleType('utils')
311
- def _fake_create_json_response(success: bool, payload: any, status_code: int):
312
- response_body = {"success": success, "payload": payload}
313
- return FakeResponse(response=json.dumps(response_body), status=status_code, headers={'Content-Type': 'application/json'})
314
- def _fake_get_api_key(llm_family=None):
315
- return 'DUMMY_KEY'
316
- def _fake_verify_firebase_token(req):
317
- return {}
318
- def _fake_get_request_json(req, strict=False):
319
- return req.get_json(silent=not strict)
320
- utils_mod.create_json_response = _fake_create_json_response
321
- utils_mod.get_api_key = _fake_get_api_key
322
- utils_mod.verify_firebase_token = _fake_verify_firebase_token
323
- utils_mod.get_request_json = _fake_get_request_json
324
- sys.modules['utils'] = utils_mod
325
-
326
- auth_mod = types.ModuleType('auth')
327
- auth_mod.validate_auth_key = lambda v: True
328
- sys.modules['auth'] = auth_mod
329
-
330
- # Fake openai client
331
- openai_mod = types.ModuleType('openai')
332
- class DummyOpenAI:
333
- def __init__(self, base_url=None, api_key=None, timeout=None):
334
- self.base_url = base_url
335
- self.api_key = api_key
336
- self.timeout = timeout
337
- class Completions:
338
- def create(self, **params):
339
- raise NotImplementedError()
340
- def parse(self, **params):
341
- raise NotImplementedError()
342
- class Chat:
343
- def __init__(self):
344
- self.completions = Completions()
345
- self.chat = Chat()
346
- openai_mod.OpenAI = DummyOpenAI
347
- openai_mod.APIError = Exception
348
- sys.modules['openai'] = openai_mod
349
-
350
- # Fake billing
351
- billing_mod = types.ModuleType('billing')
352
- billing_mod.ensure_balance_positive = lambda db, user_id: (True, 100.0)
353
- billing_mod.calculate_cost = lambda model_id, in_t, out_t: 0.0
354
- billing_mod.bill_with_retry = lambda db, user_id, model_id, usage, cost, reason='usage': ("ok", 100.0)
355
- sys.modules['billing'] = billing_mod
356
-
357
- # Fake firebase_admin
358
- firebase_admin_mod = types.ModuleType('firebase_admin')
359
- firebase_admin_mod.firestore = types.SimpleNamespace(client=lambda: types.SimpleNamespace(), Client=type('Client', (), {}))
360
- sys.modules['firebase_admin'] = firebase_admin_mod
361
-
362
- # Import package and module
363
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
364
- functions_root = os.path.join(repo_root, 'functions')
365
- pkg_init = os.path.join(functions_root, 'api_v1_02', '__init__.py')
366
- spec_pkg = importlib.util.spec_from_file_location('api_v1_02', pkg_init)
367
- pkg = importlib.util.module_from_spec(spec_pkg)
368
- spec_pkg.loader.exec_module(pkg)
369
- sys.modules['api_v1_02'] = pkg
370
-
371
- module_path = os.path.join(functions_root, 'api_v1_02', '_generic_proxy.py')
372
- spec = importlib.util.spec_from_file_location('api_v1_02._generic_proxy', module_path)
373
- _generic = importlib.util.module_from_spec(spec)
374
- spec.loader.exec_module(_generic)
375
-
376
- for name, orig in _orig_sys_modules.items():
377
- if orig is None:
378
- try:
379
- del sys.modules[name]
380
- except KeyError:
381
- pass
382
- else:
383
- sys.modules[name] = orig
384
-
385
- headers = {_generic.AUTH_HEADER_NAME: 'ok'}
386
- req_payload = {'llm_family': 'openai', 'call_type': 'create', 'call_params': {'model': 'gem-model', 'input': 'Hello ==SELECT_FILE_INSTRUCTIONS=='}}
387
- req = DummyRequest(headers=headers, method='POST', json_data=req_payload)
388
-
389
- class FakeClient:
390
- last_create_params = None
391
- def __init__(self, base_url=None, api_key=None, timeout=None):
392
- self.base_url = base_url
393
- self.api_key = api_key
394
- class Completions:
395
- def create(self_inner, **params):
396
- FakeClient.last_create_params = params
397
- class FakeResp:
398
- def model_dump(self_inner2, mode='json'):
399
- return {'result': 'ok', 'received_model': params.get('model'), 'usage': {'prompt_tokens': 1, 'completion_tokens': 2}}
400
- return FakeResp()
401
- def parse(self_inner, **params):
402
- raise NotImplementedError()
403
- class Chat:
404
- def __init__(self):
405
- self.completions = Completions()
406
- self.chat = Chat()
407
-
408
- with patch.object(_generic, 'validate_auth_key', return_value=True), \
409
- patch.object(_generic, 'verify_firebase_token', return_value={}), \
410
- patch.object(_generic, 'get_api_key', return_value='GENERIC-KEY'), \
411
- patch.object(_generic.openai, 'OpenAI', FakeClient):
412
- resp = _generic._generic_proxy(req)
413
-
414
- payload = _parse_response(resp)
415
- assert resp.status_code == 200
416
- assert payload['success'] is True
417
- assert FakeClient.last_create_params is not None
418
- inp = str(FakeClient.last_create_params.get('input', ''))
419
- assert ('Some instructions for selecting files' in inp) or ('files in my codebase' in inp)
420
-
421
-
422
- # -----------------------------
423
- # Tests for Gemini proxy (v1_02)
424
- # -----------------------------
425
-
426
-
427
- def test_gemini_generate_content_replacement_applied_to_contents():
428
- _orig_sys_modules = {}
429
- names_to_fake = ['firebase_functions', 'utils', 'auth', 'google', 'google.genai', 'google.genai.types', 'firebase_admin', 'billing']
430
- for name in names_to_fake:
431
- _orig_sys_modules[name] = sys.modules.get(name)
432
-
433
- firebase_functions = types.ModuleType('firebase_functions')
434
- firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
435
- sys.modules['firebase_functions'] = firebase_functions
436
-
437
- # Fake utils
438
- utils_mod = types.ModuleType('utils')
439
- def _fake_create_json_response(success: bool, payload: any, status_code: int):
440
- response_body = {"success": success, "payload": payload}
441
- return FakeResponse(response=json.dumps(response_body), status=status_code, headers={'Content-Type': 'application/json'})
442
- def _fake_get_api_key(llm_family=None):
443
- return 'DUMMY_KEY'
444
- def _fake_verify_firebase_token(req):
445
- return {}
446
- def _fake_get_request_json(req, strict=False):
447
- return req.get_json(silent=not strict)
448
- utils_mod.create_json_response = _fake_create_json_response
449
- utils_mod.get_api_key = _fake_get_api_key
450
- utils_mod.verify_firebase_token = _fake_verify_firebase_token
451
- utils_mod.get_request_json = _fake_get_request_json
452
- sys.modules['utils'] = utils_mod
453
-
454
- auth_mod = types.ModuleType('auth')
455
- auth_mod.validate_auth_key = lambda v: True
456
- sys.modules['auth'] = auth_mod
457
-
458
- # Fake google.genai types
459
- google_mod = types.ModuleType('google')
460
- genai_mod = types.ModuleType('google.genai')
461
- types_mod = types.ModuleType('google.genai.types')
462
-
463
- class DummyGenerateContentConfig:
464
- def __init__(self, **kwargs):
465
- self.kwargs = kwargs
466
-
467
- class DummyContent:
468
- def __init__(self, **kwargs):
469
- self.kwargs = kwargs
470
-
471
- types_mod.GenerateContentConfig = DummyGenerateContentConfig
472
- types_mod.Content = DummyContent
473
-
474
- class DummyClient:
475
- def __init__(self, api_key):
476
- self.api_key = api_key
477
- class Models:
478
- def generate_content(self_inner, **params):
479
- raise NotImplementedError()
480
- self.models = Models()
481
-
482
- genai_mod.Client = DummyClient
483
- genai_mod.types = types_mod
484
- google_mod.genai = genai_mod
485
-
486
- sys.modules['google'] = google_mod
487
- sys.modules['google.genai'] = genai_mod
488
- sys.modules['google.genai.types'] = types_mod
489
-
490
- billing_mod = types.ModuleType('billing')
491
- billing_mod.ensure_balance_positive = lambda db, user_id: (True, 100.0)
492
- billing_mod.calculate_cost = lambda model_id, in_t, out_t: 0.0
493
- billing_mod.bill_with_retry = lambda db, user_id, model_id, usage, cost, reason='usage': ("ok", 100.0)
494
- sys.modules['billing'] = billing_mod
495
-
496
- firebase_admin_mod = types.ModuleType('firebase_admin')
497
- firebase_admin_mod.firestore = types.SimpleNamespace(client=lambda: types.SimpleNamespace(), Client=type('Client', (), {}))
498
- sys.modules['firebase_admin'] = firebase_admin_mod
499
-
500
- # Import package and module
501
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
502
- functions_root = os.path.join(repo_root, 'functions')
503
- pkg_init = os.path.join(functions_root, 'api_v1_02', '__init__.py')
504
- spec_pkg = importlib.util.spec_from_file_location('api_v1_02', pkg_init)
505
- pkg = importlib.util.module_from_spec(spec_pkg)
506
- spec_pkg.loader.exec_module(pkg)
507
- sys.modules['api_v1_02'] = pkg
508
-
509
- module_path = os.path.join(functions_root, 'api_v1_02', '_gemini_proxy.py')
510
- spec = importlib.util.spec_from_file_location('api_v1_02._gemini_proxy', module_path)
511
- _gemini = importlib.util.module_from_spec(spec)
512
- spec.loader.exec_module(_gemini)
513
-
514
- # Restore sys.modules
515
- for name, orig in _orig_sys_modules.items():
516
- if orig is None:
517
- try:
518
- del sys.modules[name]
519
- except KeyError:
520
- pass
521
- else:
522
- sys.modules[name] = orig
523
-
524
- headers = {_gemini.AUTH_HEADER_NAME: 'ok'}
525
- json_params = {'model': 'gem-model', 'contents': 'Hello ==SELECT_FILE_INSTRUCTIONS==', 'config': {}}
526
- req = DummyRequest(headers=headers, method='POST', json_data=json_params)
527
-
528
- # Fake client to capture generate_content params
529
- class FakeModels:
530
- last_params = None
531
- def generate_content(self, **params):
532
- FakeModels.last_params = params
533
- class FakeResp:
534
- def to_json_dict(self_inner):
535
- return {'result': 'ok'}
536
- @property
537
- def usage_metadata(self_inner):
538
- return types.SimpleNamespace(prompt_token_count=1, total_token_count=2)
539
- return FakeResp()
540
-
541
- class FakeClient2:
542
- def __init__(self, api_key):
543
- self.api_key = api_key
544
- self.models = FakeModels()
545
-
546
- with patch.object(_gemini, 'validate_auth_key', return_value=True), \
547
- patch.object(_gemini, 'verify_firebase_token', return_value={}), \
548
- patch.object(_gemini, 'get_api_key', return_value='GEMINI-KEY'), \
549
- patch.object(_gemini.genai, 'Client', FakeClient2):
550
- resp = _gemini._gemini_proxy(req)
551
-
552
- payload = _parse_response(resp)
553
- assert resp.status_code == 200
554
- assert payload['success'] is True
555
- assert FakeModels.last_params is not None
556
- # contents might be passed as string or list depending on implementation; check string presence
557
- assert ('Some instructions for selecting files' in str(FakeModels.last_params.get('contents'))) or ('files in my codebase' in str(FakeModels.last_params.get('contents')))
@@ -1,74 +0,0 @@
1
- import os
2
- import sys
3
- import importlib.util
4
- import types
5
-
6
- # Ensure the functions/ directory is importable as a top-level module location
7
- PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
8
- FUNCTIONS_DIR = os.path.join(PROJECT_ROOT, 'functions')
9
- if FUNCTIONS_DIR not in sys.path:
10
- sys.path.insert(0, FUNCTIONS_DIR)
11
-
12
- # Load the keyword_map module from the v1_02 package
13
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
14
- functions_root = os.path.join(repo_root, 'functions')
15
- module_path = os.path.join(functions_root, 'api_v1_02', 'replace_keywords.py')
16
- spec = importlib.util.spec_from_file_location('api_v1_02.replace_keywords', module_path)
17
- _replace_keywords = importlib.util.module_from_spec(spec)
18
- spec.loader.exec_module(_replace_keywords)
19
-
20
- replace_keywords = _replace_keywords.replace_keywords
21
- KEYWORD_MAP = _replace_keywords.KEYWORD_MAP
22
-
23
-
24
- def test_recursive_replacement_nested_structures():
25
- obj = {
26
- 'input': 'Hello ' + list(KEYWORD_MAP.keys())[0] + ' world',
27
- 'messages': [
28
- {'content': 'First ' + list(KEYWORD_MAP.keys())[0] + ' end'},
29
- {'content': 'no change'}
30
- ],
31
- 'nested': {'list': ['prefix ' + list(KEYWORD_MAP.keys())[0] + ' suffix', 123]},
32
- 'tuple': ('a ' + list(KEYWORD_MAP.keys())[0] + ' b',),
33
- 'obj': types.SimpleNamespace(text=list(KEYWORD_MAP.keys())[0] + ' inside', number=7)
34
- }
35
-
36
- result = replace_keywords(obj)
37
-
38
- expected_replacement = list(KEYWORD_MAP.values())[0]
39
- assert expected_replacement in result['input']
40
- assert expected_replacement in result['messages'][0]['content']
41
- assert expected_replacement in result['nested']['list'][0]
42
- assert expected_replacement in result['tuple'][0]
43
- assert expected_replacement in result['obj'].text
44
-
45
-
46
- def test_multiple_occurrences_replaced_everywhere():
47
- key = list(KEYWORD_MAP.keys())[0]
48
- val = list(KEYWORD_MAP.values())[0]
49
- s = f"{key} and {key} again"
50
- out = replace_keywords(s)
51
- assert out == f"{val} and {val} again"
52
-
53
-
54
- def test_case_sensitivity_no_match_for_different_case():
55
- key = list(KEYWORD_MAP.keys())[0]
56
- lower_key = key.lower()
57
- s = f"This {lower_key} should remain"
58
- out = replace_keywords(s)
59
- assert out == s
60
-
61
-
62
- def test_unknown_keywords_left_unchanged():
63
- s = 'This contains ==UNKNOWN_KEYWORD== and should remain.'
64
- out = replace_keywords(s)
65
- assert out == s
66
-
67
-
68
- def test_non_string_values_unchanged():
69
- data = {'n': 42, 'f': 3.14, 'b': True, 'none': None}
70
- out = replace_keywords(data)
71
- assert out['n'] == 42
72
- assert out['f'] == 3.14
73
- assert out['b'] is True
74
- assert out['none'] is None
@@ -1,24 +0,0 @@
1
- import importlib
2
- from cryptography.fernet import Fernet
3
-
4
-
5
- def test_validate_auth_key_returns_true_for_valid_token():
6
- """Encrypt the known AUTH_KEY with the module's FERNET_KEY and verify validation passes."""
7
- auth = importlib.import_module('auth')
8
- cipher = Fernet(auth.FERNET_KEY)
9
- token = cipher.encrypt(auth.AUTH_KEY.encode()).decode('utf-8')
10
- assert auth.validate_auth_key(token) is True
11
-
12
-
13
- def test_validate_auth_key_returns_false_for_invalid_token_string():
14
- """Passing a non-Fernet string should return False rather than raise."""
15
- auth = importlib.import_module('auth')
16
- assert auth.validate_auth_key('not-a-token') is False
17
-
18
-
19
- def test_validate_auth_key_returns_false_for_wrong_plaintext():
20
- """Encrypting a different plaintext with the correct key should not validate."""
21
- auth = importlib.import_module('auth')
22
- cipher = Fernet(auth.FERNET_KEY)
23
- token = cipher.encrypt(b'WRONG').decode('utf-8')
24
- assert auth.validate_auth_key(token) is False