roksta 0.2.3__cp312-cp312-win_amd64.whl → 0.2.5__cp312-cp312-win_amd64.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.

Potentially problematic release.


This version of roksta might be problematic. Click here for more details.

Files changed (78) hide show
  1. roksta/__init__.cp312-win_amd64.pyd +0 -0
  2. roksta/ai/__init__.cp312-win_amd64.pyd +0 -0
  3. roksta/ai/call_ai.cp312-win_amd64.pyd +0 -0
  4. roksta/ai/gemini.cp312-win_amd64.pyd +0 -0
  5. roksta/ai/generic.cp312-win_amd64.pyd +0 -0
  6. roksta/ai/llm.cp312-win_amd64.pyd +0 -0
  7. roksta/ai/openai.cp312-win_amd64.pyd +0 -0
  8. roksta/ai/tools.cp312-win_amd64.pyd +0 -0
  9. roksta/analytics.cp312-win_amd64.pyd +0 -0
  10. roksta/balance.cp312-win_amd64.pyd +0 -0
  11. roksta/build_project.cp312-win_amd64.pyd +0 -0
  12. roksta/chat_workflow.cp312-win_amd64.pyd +0 -0
  13. roksta/check_for_updates.cp312-win_amd64.pyd +0 -0
  14. roksta/checkpoints.cp312-win_amd64.pyd +0 -0
  15. roksta/clarify_goal.cp312-win_amd64.pyd +0 -0
  16. roksta/codebase_listing.cp312-win_amd64.pyd +0 -0
  17. roksta/command_handlers.cp312-win_amd64.pyd +0 -0
  18. roksta/create_default_config.cp312-win_amd64.pyd +0 -0
  19. roksta/default_config.cp312-win_amd64.pyd +0 -0
  20. roksta/enums.cp312-win_amd64.pyd +0 -0
  21. roksta/env.cp312-win_amd64.pyd +0 -0
  22. roksta/extended_text_area.cp312-win_amd64.pyd +0 -0
  23. roksta/firebase.cp312-win_amd64.pyd +0 -0
  24. roksta/firebase_auth_web.cp312-win_amd64.pyd +0 -0
  25. roksta/firebase_config.cp312-win_amd64.pyd +0 -0
  26. roksta/fix_tests.cp312-win_amd64.pyd +0 -0
  27. roksta/gen_codebase_summaries.cp312-win_amd64.pyd +0 -0
  28. roksta/gen_one_line_goal.cp312-win_amd64.pyd +0 -0
  29. roksta/get_codebase_structure.cp312-win_amd64.pyd +0 -0
  30. roksta/get_failing_tests.cp312-win_amd64.pyd +0 -0
  31. roksta/goal_workflow.cp312-win_amd64.pyd +0 -0
  32. roksta/init_codebase.cp312-win_amd64.pyd +0 -0
  33. roksta/lint_code.cp312-win_amd64.pyd +0 -0
  34. roksta/logger.cp312-win_amd64.pyd +0 -0
  35. roksta/main.cp312-win_amd64.pyd +0 -0
  36. roksta/make_issue.cp312-win_amd64.pyd +0 -0
  37. roksta/new_features.cp312-win_amd64.pyd +0 -0
  38. roksta/parse_readme.cp312-win_amd64.pyd +0 -0
  39. roksta/propose_solution.cp312-win_amd64.pyd +0 -0
  40. roksta/response_formats.cp312-win_amd64.pyd +0 -0
  41. roksta/rewrite_goal.cp312-win_amd64.pyd +0 -0
  42. roksta/roksta.cp312-win_amd64.pyd +0 -0
  43. roksta/select_files.cp312-win_amd64.pyd +0 -0
  44. roksta/tips.cp312-win_amd64.pyd +0 -0
  45. roksta/utils.cp312-win_amd64.pyd +0 -0
  46. roksta/write_code.cp312-win_amd64.pyd +0 -0
  47. {roksta-0.2.3.dist-info → roksta-0.2.5.dist-info}/METADATA +1 -1
  48. roksta-0.2.5.dist-info/RECORD +77 -0
  49. {roksta-0.2.3.dist-info → roksta-0.2.5.dist-info}/top_level.txt +1 -0
  50. tests/__init__.py +2 -0
  51. tests/conftest.py +169 -0
  52. tests/functions/__init__.py +2 -0
  53. tests/functions/api_v0_01/__init__.py +2 -0
  54. tests/functions/api_v0_01/test__analytics.py +417 -0
  55. tests/functions/api_v0_01/test__gemini_proxy.py +307 -0
  56. tests/functions/api_v0_01/test__generic_proxy.py +399 -0
  57. tests/functions/api_v0_01/test__get_payment_details.py +356 -0
  58. tests/functions/api_v0_01/test__openai_proxy.py +413 -0
  59. tests/functions/api_v0_01/test__redeem_credit_code.py +167 -0
  60. tests/functions/api_v0_01/test__sync_emails.py +324 -0
  61. tests/functions/api_v0_01/test__take_payment.py +491 -0
  62. tests/functions/api_v0_01/test__use_activation_code.py +437 -0
  63. tests/functions/api_v1_00/__init__.py +2 -0
  64. tests/functions/api_v1_00/test__analytics.py +416 -0
  65. tests/functions/api_v1_00/test__gemini_proxy.py +352 -0
  66. tests/functions/api_v1_00/test__generic_proxy.py +428 -0
  67. tests/functions/api_v1_00/test__get_payment_details.py +356 -0
  68. tests/functions/api_v1_00/test__openai_proxy.py +449 -0
  69. tests/functions/api_v1_00/test__redeem_credit_code.py +167 -0
  70. tests/functions/api_v1_00/test__sync_emails.py +325 -0
  71. tests/functions/api_v1_00/test__take_payment.py +491 -0
  72. tests/functions/api_v1_00/test__use_activation_code.py +438 -0
  73. tests/functions/test_auth.py +24 -0
  74. tests/functions/test_main_functions.py +73 -0
  75. tests/functions/test_utils_functions.py +222 -0
  76. roksta-0.2.3.dist-info/RECORD +0 -51
  77. {roksta-0.2.3.dist-info → roksta-0.2.5.dist-info}/WHEEL +0 -0
  78. {roksta-0.2.3.dist-info → roksta-0.2.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,324 @@
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
+ # -----------------------------
16
+ # Provide lightweight fake modules to satisfy imports inside _sync_emails
17
+ # These are minimal and will be patched in individual tests as needed.
18
+ # -----------------------------
19
+ # Save original sys.modules entries so we can restore them after importing the module under test
20
+ _orig_sys_modules = {}
21
+ _names_to_fake = ['firebase_functions', 'utils', 'auth', 'firebase_admin']
22
+ for name in _names_to_fake:
23
+ _orig_sys_modules[name] = sys.modules.get(name)
24
+
25
+ # Fake firebase_functions.https_fn.Response to capture returned data
26
+ firebase_functions = types.ModuleType('firebase_functions')
27
+
28
+ class FakeResponse:
29
+ def __init__(self, response=None, mimetype=None, status=200, **kwargs):
30
+ # Mirror the small subset of the interface tests expect
31
+ self.status_code = status
32
+ if isinstance(response, (dict, list)):
33
+ self._body_text = json.dumps(response)
34
+ else:
35
+ self._body_text = '' if response is None else response
36
+ self.headers = kwargs.get('headers', {})
37
+
38
+ def get_data(self, as_text=False):
39
+ if as_text:
40
+ return self._body_text
41
+ return self._body_text.encode('utf-8')
42
+
43
+ firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
44
+ sys.modules['firebase_functions'] = firebase_functions
45
+
46
+ # Fake utils module (verify_firebase_token, create_json_response)
47
+ utils_mod = types.ModuleType('utils')
48
+
49
+ def _dummy_verify_firebase_token(req: object) -> dict:
50
+ # Default behavior: return a decoded token with a uid
51
+ return {'uid': 'test_user'}
52
+
53
+
54
+ def _create_json_response(success: bool, payload=None, status_code: int = 200):
55
+ # Normalize payload to include a message field
56
+ if isinstance(payload, dict):
57
+ message = payload.get('message', '')
58
+ data = {'success': success, 'message': message}
59
+ data['payload'] = payload
60
+ else:
61
+ message = payload if payload is not None else ''
62
+ data = {'success': success, 'message': message}
63
+ return firebase_functions.https_fn.Response(response=data, status=status_code)
64
+
65
+ utils_mod.verify_firebase_token = _dummy_verify_firebase_token
66
+ utils_mod.create_json_response = _create_json_response
67
+ sys.modules['utils'] = utils_mod
68
+
69
+ # Fake auth module (validate_auth_key will be patched per-test as needed)
70
+ auth_mod = types.ModuleType('auth')
71
+
72
+ def _simple_validate_auth_key(val: str) -> bool:
73
+ return True
74
+
75
+ auth_mod.validate_auth_key = _simple_validate_auth_key
76
+ sys.modules['auth'] = auth_mod
77
+
78
+ # Fake firebase_admin to prevent importing the real package during tests
79
+ firebase_admin = types.ModuleType('firebase_admin')
80
+ # Minimal fake auth and firestore objects; tests will patch their behavior as needed
81
+ fake_auth = types.SimpleNamespace(get_user=lambda uid: types.SimpleNamespace(email='test@example.com'))
82
+ # Provide a placeholder firestore client function; will be overridden by tests
83
+ fake_firestore = types.SimpleNamespace(client=lambda: None)
84
+ firebase_admin.auth = fake_auth
85
+ firebase_admin.firestore = fake_firestore
86
+ sys.modules['firebase_admin'] = firebase_admin
87
+
88
+ # -----------------------------
89
+ # Import the module under test after preparing the fake imports
90
+ # -----------------------------
91
+ repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
92
+ functions_root = os.path.join(repo_root, 'functions')
93
+ module_path = os.path.join(functions_root, 'api_v0_01', '_sync_emails.py')
94
+ spec = importlib.util.spec_from_file_location('api_v0_01._sync_emails', module_path)
95
+ _sync = importlib.util.module_from_spec(spec)
96
+ spec.loader.exec_module(_sync)
97
+
98
+ # Restore original sys.modules mappings to avoid side-effects for other tests
99
+ for name, orig in _orig_sys_modules.items():
100
+ if orig is None:
101
+ try:
102
+ del sys.modules[name]
103
+ except KeyError:
104
+ pass
105
+ else:
106
+ sys.modules[name] = orig
107
+
108
+
109
+ # -----------------------------
110
+ # Fake Firestore implementation used by tests
111
+ # -----------------------------
112
+ class FakeDocumentSnapshot:
113
+ def __init__(self, exists: bool, data: dict | None):
114
+ self.exists = exists
115
+ self._data = data
116
+
117
+ def to_dict(self):
118
+ return self._data
119
+
120
+
121
+ class FakeDocumentReference:
122
+ def __init__(self, client, collection: str, doc_id: str):
123
+ self.client = client
124
+ self.collection = collection
125
+ self.doc_id = doc_id
126
+
127
+ def get(self):
128
+ coll = self.client._data.get(self.collection, {})
129
+ if self.doc_id in coll:
130
+ return FakeDocumentSnapshot(True, coll[self.doc_id])
131
+ return FakeDocumentSnapshot(False, None)
132
+
133
+ def update(self, update_dict: dict):
134
+ # Record the update
135
+ self.client._updates.append((self.collection, self.doc_id, update_dict))
136
+ coll = self.client._data.setdefault(self.collection, {})
137
+ # Apply update to stored doc if it exists and is a dict
138
+ if isinstance(coll.get(self.doc_id), dict):
139
+ coll[self.doc_id].update(update_dict)
140
+ else:
141
+ coll[self.doc_id] = update_dict
142
+
143
+
144
+ class FakeCollectionReference:
145
+ def __init__(self, client, name: str):
146
+ self.client = client
147
+ self.name = name
148
+
149
+ def document(self, doc_id: str):
150
+ return FakeDocumentReference(self.client, self.name, doc_id)
151
+
152
+
153
+ class FakeFirestoreClient:
154
+ def __init__(self, initial_data: dict | None = None):
155
+ # initial_data should be a dict mapping collection_name -> {doc_id: doc_data}
156
+ self._data = initial_data.copy() if initial_data else {}
157
+ self._updates = []
158
+
159
+ def collection(self, name: str):
160
+ return FakeCollectionReference(self, name)
161
+
162
+ def get_updates(self):
163
+ return self._updates
164
+
165
+
166
+ # Simple helper request stub used in the tests
167
+ class DummyRequest:
168
+ def __init__(self, headers=None, method='POST', json_data=None, raise_on_get_json=False):
169
+ self.headers = headers or {}
170
+ self.method = method
171
+ self._json_data = json_data
172
+ self._raise = raise_on_get_json
173
+
174
+ def get_json(self, silent=True):
175
+ if self._raise:
176
+ raise Exception('Malformed JSON')
177
+ return self._json_data
178
+
179
+
180
+ def _parse_response(resp):
181
+ data = resp.get_data(as_text=True)
182
+ return json.loads(data)
183
+
184
+
185
+ # -----------------------------
186
+ # Tests
187
+ # -----------------------------
188
+
189
+ def test_missing_auth_header_returns_401():
190
+ req = DummyRequest(headers={}, method='POST')
191
+ # Ensure verify_firebase_token is not called when header is missing
192
+ with patch.object(_sync, 'verify_firebase_token', side_effect=Exception('Should not be called')):
193
+ resp = _sync._sync_emails(req)
194
+
195
+ assert resp.status_code == 401
196
+ payload = _parse_response(resp)
197
+ assert payload['success'] is False
198
+ assert 'Missing app authentication key' in payload['message']
199
+
200
+
201
+ def test_invalid_auth_key_returns_403():
202
+ headers = {_sync.AUTH_HEADER_NAME: 'bad-token'}
203
+ req = DummyRequest(headers=headers, method='POST')
204
+ with patch.object(_sync, 'validate_auth_key', return_value=False):
205
+ resp = _sync._sync_emails(req)
206
+ assert resp.status_code == 403
207
+ payload = _parse_response(resp)
208
+ assert payload['success'] is False
209
+ assert 'Invalid app authentication key' in payload['message']
210
+
211
+
212
+ def test_verify_token_raises_returns_403():
213
+ headers = {_sync.AUTH_HEADER_NAME: 'ok'}
214
+ req = DummyRequest(headers=headers, method='POST')
215
+ with patch.object(_sync, 'validate_auth_key', return_value=True), patch.object(_sync, 'verify_firebase_token', side_effect=Exception('boom')):
216
+ resp = _sync._sync_emails(req)
217
+
218
+ assert resp.status_code == 403
219
+ payload = _parse_response(resp)
220
+ assert payload['success'] is False
221
+ assert 'Authentication failed' in payload['message']
222
+
223
+
224
+ def test_missing_uid_in_token_returns_403():
225
+ headers = {_sync.AUTH_HEADER_NAME: 'ok'}
226
+ req = DummyRequest(headers=headers, method='POST')
227
+ with patch.object(_sync, 'validate_auth_key', return_value=True), patch.object(_sync, 'verify_firebase_token', return_value={}):
228
+ resp = _sync._sync_emails(req)
229
+
230
+ assert resp.status_code == 403
231
+ payload = _parse_response(resp)
232
+ assert payload['success'] is False
233
+ assert 'UID missing' in payload['message']
234
+
235
+
236
+ def test_get_user_failure_returns_500():
237
+ headers = {_sync.AUTH_HEADER_NAME: 'ok'}
238
+ req = DummyRequest(headers=headers, method='POST')
239
+ with patch.object(_sync, 'validate_auth_key', return_value=True), patch.object(_sync, 'verify_firebase_token', return_value={'uid': 'u'}), patch.object(_sync.auth, 'get_user', side_effect=Exception('nope')):
240
+ resp = _sync._sync_emails(req)
241
+
242
+ assert resp.status_code == 500
243
+ payload = _parse_response(resp)
244
+ assert payload['success'] is False
245
+ assert 'Failed to retrieve user data from Firebase Authentication' in payload['message']
246
+
247
+
248
+ def test_no_updates_when_emails_match_returns_200():
249
+ headers = {_sync.AUTH_HEADER_NAME: 'ok'}
250
+ req = DummyRequest(headers=headers, method='POST')
251
+
252
+ fake_data = {
253
+ 'users': {'u': {'email': 'auth@example.com'}},
254
+ 'billing': {'u': {'email': 'auth@example.com'}}
255
+ }
256
+ fake_client = FakeFirestoreClient(initial_data=fake_data)
257
+
258
+ with patch.object(_sync, 'validate_auth_key', return_value=True), \
259
+ patch.object(_sync, 'verify_firebase_token', return_value={'uid': 'u'}), \
260
+ patch.object(_sync.auth, 'get_user', return_value=types.SimpleNamespace(email='auth@example.com')), \
261
+ patch.object(_sync.firestore, 'client', return_value=fake_client):
262
+ resp = _sync._sync_emails(req)
263
+
264
+ assert resp.status_code == 200
265
+ payload = _parse_response(resp)
266
+ assert payload['success'] is True
267
+ assert 'No email updates were necessary' in payload['message']
268
+ assert fake_client.get_updates() == []
269
+
270
+
271
+ def test_updates_when_mismatch_calls_update_returns_200():
272
+ headers = {_sync.AUTH_HEADER_NAME: 'ok'}
273
+ req = DummyRequest(headers=headers, method='POST')
274
+
275
+ fake_data = {
276
+ 'users': {'u': {'email': 'old_user@example.com'}},
277
+ 'billing': {'u': {'email': 'old_billing@example.com'}}
278
+ }
279
+ fake_client = FakeFirestoreClient(initial_data=fake_data)
280
+
281
+ with patch.object(_sync, 'validate_auth_key', return_value=True), \
282
+ patch.object(_sync, 'verify_firebase_token', return_value={'uid': 'u'}), \
283
+ patch.object(_sync.auth, 'get_user', return_value=types.SimpleNamespace(email='auth@example.com')), \
284
+ patch.object(_sync.firestore, 'client', return_value=fake_client):
285
+ resp = _sync._sync_emails(req)
286
+
287
+ assert resp.status_code == 200
288
+ payload = _parse_response(resp)
289
+ assert payload['success'] is True
290
+ assert 'User profile email updated.' in payload['message']
291
+ assert 'Billing email updated.' in payload['message']
292
+
293
+ updates = fake_client.get_updates()
294
+ # Ensure we have two updates and that both updated emails match the auth email
295
+ assert any(u for u in updates if u[0] == 'users' and u[1] == 'u' and u[2] == {'email': 'auth@example.com'})
296
+ assert any(u for u in updates if u[0] == 'billing' and u[1] == 'u' and u[2] == {'email': 'auth@example.com'})
297
+
298
+
299
+ def test_billing_exists_without_email_field_only_user_updated_returns_200():
300
+ headers = {_sync.AUTH_HEADER_NAME: 'ok'}
301
+ req = DummyRequest(headers=headers, method='POST')
302
+
303
+ fake_data = {
304
+ 'users': {'u': {'email': 'old_user@example.com'}},
305
+ 'billing': {'u': {'some_other_key': 'value'}}
306
+ }
307
+ fake_client = FakeFirestoreClient(initial_data=fake_data)
308
+
309
+ with patch.object(_sync, 'validate_auth_key', return_value=True), \
310
+ patch.object(_sync, 'verify_firebase_token', return_value={'uid': 'u'}), \
311
+ patch.object(_sync.auth, 'get_user', return_value=types.SimpleNamespace(email='auth@example.com')), \
312
+ patch.object(_sync.firestore, 'client', return_value=fake_client):
313
+ resp = _sync._sync_emails(req)
314
+
315
+ assert resp.status_code == 200
316
+ payload = _parse_response(resp)
317
+ assert payload['success'] is True
318
+ assert 'User profile email updated.' in payload['message']
319
+ assert 'Billing email updated.' not in payload['message']
320
+
321
+ updates = fake_client.get_updates()
322
+ assert any(u for u in updates if u[0] == 'users' and u[1] == 'u' and u[2] == {'email': 'auth@example.com'})
323
+ # Ensure billing was not updated
324
+ assert not any(u for u in updates if u[0] == 'billing')