pytest-dsl 0.1.0__py3-none-any.whl → 0.1.1__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,232 @@
1
+ """CSRF认证提供者示例
2
+
3
+ 此模块提供了一个CSRF认证提供者的示例实现,展示如何使用AuthProvider的响应处理钩子。
4
+ """
5
+
6
+ import logging
7
+ import json
8
+ import re
9
+ from typing import Dict, Any
10
+ import requests
11
+ from pytest_dsl.core.auth_provider import CustomAuthProvider, register_auth_provider
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class CSRFAuthProvider(CustomAuthProvider):
17
+ """CSRF令牌认证提供者
18
+
19
+ 此提供者从响应中提取CSRF令牌,并将其应用到后续请求中。
20
+ 支持从Cookie、响应头或响应体中提取令牌。
21
+ """
22
+
23
+ def __init__(self,
24
+ token_source: str = "header", # header, cookie, body
25
+ source_name: str = "X-CSRF-Token", # 头名称、Cookie名称或JSON路径
26
+ header_name: str = "X-CSRF-Token", # 请求头名称
27
+ regex_pattern: str = None): # 从响应体提取的正则表达式
28
+ """初始化CSRF令牌认证提供者
29
+
30
+ Args:
31
+ token_source: 令牌来源,可以是 "header"、"cookie" 或 "body"
32
+ source_name: 源名称,取决于token_source:
33
+ - 当token_source为"header"时,表示头名称
34
+ - 当token_source为"cookie"时,表示Cookie名称
35
+ - 当token_source为"body"时,表示JSON路径或CSS选择器
36
+ header_name: 请求头名称,用于发送令牌
37
+ regex_pattern: 从响应体提取令牌的正则表达式(当token_source为"body"时)
38
+ """
39
+ super().__init__()
40
+ self.token_source = token_source
41
+ self.source_name = source_name
42
+ self.header_name = header_name
43
+ self.regex_pattern = regex_pattern
44
+ self.csrf_token = None
45
+ self._session = requests.Session()
46
+
47
+ # 标识此提供者管理会话
48
+ self.manage_session = True
49
+
50
+ logger.info(f"初始化CSRF令牌认证提供者 (源: {token_source}, 名称: {source_name})")
51
+
52
+ def apply_auth(self, request_kwargs: Dict[str, Any]) -> Dict[str, Any]:
53
+ """应用CSRF令牌认证
54
+
55
+ 将CSRF令牌添加到请求头中。
56
+
57
+ Args:
58
+ request_kwargs: 请求参数
59
+
60
+ Returns:
61
+ 更新后的请求参数
62
+ """
63
+ # 确保headers存在
64
+ if "headers" not in request_kwargs:
65
+ request_kwargs["headers"] = {}
66
+
67
+ # 如果已有令牌,添加到请求头
68
+ if self.csrf_token:
69
+ request_kwargs["headers"][self.header_name] = self.csrf_token
70
+ logger.debug(f"添加CSRF令牌到请求头: {self.header_name}={self.csrf_token}")
71
+ else:
72
+ logger.warning("尚未获取到CSRF令牌,无法添加到请求头")
73
+
74
+ return request_kwargs
75
+
76
+ def process_response(self, response: requests.Response) -> None:
77
+ """处理响应以提取CSRF令牌
78
+
79
+ 从响应的头、Cookie或正文中提取CSRF令牌。
80
+
81
+ Args:
82
+ response: 响应对象
83
+ """
84
+ token = None
85
+
86
+ # 从头中提取
87
+ if self.token_source == "header":
88
+ token = response.headers.get(self.source_name)
89
+ if token:
90
+ logger.debug(f"从响应头提取CSRF令牌: {self.source_name}={token}")
91
+ else:
92
+ logger.debug(f"响应头中未找到CSRF令牌: {self.source_name}")
93
+
94
+ # 从Cookie中提取
95
+ elif self.token_source == "cookie":
96
+ token = response.cookies.get(self.source_name)
97
+ if token:
98
+ logger.debug(f"从Cookie提取CSRF令牌: {self.source_name}={token}")
99
+ else:
100
+ logger.debug(f"Cookie中未找到CSRF令牌: {self.source_name}")
101
+
102
+ # 从响应体中提取
103
+ elif self.token_source == "body":
104
+ # 如果有正则表达式模式
105
+ if self.regex_pattern:
106
+ try:
107
+ match = re.search(self.regex_pattern, response.text)
108
+ if match and match.group(1):
109
+ token = match.group(1)
110
+ logger.debug(f"使用正则表达式从响应体提取CSRF令牌: {token}")
111
+ else:
112
+ logger.debug(f"正则表达式未匹配到CSRF令牌: {self.regex_pattern}")
113
+ except Exception as e:
114
+ logger.error(f"使用正则表达式提取CSRF令牌失败: {str(e)}")
115
+ # 如果是JSON响应,尝试使用JSON路径
116
+ elif 'application/json' in response.headers.get('Content-Type', ''):
117
+ try:
118
+ json_data = response.json()
119
+
120
+ # 简单的点路径解析
121
+ parts = self.source_name.strip('$').strip('.').split('.')
122
+ data = json_data
123
+ for part in parts:
124
+ if part in data:
125
+ data = data[part]
126
+ else:
127
+ data = None
128
+ break
129
+
130
+ if data and isinstance(data, str):
131
+ token = data
132
+ logger.debug(f"从JSON响应提取CSRF令牌: {self.source_name}={token}")
133
+ else:
134
+ logger.debug(f"JSON路径未找到CSRF令牌或值不是字符串: {self.source_name}")
135
+
136
+ except Exception as e:
137
+ logger.error(f"从JSON响应提取CSRF令牌失败: {str(e)}")
138
+ # 如果是HTML响应,可以尝试使用CSS选择器或XPath
139
+ else:
140
+ logger.debug("响应不是JSON格式,无法使用JSON路径提取CSRF令牌")
141
+ # 这里可以添加HTML解析逻辑,例如使用Beautiful Soup或lxml
142
+
143
+ # 更新令牌
144
+ if token:
145
+ logger.info(f"成功提取CSRF令牌: {token}")
146
+ self.csrf_token = token
147
+
148
+ def clean_auth_state(self, request_kwargs: Dict[str, Any] = None) -> Dict[str, Any]:
149
+ """清理认证状态
150
+
151
+ 清理CSRF认证状态,包括令牌和会话。
152
+
153
+ Args:
154
+ request_kwargs: 请求参数
155
+
156
+ Returns:
157
+ 更新后的请求参数
158
+ """
159
+ logger.info("清理CSRF认证状态")
160
+
161
+ # 重置CSRF令牌
162
+ self.csrf_token = None
163
+
164
+ # 清理会话Cookie
165
+ self._session.cookies.clear()
166
+
167
+ # 处理请求参数
168
+ if request_kwargs:
169
+ if "headers" in request_kwargs:
170
+ # 移除CSRF相关头
171
+ csrf_headers = [self.header_name, 'X-CSRF-Token', 'csrf-token', 'CSRF-Token']
172
+ for header in csrf_headers:
173
+ if header in request_kwargs["headers"]:
174
+ request_kwargs["headers"].pop(header)
175
+ logger.debug(f"已移除请求头: {header}")
176
+
177
+ return request_kwargs if request_kwargs else {}
178
+
179
+
180
+ # 使用示例
181
+ def register_csrf_auth_providers():
182
+ """注册CSRF认证提供者实例"""
183
+
184
+ # 从头中提取CSRF令牌
185
+ register_auth_provider(
186
+ "csrf_header_auth",
187
+ CSRFAuthProvider,
188
+ token_source="header",
189
+ source_name="X-CSRF-Token",
190
+ header_name="X-CSRF-Token"
191
+ )
192
+
193
+ # 从Cookie中提取CSRF令牌
194
+ register_auth_provider(
195
+ "csrf_cookie_auth",
196
+ CSRFAuthProvider,
197
+ token_source="cookie",
198
+ source_name="csrf_token",
199
+ header_name="X-CSRF-Token"
200
+ )
201
+
202
+ # 从JSON响应体中提取CSRF令牌
203
+ register_auth_provider(
204
+ "csrf_json_auth",
205
+ CSRFAuthProvider,
206
+ token_source="body",
207
+ source_name="data.security.csrf_token",
208
+ header_name="X-CSRF-Token"
209
+ )
210
+
211
+ # 使用正则表达式从HTML响应体中提取CSRF令牌
212
+ register_auth_provider(
213
+ "csrf_html_auth",
214
+ CSRFAuthProvider,
215
+ token_source="body",
216
+ header_name="X-CSRF-Token",
217
+ regex_pattern=r'<meta name="csrf-token" content="([^"]+)">'
218
+ )
219
+
220
+ logger.info("已注册CSRF认证提供者")
221
+
222
+
223
+ # 如果此模块被直接运行,注册CSRF认证提供者
224
+ if __name__ == "__main__":
225
+ # 配置日志
226
+ logging.basicConfig(
227
+ level=logging.DEBUG,
228
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
229
+ )
230
+
231
+ # 注册提供者
232
+ register_csrf_auth_providers()
@@ -0,0 +1,55 @@
1
+ @name: API测试入门示例
2
+ @description: 演示基本的API接口测试用法
3
+ @tags: [API, HTTP, 入门]
4
+ @author: Felix
5
+ @date: 2024-01-01
6
+
7
+ # 基本GET请求
8
+ [HTTP请求],客户端:'default',配置:'''
9
+ method: GET
10
+ url: https://jsonplaceholder.typicode.com/posts/1
11
+ asserts:
12
+ - ["status", "eq", 200]
13
+ - ["jsonpath", "$.id", "eq", 1]
14
+ - ["jsonpath", "$.title", "exists"]
15
+ ''',步骤名称:'获取文章详情'
16
+
17
+ # 响应数据捕获与使用
18
+ [HTTP请求],客户端:'default',配置:'''
19
+ method: GET
20
+ url: https://jsonplaceholder.typicode.com/posts
21
+ request:
22
+ params:
23
+ userId: 1
24
+ captures:
25
+ first_post_id: ["jsonpath", "$[0].id"]
26
+ post_count: ["jsonpath", "$", "length"]
27
+ asserts:
28
+ - ["status", "eq", 200]
29
+ - ["jsonpath", "$", "type", "array"]
30
+ ''',步骤名称:'获取用户文章列表'
31
+
32
+ # 打印捕获的变量
33
+ [打印],内容:'第一篇文章ID: ${first_post_id}, 文章总数: ${post_count}'
34
+
35
+ # POST请求创建资源
36
+ [HTTP请求],客户端:'default',配置:'''
37
+ method: POST
38
+ url: https://jsonplaceholder.typicode.com/posts
39
+ request:
40
+ headers:
41
+ Content-Type: application/json
42
+ json:
43
+ title: 测试标题
44
+ body: 测试内容
45
+ userId: 1
46
+ captures:
47
+ new_post_id: ["jsonpath", "$.id"]
48
+ asserts:
49
+ - ["status", "eq", 201]
50
+ - ["jsonpath", "$.title", "eq", "测试标题"]
51
+ ''',步骤名称:'创建新文章'
52
+
53
+ @teardown do
54
+ [打印],内容:'API测试完成!'
55
+ end
@@ -0,0 +1,31 @@
1
+ @name: 断言功能示例
2
+ @description: 演示断言关键字的基本用法
3
+ @tags: [断言, 入门]
4
+ @author: Felix
5
+ @date: 2024-01-01
6
+
7
+ # 基本断言
8
+ [断言],条件:'1 + 1 == 2',消息:'基本算术断言失败'
9
+
10
+ # 数字比较
11
+ num1 = 10
12
+ num2 = 5
13
+ [断言],条件:'${num1} > ${num2}',消息:'数字比较断言失败'
14
+
15
+ # JSON数据处理
16
+ json_data = '{"user": {"name": "张三", "age": 30, "roles": ["admin", "user"]}}'
17
+
18
+ # JSON断言示例
19
+ [JSON断言],JSON数据:${json_data},JSONPath:'$.user.age',预期值:30,操作符:'==',消息:'JSON断言失败:年龄不匹配'
20
+
21
+ # 类型断言
22
+ [类型断言],值:${json_data},类型:'string',消息:'类型断言失败'
23
+
24
+ # 简单变量赋值和断言
25
+ result = 53
26
+ [打印],内容:'结果: ${result}'
27
+ [断言],条件:'${result} == 53',消息:'结果不正确'
28
+
29
+ @teardown do
30
+ [打印],内容:'断言测试完成!'
31
+ end
@@ -0,0 +1,24 @@
1
+ @name: 变量和循环示例
2
+ @description: 演示变量使用和循环结构
3
+ @tags: [变量, 循环, 入门]
4
+ @author: Felix
5
+ @date: 2024-01-01
6
+
7
+ # 基本变量定义和使用
8
+ name = "pytest-dsl"
9
+ version = "1.0.0"
10
+ [打印],内容:'测试框架: ${name}, 版本: ${version}'
11
+
12
+ # 循环结构示例
13
+ [打印],内容:'开始循环测试'
14
+ count = 3
15
+
16
+ for i in range(1, ${count}) do
17
+ [打印],内容:'循环次数: ${i}'
18
+ end
19
+
20
+ [打印],内容:'循环结束'
21
+
22
+ @teardown do
23
+ [打印],内容:'变量和循环测试完成!'
24
+ end
@@ -0,0 +1,14 @@
1
+ """入门示例
2
+
3
+ 该文件展示了如何使用pytest-dsl创建简单的API测试
4
+ """
5
+
6
+ from pytest_dsl.core.auto_decorator import auto_dsl
7
+
8
+ @auto_dsl("./quickstart")
9
+ class TestQuickstart:
10
+ """入门示例测试类
11
+
12
+ 该类将自动加载quickstart目录下的所有.auto文件作为测试方法
13
+ """
14
+ pass