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,438 +0,0 @@
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_v1_00', '_use_activation_code.py')
175
- spec = importlib.util.spec_from_file_location('api_v1_00._use_activation_code', module_path)
176
- _use_activation = importlib.util.module_from_spec(spec)
177
- spec.loader.exec_module(_use_activation)
178
-
179
-
180
- # Restore original sys.modules mappings to avoid side-effects for other tests
181
- for name, orig in _orig_sys_modules.items():
182
- if orig is None:
183
- try:
184
- del sys.modules[name]
185
- except KeyError:
186
- pass
187
- else:
188
- sys.modules[name] = orig
189
-
190
- # Import env constants for auth header usage in tests
191
- import env as functions_env # noqa: E402
192
- AUTH_HEADER_NAME = functions_env.AUTH_HEADER_NAME
193
-
194
-
195
- # Simple helper request stub used in the tests
196
- class DummyRequest:
197
- def __init__(self, headers=None, method='POST', json_data=None, raise_on_get_json=False):
198
- self.headers = headers or {}
199
- self.method = method
200
- self._json_data = json_data
201
- self._raise = raise_on_get_json
202
-
203
- def get_json(self, silent=True):
204
- if self._raise:
205
- raise Exception('Malformed JSON')
206
- return self._json_data
207
-
208
-
209
- def _parse_response(resp):
210
- data = resp.get_data(as_text=True)
211
- return json.loads(data)
212
-
213
-
214
- # -----------------------------
215
- # Tests
216
- # -----------------------------
217
-
218
- def test_missing_auth_header_returns_401():
219
- fake_db_data.clear()
220
- req = DummyRequest(headers={}, method='POST')
221
- # Ensure token verification isn't called when header missing
222
- with patch.object(_use_activation, 'verify_firebase_token', side_effect=Exception('Should not be called')):
223
- resp = _use_activation._use_activation_code(req)
224
-
225
- assert resp.status_code == 401
226
- payload = _parse_response(resp)
227
- assert payload['success'] is False
228
- assert 'Missing app authentication key' in payload['payload']
229
-
230
-
231
- def test_invalid_auth_key_returns_403():
232
- fake_db_data.clear()
233
- headers = {AUTH_HEADER_NAME: 'bad-token'}
234
- req = DummyRequest(headers=headers, method='POST')
235
- with patch.object(_use_activation, 'validate_auth_key', return_value=False):
236
- resp = _use_activation._use_activation_code(req)
237
-
238
- assert resp.status_code == 403
239
- payload = _parse_response(resp)
240
- assert payload['success'] is False
241
- assert 'Invalid app authentication key' in payload['payload']
242
-
243
-
244
- def test_verify_firebase_token_failure_returns_403():
245
- fake_db_data.clear()
246
- headers = {AUTH_HEADER_NAME: 'ok'}
247
- req = DummyRequest(headers=headers, method='POST')
248
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', side_effect=Exception('boom')):
249
- resp = _use_activation._use_activation_code(req)
250
-
251
- assert resp.status_code == 403
252
- payload = _parse_response(resp)
253
- assert payload['success'] is False
254
- assert 'Authentication failed' in payload['payload']
255
-
256
-
257
- def test_missing_json_payload_returns_400():
258
- fake_db_data.clear()
259
- headers = {AUTH_HEADER_NAME: 'ok'}
260
- req = DummyRequest(headers=headers, method='POST', json_data=None)
261
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
262
- resp = _use_activation._use_activation_code(req)
263
-
264
- assert resp.status_code == 400
265
- payload = _parse_response(resp)
266
- assert payload['success'] is False
267
- assert 'Invalid or missing JSON payload' in payload['payload']
268
-
269
-
270
- def test_missing_activation_code_returns_400():
271
- fake_db_data.clear()
272
- headers = {AUTH_HEADER_NAME: 'ok'}
273
- # Provide a non-empty JSON payload without 'activation_code' to trigger the missing key branch
274
- req = DummyRequest(headers=headers, method='POST', json_data={'foo': 'bar'})
275
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
276
- resp = _use_activation._use_activation_code(req)
277
-
278
- assert resp.status_code == 400
279
- payload = _parse_response(resp)
280
- assert payload['success'] is False
281
- assert "Missing 'activation_code'" in payload['payload']
282
-
283
-
284
- def test_user_does_not_exist_returns_404():
285
- fake_db_data.clear()
286
- headers = {AUTH_HEADER_NAME: 'ok'}
287
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'code1'})
288
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
289
- resp = _use_activation._use_activation_code(req)
290
-
291
- assert resp.status_code == 404
292
- payload = _parse_response(resp)
293
- assert payload['success'] is False
294
- assert 'User does not exist' in payload['payload']
295
-
296
-
297
- def test_user_already_active_returns_200():
298
- fake_db_data.clear()
299
- fake_db_data.setdefault('users', {})['user_1'] = {'active': True}
300
-
301
- headers = {AUTH_HEADER_NAME: 'ok'}
302
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'code1'})
303
-
304
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
305
- resp = _use_activation._use_activation_code(req)
306
-
307
- assert resp.status_code == 200
308
- payload = _parse_response(resp)
309
- assert payload['success'] is True
310
- assert 'Account is already active' in payload['payload']
311
-
312
-
313
- def test_activation_code_not_found_returns_404():
314
- fake_db_data.clear()
315
- # user exists and inactive
316
- fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
317
-
318
- headers = {AUTH_HEADER_NAME: 'ok'}
319
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'CodeX'})
320
-
321
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
322
- resp = _use_activation._use_activation_code(req)
323
-
324
- assert resp.status_code == 404
325
- payload = _parse_response(resp)
326
- assert payload['success'] is False
327
- assert 'Invalid activation code' in payload['payload']
328
-
329
-
330
- def test_activation_code_missing_expiry_returns_500():
331
- fake_db_data.clear()
332
- # user exists and inactive
333
- fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
334
- # activation code exists but missing expiry_date
335
- fake_db_data.setdefault('activation_codes', {})['codea'] = {'use_count': 0, 'max_uses': 1}
336
-
337
- headers = {AUTH_HEADER_NAME: 'ok'}
338
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'codeA'})
339
-
340
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
341
- resp = _use_activation._use_activation_code(req)
342
-
343
- assert resp.status_code == 500
344
- payload = _parse_response(resp)
345
- assert payload['success'] is False
346
- assert 'Activation code expiry information is missing' in payload['payload']
347
-
348
-
349
- def test_activation_code_expired_returns_400():
350
- fake_db_data.clear()
351
- fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
352
- past = datetime.now(timezone.utc) - timedelta(days=1)
353
- fake_db_data.setdefault('activation_codes', {})['expiredcode'] = {'expiry_date': past, 'use_count': 0, 'max_uses': 5}
354
-
355
- headers = {AUTH_HEADER_NAME: 'ok'}
356
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'expiredCode'})
357
-
358
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
359
- resp = _use_activation._use_activation_code(req)
360
-
361
- assert resp.status_code == 400
362
- payload = _parse_response(resp)
363
- assert payload['success'] is False
364
- assert 'Activation code has expired' in payload['payload']
365
-
366
-
367
- def test_activation_code_usage_limit_reached_returns_400():
368
- fake_db_data.clear()
369
- fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
370
- fake_db_data.setdefault('activation_codes', {})['limitcode'] = {'expiry_date': datetime.now(timezone.utc) + timedelta(days=1), 'use_count': 5, 'max_uses': 5}
371
-
372
- headers = {AUTH_HEADER_NAME: 'ok'}
373
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'limitCode'})
374
-
375
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
376
- resp = _use_activation._use_activation_code(req)
377
-
378
- assert resp.status_code == 400
379
- payload = _parse_response(resp)
380
- assert payload['success'] is False
381
- assert 'Usage limit has been hit' in payload['payload']
382
-
383
-
384
- def test_user_already_used_code_returns_400():
385
- fake_db_data.clear()
386
- fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
387
- code = 'multi'
388
- fake_db_data.setdefault('activation_codes', {})[code] = {'expiry_date': datetime.now(timezone.utc) + timedelta(days=1), 'use_count': 0, 'max_uses': 5}
389
- # Create an activation record indicating user already used the code
390
- fake_db_data.setdefault('activations', {})[f"{code}_user_1"] = {'activation_code': code, 'user_id': 'user_1', 'timestamp': fake_firestore_mod.SERVER_TIMESTAMP}
391
-
392
- headers = {AUTH_HEADER_NAME: 'ok'}
393
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'MULTI'})
394
-
395
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
396
- resp = _use_activation._use_activation_code(req)
397
-
398
- assert resp.status_code == 400
399
- payload = _parse_response(resp)
400
- assert payload['success'] is False
401
- assert 'User has already used this activation code' in payload['payload']
402
-
403
-
404
- def test_successful_activation_returns_200_and_updates_db():
405
- fake_db_data.clear()
406
- fake_db_data.setdefault('users', {})['user_1'] = {'active': False}
407
- code = 'successcode'
408
- fake_db_data.setdefault('activation_codes', {})[code] = {'expiry_date': datetime.now(timezone.utc) + timedelta(days=1), 'use_count': 2, 'max_uses': 5}
409
-
410
- headers = {AUTH_HEADER_NAME: 'ok'}
411
- req = DummyRequest(headers=headers, method='POST', json_data={'activation_code': 'SuccessCode'})
412
-
413
- with patch.object(_use_activation, 'validate_auth_key', return_value=True), patch.object(_use_activation, 'verify_firebase_token', return_value={'uid': 'user_1'}):
414
- resp = _use_activation._use_activation_code(req)
415
-
416
- assert resp.status_code == 200
417
- payload = _parse_response(resp)
418
- assert payload['success'] is True
419
- assert 'Activation code used successfully' in payload['payload']
420
-
421
- # Verify database updates
422
- user_doc = fake_db_data.get('users', {}).get('user_1')
423
- assert user_doc is not None
424
- assert user_doc.get('active') is True
425
-
426
- code_doc = fake_db_data.get('activation_codes', {}).get(code)
427
- assert code_doc is not None
428
- assert code_doc.get('use_count') == 3
429
-
430
- activation_key = f"{code}_user_1"
431
- activations = fake_db_data.get('activations', {})
432
- assert activation_key in activations
433
- act = activations[activation_key]
434
- assert act['user_id'] == 'user_1'
435
- assert act['activation_code'] == code
436
- assert 'timestamp' in act
437
- # timestamp should be the firestore.SERVER_TIMESTAMP sentinel
438
- assert act['timestamp'] is fake_firestore_mod.SERVER_TIMESTAMP
@@ -1,2 +0,0 @@
1
- # tests.functions.api_v1_00 package marker
2
- # Allows tests under this directory to be imported with package-qualified names.