roksta 0.2.3__cp314-cp314-win_amd64.whl → 0.2.5__cp314-cp314-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__.cp314-win_amd64.pyd +0 -0
  2. roksta/ai/__init__.cp314-win_amd64.pyd +0 -0
  3. roksta/ai/call_ai.cp314-win_amd64.pyd +0 -0
  4. roksta/ai/gemini.cp314-win_amd64.pyd +0 -0
  5. roksta/ai/generic.cp314-win_amd64.pyd +0 -0
  6. roksta/ai/llm.cp314-win_amd64.pyd +0 -0
  7. roksta/ai/openai.cp314-win_amd64.pyd +0 -0
  8. roksta/ai/tools.cp314-win_amd64.pyd +0 -0
  9. roksta/analytics.cp314-win_amd64.pyd +0 -0
  10. roksta/balance.cp314-win_amd64.pyd +0 -0
  11. roksta/build_project.cp314-win_amd64.pyd +0 -0
  12. roksta/chat_workflow.cp314-win_amd64.pyd +0 -0
  13. roksta/check_for_updates.cp314-win_amd64.pyd +0 -0
  14. roksta/checkpoints.cp314-win_amd64.pyd +0 -0
  15. roksta/clarify_goal.cp314-win_amd64.pyd +0 -0
  16. roksta/codebase_listing.cp314-win_amd64.pyd +0 -0
  17. roksta/command_handlers.cp314-win_amd64.pyd +0 -0
  18. roksta/create_default_config.cp314-win_amd64.pyd +0 -0
  19. roksta/default_config.cp314-win_amd64.pyd +0 -0
  20. roksta/enums.cp314-win_amd64.pyd +0 -0
  21. roksta/env.cp314-win_amd64.pyd +0 -0
  22. roksta/extended_text_area.cp314-win_amd64.pyd +0 -0
  23. roksta/firebase.cp314-win_amd64.pyd +0 -0
  24. roksta/firebase_auth_web.cp314-win_amd64.pyd +0 -0
  25. roksta/firebase_config.cp314-win_amd64.pyd +0 -0
  26. roksta/fix_tests.cp314-win_amd64.pyd +0 -0
  27. roksta/gen_codebase_summaries.cp314-win_amd64.pyd +0 -0
  28. roksta/gen_one_line_goal.cp314-win_amd64.pyd +0 -0
  29. roksta/get_codebase_structure.cp314-win_amd64.pyd +0 -0
  30. roksta/get_failing_tests.cp314-win_amd64.pyd +0 -0
  31. roksta/goal_workflow.cp314-win_amd64.pyd +0 -0
  32. roksta/init_codebase.cp314-win_amd64.pyd +0 -0
  33. roksta/lint_code.cp314-win_amd64.pyd +0 -0
  34. roksta/logger.cp314-win_amd64.pyd +0 -0
  35. roksta/main.cp314-win_amd64.pyd +0 -0
  36. roksta/make_issue.cp314-win_amd64.pyd +0 -0
  37. roksta/new_features.cp314-win_amd64.pyd +0 -0
  38. roksta/parse_readme.cp314-win_amd64.pyd +0 -0
  39. roksta/propose_solution.cp314-win_amd64.pyd +0 -0
  40. roksta/response_formats.cp314-win_amd64.pyd +0 -0
  41. roksta/rewrite_goal.cp314-win_amd64.pyd +0 -0
  42. roksta/roksta.cp314-win_amd64.pyd +0 -0
  43. roksta/select_files.cp314-win_amd64.pyd +0 -0
  44. roksta/tips.cp314-win_amd64.pyd +0 -0
  45. roksta/utils.cp314-win_amd64.pyd +0 -0
  46. roksta/write_code.cp314-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,437 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ import types
