sefrone-api-e2e 1.2.6__tar.gz → 1.2.7__tar.gz
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.
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/PKG-INFO +1 -1
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/1_normal_auth_flow.yaml +0 -34
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_rest_api.py +56 -26
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/PKG-INFO +1 -1
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/setup.py +1 -1
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/README.md +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/__init__.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/api_e2e_manager.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/0_check_features.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/2_account_endpoints.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/3_admin_account_endpoints.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/4_workspace_endpoints.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/5_apikey_endpoints.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/6_project_endpoints.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/7_email_2fa_flow.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/8_temporary_passwords_flow.yaml +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e_scenarios_manager.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/env_patch.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/json_mock_server.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_assets.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_email_mock.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_satim_mock.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_webhook_mock.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/step_runner.py +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/SOURCES.txt +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/dependency_links.txt +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/requires.txt +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/top_level.txt +0 -0
- {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/setup.cfg +0 -0
|
@@ -221,40 +221,6 @@ steps:
|
|
|
221
221
|
- "{{body.response.linkedWorkspaces.count()}} == 1"
|
|
222
222
|
- "{{body.response.linkedWorkspaces[0].workspaceName}} == '$var(DEFAULT_WORKSPACE_NAME)'"
|
|
223
223
|
|
|
224
|
-
- name: "Register standard account"
|
|
225
|
-
skip_if:
|
|
226
|
-
- "PseudoRegister not in {$stored.0_check_features.enabled_features}"
|
|
227
|
-
method: "POST"
|
|
228
|
-
endpoint: "/api/v1/apiAuth/account/$var(AUTH_PROJECT_NAME)"
|
|
229
|
-
send_as: "json"
|
|
230
|
-
body:
|
|
231
|
-
username: "e2e_new_user_{$stored.seed_id}"
|
|
232
|
-
password: "e2e_test_password"
|
|
233
|
-
expect:
|
|
234
|
-
status: 200
|
|
235
|
-
body:
|
|
236
|
-
errorCode: number
|
|
237
|
-
errorCodeStr: string
|
|
238
|
-
message: string
|
|
239
|
-
response:
|
|
240
|
-
accountId: string
|
|
241
|
-
projectId: string
|
|
242
|
-
project: string
|
|
243
|
-
accountType: string
|
|
244
|
-
credentialType: string
|
|
245
|
-
isActive: boolean
|
|
246
|
-
username: string
|
|
247
|
-
linkedWorkspaces: []
|
|
248
|
-
save:
|
|
249
|
-
registered_account_id: "{{body.response.accountId}}"
|
|
250
|
-
registered_username: "{{body.response.username}}"
|
|
251
|
-
assertions:
|
|
252
|
-
- "{{body.response.accountType}} == 'standard'"
|
|
253
|
-
- "{{body.response.credentialType}} == 'standard'"
|
|
254
|
-
- "{{body.response.isActive}} == True"
|
|
255
|
-
- "{{body.response.linkedWorkspaces.count()}} == 0"
|
|
256
|
-
- "{{body.response.username}} include 'e2e_new_user_'"
|
|
257
|
-
|
|
258
224
|
- name: "Login with wrong password"
|
|
259
225
|
method: "POST"
|
|
260
226
|
endpoint: "/api/v1/apiAuth/account/$var(AUTH_PROJECT_NAME)/login"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import os
|
|
3
3
|
import random
|
|
4
|
+
import re
|
|
4
5
|
import secrets
|
|
5
6
|
import time
|
|
6
7
|
from typing import Any
|
|
@@ -256,13 +257,18 @@ class RestApiRunner(StepRunner):
|
|
|
256
257
|
)
|
|
257
258
|
|
|
258
259
|
# --- JSON parsing ---
|
|
260
|
+
# Best-effort: a non-JSON/empty body (e.g. a redirect response) is only fatal if
|
|
261
|
+
# the step actually needs the parsed body (`expect.body`). Steps that only need
|
|
262
|
+
# `{{headers.*}}` (assertions/save) must still work when the body isn't JSON.
|
|
259
263
|
needs_json = "body" in expect or "assertions" in step or "save" in step
|
|
260
264
|
resp_json = None
|
|
261
265
|
if needs_json:
|
|
262
266
|
try:
|
|
263
267
|
resp_json = resp.json()
|
|
264
268
|
except Exception:
|
|
265
|
-
|
|
269
|
+
if "body" in expect:
|
|
270
|
+
raise AssertionError(f"Response is not valid JSON: {resp.text}")
|
|
271
|
+
resp_json = None
|
|
266
272
|
|
|
267
273
|
# --- Content-Type assertion ---
|
|
268
274
|
expected_content_type = expect.get("content_type")
|
|
@@ -295,32 +301,56 @@ class RestApiRunner(StepRunner):
|
|
|
295
301
|
if "save_file_sha256" in step:
|
|
296
302
|
ctx.store_value(step["save_file_sha256"], hashlib.sha256(resp.content).hexdigest())
|
|
297
303
|
|
|
304
|
+
# `{{headers.*}}` is always available (even on non-JSON/redirect responses);
|
|
305
|
+
# `{{body.*}}` is only available when the response was valid JSON.
|
|
306
|
+
template_context: dict[str, Any] = {"headers": dict(resp.headers)}
|
|
298
307
|
if resp_json is not None:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
308
|
+
template_context["body"] = resp_json
|
|
309
|
+
|
|
310
|
+
if "body" in expect:
|
|
311
|
+
if ctx.is_verbose:
|
|
312
|
+
print(f"Response body: {resp_json}")
|
|
313
|
+
ctx.validate_structure(resp_json, expect["body"])
|
|
314
|
+
|
|
315
|
+
if "assertions" in step:
|
|
316
|
+
for expr in step["assertions"]:
|
|
317
|
+
try:
|
|
318
|
+
expr = ctx.substitute_stored(expr)
|
|
319
|
+
rendered = ctx.render_template(expr, template_context, add_quotes=True)
|
|
320
|
+
ctx.try_eval(rendered, expr)
|
|
321
|
+
except Exception as e:
|
|
322
|
+
raise AssertionError(
|
|
323
|
+
f"Assertion failed for expression\n -->'{expr}'\n -->{e}\n -->{resp.text}"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if "save" in step:
|
|
327
|
+
for key, path_expr in step["save"].items():
|
|
328
|
+
try:
|
|
329
|
+
if isinstance(path_expr, dict) and "from" in path_expr and "regex" in path_expr:
|
|
330
|
+
# Regex extraction mode — same shape as check_email's save:
|
|
331
|
+
# save:
|
|
332
|
+
# auth_code:
|
|
333
|
+
# from: "{{headers.Location}}"
|
|
334
|
+
# regex: "code=([^&]+)"
|
|
335
|
+
source_tmpl = ctx.substitute_stored(str(path_expr["from"]))
|
|
336
|
+
source = ctx.render_template(source_tmpl, template_context)
|
|
337
|
+
m = re.search(path_expr["regex"], source)
|
|
338
|
+
if m is None:
|
|
339
|
+
raise AssertionError(
|
|
340
|
+
f"Regex {path_expr['regex']!r} did not match. Source: {source!r}"
|
|
341
|
+
)
|
|
342
|
+
value = m.group(1) if m.lastindex else m.group(0)
|
|
343
|
+
ctx.store_value(key, value)
|
|
344
|
+
else:
|
|
345
|
+
# Plain template mode
|
|
346
|
+
rendered_path = ctx.substitute_stored(str(path_expr))
|
|
347
|
+
rendered = ctx.render_template(rendered_path, template_context)
|
|
320
348
|
ctx.store_value(key, rendered)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
349
|
+
except AssertionError:
|
|
350
|
+
raise
|
|
351
|
+
except Exception as e:
|
|
352
|
+
raise AssertionError(
|
|
353
|
+
f"Failed to render save path\n -->'{path_expr}'\n -->{e}\n -->{resp.text}"
|
|
354
|
+
)
|
|
325
355
|
|
|
326
356
|
return StepResult.passed()
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="sefrone_api_e2e",
|
|
8
|
-
version="1.2.
|
|
8
|
+
version="1.2.7",
|
|
9
9
|
author="Sefrone",
|
|
10
10
|
author_email="contact@sefrone.com",
|
|
11
11
|
description="A Python package to provide e2e testing helpers for sefrone API projects",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|