pytest-api-framework-alpha 0.3.18__tar.gz → 0.3.20__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.
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/PKG-INFO +1 -1
- pytest_api_framework_alpha-0.3.20/framework/assert_webhook.py +78 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/base_class.py +97 -3
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/conftest.py +5 -1
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/db/redis_db.py +7 -1
- pytest_api_framework_alpha-0.3.20/framework/retry_assert.py +58 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/lark_util.py +1 -1
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/validate.py +3 -3
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/pytest_api_framework_alpha.egg-info/PKG-INFO +1 -1
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/pytest_api_framework_alpha.egg-info/SOURCES.txt +2 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/setup.py +1 -1
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/__init__.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/db/__init__.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/db/mysql_db.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/exceptions.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/exit_code.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/extract.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/global_attribute.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/http_client.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/render_data.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/report.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/script.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/startapp.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/__init__.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/common.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/date_util.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/encrypt.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/log_util.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/mock_util.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/yaml_util.py +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/pytest_api_framework_alpha.egg-info/dependency_links.txt +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/pytest_api_framework_alpha.egg-info/requires.txt +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/pytest_api_framework_alpha.egg-info/top_level.txt +0 -0
- {pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/setup.cfg +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from box import Box, BoxList
|
|
3
|
+
from framework.utils.log_util import logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AssertWebhook(object):
|
|
7
|
+
|
|
8
|
+
def __init__(self, webhook):
|
|
9
|
+
self.webhook_format = "dict"
|
|
10
|
+
if isinstance(webhook, dict):
|
|
11
|
+
self.webhook = Box(webhook)
|
|
12
|
+
elif isinstance(webhook, list):
|
|
13
|
+
self.webhook = BoxList(webhook)
|
|
14
|
+
self.webhook_format = "list"
|
|
15
|
+
else:
|
|
16
|
+
self.webhook = webhook
|
|
17
|
+
self.webhook_format = "other"
|
|
18
|
+
|
|
19
|
+
def assert_equal(self, expression, expected_result, desc=None):
|
|
20
|
+
"""
|
|
21
|
+
校验webhook中的数据等于预期结果
|
|
22
|
+
:param expression:
|
|
23
|
+
:param expected_result:
|
|
24
|
+
:param desc:
|
|
25
|
+
:return:
|
|
26
|
+
"""
|
|
27
|
+
if self.webhook_format == "other":
|
|
28
|
+
logger.warning(f"不支持非标准格式的webhook(json)校验")
|
|
29
|
+
return
|
|
30
|
+
practical_result = self.get_nested_value(self.webhook, expression)
|
|
31
|
+
logger.info(f"WEBHOOK断言: 断言类型: assert_equal, 断言结果: {practical_result} == {expected_result}")
|
|
32
|
+
assert practical_result == expected_result, f"{desc},实际结果: {practical_result}"
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
def assert_is_null(self, expression, desc=None):
|
|
36
|
+
"""
|
|
37
|
+
校验webhook中的数据等于预期结果
|
|
38
|
+
:param expression:
|
|
39
|
+
:param desc:
|
|
40
|
+
:return:
|
|
41
|
+
"""
|
|
42
|
+
if self.webhook_format == "other":
|
|
43
|
+
logger.warning(f"不支持非标准格式的webhook(json)校验")
|
|
44
|
+
return
|
|
45
|
+
practical_result = self.get_nested_value(self.webhook, expression)
|
|
46
|
+
logger.info(f"WEBHOOK断言: 断言类型: assert_is_null, 断言结果: {practical_result}")
|
|
47
|
+
assert not practical_result, f"{desc},实际结果: {practical_result}"
|
|
48
|
+
return self
|
|
49
|
+
|
|
50
|
+
def assert_not_null(self, expression, desc=None):
|
|
51
|
+
"""
|
|
52
|
+
校验webhook中的数据等于预期结果
|
|
53
|
+
:param expression:
|
|
54
|
+
:param desc:
|
|
55
|
+
:return:
|
|
56
|
+
"""
|
|
57
|
+
if self.webhook_format == "other":
|
|
58
|
+
logger.warning(f"不支持非标准格式的webhook(json)校验")
|
|
59
|
+
return
|
|
60
|
+
practical_result = self.get_nested_value(self.webhook, expression)
|
|
61
|
+
logger.info(f"WEBHOOK断言: 断言类型: assert_not_null, 断言结果: {practical_result}")
|
|
62
|
+
assert practical_result is not None, f"{desc},实际结果: {practical_result}"
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def get_nested_value(obj, attr_path):
|
|
67
|
+
"""通过字符串路径(如 'a.b[0].c')获取嵌套属性值"""
|
|
68
|
+
# 使用正则表达式分解路径,支持属性和索引的组合
|
|
69
|
+
path_elements = re.findall(r'(\w+)|\[(\d+)]', attr_path)
|
|
70
|
+
try:
|
|
71
|
+
for attr, index in path_elements:
|
|
72
|
+
if attr: # 属性部分
|
|
73
|
+
obj = getattr(obj, attr)
|
|
74
|
+
if index: # 索引部分
|
|
75
|
+
obj = obj[int(index)]
|
|
76
|
+
return obj
|
|
77
|
+
except Exception:
|
|
78
|
+
return None
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/base_class.py
RENAMED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import time
|
|
3
|
+
import json
|
|
1
4
|
import traceback
|
|
2
5
|
import importlib
|
|
3
6
|
from typing import Union
|
|
@@ -15,6 +18,9 @@ from framework.utils.log_util import logger
|
|
|
15
18
|
from framework.render_data import RenderData
|
|
16
19
|
from framework.http_client import ResponseUtil
|
|
17
20
|
from framework.utils.date_util import DateUtil
|
|
21
|
+
from framework.retry_assert import RetryAssert
|
|
22
|
+
from framework.assert_webhook import AssertWebhook
|
|
23
|
+
from framework.utils.encrypt import b64_encode, b64_decode
|
|
18
24
|
from framework.utils.common import snake_to_pascal, SingletonFaker
|
|
19
25
|
from framework.global_attribute import GlobalAttribute, _FRAMEWORK_CONTEXT, CONTEXT
|
|
20
26
|
from framework.exceptions import ValidateException, RenderException, RequestException, GetAccountError, GetAppHttpError
|
|
@@ -22,7 +28,7 @@ from framework.utils.mock_util import get_customized_kytmock, set_customized_kyt
|
|
|
22
28
|
mock_mq
|
|
23
29
|
|
|
24
30
|
from handlers.extend_base_test_case_attr import ExtendBaseTestCase
|
|
25
|
-
from config.settings import UNAUTHORIZED_CODE, FAKER_LANGUAGE
|
|
31
|
+
from config.settings import UNAUTHORIZED_CODE, FAKER_LANGUAGE, GET_WEBHOOK_TIMEOUT, GET_WEBHOOK_TRIES
|
|
26
32
|
|
|
27
33
|
module = importlib.import_module("test_case.conftest")
|
|
28
34
|
|
|
@@ -39,6 +45,7 @@ class BaseTestCase(ExtendBaseTestCase):
|
|
|
39
45
|
http = None
|
|
40
46
|
data: Box = None
|
|
41
47
|
belong_app = None
|
|
48
|
+
retry_assert = RetryAssert.eventually
|
|
42
49
|
scenario: Scenario = None
|
|
43
50
|
response: ResponseUtil = None
|
|
44
51
|
context: Union[GlobalAttribute, Box] = None
|
|
@@ -64,6 +71,7 @@ class BaseTestCase(ExtendBaseTestCase):
|
|
|
64
71
|
data = RenderData(data).render()
|
|
65
72
|
data.request.url = self.replace_domain(data.request.url, domain)
|
|
66
73
|
try:
|
|
74
|
+
self.set_webhook_url(account)
|
|
67
75
|
self.response = getattr(app_http, account).request(data=data, **kwargs)
|
|
68
76
|
except AttributeError as e:
|
|
69
77
|
raise GetAccountError(e)
|
|
@@ -173,6 +181,90 @@ class BaseTestCase(ExtendBaseTestCase):
|
|
|
173
181
|
)
|
|
174
182
|
return urlunparse(updated_url)
|
|
175
183
|
|
|
184
|
+
def set_webhook_url(self, account):
|
|
185
|
+
"""
|
|
186
|
+
修改webhook url地址,在原地址基础上拼接上/uuid,保证地址唯一
|
|
187
|
+
:param account: 账户名
|
|
188
|
+
:return:
|
|
189
|
+
"""
|
|
190
|
+
account_info = self.context.get(self.belong_app).get("accounts").get(account)
|
|
191
|
+
info = self.mysql_conn("db_camp_crm").query(
|
|
192
|
+
f"SELECT webhook_url FROM tbl_participant WHERE participant_code = '{account_info.client_id}';",
|
|
193
|
+
log=False)
|
|
194
|
+
if not info:
|
|
195
|
+
# self.logger.warning(f"账号{account}未配置webhook地址")
|
|
196
|
+
return None
|
|
197
|
+
original_webhook_url = info.get("webhook_url").rsplit("/", 1)[0]
|
|
198
|
+
new_webhook_url = f"{original_webhook_url}/{str(uuid.uuid4()).replace("-", "_")}"
|
|
199
|
+
try:
|
|
200
|
+
self.mysql_conn("db_camp_crm").execute(
|
|
201
|
+
f"UPDATE tbl_participant set webhook_url = '{new_webhook_url}' WHERE participant_code = '{account_info.client_id}';",
|
|
202
|
+
log=False)
|
|
203
|
+
self.context_set(f"{account_info.client_id}_webhook", urlparse(new_webhook_url).path)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
self.logger.error(f"账号{account}修改webhook地址异常: {e}")
|
|
206
|
+
|
|
207
|
+
def get_webhook(self, account, count=None, timeout=GET_WEBHOOK_TIMEOUT, interval=GET_WEBHOOK_TRIES):
|
|
208
|
+
"""
|
|
209
|
+
获取 webhook 日志,若为空则重试
|
|
210
|
+
|
|
211
|
+
:param account: 账户名
|
|
212
|
+
:param count: 期望日志条数(None 表示只要非空即可)
|
|
213
|
+
:param timeout: 最大等待时间(秒)
|
|
214
|
+
:param interval: 重试间隔(秒)
|
|
215
|
+
:return: webhook 日志列表 or []
|
|
216
|
+
"""
|
|
217
|
+
account_info = self.context.get(self.belong_app).get("accounts").get(account)
|
|
218
|
+
webhook_url = self.context_get(f"{account_info.client_id}_webhook")
|
|
219
|
+
|
|
220
|
+
if not webhook_url:
|
|
221
|
+
self.logger.warning(f"账号{account}未配置webhook地址")
|
|
222
|
+
return
|
|
223
|
+
webhook_url = webhook_url.replace("/webhook_vmock", "")
|
|
224
|
+
base64_webhook_url = b64_encode(webhook_url.encode("utf8"))
|
|
225
|
+
self.logger.info(f'原始webhook_url: {webhook_url}, base64格式webhook_url: {base64_webhook_url}')
|
|
226
|
+
|
|
227
|
+
redis_key = f"vmock_webhook_log:{base64_webhook_url}"
|
|
228
|
+
redis = self.redis_conn(db="database")
|
|
229
|
+
end_time = time.time() + timeout
|
|
230
|
+
webhook_list = []
|
|
231
|
+
while time.time() < end_time:
|
|
232
|
+
webhook_list = redis.lrange_all(redis_key)
|
|
233
|
+
if webhook_list:
|
|
234
|
+
webhook_list = [
|
|
235
|
+
json.loads(raw[raw.find(b"{"):].decode("utf-8"))
|
|
236
|
+
for raw in webhook_list
|
|
237
|
+
if isinstance(raw, (bytes, bytearray)) and raw.find(b"{") != -1
|
|
238
|
+
]
|
|
239
|
+
current_count = len(webhook_list)
|
|
240
|
+
if count is None:
|
|
241
|
+
self.logger.info(f"成功获取webhook{current_count}条: {json.dumps(webhook_list)}")
|
|
242
|
+
redis.lpop_all(redis_key)
|
|
243
|
+
return webhook_list
|
|
244
|
+
|
|
245
|
+
if current_count == count:
|
|
246
|
+
self.logger.info(f"成功获取webhook{current_count}条: {json.dumps(webhook_list)}")
|
|
247
|
+
redis.lpop_all(redis_key)
|
|
248
|
+
return webhook_list
|
|
249
|
+
|
|
250
|
+
self.logger.info(f"已获取webhook {current_count} 条,未达到预期 {count} 条,5 秒后重试...")
|
|
251
|
+
else:
|
|
252
|
+
self.logger.info(f"暂未收到webhook,{interval} 秒后重试...")
|
|
253
|
+
time.sleep(interval)
|
|
254
|
+
current_count = len(webhook_list)
|
|
255
|
+
if current_count == 0:
|
|
256
|
+
self.logger.warning(f"等待webhook超时({GET_WEBHOOK_TIMEOUT} 秒),redis key: {redis_key}")
|
|
257
|
+
raise AssertionError('暂未收到webhook')
|
|
258
|
+
else:
|
|
259
|
+
self.logger.info(f"已获取部分webhook: {json.dumps(webhook_list)}")
|
|
260
|
+
if count:
|
|
261
|
+
raise AssertionError(f'webhook条数错误,当前条数: {current_count} 期望条数: {count}')
|
|
262
|
+
redis.lpop_all(redis_key)
|
|
263
|
+
return webhook_list
|
|
264
|
+
|
|
265
|
+
def webhook(self, webhook):
|
|
266
|
+
return AssertWebhook(webhook)
|
|
267
|
+
|
|
176
268
|
def get_customized_kytmock(self, request_id):
|
|
177
269
|
return get_customized_kytmock(self.context.get("env"), request_id)
|
|
178
270
|
|
|
@@ -222,7 +314,8 @@ class BaseTestCase(ExtendBaseTestCase):
|
|
|
222
314
|
crm = self.mysql_conn(db=self.db.DB_CAMP_CRM)
|
|
223
315
|
crm.execute(
|
|
224
316
|
f"delete from tbl_crypto_wallet where id in (select wallet_id from tbl_participant_crypto_wallet where wallet_tag ='MY HOT WALLET' and participant_code='{participant_code}');")
|
|
225
|
-
crm.execute(
|
|
317
|
+
crm.execute(
|
|
318
|
+
f"delete from tbl_participant_crypto_wallet where wallet_tag ='MY HOT WALLET' and participant_code='{participant_code}';")
|
|
226
319
|
|
|
227
320
|
# 重新生成新钱包
|
|
228
321
|
self.post(
|
|
@@ -254,7 +347,8 @@ class BaseTestCase(ExtendBaseTestCase):
|
|
|
254
347
|
crm = self.mysql_conn(db=self.db.DB_CAMP_CRM)
|
|
255
348
|
crm.execute(
|
|
256
349
|
f"delete from tbl_crypto_wallet where id in (select crypto_wallet_id from tbl_buyer_crypto_wallet where wallet_tag ='MY HOT WALLET' and participant_code='{buyer_participant_code}');")
|
|
257
|
-
crm.execute(
|
|
350
|
+
crm.execute(
|
|
351
|
+
f"delete from tbl_buyer_crypto_wallet where wallet_tag ='MY HOT WALLET' and participant_code='{buyer_participant_code}';")
|
|
258
352
|
|
|
259
353
|
# 重新生成新钱包
|
|
260
354
|
self.post(
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/conftest.py
RENAMED
|
@@ -27,6 +27,7 @@ from framework.exit_code import ExitCode
|
|
|
27
27
|
from framework.db.mysql_db import MysqlDB
|
|
28
28
|
from framework.db.redis_db import RedisDB
|
|
29
29
|
from framework.utils.log_util import logger
|
|
30
|
+
from framework.retry_assert import RetryAssert
|
|
30
31
|
from framework.utils.lark_util import LarkUtil
|
|
31
32
|
from framework.utils.yaml_util import CachedYamlLoader
|
|
32
33
|
from framework.exceptions import MysqlDBError, RedisDBError
|
|
@@ -392,6 +393,7 @@ def pytest_runtest_setup(item):
|
|
|
392
393
|
if item.funcargs.get("first"):
|
|
393
394
|
test_object = item.instance
|
|
394
395
|
test_object.context = CONTEXT
|
|
396
|
+
test_object.retry_assert = RetryAssert.eventually
|
|
395
397
|
test_object.config = CONFIG
|
|
396
398
|
test_object.http = _FRAMEWORK_CONTEXT.get(key="_http")
|
|
397
399
|
data = item.callspec.params.get("data")
|
|
@@ -456,6 +458,7 @@ def pytest_runtest_call(item):
|
|
|
456
458
|
item.funcargs["belong_app"] = item.instance.belong_app = _belong_app
|
|
457
459
|
item.funcargs["config"] = item.instance.config = CONFIG
|
|
458
460
|
item.funcargs["context"] = item.instance.context = CONTEXT
|
|
461
|
+
item.funcargs["retry_assert"] = item.instance.retry_assert = RetryAssert.eventually
|
|
459
462
|
# 类式测试用例添加参数http,data, belong_app
|
|
460
463
|
item.instance.http = http
|
|
461
464
|
|
|
@@ -477,6 +480,7 @@ def pytest_runtest_teardown(item):
|
|
|
477
480
|
test_object = item.instance
|
|
478
481
|
test_object.context = CONTEXT
|
|
479
482
|
test_object.config = CONFIG
|
|
483
|
+
test_object.retry_assert = RetryAssert.eventually
|
|
480
484
|
test_object.http = _FRAMEWORK_CONTEXT.get(key="_http")
|
|
481
485
|
data = item.callspec.params.get("data")
|
|
482
486
|
test_object.data = Box(data)
|
|
@@ -553,7 +557,7 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
|
|
|
553
557
|
passed=passed,
|
|
554
558
|
failed=failed,
|
|
555
559
|
skipped=skipped,
|
|
556
|
-
job_name=
|
|
560
|
+
job_name=CONTEXT.get("mark"),
|
|
557
561
|
env=CONTEXT.get("env")
|
|
558
562
|
)
|
|
559
563
|
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/db/redis_db.py
RENAMED
|
@@ -101,6 +101,12 @@ class RedisDB:
|
|
|
101
101
|
items.append(val)
|
|
102
102
|
return items
|
|
103
103
|
|
|
104
|
+
@safe_redis_call
|
|
105
|
+
def lrange_all(self, name):
|
|
106
|
+
"""弹出并返回队列中所有元素(不会清空队列)"""
|
|
107
|
+
res = self.conn.lrange(name, 0, -1)
|
|
108
|
+
return res if res else []
|
|
109
|
+
|
|
104
110
|
# -------------------- meta --------------------
|
|
105
111
|
@safe_redis_call
|
|
106
112
|
def set_ttl(self, name, ttl, log=True):
|
|
@@ -139,4 +145,4 @@ class RedisDB:
|
|
|
139
145
|
|
|
140
146
|
@safe_redis_call
|
|
141
147
|
def rpop(self, name):
|
|
142
|
-
return self.conn.rpop(name)
|
|
148
|
+
return self.conn.rpop(name)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import traceback
|
|
3
|
+
from framework.utils.log_util import logger
|
|
4
|
+
from config.settings import ASYNC_ASSERT_TRIES, ASYNC_ASSERT_TIMEOUT
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RetryAssert:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def eventually(
|
|
10
|
+
assert_func,
|
|
11
|
+
*args,
|
|
12
|
+
timeout=ASYNC_ASSERT_TIMEOUT,
|
|
13
|
+
interval=ASYNC_ASSERT_TRIES,
|
|
14
|
+
desc=None,
|
|
15
|
+
**kwargs
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
在超时时间内不断重试断言,直到成功或超时
|
|
19
|
+
|
|
20
|
+
:param assert_func: 断言函数
|
|
21
|
+
:param args: 传给断言函数的位置参数
|
|
22
|
+
:param kwargs: 传给断言函数的关键字参数
|
|
23
|
+
"""
|
|
24
|
+
start = time.time()
|
|
25
|
+
last_exception = None
|
|
26
|
+
attempt = 0
|
|
27
|
+
|
|
28
|
+
while True:
|
|
29
|
+
attempt += 1
|
|
30
|
+
elapsed = time.time() - start
|
|
31
|
+
|
|
32
|
+
if elapsed > timeout:
|
|
33
|
+
break
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
assert_func(*args, **kwargs)
|
|
37
|
+
return
|
|
38
|
+
except AssertionError as e:
|
|
39
|
+
last_exception = e
|
|
40
|
+
logger.warning(
|
|
41
|
+
f"第 {attempt} 次断言失败 "
|
|
42
|
+
f"(已等待 {elapsed:.1f}s / {timeout}s): {e}"
|
|
43
|
+
)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
last_exception = e
|
|
46
|
+
logger.warning(
|
|
47
|
+
f"第 {attempt} 次执行异常 "
|
|
48
|
+
f"(已等待 {elapsed:.1f}s / {timeout}s): {e}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
time.sleep(interval)
|
|
52
|
+
|
|
53
|
+
error_msg = desc or "异步断言失败"
|
|
54
|
+
raise AssertionError(
|
|
55
|
+
f"{error_msg}\n"
|
|
56
|
+
f"重试次数: {attempt}\n"
|
|
57
|
+
f"最后异常: {last_exception}"
|
|
58
|
+
)
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/validate.py
RENAMED
|
@@ -115,7 +115,7 @@ class Validate(object):
|
|
|
115
115
|
|
|
116
116
|
def assert_equal(self, expectant_expression, practical_result):
|
|
117
117
|
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
118
|
-
if isinstance(expectant_result, (int, float))
|
|
118
|
+
if isinstance(expectant_result, (int, float)) or isinstance(practical_result, str):
|
|
119
119
|
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith(
|
|
120
120
|
"}"):
|
|
121
121
|
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
@@ -126,7 +126,7 @@ class Validate(object):
|
|
|
126
126
|
|
|
127
127
|
def assert_not_equal(self, expectant_expression, practical_result):
|
|
128
128
|
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
129
|
-
if isinstance(expectant_result, (int, float))
|
|
129
|
+
if isinstance(expectant_result, (int, float)) or isinstance(practical_result, str):
|
|
130
130
|
if isinstance(practical_result, str) and practical_result.startswith("${") and practical_result.endswith(
|
|
131
131
|
"}"):
|
|
132
132
|
practical_result = RenderData(self.data).get_attribute(practical_result[2:-1])
|
|
@@ -267,7 +267,7 @@ class Validate(object):
|
|
|
267
267
|
|
|
268
268
|
def assert_is_null(self, expectant_expression, practical_result):
|
|
269
269
|
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
270
|
-
assert expectant_result
|
|
270
|
+
assert not expectant_result
|
|
271
271
|
return expectant_result
|
|
272
272
|
|
|
273
273
|
def assert_is_not_null(self, expectant_expression, practical_result):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
setup.py
|
|
2
2
|
framework/__init__.py
|
|
3
|
+
framework/assert_webhook.py
|
|
3
4
|
framework/base_class.py
|
|
4
5
|
framework/conftest.py
|
|
5
6
|
framework/exceptions.py
|
|
@@ -9,6 +10,7 @@ framework/global_attribute.py
|
|
|
9
10
|
framework/http_client.py
|
|
10
11
|
framework/render_data.py
|
|
11
12
|
framework/report.py
|
|
13
|
+
framework/retry_assert.py
|
|
12
14
|
framework/script.py
|
|
13
15
|
framework/startapp.py
|
|
14
16
|
framework/validate.py
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/__init__.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/db/__init__.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/db/mysql_db.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/exceptions.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/exit_code.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/extract.py
RENAMED
|
File without changes
|
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/http_client.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/render_data.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/startapp.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/__init__.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/common.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/date_util.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/encrypt.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/log_util.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/mock_util.py
RENAMED
|
File without changes
|
{pytest_api_framework_alpha-0.3.18 → pytest_api_framework_alpha-0.3.20}/framework/utils/yaml_util.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|