sefrone-api-e2e 1.0.0__py3-none-any.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.
@@ -0,0 +1,5 @@
1
+ from .api_e2e_manager import ApiE2ETestsManager
2
+
3
+ __all__ = [
4
+ "ApiE2ETestsManager"
5
+ ]
@@ -0,0 +1,168 @@
1
+ import yaml
2
+ import requests
3
+ import re
4
+ import os
5
+ import datetime
6
+
7
+ class ApiE2ETestsManager:
8
+ @staticmethod
9
+ def run_all_tests(folder_path, is_verbose=False):
10
+ print("\nRunning API E2E tests...")
11
+ for filename in os.listdir(folder_path):
12
+ if filename.endswith(".yaml") or filename.endswith(".yml"):
13
+ file_path = os.path.join(folder_path, filename)
14
+ print(f"\n--- Running scenario: {filename} ---")
15
+ ApiE2ETestsManager.run_yaml_test(file_path, is_verbose)
16
+ print("\nAPI E2E tests completed.")
17
+
18
+ @staticmethod
19
+ def load_yaml(path):
20
+ with open(path, "r", encoding="utf-8") as f:
21
+ return yaml.safe_load(f)
22
+
23
+ @staticmethod
24
+ def check_type(value, expected_type):
25
+ """Check if value matches expected type keyword."""
26
+ type_map = {
27
+ "string": str,
28
+ "number": (int, float),
29
+ "boolean": bool,
30
+ "datetime": str # basic check; could extend with parsing
31
+ }
32
+ if expected_type not in type_map:
33
+ raise ValueError(f"Unknown type in YAML: {expected_type}")
34
+ if expected_type == "datetime":
35
+ try:
36
+ datetime.datetime.fromisoformat(value)
37
+ return True
38
+ except Exception:
39
+ try:
40
+ if re.match(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,6}$", value):
41
+ datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
42
+ return True
43
+ except Exception:
44
+ return False
45
+ return isinstance(value, type_map[expected_type])
46
+
47
+ @staticmethod
48
+ def validate_structure(data, expected):
49
+ """Recursively validate structure & type expectations."""
50
+ if isinstance(expected, dict):
51
+ if not isinstance(data, dict):
52
+ raise AssertionError(f"Expected dict but got {type(data)}")
53
+ for k, v in expected.items():
54
+ if k not in data:
55
+ raise AssertionError(f"Missing key: {k} (Data keys: {list(data.keys())})")
56
+ ApiE2ETestsManager.validate_structure(data[k], v)
57
+ elif isinstance(expected, list):
58
+ if not isinstance(data, list):
59
+ raise AssertionError(f"Expected list but got {type(data)}")
60
+ if len(expected) > 0:
61
+ for item in data:
62
+ ApiE2ETestsManager.validate_structure(item, expected[0])
63
+ elif isinstance(expected, str): # type keyword
64
+ if not ApiE2ETestsManager.check_type(data, expected):
65
+ raise AssertionError(f"Expected {expected}, got {data} ({type(data)})")
66
+ else:
67
+ raise ValueError(f"Invalid expected type: {expected}")
68
+
69
+ @staticmethod
70
+ def render_template(template_str, context, add_quotes=False):
71
+ def resolve_path(expr, ctx):
72
+ parts = expr.split(".")
73
+ val = ctx
74
+ for p in parts:
75
+ if isinstance(val, dict) and p in val:
76
+ val = val[p]
77
+ else:
78
+ raise KeyError(f"Cannot resolve '{expr}' in context: {p} not found")
79
+ return val
80
+
81
+ def replacer(match):
82
+ expr = match.group(1).strip()
83
+ for root_key in context:
84
+ if expr.startswith(root_key + "."):
85
+ val = resolve_path(expr, context)
86
+ if isinstance(val, str):
87
+ return repr(val) if add_quotes else str(val) # adds quotes safely
88
+ elif val is True:
89
+ return "True"
90
+ elif val is False:
91
+ return "False"
92
+ elif val is None:
93
+ return "None"
94
+ else:
95
+ return str(val)
96
+ raise KeyError(f"Unknown variable: {expr}")
97
+
98
+ return re.sub(r"\{\{\s*([^}]+?)\s*\}\}", replacer, template_str)
99
+
100
+ @staticmethod
101
+ def substitute_stored(endpoint, stored):
102
+ """Substitute {$stored.key} placeholders."""
103
+ def repl(match):
104
+ key = match.group(1)
105
+ return str(stored.get(key, f"<MISSING:{key}>"))
106
+ return re.sub(r"\{\$stored\.([a-zA-Z0-9_]+)\}", repl, endpoint)
107
+
108
+ @staticmethod
109
+ def run_yaml_test(path, is_verbose=False):
110
+ spec = ApiE2ETestsManager.load_yaml(path)
111
+ base_url = spec.get("base_url", "")
112
+ steps = spec.get("steps", [])
113
+ stored = {}
114
+
115
+ print(f"Running test: {spec['name']}")
116
+ print("=" * 60)
117
+
118
+ for step in steps:
119
+ print(f"Step: {step['name']}")
120
+ url = base_url + ApiE2ETestsManager.substitute_stored(step['endpoint'], stored)
121
+ method = step['method'].upper()
122
+ body = step.get("body")
123
+ expect = step.get("expect", {})
124
+
125
+ # Make HTTP request
126
+ resp = requests.request(method, url, json=body)
127
+ print(f" → {method} {url} -> {resp.status_code}")
128
+
129
+ # Validate status code
130
+ expected_status = expect.get("status")
131
+ if expected_status and resp.status_code != expected_status:
132
+ if is_verbose:
133
+ print(f"Response body: {resp.text}")
134
+ raise AssertionError(f"Expected {expected_status}, got {resp.status_code}")
135
+
136
+ # Parse response JSON
137
+ try:
138
+ resp_json = resp.json()
139
+ except Exception:
140
+ raise AssertionError("Response is not valid JSON")
141
+
142
+ # Validate body structure
143
+ if "body" in expect:
144
+ if is_verbose:
145
+ print(f"Response body: {resp_json}")
146
+ ApiE2ETestsManager.validate_structure(resp_json, expect["body"])
147
+
148
+ # Save variables
149
+ if "save" in step:
150
+ for key, path_expr in step["save"].items():
151
+ rendered = ApiE2ETestsManager.render_template(path_expr, {"body": resp_json, "stored": stored})
152
+ stored[key] = rendered
153
+ print(f" Saved: {key} = {rendered}")
154
+
155
+ # Assertions
156
+ if "assertions" in step:
157
+ for expr in step["assertions"]:
158
+ rendered = ApiE2ETestsManager.render_template(expr, {"body": resp_json, "stored": stored}, add_quotes=True)
159
+ try:
160
+ if not eval(rendered):
161
+ raise AssertionError(f"Assertion failed: {expr}, rendered as '{rendered}'")
162
+ except Exception as e:
163
+ raise AssertionError(f"Assertion error in '{expr}', rendered as '{rendered}': {e}")
164
+
165
+ print(" ✅ Step passed.\n")
166
+
167
+ print("=" * 60)
168
+ print("🎉 All steps passed successfully!")
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.1
2
+ Name: sefrone-api-e2e
3
+ Version: 1.0.0
4
+ Summary: A Python package to provide e2e testing helpers for sefrone API projects
5
+ Home-page: https://bitbucket.org/sefrone/sefrone_pypi
6
+ Author: Sefrone
7
+ Author-email: contact@sefrone.com
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: Other/Proprietary License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.7
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: PyYAML>=6.0.1
16
+
17
+ This is not a usable Python package, but the name is reserved by SARL Sefrone.
18
+
19
+ You can find other packages published by Sefrone at pypi.org/user/gnasreddine
20
+
@@ -0,0 +1,6 @@
1
+ sefrone_api_e2e/__init__.py,sha256=NNUHigTCBkjdwCfQXVEA7urxF1-A8HF1_IuWJ8LuO-k,91
2
+ sefrone_api_e2e/api_e2e_manager.py,sha256=3LDA-kIZLyhVTvxZFE4huJdVTKQfsZmIdiTpeGwoVPs,6984
3
+ sefrone_api_e2e-1.0.0.dist-info/METADATA,sha256=k4CtrauVJStV0vCz3tb5hkNM552nz5KyMro1wG7AK4U,696
4
+ sefrone_api_e2e-1.0.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
5
+ sefrone_api_e2e-1.0.0.dist-info/top_level.txt,sha256=19WO3CsUWUiGtZBotT587N-tkxxjctKOPDEHWpHpS8M,16
6
+ sefrone_api_e2e-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.45.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ sefrone_api_e2e