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.
Files changed (29) hide show
  1. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/PKG-INFO +1 -1
  2. {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
  3. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_rest_api.py +56 -26
  4. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/PKG-INFO +1 -1
  5. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/setup.py +1 -1
  6. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/README.md +0 -0
  7. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/__init__.py +0 -0
  8. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/api_e2e_manager.py +0 -0
  9. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/0_check_features.yaml +0 -0
  10. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/2_account_endpoints.yaml +0 -0
  11. {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
  12. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/4_workspace_endpoints.yaml +0 -0
  13. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/5_apikey_endpoints.yaml +0 -0
  14. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e/Scenarios/Auth/6_project_endpoints.yaml +0 -0
  15. {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
  16. {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
  17. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/e2e_scenarios_manager.py +0 -0
  18. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/env_patch.py +0 -0
  19. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/json_mock_server.py +0 -0
  20. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_assets.py +0 -0
  21. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_email_mock.py +0 -0
  22. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_satim_mock.py +0 -0
  23. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/runner_webhook_mock.py +0 -0
  24. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e/step_runner.py +0 -0
  25. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/SOURCES.txt +0 -0
  26. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/dependency_links.txt +0 -0
  27. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/requires.txt +0 -0
  28. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/sefrone_api_e2e.egg-info/top_level.txt +0 -0
  29. {sefrone_api_e2e-1.2.6 → sefrone_api_e2e-1.2.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sefrone_api_e2e
3
- Version: 1.2.6
3
+ Version: 1.2.7
4
4
  Summary: A Python package to provide e2e testing helpers for sefrone API projects
5
5
  Home-page: https://bitbucket.org/sefrone/sefrone_pypi
6
6
  Author: Sefrone
@@ -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
- raise AssertionError(f"Response is not valid JSON: {resp.text}")
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
- if "body" in expect:
300
- if ctx.is_verbose:
301
- print(f"Response body: {resp_json}")
302
- ctx.validate_structure(resp_json, expect["body"])
303
-
304
- if "assertions" in step:
305
- for expr in step["assertions"]:
306
- try:
307
- expr = ctx.substitute_stored(expr)
308
- rendered = ctx.render_template(expr, {"body": resp_json}, add_quotes=True)
309
- ctx.try_eval(rendered, expr)
310
- except Exception as e:
311
- raise AssertionError(
312
- f"Assertion failed for expression\n -->'{expr}'\n -->{e}\n -->{resp.text}"
313
- )
314
-
315
- if "save" in step:
316
- for key, path_expr in step["save"].items():
317
- try:
318
- path_expr = ctx.substitute_stored(str(path_expr))
319
- rendered = ctx.render_template(path_expr, {"body": resp_json})
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
- except Exception as e:
322
- raise AssertionError(
323
- f"Failed to render save path\n -->'{path_expr}'\n -->{e}\n -->{resp.text}"
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sefrone_api_e2e
3
- Version: 1.2.6
3
+ Version: 1.2.7
4
4
  Summary: A Python package to provide e2e testing helpers for sefrone API projects
5
5
  Home-page: https://bitbucket.org/sefrone/sefrone_pypi
6
6
  Author: Sefrone
@@ -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.6",
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",