pytest-api-framework-alpha 0.1.3__tar.gz → 0.1.4__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 (30) hide show
  1. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/PKG-INFO +1 -1
  2. pytest_api_framework_alpha-0.1.4/framework/base_class.py +100 -0
  3. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/conftest.py +24 -24
  4. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/global_attribute.py +14 -1
  5. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/http_client.py +5 -4
  6. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/startapp.py +1 -1
  7. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/utils/common.py +23 -1
  8. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/pytest_api_framework_alpha.egg-info/PKG-INFO +1 -1
  9. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/setup.py +1 -2
  10. pytest_api_framework_alpha-0.1.3/framework/base_class.py +0 -74
  11. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/__init__.py +0 -0
  12. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/allure_report.py +0 -0
  13. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/db/__init__.py +0 -0
  14. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/db/mysql_db.py +0 -0
  15. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/db/redis_db.py +0 -0
  16. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/exit_code.py +0 -0
  17. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/extract.py +0 -0
  18. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/render_data.py +0 -0
  19. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/report.py +0 -0
  20. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/utils/__init__.py +0 -0
  21. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/utils/encrypt.py +0 -0
  22. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/utils/log_util.py +0 -0
  23. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/utils/teams_util.py +0 -0
  24. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/utils/yaml_util.py +0 -0
  25. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/framework/validate.py +0 -0
  26. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/pytest_api_framework_alpha.egg-info/SOURCES.txt +0 -0
  27. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/pytest_api_framework_alpha.egg-info/dependency_links.txt +0 -0
  28. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/pytest_api_framework_alpha.egg-info/requires.txt +0 -0
  29. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/pytest_api_framework_alpha.egg-info/top_level.txt +0 -0
  30. {pytest_api_framework_alpha-0.1.3 → pytest_api_framework_alpha-0.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-api-framework-alpha
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Author: alpha
5
5
  Author-email:
6
6
  Requires-Python: >=3.6
@@ -0,0 +1,100 @@
1
+ import traceback
2
+ from urllib.parse import urlparse, urlunparse, urljoin
3
+
4
+ import pytest
5
+ from box import Box
6
+
7
+ from framework.exit_code import ExitCode
8
+ from framework.db.mysql_db import MysqlDB
9
+ from framework.db.redis_db import RedisDB
10
+ from framework.utils.log_util import logger
11
+ from framework.http_client import ResponseUtil
12
+ from framework.global_attribute import CONFIG, GlobalAttribute
13
+
14
+
15
+ class BaseTestCase(object):
16
+ context: GlobalAttribute = None
17
+ config: GlobalAttribute = None
18
+ http = None
19
+ data: Box = None
20
+ scenario: Box = None
21
+ belong_app = None
22
+ response: ResponseUtil = None
23
+
24
+ def request(self, app=None, *, account, data, **kwargs):
25
+ try:
26
+ app = self.default_app(app)
27
+ app_http = getattr(self.http, app)
28
+ domain = self.context.get(app).get("domain")
29
+ data.request.url = self.replace_domain(data.request.url, domain)
30
+ self.response = getattr(app_http, account).request(data=data, kwargs=kwargs)
31
+ return self.response
32
+ except AttributeError as e:
33
+ logger.error(f"app {app} or account {account} no exist: {e}")
34
+ traceback.print_exc()
35
+ pytest.exit(ExitCode.APP_OR_ACCOUNT_NOT_EXIST)
36
+ return None
37
+
38
+ def post(self, app, account, url, data=None, json=None, **kwargs):
39
+ domain = self.context.get(app).get("domain")
40
+ url = urljoin(domain, url)
41
+ self.response = getattr(getattr(self.http, app), account).post(app, url, data=data, json=json, **kwargs)
42
+ return self.response
43
+
44
+ def get(self, app, account, url, params=None, **kwargs):
45
+ domain = self.context.get(app).get("domain")
46
+ url = urljoin(domain, url)
47
+ self.response = getattr(getattr(self.http, app), account).get(app, url, params=params, **kwargs)
48
+ return self.response
49
+
50
+ def put(self, app, account, url, data=None, **kwargs):
51
+ domain = self.context.get(app).get("domain")
52
+ url = urljoin(domain, url)
53
+ self.response = getattr(getattr(self.http, app), account).put(app, url, data=data, **kwargs)
54
+ return self.response
55
+
56
+ def delete(self, app, account, url, **kwargs):
57
+ domain = self.context.get(app).get("domain")
58
+ url = urljoin(domain, url)
59
+ self.response = getattr(getattr(self.http, app), account).delete(app, url, **kwargs)
60
+ return self.response
61
+
62
+ def mysql_conn(self, db, app=None):
63
+ try:
64
+ return MysqlDB(**CONFIG.get(app=self.default_app(app), key="mysql").get(db))
65
+ except AttributeError as e:
66
+ traceback.print_exc()
67
+ pytest.exit(ExitCode.LOAD_DATABASE_INFO_ERROR)
68
+
69
+ def redis_conn(self, db, app=None):
70
+ try:
71
+ return RedisDB(**CONFIG.get(app=self.default_app(app), key="redis").get(db))
72
+ except AttributeError as e:
73
+ traceback.print_exc()
74
+ pytest.exit(ExitCode.LOAD_DATABASE_INFO_ERROR)
75
+
76
+ def context_set(self, key, value):
77
+ self.context.set(app=self.belong_app, key=key, value=value)
78
+
79
+ def context_get(self, key):
80
+ return self.context.get(app=self.belong_app, key=key)
81
+
82
+ def default_app(self, app):
83
+ return app or self.belong_app
84
+
85
+ @staticmethod
86
+ def replace_domain(url: str, new_base: str) -> str:
87
+ """
88
+ 替换 URL 的 scheme 和 netloc(协议和域名)。
89
+ :param url: 原始 URL
90
+ :param new_base: 新的 base,如 'https://new.example.com'
91
+ :return: 替换后的 URL
92
+ """
93
+ parsed_url = urlparse(url)
94
+ new_base_parsed = urlparse(new_base)
95
+
96
+ updated_url = parsed_url._replace(
97
+ scheme=new_base_parsed.scheme,
98
+ netloc=new_base_parsed.netloc
99
+ )
100
+ return urlunparse(updated_url)
@@ -20,33 +20,13 @@ from framework.utils.log_util import logger
20
20
  from framework.render_data import RenderData
21
21
  from framework.utils.yaml_util import YamlUtil
22
22
  from framework.allure_report import generate_report
23
- from framework.utils.common import snake_to_pascal, get_apps
24
23
  from framework.global_attribute import CONTEXT, CONFIG, _FRAMEWORK_CONTEXT
24
+ from framework.utils.common import snake_to_pascal, get_apps, convert_numbers_to_decimal
25
25
  from config.settings import DATA_DIR, CASES_DIR
26
26
 
27
27
  all_app = get_apps()
28
28
 
29
29
 
30
- def pytest_configure(config):
31
- """
32
- 初始化时被调用,可以用于设置全局状态或配置
33
- :param config:
34
- :return:
35
- """
36
-
37
- for app in all_app:
38
- # 将所有app对应环境的基础测试数据加到全局
39
- CONTEXT.set_from_yaml(f"config/{app}/context.yaml", CONTEXT.env, app)
40
- # 将所有app对应环境的中间件配置加到全局
41
- CONFIG.set_from_yaml(f"config/{app}/config.yaml", CONTEXT.env, app)
42
- CONTEXT.set(key="all_app", value=all_app)
43
- sys.path.append(CASES_DIR)
44
-
45
-
46
- def pytest_addoption(parser):
47
- parser.addini(name="ignore_error_and_continue", help="是否忽略失败case,继续执行")
48
-
49
-
50
30
  def find_data_path_by_case(app, case_file_name):
51
31
  """
52
32
  基于case文件名称查找与之对应的yml文件路径
@@ -75,6 +55,26 @@ def __init_allure(params):
75
55
  allure.dynamic.story(params.get("describe"))
76
56
 
77
57
 
58
+ def pytest_configure(config):
59
+ """
60
+ 初始化时被调用,可以用于设置全局状态或配置
61
+ :param config:
62
+ :return:
63
+ """
64
+
65
+ for app in all_app:
66
+ # 将所有app对应环境的基础测试数据加到全局
67
+ CONTEXT.set_from_yaml(f"config/{app}/context.yaml", CONTEXT.env, app)
68
+ # 将所有app对应环境的中间件配置加到全局
69
+ CONFIG.set_from_yaml(f"config/{app}/config.yaml", CONTEXT.env, app)
70
+ CONTEXT.set(key="all_app", value=all_app)
71
+ sys.path.append(CASES_DIR)
72
+
73
+
74
+ def pytest_addoption(parser):
75
+ parser.addini(name="ignore_error_and_continue", help="是否忽略失败case,继续执行")
76
+
77
+
78
78
  def pytest_generate_tests(metafunc):
79
79
  """
80
80
  生成(多个)对测试函数的参数化调用
@@ -243,7 +243,7 @@ def pytest_runtest_setup(item):
243
243
  test_object.http = _FRAMEWORK_CONTEXT.get(key="_http")
244
244
  data = item.callspec.params.get("data")
245
245
  test_object.data = Box(data)
246
- test_object.scenario = Box(data.get("_scenario").get("data"))
246
+ test_object.scenario = Box(convert_numbers_to_decimal(data.get("_scenario").get("data")))
247
247
  test_object.belong_app = data.get("_belong_app")
248
248
  test_setup = getattr(test_object, "test_setup", None)
249
249
  if test_setup:
@@ -292,14 +292,14 @@ def pytest_runtest_call(item):
292
292
  # 函数式测试用例添加参数data, belong_app
293
293
  http = item.funcargs.get("http")
294
294
  item.funcargs["data"] = Box(rendered_data)
295
- item.funcargs["scenario"] = Box(rendered_data.get("_scenario").get("data"))
295
+ item.funcargs["scenario"] = Box(convert_numbers_to_decimal(rendered_data.get("_scenario").get("data")))
296
296
  item.funcargs["belong_app"] = origin_data.get("_belong_app")
297
297
  item.funcargs["config"] = CONFIG
298
298
  item.funcargs["context"] = CONTEXT
299
299
  # 类式测试用例添加参数http,data, belong_app
300
300
  item.instance.http = http
301
301
  item.instance.data = Box(rendered_data)
302
- item.instance.scenario = Box(rendered_data.get("_scenario").get("data"))
302
+ item.instance.scenario = Box(convert_numbers_to_decimal(rendered_data.get("_scenario").get("data")))
303
303
  item.instance.belong_app = origin_data.get("_belong_app")
304
304
  item.instance.context = CONTEXT
305
305
  item.instance.config = CONFIG
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import traceback
3
+ import yaml
3
4
 
4
5
  import pytest
5
6
  from box import Box
@@ -11,6 +12,18 @@ from framework.utils.log_util import logger
11
12
  from framework.utils.common import singleton
12
13
 
13
14
 
15
+ class NoDatesSafeLoader(yaml.SafeLoader):
16
+ pass
17
+
18
+
19
+ # 禁用 YAML 中的 timestamp 类型自动转换
20
+ for ch in list(NoDatesSafeLoader.yaml_implicit_resolvers):
21
+ resolvers = NoDatesSafeLoader.yaml_implicit_resolvers[ch]
22
+ NoDatesSafeLoader.yaml_implicit_resolvers[ch] = [
23
+ (tag, regexp) for tag, regexp in resolvers if tag != 'tag:yaml.org,2002:timestamp'
24
+ ]
25
+
26
+
14
27
  class GlobalAttribute(object):
15
28
  def __setattr__(self, key, value):
16
29
  super().__setattr__(
@@ -66,7 +79,7 @@ class GlobalAttribute(object):
66
79
  if not os.path.exists(file):
67
80
  logger.error(f"{file}文件不存在")
68
81
  pytest.exit(ExitCode.CONTEXT_YAML_NOT_EXIST)
69
- self.set_from_dict(dict(Box().from_yaml(filename=file).get(env)), app)
82
+ self.set_from_dict(dict(Box().from_yaml(filename=file, Loader=NoDatesSafeLoader).get(env)), app)
70
83
  except BoxError as e:
71
84
  logger.error(f"{file}文件内容不是字典类型:{e}")
72
85
  traceback.print_exc()
@@ -12,15 +12,16 @@ from framework.extract import Extract
12
12
  from framework.utils.log_util import logger
13
13
  from framework.global_attribute import CONTEXT
14
14
  from requests_toolbelt import MultipartEncoder
15
+ from framework.utils.common import convert_numbers_to_decimal
15
16
 
16
17
 
17
18
  class ResponseUtil(object):
18
19
  def __init__(self, response: Response):
19
20
  self.response = response
20
21
  if isinstance(self.response.json(), dict):
21
- self.box = Box(self.response.json())
22
+ self.box = Box(convert_numbers_to_decimal(self.response.json()))
22
23
  elif isinstance(self.response.json(), list):
23
- self.box = BoxList(self.response.json())
24
+ self.box = BoxList(convert_numbers_to_decimal(self.response.json()))
24
25
  else:
25
26
  self.box = self.response.text
26
27
 
@@ -122,8 +123,8 @@ class HttpClient(object):
122
123
  logger.info(f"请求参数json: {request_obj.get('json')}")
123
124
 
124
125
  with allure.step("响应结果"):
125
- with allure.step(f"响应状态码: {self.response.status_code}"):
126
- logger.info(f"响应状态码: {self.response.status_code}")
126
+ with allure.step(f"响应code: {self.response.status_code}"):
127
+ logger.info(f"响应code: {self.response.status_code}")
127
128
  with allure.step(f"响应headers: {self.response.headers}"):
128
129
  logger.info(f"响应headers: {self.response.headers}")
129
130
  with allure.step(f"响应body: {self.response.json()}"):
@@ -104,7 +104,7 @@ def create_app_session(app):
104
104
 
105
105
  class {snake_to_pascal(app)}Login(Login):
106
106
 
107
- def login(self, username, password, secret_key):
107
+ def login(self, username, password, secret_key, **kwargs):
108
108
  client = HttpClient()
109
109
  # TODO 需要实现登录逻辑,将登录获取到的token添加到headers中
110
110
  token = None
@@ -1,7 +1,10 @@
1
+ import decimal
1
2
  import re
2
3
  import os
3
4
  import time
4
5
  import binascii
6
+ from typing import Any
7
+ from decimal import Decimal
5
8
  from datetime import datetime
6
9
  from urllib.parse import unquote, quote, quote_plus, unquote_plus
7
10
  import pyotp
@@ -207,5 +210,24 @@ def get_apps():
207
210
  os.path.isdir(os.path.join(CONFIG_DIR, name)) and not name.startswith(("__", "."))]
208
211
 
209
212
 
213
+ def convert_numbers_to_decimal(obj: Any) -> Any:
214
+ if isinstance(obj, str):
215
+ stripped = obj.replace(",", "")
216
+ try:
217
+ return Decimal(stripped)
218
+ except Exception:
219
+ return obj
220
+ elif isinstance(obj, dict):
221
+ return {k: convert_numbers_to_decimal(v) for k, v in obj.items()}
222
+ elif isinstance(obj, list):
223
+ return [convert_numbers_to_decimal(item) for item in obj]
224
+ elif isinstance(obj, (int, float)) and not isinstance(obj, bool):
225
+ return Decimal(str(obj))
226
+ else:
227
+ return obj
228
+
229
+
210
230
  if __name__ == '__main__':
211
- print(get_apps())
231
+ a = decimal.Decimal("123")
232
+ print(a)
233
+ assert a == 123
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-api-framework-alpha
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Author: alpha
5
5
  Author-email:
6
6
  Requires-Python: >=3.6
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="pytest-api-framework-alpha", # 包名(必须唯一)
5
- version="0.1.3",
5
+ version="0.1.4",
6
6
  packages=find_packages(),
7
7
  author="alpha",
8
8
  author_email="",
@@ -31,5 +31,4 @@ setup(
31
31
  "pytest-timeout==2.2.0",
32
32
  "dill==0.3.8"
33
33
  ]
34
-
35
34
  )
