pytest-api-framework-alpha 0.1.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.
- framework/__init__.py +0 -0
- framework/allure_report.py +35 -0
- framework/base_class.py +61 -0
- framework/conftest.py +523 -0
- framework/db/__init__.py +0 -0
- framework/db/mysql_db.py +111 -0
- framework/db/redis_db.py +142 -0
- framework/exit_code.py +19 -0
- framework/extract.py +101 -0
- framework/global_attribute.py +100 -0
- framework/http_client.py +163 -0
- framework/render_data.py +185 -0
- framework/report.py +102 -0
- framework/startapp.py +126 -0
- framework/utils/__init__.py +0 -0
- framework/utils/common.py +211 -0
- framework/utils/encrypt.py +387 -0
- framework/utils/log_util.py +3 -0
- framework/utils/teams_util.py +48 -0
- framework/utils/yaml_util.py +25 -0
- framework/validate.py +207 -0
- pytest_api_framework_alpha-0.1.0.dist-info/METADATA +29 -0
- pytest_api_framework_alpha-0.1.0.dist-info/RECORD +25 -0
- pytest_api_framework_alpha-0.1.0.dist-info/WHEEL +5 -0
- pytest_api_framework_alpha-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import base64
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
from Crypto.PublicKey import RSA
|
|
6
|
+
from Crypto.Util.Padding import pad, unpad
|
|
7
|
+
from Crypto.Cipher import AES, DES, DES3, PKCS1_v1_5 as PKCS1_cipher
|
|
8
|
+
from framework.utils.common import valid_hex_format, valid_b64_format, hex_to_bytes, bytes_to_hex
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def md5(string, byte=False, encoding="utf-8"):
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
:param string: 加密内容
|
|
15
|
+
:param byte: 返回字节类型,默认返回16进制
|
|
16
|
+
:param encoding: 编码
|
|
17
|
+
:return: 返回字符串
|
|
18
|
+
"""
|
|
19
|
+
md_obj = hashlib.md5()
|
|
20
|
+
if isinstance(string, str):
|
|
21
|
+
string = string.encode(encoding=encoding)
|
|
22
|
+
md_obj.update(string)
|
|
23
|
+
if byte:
|
|
24
|
+
return md_obj.digest()
|
|
25
|
+
return md_obj.hexdigest()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def sha(str_list, mode, byte=False, length=None, encoding="utf-8"):
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
:param str_list: 加密内容,字节或字符串
|
|
32
|
+
:param mode: sha系列加密方式
|
|
33
|
+
:param byte: 返回字节类型,默认返回16进制
|
|
34
|
+
:param length: 'shake_128', 'shake_256'需要指定返回的长度
|
|
35
|
+
:param encoding: 编码
|
|
36
|
+
:return: 返回字符串
|
|
37
|
+
"""
|
|
38
|
+
sha_obj = hashlib.new(mode)
|
|
39
|
+
if not isinstance(str_list, list):
|
|
40
|
+
raise Exception(f"{str_list} 必须是list类型")
|
|
41
|
+
for i in str_list:
|
|
42
|
+
if not isinstance(i, (str, bytes)):
|
|
43
|
+
raise Exception(f"{i} 必须是str或bytes类型")
|
|
44
|
+
if isinstance(i, str):
|
|
45
|
+
i = i.encode(encoding=encoding)
|
|
46
|
+
sha_obj.update(i)
|
|
47
|
+
|
|
48
|
+
if mode in ['shake_128', 'shake_256']:
|
|
49
|
+
if byte:
|
|
50
|
+
return sha_obj.digest(length)
|
|
51
|
+
return sha_obj.hexdigest(length)
|
|
52
|
+
else:
|
|
53
|
+
if byte:
|
|
54
|
+
return sha_obj.digest()
|
|
55
|
+
return sha_obj.hexdigest()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def b64_encode(bs, byte=False, encoding="utf-8"):
|
|
59
|
+
"""
|
|
60
|
+
bytes -> b64字符串
|
|
61
|
+
:param bs: 二进制字节串
|
|
62
|
+
:param byte: 返回二进制字节或b64字符串,默认b64字符串
|
|
63
|
+
:param encoding:
|
|
64
|
+
:return:
|
|
65
|
+
"""
|
|
66
|
+
if not isinstance(bs, bytes):
|
|
67
|
+
raise Exception(f"{bs} 必须是bytes类型")
|
|
68
|
+
if byte:
|
|
69
|
+
return base64.b64encode(bs)
|
|
70
|
+
return base64.b64encode(bs).decode(encoding)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def b64_decode(string, byte=True, encoding="utf-8"):
|
|
74
|
+
"""
|
|
75
|
+
b64字符串 -> bytes
|
|
76
|
+
:param string: b64字符串
|
|
77
|
+
:param byte: 返回二进制字节或字符串,默认返回二进制字节
|
|
78
|
+
:param encoding:
|
|
79
|
+
:return:
|
|
80
|
+
"""
|
|
81
|
+
if not isinstance(string, str):
|
|
82
|
+
raise Exception(f"{string} 必须是b64字符串")
|
|
83
|
+
|
|
84
|
+
missing_padding = 4 - len(string) % 4
|
|
85
|
+
if missing_padding:
|
|
86
|
+
string += '=' * missing_padding
|
|
87
|
+
if byte:
|
|
88
|
+
return base64.b64decode(string)
|
|
89
|
+
return base64.b64decode(string).decode(encoding=encoding)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class RsaByPubKey(object):
|
|
93
|
+
|
|
94
|
+
def __init__(self, pub_key):
|
|
95
|
+
"""
|
|
96
|
+
:param pub_key: 公钥
|
|
97
|
+
1.可以直接传公钥的base64字符串;
|
|
98
|
+
2.通过save_publish_key方法先将公钥的base64字符串存到文件,然后使用get_publish_key方法读取文件获取公钥,然后传入
|
|
99
|
+
"""
|
|
100
|
+
self.pub_key = pub_key
|
|
101
|
+
|
|
102
|
+
def encrypt(self, data, b64=False, encoding="utf-8"):
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
:param data: 要加密的数据
|
|
106
|
+
:param b64: 默认返回16进制字符串,True返回base64字符
|
|
107
|
+
:param encoding:
|
|
108
|
+
:return:
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
if valid_hex_format(self.pub_key):
|
|
112
|
+
self.pub_key = hex_to_bytes(self.pub_key)
|
|
113
|
+
|
|
114
|
+
elif valid_b64_format(self.pub_key):
|
|
115
|
+
self.pub_key = b64_decode(self.pub_key)
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
raise Exception("pub_key参数必须是16进制字符串或b64字符串")
|
|
119
|
+
|
|
120
|
+
if not isinstance(data, bytes):
|
|
121
|
+
data = data.encode(encoding=encoding)
|
|
122
|
+
cipher = PKCS1_cipher.new(RSA.import_key(self.pub_key))
|
|
123
|
+
encrypt_text = cipher.encrypt(data)
|
|
124
|
+
if b64:
|
|
125
|
+
return b64_encode(cipher.encrypt(data)) # 转成base64字符串
|
|
126
|
+
return bytes_to_hex(encrypt_text).decode(encoding=encoding) # 转成base64字符
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Aes(object):
|
|
130
|
+
AES_MODE_MAP = {
|
|
131
|
+
"CBC": AES.MODE_CBC,
|
|
132
|
+
"ECB": AES.MODE_ECB,
|
|
133
|
+
"CFB": AES.MODE_CFB,
|
|
134
|
+
"OFB": AES.MODE_OFB,
|
|
135
|
+
"CTR": AES.MODE_CTR,
|
|
136
|
+
"OPENPGP": AES.MODE_OPENPGP,
|
|
137
|
+
"CCM": AES.MODE_CCM,
|
|
138
|
+
"EAX": AES.MODE_EAX,
|
|
139
|
+
"SIV": AES.MODE_SIV,
|
|
140
|
+
"GCM": AES.MODE_GCM,
|
|
141
|
+
"OCB": AES.MODE_OCB
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
def __init__(self, key, mode, iv=None, encoding="utf-8"):
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
:param key: 必须是16 or 24 or 32字节长度
|
|
148
|
+
:param mode:
|
|
149
|
+
:param iv: 必须是16位字节
|
|
150
|
+
:param encoding:
|
|
151
|
+
"""
|
|
152
|
+
self.iv = iv
|
|
153
|
+
self.key = key
|
|
154
|
+
self.mode = mode.upper()
|
|
155
|
+
self.encoding = encoding
|
|
156
|
+
if self.mode in ["CBC", "CFB", "OFB"]:
|
|
157
|
+
if not self.iv or len(self.iv) != 16:
|
|
158
|
+
raise Exception(f"{iv} 必须是16字节长度")
|
|
159
|
+
|
|
160
|
+
if len(self.key) not in [16, 24, 32]:
|
|
161
|
+
raise Exception(f"{self.key} 必须是16、24或32字节长度")
|
|
162
|
+
|
|
163
|
+
if not isinstance(self.key, bytes):
|
|
164
|
+
self.key = self.key.encode(encoding=self.encoding)
|
|
165
|
+
|
|
166
|
+
if self.iv:
|
|
167
|
+
if not isinstance(self.iv, bytes):
|
|
168
|
+
self.iv = self.iv.encode(encoding=self.encoding)
|
|
169
|
+
self.mode = self.AES_MODE_MAP.get(mode)
|
|
170
|
+
|
|
171
|
+
def new_aes(self):
|
|
172
|
+
if self.iv:
|
|
173
|
+
return AES.new(key=self.key, mode=self.mode, IV=self.iv)
|
|
174
|
+
return AES.new(key=self.key, mode=self.mode)
|
|
175
|
+
|
|
176
|
+
def encrypt(self, data, byte=False, separate=False):
|
|
177
|
+
"""
|
|
178
|
+
字符串/字节->加密->b64字符串
|
|
179
|
+
:param data: 加密内容,字节或者字符串,如果是字符串,默认转为字节
|
|
180
|
+
:param byte: 返回b64字符或字节,默认返回b64字符
|
|
181
|
+
:param separate: 序列化时是否需要:,,后面的空格,默认不需要
|
|
182
|
+
:return:b64字符串
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
aes_obj = self.new_aes()
|
|
186
|
+
if isinstance(data, dict):
|
|
187
|
+
if separate:
|
|
188
|
+
data = json.dumps(data, separators=(',', ':')).encode(encoding=self.encoding)
|
|
189
|
+
else:
|
|
190
|
+
data = json.dumps(data).encode(encoding=self.encoding)
|
|
191
|
+
else:
|
|
192
|
+
data = str(data).encode(encoding=self.encoding)
|
|
193
|
+
data = pad(data, 16)
|
|
194
|
+
if byte:
|
|
195
|
+
return aes_obj.encrypt(data)
|
|
196
|
+
return b64_encode(aes_obj.encrypt(data))
|
|
197
|
+
|
|
198
|
+
def decrypt(self, data, byte=False):
|
|
199
|
+
"""
|
|
200
|
+
b64字符串->解密->字符串/字节
|
|
201
|
+
:param data: 解密内容,字节或者字符串,如果是字符串,转为b64字节
|
|
202
|
+
:param byte: 返回字符串或字节,默认字符串
|
|
203
|
+
:return:
|
|
204
|
+
"""
|
|
205
|
+
aes_obj = self.new_aes()
|
|
206
|
+
if not isinstance(data, bytes):
|
|
207
|
+
data = b64_decode(data)
|
|
208
|
+
if byte:
|
|
209
|
+
return unpad(aes_obj.decrypt(data), 16)
|
|
210
|
+
try:
|
|
211
|
+
return json.loads(unpad(aes_obj.decrypt(data), 16).decode(encoding=self.encoding))
|
|
212
|
+
except Exception:
|
|
213
|
+
return unpad(aes_obj.decrypt(data), 16).decode(encoding=self.encoding)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class Des(object):
|
|
217
|
+
DES_MODE_MAP = {
|
|
218
|
+
"CBC": DES.MODE_CBC,
|
|
219
|
+
"ECB": DES.MODE_ECB,
|
|
220
|
+
"CFB": DES.MODE_CFB,
|
|
221
|
+
"OFB": DES.MODE_OFB,
|
|
222
|
+
"CTR": DES.MODE_CTR,
|
|
223
|
+
"OPENPGP": DES.MODE_OPENPGP,
|
|
224
|
+
"EAX": DES.MODE_EAX
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
def __init__(self, key, mode, iv=None, encoding="utf-8"):
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
:param key: 必须是8位字节
|
|
231
|
+
:param mode:
|
|
232
|
+
:param iv: 必须是8位字节
|
|
233
|
+
:param encoding:
|
|
234
|
+
"""
|
|
235
|
+
self.iv = iv
|
|
236
|
+
self.key = key
|
|
237
|
+
self.mode = mode.upper()
|
|
238
|
+
self.encoding = encoding
|
|
239
|
+
if self.mode in ["CBC", "CFB", "OFB"]:
|
|
240
|
+
if not self.iv or len(self.iv) != 8:
|
|
241
|
+
raise Exception(f"{self.iv} 必须是8字节长度")
|
|
242
|
+
|
|
243
|
+
if len(self.key) != 8:
|
|
244
|
+
raise Exception(f"{self.key} 必须是8字节长度")
|
|
245
|
+
|
|
246
|
+
if not isinstance(self.key, bytes):
|
|
247
|
+
self.key = self.key.encode(encoding=self.encoding)
|
|
248
|
+
|
|
249
|
+
if self.iv:
|
|
250
|
+
if not isinstance(self.iv, bytes):
|
|
251
|
+
self.iv = self.iv.encode(encoding=self.encoding)
|
|
252
|
+
|
|
253
|
+
self.mode = self.DES_MODE_MAP.get(mode)
|
|
254
|
+
|
|
255
|
+
def new_des(self):
|
|
256
|
+
if self.iv:
|
|
257
|
+
return DES.new(key=self.key, mode=self.mode, IV=self.iv)
|
|
258
|
+
return DES.new(key=self.key, mode=self.mode)
|
|
259
|
+
|
|
260
|
+
def encrypt(self, data, byte=False, separate=False):
|
|
261
|
+
"""
|
|
262
|
+
:param data: 加密内容,字节或者字符串,如果是字符串,默认转为字节
|
|
263
|
+
:param byte: 返回base64字符或字节,默认返回base64字符
|
|
264
|
+
:param separate: 序列化时是否需要:,,后面的空格,默认不需要
|
|
265
|
+
:return:
|
|
266
|
+
"""
|
|
267
|
+
des_obj = self.new_des()
|
|
268
|
+
if isinstance(data, dict):
|
|
269
|
+
if separate:
|
|
270
|
+
data = json.dumps(data, separators=(',', ':')).encode(encoding=self.encoding)
|
|
271
|
+
else:
|
|
272
|
+
data = json.dumps(data).encode(encoding=self.encoding)
|
|
273
|
+
else:
|
|
274
|
+
data = str(data).encode(encoding=self.encoding)
|
|
275
|
+
data = pad(data, 8)
|
|
276
|
+
if byte:
|
|
277
|
+
return des_obj.encrypt(data)
|
|
278
|
+
return b64_encode(des_obj.encrypt(data))
|
|
279
|
+
|
|
280
|
+
def decrypt(self, data, byte=False):
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
:param data: 解密内容,字节或者字符串,如果是字符串,默认转为base64字符
|
|
284
|
+
:param byte: 返回字符串或字节,默认字符串
|
|
285
|
+
:return:
|
|
286
|
+
"""
|
|
287
|
+
des_obj = self.new_des()
|
|
288
|
+
if not isinstance(data, bytes):
|
|
289
|
+
data = b64_decode(data)
|
|
290
|
+
if byte:
|
|
291
|
+
return unpad(des_obj.decrypt(data), 8)
|
|
292
|
+
try:
|
|
293
|
+
return json.loads(unpad(des_obj.decrypt(data), 8).decode(encoding=self.encoding))
|
|
294
|
+
except Exception:
|
|
295
|
+
return unpad(des_obj.decrypt(data), 8).decode(encoding=self.encoding)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class Des3(object):
|
|
299
|
+
DES3_MODE_MAP = {
|
|
300
|
+
"CBC": DES3.MODE_CBC,
|
|
301
|
+
"ECB": DES3.MODE_ECB,
|
|
302
|
+
"CFB": DES3.MODE_CFB,
|
|
303
|
+
"OFB": DES3.MODE_OFB,
|
|
304
|
+
"CTR": DES3.MODE_CTR,
|
|
305
|
+
"OPENPGP": DES3.MODE_OPENPGP,
|
|
306
|
+
"EAX": DES3.MODE_EAX
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
def __init__(self, key, mode, iv=None, encoding="utf-8"):
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
:param key: 必须是16,24位字节
|
|
313
|
+
:param mode:
|
|
314
|
+
:param iv: 必须是8位字节
|
|
315
|
+
:param encoding:
|
|
316
|
+
"""
|
|
317
|
+
self.iv = iv
|
|
318
|
+
self.key = key
|
|
319
|
+
self.mode = mode.upper()
|
|
320
|
+
self.encoding = encoding
|
|
321
|
+
|
|
322
|
+
if self.mode in ["CBC", "CFB", "OFB"]:
|
|
323
|
+
if not self.iv or len(self.iv) != 8:
|
|
324
|
+
raise Exception(f"{self.iv} 必须是8字节长度")
|
|
325
|
+
|
|
326
|
+
if len(self.key) not in [16, 24]:
|
|
327
|
+
raise Exception(f"{self.key} 必须是16或24字节长度")
|
|
328
|
+
|
|
329
|
+
if not isinstance(self.key, bytes):
|
|
330
|
+
self.key = self.key.encode(encoding=self.encoding)
|
|
331
|
+
|
|
332
|
+
if self.iv:
|
|
333
|
+
if not isinstance(iv, bytes):
|
|
334
|
+
self.iv = self.iv.encode(encoding=self.encoding)
|
|
335
|
+
|
|
336
|
+
self.mode = self.DES3_MODE_MAP.get(mode)
|
|
337
|
+
|
|
338
|
+
def new_des3(self):
|
|
339
|
+
if self.iv:
|
|
340
|
+
return DES3.new(key=self.key, mode=self.mode, IV=self.iv)
|
|
341
|
+
return DES3.new(key=self.key, mode=self.mode)
|
|
342
|
+
|
|
343
|
+
def encrypt(self, data, byte=False, separate=False):
|
|
344
|
+
"""
|
|
345
|
+
:param data: 加密内容,字节或者字符串,如果是字符串,默认转为字节
|
|
346
|
+
:param byte: 返回base64字符或字节,默认返回base64字符
|
|
347
|
+
:param separate: 序列化时是否需要:,,后面的空格,默认不需要
|
|
348
|
+
:return:
|
|
349
|
+
"""
|
|
350
|
+
des3_obj = self.new_des3()
|
|
351
|
+
if isinstance(data, dict):
|
|
352
|
+
if separate:
|
|
353
|
+
data = json.dumps(data, separators=(',', ':')).encode(encoding=self.encoding)
|
|
354
|
+
else:
|
|
355
|
+
data = json.dumps(data).encode(encoding=self.encoding)
|
|
356
|
+
else:
|
|
357
|
+
data = str(data).encode(encoding=self.encoding)
|
|
358
|
+
data = pad(data, 8)
|
|
359
|
+
if byte:
|
|
360
|
+
return des3_obj.encrypt(data)
|
|
361
|
+
else:
|
|
362
|
+
return b64_encode(des3_obj.encrypt(data))
|
|
363
|
+
|
|
364
|
+
def decrypt(self, data, byte=False):
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
:param data: 解密内容,字节或者字符串,如果是字符串,默认转为base64字符
|
|
368
|
+
:param byte: 返回字符串或字节,默认字符串
|
|
369
|
+
:return:
|
|
370
|
+
"""
|
|
371
|
+
des3_obj = self.new_des3()
|
|
372
|
+
if not isinstance(data, bytes):
|
|
373
|
+
data = b64_decode(data)
|
|
374
|
+
if byte:
|
|
375
|
+
return unpad(des3_obj.decrypt(data), 8)
|
|
376
|
+
else:
|
|
377
|
+
try:
|
|
378
|
+
return json.loads(unpad(des3_obj.decrypt(data), 8).decode(encoding=self.encoding))
|
|
379
|
+
except Exception:
|
|
380
|
+
return unpad(des3_obj.decrypt(data), 8).decode(encoding=self.encoding)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
if __name__ == '__main__':
|
|
384
|
+
key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqJA8yliEYgp9aorMNzayIyJex4ukgwEXi+Us2xVJlttB2Uy9Bsh9ugTqNcc1bf7R5WW/QIN/EbA+yJC1FCqZHzZdYw54O+IN9oV9I+pE2ziK6vlOjUYmKbi2NO84xAYW83uaWee6MkH8m87qn5hrd7JzksPJS3HdHNZCcOOOemwIDAQAB"
|
|
385
|
+
rsa = RsaByPubKey(pub_key=key)
|
|
386
|
+
a = rsa.encrypt(test_data="password", b64=True)
|
|
387
|
+
print(a)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from framework.utils.common import get_current_datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TeamsUtil(object):
|
|
7
|
+
|
|
8
|
+
def __init__(self, webhook_url):
|
|
9
|
+
self.webhook_url = webhook_url
|
|
10
|
+
|
|
11
|
+
def send_teams_card(self, title, content_list):
|
|
12
|
+
headers = {
|
|
13
|
+
"Content-Type": "application/json"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
payload = {
|
|
17
|
+
"type": "message",
|
|
18
|
+
"attachments": [
|
|
19
|
+
{
|
|
20
|
+
"contentType": "application/vnd.microsoft.card.adaptive",
|
|
21
|
+
"content": {
|
|
22
|
+
"type": "AdaptiveCard",
|
|
23
|
+
"body": [
|
|
24
|
+
{
|
|
25
|
+
"type": "TextBlock",
|
|
26
|
+
"size": "Large",
|
|
27
|
+
"weight": "Bolder",
|
|
28
|
+
"text": title
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
32
|
+
"version": "1.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
content_list.append(f"发送时间: {get_current_datetime()}")
|
|
38
|
+
for item in content_list:
|
|
39
|
+
payload["attachments"][0]["content"]["body"].append({
|
|
40
|
+
"type": "TextBlock",
|
|
41
|
+
"text": item,
|
|
42
|
+
"wrap": True
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
response = requests.post(self.webhook_url, headers=headers, json=payload)
|
|
46
|
+
|
|
47
|
+
if not response.status_code == 200:
|
|
48
|
+
raise Exception(f"Failed to send card. Status code: {response.status_code}, Response: {response.text}")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
from box import Box
|
|
4
|
+
|
|
5
|
+
from config.settings import ROOT_DIR
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class YamlUtil(object):
|
|
9
|
+
def __init__(self, file_path):
|
|
10
|
+
self.file_path = file_path
|
|
11
|
+
|
|
12
|
+
def load_yml(self, key=None, is_box=False):
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@param key:
|
|
16
|
+
@param is_box:
|
|
17
|
+
@return:
|
|
18
|
+
"""
|
|
19
|
+
with open(os.path.join(ROOT_DIR, self.file_path), mode="r", encoding="utf-8") as f:
|
|
20
|
+
result = yaml.safe_load(stream=f) or dict()
|
|
21
|
+
if key:
|
|
22
|
+
result = result.get(key)
|
|
23
|
+
if is_box:
|
|
24
|
+
result = Box(result) if isinstance(result, dict) else result
|
|
25
|
+
return result
|
framework/validate.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import allure
|
|
4
|
+
from utils.log_util import logger
|
|
5
|
+
from jsonpath import jsonpath
|
|
6
|
+
from utils.common import an2cn, is_digit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Validate(object):
|
|
10
|
+
def __init__(self, response):
|
|
11
|
+
self.response = response
|
|
12
|
+
|
|
13
|
+
self.mapping = {
|
|
14
|
+
"eq": "assert_equal",
|
|
15
|
+
"not_eq": "assert_not_equal",
|
|
16
|
+
"lt": "assert_less_than",
|
|
17
|
+
"le": "assert_less_than_or_equals",
|
|
18
|
+
"gt": "assert_greater_than",
|
|
19
|
+
"ge": "assert_greater_than_or_equals",
|
|
20
|
+
"len_eq": "assert_length_equals",
|
|
21
|
+
"len_lt": "assert_length_less_than",
|
|
22
|
+
"len_le": "assert_length_less_than_or_equals",
|
|
23
|
+
"len_gt": "assert_length_greater_than",
|
|
24
|
+
"len_ge": "assert_length_greater_than_or_equals",
|
|
25
|
+
"contains": "assert_contains",
|
|
26
|
+
"contained_by": "assert_contained_by",
|
|
27
|
+
"startswith": "assert_startswith",
|
|
28
|
+
"endswith": "assert_endswith"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def valid(self, validates):
|
|
32
|
+
for valid_item in validates:
|
|
33
|
+
key = list(valid_item.keys())[0]
|
|
34
|
+
valid_list = [i.strip() for i in valid_item.get(key).split(",")]
|
|
35
|
+
expression = valid_list[0]
|
|
36
|
+
expectant_result = valid_list[1]
|
|
37
|
+
func = self.mapping.get(key)
|
|
38
|
+
try:
|
|
39
|
+
if func:
|
|
40
|
+
getattr(self, func)(expression, expectant_result)
|
|
41
|
+
with allure.step(
|
|
42
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: 断言通过"):
|
|
43
|
+
logger.info(
|
|
44
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: 断言通过")
|
|
45
|
+
else:
|
|
46
|
+
raise Exception(f"不支持的断言方式: {key}")
|
|
47
|
+
except AssertionError as e:
|
|
48
|
+
with allure.step(
|
|
49
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: 断言失败。 失败原因: {e}"):
|
|
50
|
+
logger.error(
|
|
51
|
+
f"断言({an2cn(validates.index(valid_item) + 1)}): 断言类型: {self.mapping.get(key)}, 断言内容: {valid_list}, 断言结果: 断言失败。 失败原因: {e}")
|
|
52
|
+
raise AssertionError(e)
|
|
53
|
+
|
|
54
|
+
def parse_expectant_expression(self, expectant_expression):
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# jsonpath表达式取值
|
|
58
|
+
if expectant_expression.lower().startswith("$."):
|
|
59
|
+
return self.exec_jsonpath(expectant_expression)
|
|
60
|
+
# 正则表达式取值
|
|
61
|
+
elif expectant_expression.startswith("/") and expectant_expression.endswith("/"):
|
|
62
|
+
return self.exec_reg(expectant_expression[1: -1])
|
|
63
|
+
else:
|
|
64
|
+
# response对象取值
|
|
65
|
+
return self.exec_response(expectant_expression)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
with allure.step(f"不支持的变量提取方式{expectant_expression}: {e}"):
|
|
68
|
+
logger.error(f"不支持的变量提取方式{expectant_expression}: {e}")
|
|
69
|
+
|
|
70
|
+
def exec_jsonpath(self, expression):
|
|
71
|
+
try:
|
|
72
|
+
return jsonpath(self.response.json(), expression)[0]
|
|
73
|
+
except Exception:
|
|
74
|
+
raise Exception(f"jsonpath表达式错误或非预期响应内容。表达式: {expression};响应内容: {self.response.json()}")
|
|
75
|
+
|
|
76
|
+
def exec_reg(self, reg_expression):
|
|
77
|
+
try:
|
|
78
|
+
result = re.search(reg_expression, self.response.text, flags=re.S).group()
|
|
79
|
+
if is_digit(result):
|
|
80
|
+
return eval(result)
|
|
81
|
+
else:
|
|
82
|
+
return result
|
|
83
|
+
except AttributeError:
|
|
84
|
+
raise Exception(f"正则表达式或非预期响应内容。表达式: {reg_expression}; 响应内容: {self.response.text}")
|
|
85
|
+
|
|
86
|
+
def exec_response(self, attr):
|
|
87
|
+
if attr in ["status_code", "url", "ok", "encoding"]:
|
|
88
|
+
return getattr(self.response, attr)
|
|
89
|
+
else:
|
|
90
|
+
raise Exception(f"不支持提取的Response对象属性{attr}")
|
|
91
|
+
|
|
92
|
+
def assert_equal(self, expectant_expression, practical_result):
|
|
93
|
+
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
94
|
+
if isinstance(expectant_result, (int, float)) and isinstance(practical_result, str):
|
|
95
|
+
practical_result = eval(practical_result)
|
|
96
|
+
assert practical_result == expectant_result, f'{expectant_result} == {practical_result}'
|
|
97
|
+
|
|
98
|
+
def assert_not_equal(self, expectant_expression, practical_result):
|
|
99
|
+
expectant_result = self.parse_expectant_expression(expectant_expression)
|
|
100
|
+
if isinstance(expectant_result, (int, float)) and isinstance(practical_result, str):
|
|
101
|
+
practical_result = eval(practical_result)
|
|
102
|
+
assert practical_result != expectant_result, f'{expectant_result} != {practical_result}'
|
|
103
|
+
|
|
104
|
+
def assert_less_than(self, expectant_expression, practical_result):
|
|
105
|
+
expectant_result, practical_result = self.__to_digit(
|
|
106
|
+
self.parse_expectant_expression(expectant_expression),
|
|
107
|
+
practical_result
|
|
108
|
+
)
|
|
109
|
+
assert expectant_result < practical_result, f'{expectant_result} < {practical_result}'
|
|
110
|
+
|
|
111
|
+
def assert_less_than_or_equals(self, expectant_expression, practical_result):
|
|
112
|
+
expectant_result, practical_result = self.__to_digit(
|
|
113
|
+
self.parse_expectant_expression(expectant_expression),
|
|
114
|
+
practical_result
|
|
115
|
+
)
|
|
116
|
+
assert expectant_result <= practical_result, f'{expectant_result} <= {practical_result}'
|
|
117
|
+
|
|
118
|
+
def assert_greater_than(self, expectant_expression, practical_result):
|
|
119
|
+
expectant_result, practical_result = self.__to_digit(
|
|
120
|
+
self.parse_expectant_expression(expectant_expression),
|
|
121
|
+
practical_result
|
|
122
|
+
)
|
|
123
|
+
assert expectant_result > practical_result, f'{expectant_result} > {practical_result}'
|
|
124
|
+
|
|
125
|
+
def assert_greater_than_or_equals(self, expectant_expression, practical_result):
|
|
126
|
+
expectant_result, practical_result = self.__to_digit(
|
|
127
|
+
self.parse_expectant_expression(expectant_expression),
|
|
128
|
+
practical_result
|
|
129
|
+
)
|
|
130
|
+
assert expectant_result >= practical_result, f'{expectant_result} >= {practical_result}'
|
|
131
|
+
|
|
132
|
+
def assert_contains(self, expectant_expression, practical_result):
|
|
133
|
+
expectant_result, practical_result = self.__to_str(
|
|
134
|
+
self.parse_expectant_expression(expectant_expression),
|
|
135
|
+
practical_result
|
|
136
|
+
)
|
|
137
|
+
assert practical_result in expectant_result, f'{practical_result} in {expectant_result}'
|
|
138
|
+
|
|
139
|
+
def assert_contained_by(self, expectant_expression, practical_result):
|
|
140
|
+
expectant_result, practical_result = self.__to_str(
|
|
141
|
+
self.parse_expectant_expression(expectant_expression),
|
|
142
|
+
practical_result
|
|
143
|
+
)
|
|
144
|
+
assert expectant_result in practical_result, f'{expectant_result} in {practical_result}'
|
|
145
|
+
|
|
146
|
+
def assert_startswith(self, expectant_expression, practical_result):
|
|
147
|
+
expectant_result, practical_result = self.__to_str(
|
|
148
|
+
self.parse_expectant_expression(expectant_expression),
|
|
149
|
+
practical_result
|
|
150
|
+
)
|
|
151
|
+
assert expectant_result.startswith(practical_result), f'{expectant_result} 以 {practical_result} 开头'
|
|
152
|
+
|
|
153
|
+
def assert_endswith(self, expectant_expression, practical_result):
|
|
154
|
+
expectant_result, practical_result = self.__to_str(
|
|
155
|
+
self.parse_expectant_expression(expectant_expression),
|
|
156
|
+
practical_result
|
|
157
|
+
)
|
|
158
|
+
assert expectant_result.endswith(practical_result), f'{expectant_result} 以 {practical_result} 结尾'
|
|
159
|
+
|
|
160
|
+
def assert_length_equals(self, expectant_expression, practical_result):
|
|
161
|
+
expectant_result, practical_result = self.__to_str(
|
|
162
|
+
self.parse_expectant_expression(expectant_expression),
|
|
163
|
+
practical_result
|
|
164
|
+
)
|
|
165
|
+
assert len(expectant_result) == int(practical_result), f'{len(expectant_result)} == {practical_result}'
|
|
166
|
+
|
|
167
|
+
def assert_length_less_than(self, expectant_expression, practical_result):
|
|
168
|
+
expectant_result, practical_result = self.__to_str(
|
|
169
|
+
self.parse_expectant_expression(expectant_expression),
|
|
170
|
+
practical_result
|
|
171
|
+
)
|
|
172
|
+
assert len(expectant_result) < int(practical_result), f'{len(expectant_result)} < {practical_result}'
|
|
173
|
+
|
|
174
|
+
def assert_length_less_than_or_equals(self, expectant_expression, practical_result):
|
|
175
|
+
expectant_result, practical_result = self.__to_str(
|
|
176
|
+
self.parse_expectant_expression(expectant_expression),
|
|
177
|
+
practical_result
|
|
178
|
+
)
|
|
179
|
+
assert len(expectant_result) <= int(practical_result), f'{len(expectant_result)} <= {practical_result}'
|
|
180
|
+
|
|
181
|
+
def assert_length_greater_than(self, expectant_expression, practical_result):
|
|
182
|
+
expectant_result, practical_result = self.__to_str(
|
|
183
|
+
self.parse_expectant_expression(expectant_expression),
|
|
184
|
+
practical_result
|
|
185
|
+
)
|
|
186
|
+
assert len(expectant_result) > int(practical_result), f'{len(expectant_result)} > {practical_result}'
|
|
187
|
+
|
|
188
|
+
def assert_length_greater_than_or_equals(self, expectant_expression, practical_result):
|
|
189
|
+
expectant_result, practical_result = self.__to_str(
|
|
190
|
+
self.parse_expectant_expression(expectant_expression),
|
|
191
|
+
practical_result
|
|
192
|
+
)
|
|
193
|
+
assert len(expectant_result) >= int(practical_result), f'{len(expectant_result)} >= {practical_result}'
|
|
194
|
+
|
|
195
|
+
def __to_str(self, expectant_expression, practical_result):
|
|
196
|
+
if isinstance(expectant_expression, (int, float)):
|
|
197
|
+
expectant_expression = str(expectant_expression)
|
|
198
|
+
if isinstance(practical_result, (int, float)):
|
|
199
|
+
practical_result = str(practical_result)
|
|
200
|
+
return expectant_expression, practical_result
|
|
201
|
+
|
|
202
|
+
def __to_digit(self, expectant_expression, practical_result):
|
|
203
|
+
if isinstance(expectant_expression, str) and is_digit(expectant_expression):
|
|
204
|
+
expectant_expression = eval(expectant_expression)
|
|
205
|
+
if isinstance(practical_result, str) and is_digit(practical_result):
|
|
206
|
+
practical_result = eval(practical_result)
|
|
207
|
+
return expectant_expression, practical_result
|