5
+ import importlib
6
+ from unittest.mock import patch
7
+ from datetime import datetime, timezone, timedelta
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 _use_activation_code
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 = [
22
+ 'firebase_functions',
23
+ 'utils',
24
+ 'auth',
25
+ 'firebase_admin',
26
+ 'firebase_admin.firestore',
27
+ ]
28
+ for name in _names_to_fake:
29
+ _orig_sys_modules[name] = sys.modules.get(name)
30
+
31
+ # Fake firebase_functions.https_fn.Response to capture returned data
32
+ firebase_functions = types.ModuleType('firebase_functions')
33
+
34
+ class FakeResponse:
35
+ def __init__(self, response=None, mimetype=None, status=200, **kwargs):
36
+ # Mirror the small subset of the interface tests expect
37
+ self.status_code = status
38
+ if isinstance(response, (dict, list)):
39
+ self._body_text = json.dumps(response)
40
+ else:
41
+ self._body_text = '' if response is None else response
42
+ self.headers = kwargs.get('headers', {})
43
+
44
+ def get_data(self, as_text=False):
45
+ if as_text:
46
+ return self._body_text
47
+ return self._body_text.encode('utf-8')
48
+
49
+ firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
50
+ sys.modules['firebase_functions'] = firebase_functions
51
+
52
+ # Fake utils module (create_json_response, verify_firebase_token)
53
+ utils_mod = types.ModuleType('utils')
54
+
55
+
56
+ def _fake_create_json_response(success: bool, payload: any, status_code: int):
57
+ response_body = {"success": success, "payload": payload}
58
+ return firebase_functions.https_fn.Response(response=response_body, status=status_code, headers={'Content-Type': 'application/json'})
59
+
60
+
61
+ def _fake_verify_firebase_token(req):
62
+ # Default: successful verification returning a uid
63
+ return {'uid': 'user_1'}
64
+
65
+
66
+ utils_mod.create_json_response = _fake_create_json_response
67
+ utils_mod.verify_firebase_token = _fake_verify_firebase_token
68
+ sys.modules['utils'] = utils_mod
69
+
70
+ # Fake top-level auth module (validate_auth_key will be patched per-test as needed)
71
+ auth_mod = types.ModuleType('auth')
72
+
73
+
74
+ def _simple_validate_auth_key(val: str) -> bool:
75
+ return bool(val)
76
+
77
+
78
+ auth_mod.validate_auth_key = _simple_validate_auth_key
79
+ sys.modules['auth'] = auth_mod
80
+
81
+ # Fake firebase_admin.firestore using an in-memory dict
82
+ firebase_admin_mod = types.ModuleType('firebase_admin')
83
+
84
+ # In-memory fake DB used by the fake Firestore implementation
85
+ fake_db_data = {}
86
+
87
+ class DocumentSnapshot:
88
+ def __init__(self, exists, data):
89
+ self.exists = exists
90
+ self._data = data
91
+
92
+ def to_dict(self):
93
+ return self._data
94
+
95
+ def get(self, key):
96
+ if not self._data:
97
+ return None
98
+ return self._data.get(key)
99
+
100
+
101
+ class DocumentRef:
102
+ def __init__(self, collection_name, doc_id):
103
+ self.collection = collection_name
104
+ self.doc_id = doc_id
105
+
106
+ def get(self, transaction=None):
107
+ coll = fake_db_data.get(self.collection, {})
108
+ if self.doc_id in coll:
109
+ return DocumentSnapshot(True, coll[self.doc_id])
110
+ return DocumentSnapshot(False, None)
111
+
112
+ def update(self, update_dict):
113
+ coll = fake_db_data.setdefault(self.collection, {})
114
+ doc = coll.setdefault(self.doc_id, {})
115
+ if isinstance(doc, dict):
116
+ doc.update(update_dict)
117
+ else:
118
+ coll[self.doc_id] = update_dict
119
+
120
+ def set(self, data):
121
+ coll = fake_db_data.setdefault(self.collection, {})
122
+ coll[self.doc_id] = data
123
+
124
+
125
+ class CollectionRef:
126
+ def __init__(self, name):
127
+ self.name = name
128
+
129
+ def document(self, doc_id):
130
+ return DocumentRef(self.name, doc_id)
131
+
132
+
133
+ class FakeTransaction:
134
+ def __init__(self):
135
+ pass
136
+
137
+ def update(self, doc_ref, update_dict):
138
+ coll = fake_db_data.setdefault(doc_ref.collection, {})
139
+ doc = coll.setdefault(doc_ref.doc_id, {})
140
+ if isinstance(doc, dict):
141
+ doc.update(update_dict)
142
+ else:
143
+ coll[doc_ref.doc_id] = update_dict
144
+
145
+ def set(self, doc_ref, data):
146
+ coll = fake_db_data.setdefault(doc_ref.collection, {})
147
+ coll[doc_ref.doc_id] = data
148
+
149
+
150
+ class FakeFirestoreClient:
151
+ def __init__(self):
152
+ pass
153
+
154
+ def collection(self, name):
155
+ return CollectionRef(name)
156
+
157
+ def transaction(self):
158
+ return FakeTransaction()
159
+
160
+
161
+ fake_firestore_mod = types.ModuleType('firebase_admin.firestore')
162
+ fake_firestore_mod.client = lambda: FakeFirestoreClient()
163
+ fake_firestore_mod.transactional = lambda f: f
164
+ fake_firestore_mod.SERVER_TIMESTAMP = object()
165
+ sys.modules['firebase_admin.firestore'] = fake_firestore_mod
166
+ firebase_admin_mod.firestore = fake_firestore_mod
167
+ sys.modules['firebase_admin'] = firebase_admin_mod
168
+
169
+ # -----------------------------
170
+ # Import the module under test after preparing the fake imports
171
+ # -----------------------------
172
+ repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
173
+ functions_root = os.path.join(repo_root, 'functions')
174
+ module_path = os.path.join(functions_root, 'api_v0_01', '_use_activation_code.py')
175
+ spec = importlib.util.spec_from_file_location('api_v0_01._use_activation_code', module_path)
176
+ _use_activation = importlib.util.module_from_spec(spec)
177
+ spec.loader.exec_module(_use_activation)
178
+
179
+ # Restore original sys.modules mappings to avoid side-effects for other tests
180
+ for name, orig in _orig_sys_modules.items():
181
+ if orig is None:
182
+ try:
183
+ del sys.modules[name]
184
+ except KeyError:
185
+ pass
186
+ else:
187
+ sys.modules[name] = orig
188
+
189
+ # Import env constants for auth header usage in tests
190
+ import env as functions_env # noqa: E402
191
+ AUTH_HEADER_NAME = functions_env.AUTH_HEADER_NAME
192
+
193
+
194
+ # Simple helper request stub used in the tests
195
+ class DummyRequest:
196
+ def __init__(self, headers=None, method='POST', json_data=None, raise_on_get_json=False):
197
+ self.headers = headers or {}
198
+ self.method = method
199
+ self._json_data = json_data
200
+ self._raise = raise_on_get_json
201
+
202
+ def get_json(self, silent=True):
203
+ if self._raise:
204
+ raise Exception('Malformed JSON')
205
+ return self._json_data
206
+
207
+
208
+ def _parse_response(resp):
209
+ data = resp.get_data(as_text=True)
210
+ return json.loads(data)
211
+
212
+
213
+ # -----------------------------
214
+ # Tests
215
+ # -----------------------------
216
+
217
+ def test_missing_auth_header_returns_401():
218
+ fake_db_data.clear()
219
+ req = DummyRequest(headers={}, method='POST')
220
+ # Ensure token verification isn't called when header missing
221
+ with patch.object(_use_activation, 'verify_firebase_token', side_effect=Exception('Should not be called')):
222
+ resp = _use_activation._use_activation_code(req)
223
+
224
+ assert resp.status_code == 401
225
+ payload = _parse_response(resp)
226
+ assert payload['success'] is False
227
+ assert 'Missing app authentication key' in payload['payload']
228
+
229
+
230
+ def test_invalid_auth_key_returns_403():
231
+ fake_db_data.clear()
232
+ headers = {AUTH_HEADER_NAME: 'bad-token'}
233
+ req = DummyRequest(headers=headers, method='POST')
234
+ with patch.object(_use_activation, 'validate_auth_key', return_value=False):
235
+ resp = _use_activation._use_activation_code(req)
236
+
237
+ assert resp.status_code == 403
238
+ payload = _parse_response(resp)
239
+ assert payload['success'] is False
240
+ assert 'Invalid app authentication key' in payload['payload']
241
+
242
+
243
+ def test_verify_firebase_token_failure_returns_403():
244
+ fake_db_data.clear()
245
+ headers = {AUTH_HEADER_NAME: 'ok'}
246
+ req = DummyRequest(headers=headers, method='POST')
247
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', side_effect=Exception('boom')):
248
+ resp = _use_activation._use_activation_code(req)
249
+
250
+ assert resp.status_code == 403
251
+ payload = _parse_response(resp)
252
+ assert payload['success'] is False
253
+ assert 'Authentication failed' in payload['payload']
254
+
255
+
256
+ def test_missing_json_payload_returns_400():
257
+ fake_db_data.clear()
258
+ headers = {AUTH_HEADER_NAME: 'ok'}
259
+ req = DummyRequest(headers=headers, method='POST', json_data=None)
260
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
261
+ resp = _use_activation._use_activation_code(req)
262
+
263
+ assert resp.status_code == 400
264
+ payload = _parse_response(resp)
265
+ assert payload['success'] is False
266
+ assert 'Invalid or missing JSON payload' in payload['payload']
267
+
268
+
269
+ def test_missing_activation_code_returns_400():
270
+ fake_db_data.clear()
271
+ headers = {AUTH_HEADER_NAME: 'ok'}
272
+ # Provide a non-empty JSON payload without 'activation_code' to trigger the missing key branch
273
+ req = DummyRequest(headers=headers, method='POST', json_data={'foo': 'bar'})
274
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
275
+ resp = _use_activation._use_activation_code(req)
276
+
277
+ assert resp.status_code == 400
278
+ payload = _parse_response(resp)
279
+ assert payload['success'] is False
280
+ assert "Missing 'activation_code'" in payload['payload']
281
+
282
+
283
+ def test_user_does_not_exist_returns_404():
284
+ fake_db_data.clear()
285
+ headers = {AUTH_HEADER_NAME: 'ok'}
286
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'code1'})
287
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
288
+ resp = _use_activation._use_activation_code(req)
289
+
290
+ assert resp.status_code == 404
291
+ payload = _parse_response(resp)
292
+ assert payload['success'] is False
293
+ assert 'User does not exist' in payload['payload']
294
+
295
+
296
+ def test_user_already_active_returns_200():
297
+ fake_db_data.clear()
298
+ fake_db_data.setdefault('users', {})['user_1'] = {'active': True}
299
+
300
+ headers = {AUTH_HEADER_NAME: 'ok'}
301
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'code1'})
302
+
303
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
304
+ resp = _use_activation._use_activation_code(req)
305
+
306
+ assert resp.status_code == 200
307
+ payload = _parse_response(resp)
308
+ assert payload['success'] is True
309
+ assert 'Account is already active' in payload['payload']
310
+
311
+
312
+ def test_activation_code_not_found_returns_404():
313
+ fake_db_data.clear()
314
+ # user exists and inactive
315
+ fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
316
+
317
+ headers = {AUTH_HEADER_NAME: 'ok'}
318
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'CodeX'})
319
+
320
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
321
+ resp = _use_activation._use_activation_code(req)
322
+
323
+ assert resp.status_code == 404
324
+ payload = _parse_response(resp)
325
+ assert payload['success'] is False
326
+ assert 'Invalid activation code' in payload['payload']
327
+
328
+
329
+ def test_activation_code_missing_expiry_returns_500():
330
+ fake_db_data.clear()
331
+ # user exists and inactive
332
+ fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
333
+ # activation code exists but missing expiry_date
334
+ fake_db_data.setdefault('activation_codes', {})['codea'] = {'use_count': 0, 'max_uses': 1}
335
+
336
+ headers = {AUTH_HEADER_NAME: 'ok'}
337
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'codeA'})
338
+
339
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
340
+ resp = _use_activation._use_activation_code(req)
341
+
342
+ assert resp.status_code == 500
343
+ payload = _parse_response(resp)
344
+ assert payload['success'] is False
345
+ assert 'Activation code expiry information is missing' in payload['payload']
346
+
347
+
348
+ def test_activation_code_expired_returns_400():
349
+ fake_db_data.clear()
350
+ fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
351
+ past = datetime.now(timezone.utc) - timedelta(days=1)
352
+ fake_db_data.setdefault('activation_codes', {})['expiredcode'] = {'expiry_date': past, 'use_count': 0, 'max_uses': 5}
353
+
354
+ headers = {AUTH_HEADER_NAME: 'ok'}
355
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'expiredCode'})
356
+
357
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
358
+ resp = _use_activation._use_activation_code(req)
359
+
360
+ assert resp.status_code == 400
361
+ payload = _parse_response(resp)
362
+ assert payload['success'] is False
363
+ assert 'Activation code has expired' in payload['payload']
364
+
365
+
366
+ def test_activation_code_usage_limit_reached_returns_400():
367
+ fake_db_data.clear()
368
+ fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
369
+ fake_db_data.setdefault('activation_codes', {})['limitcode'] = {'expiry_date': datetime.now(timezone.utc) + timedelta(days=1), 'use_count': 5, 'max_uses': 5}
370
+
371
+ headers = {AUTH_HEADER_NAME: 'ok'}
372
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'limitCode'})
373
+
374
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
375
+ resp = _use_activation._use_activation_code(req)
376
+
377
+ assert resp.status_code == 400
378
+ payload = _parse_response(resp)
379
+ assert payload['success'] is False
380
+ assert 'Usage limit has been hit' in payload['payload']
381
+
382
+
383
+ def test_user_already_used_code_returns_400():
384
+ fake_db_data.clear()
385
+ fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
386
+ code = 'multi'
387
+ fake_db_data.setdefault('activation_codes', {})[code] = {'expiry_date': datetime.now(timezone.utc) + timedelta(days=1), 'use_count': 0, 'max_uses': 5}
388
+ # Create an activation record indicating user already used the code
389
+ fake_db_data.setdefault('activations', {})[f"{code}_user_1"] = {'activation_code': code, 'user_id': 'user_1', 'timestamp': fake_firestore_mod.SERVER_TIMESTAMP}
390
+
391
+ headers = {AUTH_HEADER_NAME: 'ok'}
392
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'MULTI'})
393
+
394
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
395
+ resp = _use_activation._use_activation_code(req)
396
+
397
+ assert resp.status_code == 400
398
+ payload = _parse_response(resp)
399
+ assert payload['success'] is False
400
+ assert 'User has already used this activation code' in payload['payload']
401
+
402
+
403
+ def test_successful_activation_returns_200_and_updates_db():
404
+ fake_db_data.clear()
405
+ fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
406
+ code = 'successcode'
407
+ fake_db_data.setdefault('activation_codes', {})[code] = {'expiry_date': datetime.now(timezone.utc) + timedelta(days=1), 'use_count': 2, 'max_uses': 5}
408
+
409
+ headers = {AUTH_HEADER_NAME: 'ok'}
410
+ req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'SuccessCode'})
411
+
412
+ with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
413
+ resp = _use_activation._use_activation_code(req)
414
+
415
+ assert resp.status_code == 200
416
+ payload = _parse_response(resp)
417
+ assert payload['success'] is True
418
+ assert 'Activation code used successfully' in payload['payload']
419
+
420
+ # Verify database updates
421
+ user_doc = fake_db_data.get('users', {}).get('user_1')
422
+ assert user_doc is not None
423
+ assert user_doc.get('active') is True
424
+
425
+ code_doc = fake_db_data.get('activation_codes', {}).get(code)
426
+ assert code_doc is not None
427
+ assert code_doc.get('use_count') == 3
428
+
429
+ activation_key = f"{code}_user_1"
430
+ activations = fake_db_data.get('activations', {})
431
+ assert activation_key in activations
432
+ act = activations[activation_key]
433
+ assert act['user_id'] == 'user_1'
434
+ assert act['activation_code'] == code
435
+ assert 'timestamp' in act
436
+ # timestamp should be the firestore.SERVER_TIMESTAMP sentinel
437
+ assert act['timestamp'] is fake_firestore_mod.SERVER_TIMESTAMP
@@ -0,0 +1,2 @@
1
+ # tests.functions.api_v1_00 package marker
2
+ # Allows tests under this directory to be imported with package-qualified names.