roksta 0.3.2__cp311-cp311-macosx_10_9_universal2.whl → 0.3.8__cp311-cp311-macosx_10_9_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roksta/__init__.cpython-311-darwin.so +0 -0
- roksta/ai/__init__.cpython-311-darwin.so +0 -0
- roksta/ai/call_ai.cpython-311-darwin.so +0 -0
- roksta/ai/gemini.cpython-311-darwin.so +0 -0
- roksta/ai/generic.cpython-311-darwin.so +0 -0
- roksta/ai/llm.cpython-311-darwin.so +0 -0
- roksta/ai/openai.cpython-311-darwin.so +0 -0
- roksta/ai/tools/__init__.cpython-311-darwin.so +0 -0
- roksta/ai/tools/delete_file.cpython-311-darwin.so +0 -0
- roksta/ai/tools/edit_file.cpython-311-darwin.so +0 -0
- roksta/ai/tools/final_response.cpython-311-darwin.so +0 -0
- roksta/ai/tools/get_file_summaries.cpython-311-darwin.so +0 -0
- roksta/ai/tools/read_file.cpython-311-darwin.so +0 -0
- roksta/ai/tools/regex_replace.cpython-311-darwin.so +0 -0
- roksta/ai/tools/shell_any.cpython-311-darwin.so +0 -0
- roksta/ai/tools/shell_limited.cpython-311-darwin.so +0 -0
- roksta/ai/tools/tool_defs.cpython-311-darwin.so +0 -0
- roksta/ai/tools/tool_utils.cpython-311-darwin.so +0 -0
- roksta/ai/tools/web_fetch.cpython-311-darwin.so +0 -0
- roksta/ai/tools/write_file.cpython-311-darwin.so +0 -0
- roksta/analytics.cpython-311-darwin.so +0 -0
- roksta/balance.cpython-311-darwin.so +0 -0
- roksta/build_project.cpython-311-darwin.so +0 -0
- roksta/chat_workflow.cpython-311-darwin.so +0 -0
- roksta/check_for_updates.cpython-311-darwin.so +0 -0
- roksta/check_subtask_sequence.cpython-311-darwin.so +0 -0
- roksta/checkpoints.cpython-311-darwin.so +0 -0
- roksta/clarify_goal.cpython-311-darwin.so +0 -0
- roksta/codebase_listing.cpython-311-darwin.so +0 -0
- roksta/command_handlers/__init__.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_activate_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_add_funds_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_auto_charge_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_auto_commit_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_building_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_chat_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_dev_rate_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_feedback_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_goal_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_help_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_init_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_linting_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_login_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_logout_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_payment_details_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_quit_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_redeem_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_request_activation_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_testing_command.cpython-311-darwin.so +0 -0
- roksta/command_handlers/handle_usage_command.cpython-311-darwin.so +0 -0
- roksta/create_default_config.cpython-311-darwin.so +0 -0
- roksta/create_default_ignore_file.cpython-311-darwin.so +0 -0
- roksta/default_config.cpython-311-darwin.so +0 -0
- roksta/default_ignores.cpython-311-darwin.so +0 -0
- roksta/discover_test_command.cpython-311-darwin.so +0 -0
- roksta/enums.cpython-311-darwin.so +0 -0
- roksta/env.cpython-311-darwin.so +0 -0
- roksta/extended_text_area.cpython-311-darwin.so +0 -0
- roksta/firebase.cpython-311-darwin.so +0 -0
- roksta/firebase_auth_web.cpython-311-darwin.so +0 -0
- roksta/firebase_config.cpython-311-darwin.so +0 -0
- roksta/fix_tests.cpython-311-darwin.so +0 -0
- roksta/gen_codebase_summaries.cpython-311-darwin.so +0 -0
- roksta/gen_one_line_goal.cpython-311-darwin.so +0 -0
- roksta/gen_subtasks.cpython-311-darwin.so +0 -0
- roksta/get_codebase_structure.cpython-311-darwin.so +0 -0
- roksta/get_failing_tests.cpython-311-darwin.so +0 -0
- roksta/goal_workflow.cpython-311-darwin.so +0 -0
- roksta/init_codebase.cpython-311-darwin.so +0 -0
- roksta/lint_code.cpython-311-darwin.so +0 -0
- roksta/logger.cpython-311-darwin.so +0 -0
- roksta/main.cpython-311-darwin.so +0 -0
- roksta/make_issue.cpython-311-darwin.so +0 -0
- roksta/new_features.cpython-311-darwin.so +0 -0
- roksta/parse_directive_cli_tokens.cpython-311-darwin.so +0 -0
- roksta/parse_readme.cpython-311-darwin.so +0 -0
- roksta/propose_solution.cpython-311-darwin.so +0 -0
- roksta/response_formats.cpython-311-darwin.so +0 -0
- roksta/rewrite_goal.cpython-311-darwin.so +0 -0
- roksta/roksta.cpython-311-darwin.so +0 -0
- roksta/run_cli_goal.cpython-311-darwin.so +0 -0
- roksta/save_chat_transcript.cpython-311-darwin.so +0 -0
- roksta/select_files.cpython-311-darwin.so +0 -0
- roksta/tips.cpython-311-darwin.so +0 -0
- roksta/utils.cpython-311-darwin.so +0 -0
- roksta/write_code.cpython-311-darwin.so +0 -0
- roksta-0.3.8.dist-info/METADATA +471 -0
- roksta-0.3.8.dist-info/RECORD +91 -0
- {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/top_level.txt +0 -1
- roksta-0.3.2.dist-info/METADATA +0 -40
- roksta-0.3.2.dist-info/RECORD +0 -121
- tests/__init__.py +0 -2
- tests/conftest.py +0 -211
- tests/functions/__init__.py +0 -2
- tests/functions/api_v1_00/__init__.py +0 -2
- tests/functions/api_v1_00/test__analytics.py +0 -416
- tests/functions/api_v1_00/test__gemini_proxy.py +0 -352
- tests/functions/api_v1_00/test__generic_proxy.py +0 -428
- tests/functions/api_v1_00/test__get_payment_details.py +0 -356
- tests/functions/api_v1_00/test__openai_proxy.py +0 -449
- tests/functions/api_v1_00/test__redeem_credit_code.py +0 -167
- tests/functions/api_v1_00/test__sync_emails.py +0 -325
- tests/functions/api_v1_00/test__take_payment.py +0 -491
- tests/functions/api_v1_00/test__use_activation_code.py +0 -438
- tests/functions/api_v1_01/__init__.py +0 -2
- tests/functions/api_v1_01/test__analytics.py +0 -416
- tests/functions/api_v1_01/test__gemini_proxy.py +0 -352
- tests/functions/api_v1_01/test__generic_proxy.py +0 -428
- tests/functions/api_v1_01/test__get_payment_details.py +0 -356
- tests/functions/api_v1_01/test__openai_proxy.py +0 -449
- tests/functions/api_v1_01/test__redeem_credit_code.py +0 -167
- tests/functions/api_v1_01/test__sync_emails.py +0 -325
- tests/functions/api_v1_01/test__take_payment.py +0 -491
- tests/functions/api_v1_01/test__use_activation_code.py +0 -438
- tests/functions/api_v1_02/__init__.py +0 -2
- tests/functions/api_v1_02/test__analytics.py +0 -416
- tests/functions/api_v1_02/test__gemini_proxy.py +0 -352
- tests/functions/api_v1_02/test__generic_proxy.py +0 -428
- tests/functions/api_v1_02/test__get_payment_details.py +0 -356
- tests/functions/api_v1_02/test__openai_proxy.py +0 -449
- tests/functions/api_v1_02/test__redeem_credit_code.py +0 -167
- tests/functions/api_v1_02/test__sync_emails.py +0 -325
- tests/functions/api_v1_02/test__take_payment.py +0 -491
- tests/functions/api_v1_02/test__use_activation_code.py +0 -438
- tests/functions/api_v1_02/test_proxy_keyword_replacement.py +0 -557
- tests/functions/api_v1_02/test_replace_keywords.py +0 -74
- tests/functions/test_auth.py +0 -24
- tests/functions/test_main.py +0 -73
- tests/functions/test_utils.py +0 -484
- {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/WHEEL +0 -0
- {roksta-0.3.2.dist-info → roksta-0.3.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
import json
|
|
4
|
-
import types
|
|
5
|
-
import importlib
|
|
6
|
-
from unittest.mock import patch
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# Ensure the functions/ directory is importable as a top-level module location
|
|
10
|
-
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
11
|
-
FUNCTIONS_DIR = os.path.join(PROJECT_ROOT, 'functions')
|
|
12
|
-
if FUNCTIONS_DIR not in sys.path:
|
|
13
|
-
sys.path.insert(0, FUNCTIONS_DIR)
|
|
14
|
-
|
|
15
|
-
# -----------------------------
|
|
16
|
-
# Provide lightweight fake modules to satisfy imports inside _take_payment
|
|
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.auth',
|
|
27
|
-
'firebase_admin.firestore',
|
|
28
|
-
'stripe',
|
|
29
|
-
'ulid',
|
|
30
|
-
]
|
|
31
|
-
for name in _names_to_fake:
|
|
32
|
-
_orig_sys_modules[name] = sys.modules.get(name)
|
|
33
|
-
|
|
34
|
-
# Fake firebase_functions.https_fn.Response to capture returned data
|
|
35
|
-
firebase_functions = types.ModuleType('firebase_functions')
|
|
36
|
-
|
|
37
|
-
class FakeResponse:
|
|
38
|
-
def __init__(self, response=None, mimetype=None, status=200, **kwargs):
|
|
39
|
-
# Mirror the small subset of the interface tests expect
|
|
40
|
-
self.status_code = status
|
|
41
|
-
if isinstance(response, (dict, list)):
|
|
42
|
-
self._body_text = json.dumps(response)
|
|
43
|
-
else:
|
|
44
|
-
self._body_text = '' if response is None else response
|
|
45
|
-
self.headers = kwargs.get('headers', {})
|
|
46
|
-
|
|
47
|
-
def get_data(self, as_text=False):
|
|
48
|
-
if as_text:
|
|
49
|
-
return self._body_text
|
|
50
|
-
return self._body_text.encode('utf-8')
|
|
51
|
-
|
|
52
|
-
firebase_functions.https_fn = types.SimpleNamespace(Request=object, Response=FakeResponse)
|
|
53
|
-
sys.modules['firebase_functions'] = firebase_functions
|
|
54
|
-
|
|
55
|
-
# Fake utils module (create_json_response, get_secret_key, verify_firebase_token)
|
|
56
|
-
utils_mod = types.ModuleType('utils')
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _fake_create_json_response(success: bool, payload: any, status_code: int):
|
|
60
|
-
response_body = {"success": success, "payload": payload}
|
|
61
|
-
return firebase_functions.https_fn.Response(response=response_body, status=status_code, headers={'Content-Type': 'application/json'})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _fake_get_secret_key(name: str) -> str:
|
|
65
|
-
return 'DUMMY_STRIPE_KEY'
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _fake_verify_firebase_token(req):
|
|
69
|
-
# Default: successful verification returning a uid
|
|
70
|
-
return {'uid': 'user_1'}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
utils_mod.create_json_response = _fake_create_json_response
|
|
74
|
-
utils_mod.get_secret_key = _fake_get_secret_key
|
|
75
|
-
utils_mod.verify_firebase_token = _fake_verify_firebase_token
|
|
76
|
-
sys.modules['utils'] = utils_mod
|
|
77
|
-
|
|
78
|
-
# Fake top-level auth module (validate_auth_key will be patched per-test as needed)
|
|
79
|
-
auth_mod = types.ModuleType('auth')
|
|
80
|
-
|
|
81
|
-
def _simple_validate_auth_key(val: str) -> bool:
|
|
82
|
-
return True
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
auth_mod.validate_auth_key = _simple_validate_auth_key
|
|
86
|
-
sys.modules['auth'] = auth_mod
|
|
87
|
-
|
|
88
|
-
# Fake firebase_admin.auth and firebase_admin.firestore
|
|
89
|
-
firebase_admin_mod = types.ModuleType('firebase_admin')
|
|
90
|
-
|
|
91
|
-
# firebase_admin.auth fake
|
|
92
|
-
fauth_mod = types.ModuleType('firebase_admin.auth')
|
|
93
|
-
|
|
94
|
-
def _fake_get_user(uid):
|
|
95
|
-
return types.SimpleNamespace(email='user@example.com')
|
|
96
|
-
|
|
97
|
-
fauth_mod.get_user = _fake_get_user
|
|
98
|
-
sys.modules['firebase_admin.auth'] = fauth_mod
|
|
99
|
-
firebase_admin_mod.auth = fauth_mod
|
|
100
|
-
|
|
101
|
-
# In-memory fake DB used by the fake Firestore implementation
|
|
102
|
-
fake_db_data = {}
|
|
103
|
-
|
|
104
|
-
# Fake Firestore implementation
|
|
105
|
-
class DocumentSnapshot:
|
|
106
|
-
def __init__(self, exists, data):
|
|
107
|
-
self.exists = exists
|
|
108
|
-
self._data = data
|
|
109
|
-
|
|
110
|
-
def to_dict(self):
|
|
111
|
-
return self._data
|
|
112
|
-
|
|
113
|
-
def get(self, key):
|
|
114
|
-
if not self._data:
|
|
115
|
-
return None
|
|
116
|
-
return self._data.get(key)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class DocumentRef:
|
|
120
|
-
def __init__(self, collection_name, doc_id):
|
|
121
|
-
self.collection = collection_name
|
|
122
|
-
self.doc_id = doc_id
|
|
123
|
-
|
|
124
|
-
def get(self, transaction=None):
|
|
125
|
-
coll = fake_db_data.get(self.collection, {})
|
|
126
|
-
if self.doc_id in coll:
|
|
127
|
-
return DocumentSnapshot(True, coll[self.doc_id])
|
|
128
|
-
return DocumentSnapshot(False, None)
|
|
129
|
-
|
|
130
|
-
def update(self, update_dict):
|
|
131
|
-
coll = fake_db_data.setdefault(self.collection, {})
|
|
132
|
-
doc = coll.setdefault(self.doc_id, {})
|
|
133
|
-
if isinstance(doc, dict):
|
|
134
|
-
doc.update(update_dict)
|
|
135
|
-
else:
|
|
136
|
-
coll[self.doc_id] = update_dict
|
|
137
|
-
|
|
138
|
-
def set(self, data):
|
|
139
|
-
coll = fake_db_data.setdefault(self.collection, {})
|
|
140
|
-
coll[self.doc_id] = data
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
class CollectionRef:
|
|
144
|
-
def __init__(self, name):
|
|
145
|
-
self.name = name
|
|
146
|
-
|
|
147
|
-
def document(self, doc_id):
|
|
148
|
-
return DocumentRef(self.name, doc_id)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
class FakeTransaction:
|
|
152
|
-
def __init__(self):
|
|
153
|
-
pass
|
|
154
|
-
|
|
155
|
-
def update(self, doc_ref, update_dict):
|
|
156
|
-
coll = fake_db_data.setdefault(doc_ref.collection, {})
|
|
157
|
-
doc = coll.setdefault(doc_ref.doc_id, {})
|
|
158
|
-
if isinstance(doc, dict):
|
|
159
|
-
doc.update(update_dict)
|
|
160
|
-
else:
|
|
161
|
-
coll[doc_ref.doc_id] = update_dict
|
|
162
|
-
|
|
163
|
-
def set(self, doc_ref, data):
|
|
164
|
-
coll = fake_db_data.setdefault(doc_ref.collection, {})
|
|
165
|
-
coll[doc_ref.doc_id] = data
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
class FakeFirestoreClient:
|
|
169
|
-
def __init__(self):
|
|
170
|
-
pass
|
|
171
|
-
|
|
172
|
-
def collection(self, name):
|
|
173
|
-
return CollectionRef(name)
|
|
174
|
-
|
|
175
|
-
def transaction(self):
|
|
176
|
-
return FakeTransaction()
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
fake_firestore_mod = types.ModuleType('firebase_admin.firestore')
|
|
180
|
-
fake_firestore_mod.client = lambda: FakeFirestoreClient()
|
|
181
|
-
fake_firestore_mod.transactional = lambda f: f
|
|
182
|
-
fake_firestore_mod.SERVER_TIMESTAMP = object()
|
|
183
|
-
sys.modules['firebase_admin.firestore'] = fake_firestore_mod
|
|
184
|
-
firebase_admin_mod.firestore = fake_firestore_mod
|
|
185
|
-
sys.modules['firebase_admin'] = firebase_admin_mod
|
|
186
|
-
|
|
187
|
-
# Fake stripe module
|
|
188
|
-
stripe_mod = types.ModuleType('stripe')
|
|
189
|
-
|
|
190
|
-
class StripeError(Exception):
|
|
191
|
-
pass
|
|
192
|
-
|
|
193
|
-
stripe_mod.error = types.SimpleNamespace(StripeError=StripeError)
|
|
194
|
-
stripe_mod.api_key = None
|
|
195
|
-
|
|
196
|
-
class _PaymentIntent:
|
|
197
|
-
@staticmethod
|
|
198
|
-
def create(**kwargs):
|
|
199
|
-
raise NotImplementedError("PaymentIntent.create not implemented for test stub")
|
|
200
|
-
|
|
201
|
-
stripe_mod.PaymentIntent = _PaymentIntent
|
|
202
|
-
sys.modules['stripe'] = stripe_mod
|
|
203
|
-
|
|
204
|
-
# Fake ulid module
|
|
205
|
-
ulid_mod = types.ModuleType('ulid')
|
|
206
|
-
ulid_mod.new = lambda: 'ULID123'
|
|
207
|
-
sys.modules['ulid'] = ulid_mod
|
|
208
|
-
|
|
209
|
-
# -----------------------------
|
|
210
|
-
# Import the module under test after preparing the fake imports
|
|
211
|
-
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
|
|
212
|
-
functions_root = os.path.join(repo_root, 'functions')
|
|
213
|
-
module_path = os.path.join(functions_root, 'api_v1_00', '_take_payment.py')
|
|
214
|
-
spec = importlib.util.spec_from_file_location('api_v1_00._take_payment', module_path)
|
|
215
|
-
_take_payment = importlib.util.module_from_spec(spec)
|
|
216
|
-
spec.loader.exec_module(_take_payment)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
# Restore original sys.modules mappings to avoid side-effects for other tests
|
|
220
|
-
for name, orig in _orig_sys_modules.items():
|
|
221
|
-
if orig is None:
|
|
222
|
-
try:
|
|
223
|
-
del sys.modules[name]
|
|
224
|
-
except KeyError:
|
|
225
|
-
pass
|
|
226
|
-
else:
|
|
227
|
-
sys.modules[name] = orig
|
|
228
|
-
|
|
229
|
-
# Import env constants for auth header usage in tests
|
|
230
|
-
import env as functions_env # noqa: E402
|
|
231
|
-
AUTH_HEADER_NAME = functions_env.AUTH_HEADER_NAME
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
# Simple helper request stub used in the tests
|
|
235
|
-
class DummyRequest:
|
|
236
|
-
def __init__(self, headers=None, method='POST', json_data=None, raise_on_get_json=False):
|
|
237
|
-
self.headers = headers or {}
|
|
238
|
-
self.method = method
|
|
239
|
-
self._json_data = json_data
|
|
240
|
-
self._raise = raise_on_get_json
|
|
241
|
-
|
|
242
|
-
def get_json(self, silent=True):
|
|
243
|
-
if self._raise:
|
|
244
|
-
raise Exception('Malformed JSON')
|
|
245
|
-
return self._json_data
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def _parse_response(resp):
|
|
249
|
-
data = resp.get_data(as_text=True)
|
|
250
|
-
return json.loads(data)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
# -----------------------------
|
|
254
|
-
# Tests
|
|
255
|
-
# -----------------------------
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def test_missing_auth_header_returns_401():
|
|
259
|
-
req = DummyRequest(headers={}, method='POST')
|
|
260
|
-
resp = _take_payment._take_payment(req)
|
|
261
|
-
|
|
262
|
-
assert resp.status_code == 401
|
|
263
|
-
payload = _parse_response(resp)
|
|
264
|
-
assert payload['success'] is False
|
|
265
|
-
assert 'Missing app authentication key' in payload['payload']
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
def test_invalid_auth_key_returns_403():
|
|
269
|
-
headers = {AUTH_HEADER_NAME: 'bad-token'}
|
|
270
|
-
req = DummyRequest(headers=headers, method='POST')
|
|
271
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=False):
|
|
272
|
-
resp = _take_payment._take_payment(req)
|
|
273
|
-
|
|
274
|
-
assert resp.status_code == 403
|
|
275
|
-
payload = _parse_response(resp)
|
|
276
|
-
assert payload['success'] is False
|
|
277
|
-
assert 'Invalid app authentication key' in payload['payload']
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def test_verify_firebase_token_failure_returns_403():
|
|
281
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
282
|
-
req = DummyRequest(headers=headers, method='POST')
|
|
283
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), patch.object(_take_payment, 'verify_firebase_token', side_effect=Exception('boom')):
|
|
284
|
-
resp = _take_payment._take_payment(req)
|
|
285
|
-
|
|
286
|
-
assert resp.status_code == 403
|
|
287
|
-
payload = _parse_response(resp)
|
|
288
|
-
assert payload['success'] is False
|
|
289
|
-
assert 'Authentication failed' in payload['payload']
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def test_missing_json_payload_returns_400():
|
|
293
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
294
|
-
req = DummyRequest(headers=headers, method='POST', json_data=None)
|
|
295
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}):
|
|
296
|
-
resp = _take_payment._take_payment(req)
|
|
297
|
-
|
|
298
|
-
assert resp.status_code == 400
|
|
299
|
-
payload = _parse_response(resp)
|
|
300
|
-
assert payload['success'] is False
|
|
301
|
-
assert 'No JSON payload provided' in payload['payload']
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
def test_missing_amount_returns_400():
|
|
305
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
306
|
-
# Provide a non-empty JSON object so get_json() returns a truthy value but without 'amount'
|
|
307
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'foo': 'bar'})
|
|
308
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}):
|
|
309
|
-
resp = _take_payment._take_payment(req)
|
|
310
|
-
|
|
311
|
-
assert resp.status_code == 400
|
|
312
|
-
payload = _parse_response(resp)
|
|
313
|
-
assert payload['success'] is False
|
|
314
|
-
assert 'Missing amount' in payload['payload']
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def test_invalid_amount_returns_400():
|
|
318
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
319
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': 'not-a-number'})
|
|
320
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}):
|
|
321
|
-
resp = _take_payment._take_payment(req)
|
|
322
|
-
|
|
323
|
-
assert resp.status_code == 400
|
|
324
|
-
payload = _parse_response(resp)
|
|
325
|
-
assert payload['success'] is False
|
|
326
|
-
assert 'Invalid amount value provided' in payload['payload']
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def test_billing_doc_missing_returns_404():
|
|
330
|
-
fake_db_data.clear()
|
|
331
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
332
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': '10'})
|
|
333
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}):
|
|
334
|
-
resp = _take_payment._take_payment(req)
|
|
335
|
-
|
|
336
|
-
assert resp.status_code == 404
|
|
337
|
-
payload = _parse_response(resp)
|
|
338
|
-
assert payload['success'] is False
|
|
339
|
-
assert 'Billing document does not exist' in payload['payload']
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def test_missing_billing_fields_returns_400():
|
|
343
|
-
fake_db_data.clear()
|
|
344
|
-
fake_db_data.setdefault('billing', {})['user_1'] = {}
|
|
345
|
-
|
|
346
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
347
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': '10'})
|
|
348
|
-
|
|
349
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}):
|
|
350
|
-
resp = _take_payment._take_payment(req)
|
|
351
|
-
|
|
352
|
-
assert resp.status_code == 400
|
|
353
|
-
payload = _parse_response(resp)
|
|
354
|
-
assert payload['success'] is False
|
|
355
|
-
assert 'Missing field(s) in billing document' in payload['payload']
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
def test_get_user_failure_returns_400():
|
|
359
|
-
fake_db_data.clear()
|
|
360
|
-
fake_db_data.setdefault('billing', {})['user_1'] = {
|
|
361
|
-
'stripe_customer_id': 'cus_x',
|
|
362
|
-
'stripe_payment_method_id': 'pm_x',
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
366
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': '10'})
|
|
367
|
-
|
|
368
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), \
|
|
369
|
-
patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}), \
|
|
370
|
-
patch.object(_take_payment.auth, 'get_user', side_effect=Exception('nope')):
|
|
371
|
-
resp = _take_payment._take_payment(req)
|
|
372
|
-
|
|
373
|
-
assert resp.status_code == 400
|
|
374
|
-
payload = _parse_response(resp)
|
|
375
|
-
assert payload['success'] is False
|
|
376
|
-
assert 'Failed to retrieve user auth record' in payload['payload']
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
def test_get_secret_key_failure_returns_500():
|
|
380
|
-
fake_db_data.clear()
|
|
381
|
-
fake_db_data.setdefault('billing', {})['user_1'] = {
|
|
382
|
-
'stripe_customer_id': 'cus_x',
|
|
383
|
-
'stripe_payment_method_id': 'pm_x',
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
387
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': '10'})
|
|
388
|
-
|
|
389
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), \
|
|
390
|
-
patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}), \
|
|
391
|
-
patch.object(_take_payment.auth, 'get_user', return_value=types.SimpleNamespace(email='user@example.com')), \
|
|
392
|
-
patch.object(_take_payment, 'get_secret_key', side_effect=Exception('boom')):
|
|
393
|
-
resp = _take_payment._take_payment(req)
|
|
394
|
-
|
|
395
|
-
assert resp.status_code == 500
|
|
396
|
-
payload = _parse_response(resp)
|
|
397
|
-
assert payload['success'] is False
|
|
398
|
-
assert 'Failed to retrieve Stripe configuration' in payload['payload']
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
def test_stripe_raises_StripeError_returns_400():
|
|
402
|
-
fake_db_data.clear()
|
|
403
|
-
fake_db_data.setdefault('billing', {})['user_1'] = {
|
|
404
|
-
'stripe_customer_id': 'cus_x',
|
|
405
|
-
'stripe_payment_method_id': 'pm_x',
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
409
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': '10'})
|
|
410
|
-
|
|
411
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), \
|
|
412
|
-
patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}), \
|
|
413
|
-
patch.object(_take_payment.auth, 'get_user', return_value=types.SimpleNamespace(email='user@example.com')), \
|
|
414
|
-
patch.object(_take_payment, 'get_secret_key', return_value='DUMMY_KEY'), \
|
|
415
|
-
patch.object(_take_payment.stripe.PaymentIntent, 'create', side_effect=_take_payment.stripe.error.StripeError('fail')):
|
|
416
|
-
resp = _take_payment._take_payment(req)
|
|
417
|
-
|
|
418
|
-
assert resp.status_code == 400
|
|
419
|
-
payload = _parse_response(resp)
|
|
420
|
-
assert payload['success'] is False
|
|
421
|
-
assert 'Stripe error during payment processing' in payload['payload']
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
def test_payment_intent_non_succeeded_returns_402():
|
|
425
|
-
fake_db_data.clear()
|
|
426
|
-
fake_db_data.setdefault('billing', {})['user_1'] = {
|
|
427
|
-
'stripe_customer_id': 'cus_x',
|
|
428
|
-
'stripe_payment_method_id': 'pm_x',
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
432
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': '10'})
|
|
433
|
-
|
|
434
|
-
fake_resp = types.SimpleNamespace(status='requires_payment_method')
|
|
435
|
-
|
|
436
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), \
|
|
437
|
-
patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}), \
|
|
438
|
-
patch.object(_take_payment.auth, 'get_user', return_value=types.SimpleNamespace(email='user@example.com')), \
|
|
439
|
-
patch.object(_take_payment, 'get_secret_key', return_value='DUMMY_KEY'), \
|
|
440
|
-
patch.object(_take_payment.stripe.PaymentIntent, 'create', return_value=fake_resp):
|
|
441
|
-
resp = _take_payment._take_payment(req)
|
|
442
|
-
|
|
443
|
-
assert resp.status_code == 402
|
|
444
|
-
payload = _parse_response(resp)
|
|
445
|
-
assert payload['success'] is False
|
|
446
|
-
assert 'Payment failed' in payload['payload']
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
def test_successful_payment_updates_balance_and_records_transaction_returns_200():
|
|
450
|
-
fake_db_data.clear()
|
|
451
|
-
fake_db_data.setdefault('billing', {})['user_1'] = {
|
|
452
|
-
'stripe_customer_id': 'cus_x',
|
|
453
|
-
'stripe_payment_method_id': 'pm_x',
|
|
454
|
-
'balance': 5.0,
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
headers = {AUTH_HEADER_NAME: 'ok'}
|
|
458
|
-
req = DummyRequest(headers=headers, method='POST', json_data={'amount': '10'})
|
|
459
|
-
|
|
460
|
-
fake_payment_intent = types.SimpleNamespace(status='succeeded')
|
|
461
|
-
|
|
462
|
-
with patch.object(_take_payment, 'validate_auth_key', return_value=True), \
|
|
463
|
-
patch.object(_take_payment, 'verify_firebase_token', return_value={'uid': 'user_1'}), \
|
|
464
|
-
patch.object(_take_payment.auth, 'get_user', return_value=types.SimpleNamespace(email='user@example.com')), \
|
|
465
|
-
patch.object(_take_payment, 'get_secret_key', return_value='DUMMY_KEY'), \
|
|
466
|
-
patch.object(_take_payment.stripe.PaymentIntent, 'create', return_value=fake_payment_intent), \
|
|
467
|
-
patch.object(_take_payment.ulid, 'new', return_value='ULID123'):
|
|
468
|
-
resp = _take_payment._take_payment(req)
|
|
469
|
-
|
|
470
|
-
assert resp.status_code == 200
|
|
471
|
-
payload = _parse_response(resp)
|
|
472
|
-
assert payload['success'] is True
|
|
473
|
-
assert 'Payment processed and balance updated successfully' in payload['payload']
|
|
474
|
-
|
|
475
|
-
# Check that billing balance was updated
|
|
476
|
-
billing_doc = fake_db_data.get('billing', {}).get('user_1')
|
|
477
|
-
assert billing_doc is not None
|
|
478
|
-
assert billing_doc.get('balance') == 15.0
|
|
479
|
-
|
|
480
|
-
# Check that transaction was recorded
|
|
481
|
-
transactions = fake_db_data.get('transactions', {})
|
|
482
|
-
# Expect a key like 'credit_user_1_ULID123'
|
|
483
|
-
matching = [k for k in transactions.keys() if k.startswith('credit_user_1_')]
|
|
484
|
-
assert matching, f"No transaction created, transactions keys: {list(transactions.keys())}"
|
|
485
|
-
tx = transactions[matching[0]]
|
|
486
|
-
assert tx['user_id'] == 'user_1'
|
|
487
|
-
assert tx['value'] == 10.0
|
|
488
|
-
assert tx['balance'] == 15.0
|
|
489
|
-
assert tx['type'] == 'credit'
|
|
490
|
-
assert tx['reason'] == 'payment'
|
|
491
|
-
assert 'timestamp' in tx
|