@@ -1,74 +0,0 @@
1
- import traceback
2
-
3
- import pytest
4
- from box import Box
5
-
6
- from framework.exit_code import ExitCode
7
- from framework.db.mysql_db import MysqlDB
8
- from framework.db.redis_db import RedisDB
9
- from framework.utils.log_util import logger
10
- from framework.http_client import ResponseUtil
11
- from framework.global_attribute import CONFIG, GlobalAttribute
12
-
13
-
14
- class BaseTestCase(object):
15
- context: GlobalAttribute = None
16
- config: GlobalAttribute = None
17
- http = None
18
- data: Box = None
19
- scenario: Box = None
20
- belong_app = None
21
- response: ResponseUtil = None
22
-
23
- def request(self, app=None, *, account, data, **kwargs):
24
- try:
25
- app = self.default_app(app)
26
- app_http = getattr(self.http, app)
27
- self.response = getattr(app_http, account).request(data=data, keyword=kwargs)
28
- return self.response
29
- except AttributeError as e:
30
- logger.error(f"app {app} or account {account} no exist: {e}")
31
- traceback.print_exc()
32
- pytest.exit(ExitCode.APP_OR_ACCOUNT_NOT_EXIST)
33
- return None
34
-
35
- def post(self, app, account, url, data=None, json=None, **kwargs):
36
- return getattr(getattr(self.http, app), account).post(app, url, data=data, json=json, **kwargs)
37
-
38
- def get(self, app, account, url, params=None, **kwargs):
39
- return getattr(getattr(self.http, app), account).get(app, url, params=params, **kwargs)
40
-
41
- def put(self, app, account, url, data=None, **kwargs):
42
- return getattr(getattr(self.http, app), account).put(app, url, data=data, **kwargs)
43
-
44
- def delete(self, app, account, url, **kwargs):
45
- return getattr(getattr(self.http, app), account).delete(app, url, **kwargs)
46
-
47
- def mysql_conn(self, db, app=None):
48
- try:
49
- return MysqlDB(**CONFIG.get(app=self.default_app(app), key="mysql").get(db))
50
- except AttributeError as e:
51
- traceback.print_exc()
52
- pytest.exit(ExitCode.LOAD_DATABASE_INFO_ERROR)
53
-
54
- def redis_conn(self, db, app=None):
55
- try:
56
- return RedisDB(**CONFIG.get(app=self.default_app(app), key="redis").get(db))
57
- except AttributeError as e:
58
- traceback.print_exc()
59
- pytest.exit(ExitCode.LOAD_DATABASE_INFO_ERROR)
60
-
61
- def context_set(self, app=None, *, key, value):
62
- app = self.default_app(app)
63
- self.context.set(app=app, key=key, value=value)
64
-
65
- def context_get(self, app=None, *, key):
66
- app = self.default_app(app)
67
- return self.context.get(app=app, key=key)
68
-
69
- def config_get(self, app=None, *, key):
70
- app = self.default_app(app)
71
- return self.config.get(app=app, key=key)
72
-
73
- def default_app(self, app):
74
- return app or self.belong_app