apitestgen-cli 0.5.35__cp315-cp315-win_amd64.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.
- apitestgen/__init__.py +0 -0
- apitestgen/_version.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/__init__.py +0 -0
- apitestgen/analyzer/archive.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/__init__.py +13 -0
- apitestgen/analyzer/backend_enrich/class_index.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/cli.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/controller_index.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/dto_resolver.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/error_codes.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/java_lex.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/merge.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/meta_extractor.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/module_inference.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/backend_enrich/response_resolver.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/curl_auth.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/dependency_detector.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/generated_project.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/json_converter.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/links_viewer.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/list_sessions.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/login_detector.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/output_formatter.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/planning.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/report_html.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/response_contract_inferrer.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/run_diff.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/schema_inferrer.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/validate_generated_project.cp315-win_amd64.pyd +0 -0
- apitestgen/analyzer/validate_recording_baseline.cp315-win_amd64.pyd +0 -0
- apitestgen/cli.cp315-win_amd64.pyd +0 -0
- apitestgen/commands/__init__.py +0 -0
- apitestgen/commands/init.cp315-win_amd64.pyd +0 -0
- apitestgen/commands/upgrade.cp315-win_amd64.pyd +0 -0
- apitestgen/common/__init__.py +3 -0
- apitestgen/common/host_classifier.cp315-win_amd64.pyd +0 -0
- apitestgen/common/path_utils.cp315-win_amd64.pyd +0 -0
- apitestgen/common/stdio.cp315-win_amd64.pyd +0 -0
- apitestgen/common/value_flattener.cp315-win_amd64.pyd +0 -0
- apitestgen/integrations/__init__.py +1 -0
- apitestgen/integrations/cloudtest_pusher.cp315-win_amd64.pyd +0 -0
- apitestgen/mcp_server.cp315-win_amd64.pyd +0 -0
- apitestgen/recorder/__init__.py +0 -0
- apitestgen/recorder/_normalize.cp315-win_amd64.pyd +0 -0
- apitestgen/recorder/addon.py +244 -0
- apitestgen/recorder/cli.cp315-win_amd64.pyd +0 -0
- apitestgen/recorder/db.cp315-win_amd64.pyd +0 -0
- apitestgen/recorder/inproc.cp315-win_amd64.pyd +0 -0
- apitestgen/recorder/runner.cp315-win_amd64.pyd +0 -0
- apitestgen/templates/apitestgen.yaml +101 -0
- apitestgen/templates/conftest.py +1108 -0
- apitestgen/templates/env.yaml +107 -0
- apitestgen/templates/fake_strategies.py +184 -0
- apitestgen/templates/pytest.ini +7 -0
- apitestgen_cli-0.5.35.dist-info/METADATA +443 -0
- apitestgen_cli-0.5.35.dist-info/RECORD +59 -0
- apitestgen_cli-0.5.35.dist-info/WHEEL +5 -0
- apitestgen_cli-0.5.35.dist-info/entry_points.txt +2 -0
- apitestgen_cli-0.5.35.dist-info/top_level.txt +1 -0
apitestgen/__init__.py
ADDED
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Enrich `.apitestgen/*.json` with information mined from a Java Spring backend repo.
|
|
2
|
+
|
|
3
|
+
The package is a self-contained `analyze enrich` subcommand. Submodules:
|
|
4
|
+
|
|
5
|
+
- ``cli`` — argparse entry point mounted via ``apitestgen analyze enrich``
|
|
6
|
+
- ``java_lex`` — shared regex / Java string-literal helpers
|
|
7
|
+
- ``controller_index`` — A1-A4: scan controllers, build (method, path) → (file, java_method, ...)
|
|
8
|
+
- ``dto_resolver`` — B5-B9: locate request DTOs, extract JSR-380 annotations + enums
|
|
9
|
+
- ``response_resolver`` — C10: identify wrapper class (R<T> / Result<T> / ...) + inner T
|
|
10
|
+
- ``error_codes`` — C11-C12: error-code constant dictionary + service-layer throw inversion
|
|
11
|
+
- ``meta_extractor`` — E13-E14: @Tag module label + BaseEntity audit fields
|
|
12
|
+
- ``merge`` — fold resolver outputs into ``<api>.enriched.json``
|
|
13
|
+
"""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Outbound integrations with external test-management platforms."""
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""mitmproxy addon that records API/browser traffic into SQLite."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from mitmproxy import ctx, http
|
|
10
|
+
from mitmproxy.addonmanager import Loader
|
|
11
|
+
|
|
12
|
+
from apitestgen.common.host_classifier import infer_primary_base_url
|
|
13
|
+
from apitestgen.recorder._normalize import (
|
|
14
|
+
TEST_ID_HEADER,
|
|
15
|
+
extract_response_headers,
|
|
16
|
+
filter_request_headers,
|
|
17
|
+
flatten_query_params,
|
|
18
|
+
host_with_port,
|
|
19
|
+
normalize_allowed_hosts,
|
|
20
|
+
parse_request_body,
|
|
21
|
+
parse_response_body,
|
|
22
|
+
should_record,
|
|
23
|
+
)
|
|
24
|
+
from apitestgen.recorder.db import create_session, init_db, insert_api_call, update_session_base_url
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("apitestgen.recorder")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RecorderAddon:
|
|
30
|
+
"""Record filtered browser/API flows into SQLite."""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self.db_path: str = ".apitestgen/recordings.db"
|
|
34
|
+
self.session_name: str = "default"
|
|
35
|
+
self.filter_prefixes: list[str] = []
|
|
36
|
+
self.allowed_hosts: list[str] = []
|
|
37
|
+
self.role: str = ""
|
|
38
|
+
self.include_browser_noise: bool = False
|
|
39
|
+
self.conn = None
|
|
40
|
+
self.session_id: int | None = None
|
|
41
|
+
self.seq_counter: int = 0
|
|
42
|
+
self._flow_start_times: dict[str, float] = {}
|
|
43
|
+
self._recorded_calls: list[dict] = []
|
|
44
|
+
self._seen_host_keys: set[tuple[str, str]] = set()
|
|
45
|
+
self._current_base_url: str = ""
|
|
46
|
+
|
|
47
|
+
def load(self, loader: Loader):
|
|
48
|
+
loader.add_option(
|
|
49
|
+
"apitestgen_db",
|
|
50
|
+
str,
|
|
51
|
+
".apitestgen/recordings.db",
|
|
52
|
+
"SQLite database path",
|
|
53
|
+
)
|
|
54
|
+
loader.add_option(
|
|
55
|
+
"apitestgen_session",
|
|
56
|
+
str,
|
|
57
|
+
"default",
|
|
58
|
+
"Recording session name",
|
|
59
|
+
)
|
|
60
|
+
loader.add_option(
|
|
61
|
+
"apitestgen_filter",
|
|
62
|
+
str,
|
|
63
|
+
"",
|
|
64
|
+
"Comma-separated URL path prefixes to record",
|
|
65
|
+
)
|
|
66
|
+
loader.add_option(
|
|
67
|
+
"apitestgen_domains",
|
|
68
|
+
str,
|
|
69
|
+
"",
|
|
70
|
+
"Comma-separated exact hostnames to record",
|
|
71
|
+
)
|
|
72
|
+
loader.add_option(
|
|
73
|
+
"apitestgen_role",
|
|
74
|
+
str,
|
|
75
|
+
"",
|
|
76
|
+
"Role label for this session — every flow gets tagged with it. For multi-role flows, run one mitmdump per role on different ports.",
|
|
77
|
+
)
|
|
78
|
+
loader.add_option(
|
|
79
|
+
"apitestgen_include_browser_noise",
|
|
80
|
+
bool,
|
|
81
|
+
False,
|
|
82
|
+
"Record browser-internal chatter (Safe Browsing, OCSP, DoH, telemetry, etc.). Off by default.",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def configure(self, updated):
|
|
86
|
+
self.db_path = ctx.options.apitestgen_db
|
|
87
|
+
self.session_name = ctx.options.apitestgen_session
|
|
88
|
+
self.filter_prefixes = [
|
|
89
|
+
prefix.strip() for prefix in ctx.options.apitestgen_filter.split(",") if prefix.strip()
|
|
90
|
+
]
|
|
91
|
+
self.allowed_hosts = normalize_allowed_hosts(ctx.options.apitestgen_domains)
|
|
92
|
+
self.role = (ctx.options.apitestgen_role or "").strip()
|
|
93
|
+
self.include_browser_noise = bool(ctx.options.apitestgen_include_browser_noise)
|
|
94
|
+
|
|
95
|
+
if self.conn is None:
|
|
96
|
+
self.conn = init_db(self.db_path)
|
|
97
|
+
self.session_id = create_session(
|
|
98
|
+
self.conn, self.session_name, role=self.role or None
|
|
99
|
+
)
|
|
100
|
+
logger.info(
|
|
101
|
+
"Recording session #%d (%s) -> %s (role=%s)",
|
|
102
|
+
self.session_id,
|
|
103
|
+
self.session_name,
|
|
104
|
+
self.db_path,
|
|
105
|
+
self.role or "-",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def request(self, flow: http.HTTPFlow):
|
|
109
|
+
self._flow_start_times[flow.id] = time.time()
|
|
110
|
+
|
|
111
|
+
def response(self, flow: http.HTTPFlow):
|
|
112
|
+
if not self._should_record(flow):
|
|
113
|
+
self._flow_start_times.pop(flow.id, None)
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
start_time = self._flow_start_times.pop(flow.id, None)
|
|
117
|
+
duration_ms = int((time.time() - start_time) * 1000) if start_time else 0
|
|
118
|
+
|
|
119
|
+
self.seq_counter += 1
|
|
120
|
+
req = flow.request
|
|
121
|
+
resp = flow.response
|
|
122
|
+
if resp is None:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
query_params = flatten_query_params(urlparse(req.url).query)
|
|
126
|
+
|
|
127
|
+
test_id: str | None = None
|
|
128
|
+
raw_request_headers: dict[str, str] = {}
|
|
129
|
+
for key, value in req.headers.items():
|
|
130
|
+
if key.lower() == TEST_ID_HEADER.lower():
|
|
131
|
+
if not test_id:
|
|
132
|
+
test_id = value
|
|
133
|
+
continue
|
|
134
|
+
raw_request_headers[key] = value
|
|
135
|
+
request_headers = filter_request_headers(raw_request_headers)
|
|
136
|
+
# mitmproxy can multi-value headers; expand via get_all so the shared
|
|
137
|
+
# extract_response_headers sees every occurrence.
|
|
138
|
+
response_header_pairs = [
|
|
139
|
+
(name, value)
|
|
140
|
+
for name in resp.headers.keys()
|
|
141
|
+
for value in resp.headers.get_all(name)
|
|
142
|
+
]
|
|
143
|
+
response_headers = extract_response_headers(response_header_pairs)
|
|
144
|
+
|
|
145
|
+
request_content_type = req.headers.get("content-type", "")
|
|
146
|
+
request_body, request_body_text = parse_request_body(
|
|
147
|
+
self._safe_get_text(req),
|
|
148
|
+
request_content_type,
|
|
149
|
+
)
|
|
150
|
+
response_body, response_body_text = parse_response_body(
|
|
151
|
+
self._safe_get_text(resp),
|
|
152
|
+
resp.headers.get("content-type", ""),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
scheme = req.scheme
|
|
156
|
+
host_str = host_with_port(req.host, req.port)
|
|
157
|
+
|
|
158
|
+
actor = self.role or None
|
|
159
|
+
|
|
160
|
+
call_snapshot = {
|
|
161
|
+
"seq": self.seq_counter,
|
|
162
|
+
"scheme": scheme,
|
|
163
|
+
"host": host_str,
|
|
164
|
+
"path": req.path.split("?")[0],
|
|
165
|
+
"method": req.method,
|
|
166
|
+
"status_code": resp.status_code,
|
|
167
|
+
"content_type": resp.headers.get("content-type", ""),
|
|
168
|
+
"request_content_type": request_content_type,
|
|
169
|
+
"request_headers": request_headers,
|
|
170
|
+
"actor": actor,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
call_id = insert_api_call(
|
|
175
|
+
self.conn,
|
|
176
|
+
self.session_id,
|
|
177
|
+
self.seq_counter,
|
|
178
|
+
method=req.method,
|
|
179
|
+
scheme=scheme,
|
|
180
|
+
host=host_str,
|
|
181
|
+
path=req.path.split("?")[0],
|
|
182
|
+
query_params=query_params if query_params else None,
|
|
183
|
+
request_content_type=request_content_type,
|
|
184
|
+
request_headers=request_headers if request_headers else None,
|
|
185
|
+
request_body=request_body,
|
|
186
|
+
request_body_text=request_body_text,
|
|
187
|
+
status_code=resp.status_code,
|
|
188
|
+
response_body=response_body,
|
|
189
|
+
response_body_text=response_body_text,
|
|
190
|
+
response_headers=response_headers if response_headers else None,
|
|
191
|
+
content_type=resp.headers.get("content-type", ""),
|
|
192
|
+
duration_ms=duration_ms,
|
|
193
|
+
test_id=test_id,
|
|
194
|
+
actor=actor,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self._recorded_calls.append(call_snapshot)
|
|
198
|
+
host_key = (scheme, host_str)
|
|
199
|
+
if host_key not in self._seen_host_keys:
|
|
200
|
+
self._seen_host_keys.add(host_key)
|
|
201
|
+
inferred_base_url = infer_primary_base_url(self._recorded_calls)
|
|
202
|
+
if inferred_base_url and inferred_base_url != self._current_base_url:
|
|
203
|
+
self._current_base_url = inferred_base_url
|
|
204
|
+
update_session_base_url(self.conn, self.session_id, inferred_base_url)
|
|
205
|
+
logger.info("Updated inferred base_url: %s", inferred_base_url)
|
|
206
|
+
|
|
207
|
+
logger.info(
|
|
208
|
+
"[#%d] %s %s -> %d (%dms)%s",
|
|
209
|
+
call_id,
|
|
210
|
+
req.method,
|
|
211
|
+
req.path.split("?")[0],
|
|
212
|
+
resp.status_code,
|
|
213
|
+
duration_ms,
|
|
214
|
+
f" actor={actor}" if actor else "",
|
|
215
|
+
)
|
|
216
|
+
except Exception:
|
|
217
|
+
logger.exception("Failed to record API call")
|
|
218
|
+
|
|
219
|
+
def _should_record(self, flow: http.HTTPFlow) -> bool:
|
|
220
|
+
if flow.response is None:
|
|
221
|
+
return False
|
|
222
|
+
path = flow.request.path.split("?")[0]
|
|
223
|
+
return should_record(
|
|
224
|
+
flow.request.host,
|
|
225
|
+
path,
|
|
226
|
+
self.filter_prefixes,
|
|
227
|
+
self.include_browser_noise,
|
|
228
|
+
self.allowed_hosts,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def _safe_get_text(message) -> str | None:
|
|
233
|
+
try:
|
|
234
|
+
return message.get_text(strict=False)
|
|
235
|
+
except TypeError:
|
|
236
|
+
try:
|
|
237
|
+
return message.get_text()
|
|
238
|
+
except Exception:
|
|
239
|
+
return None
|
|
240
|
+
except Exception:
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
addons = [RecorderAddon()]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# ApiTestGen v2 配置
|
|
2
|
+
# 后端 Java Web 仓库本地绝对路径
|
|
3
|
+
# - 仅 /apitestgen-backend-enrich(及底层 `apitestgen analyze enrich`)需要它
|
|
4
|
+
# - 录制 → 分析 → 生成 主流程不依赖此字段;留默认占位也能跑
|
|
5
|
+
# - 配置后会:扫 controller 注解填 controller_file/permission/path_params,
|
|
6
|
+
# 读 DTO 类拿精确的 @NotNull/@Size/@Pattern,识别 R<T>/CommonResult<T> 包装类,
|
|
7
|
+
# 倒排 service 层 throw 链路给 negative case 提供具体错误码
|
|
8
|
+
backend:
|
|
9
|
+
repo_path: /absolute/path/to/your/spring-backend
|
|
10
|
+
# 可选:限定扫描子目录,加速 Grep。逗号分隔多个根。
|
|
11
|
+
module_roots:
|
|
12
|
+
- src/main/java
|
|
13
|
+
|
|
14
|
+
# 测试输出目录(相对当前项目)
|
|
15
|
+
output_dir: api_tests
|
|
16
|
+
|
|
17
|
+
# 测试策略:控制每个接口生成的用例数量
|
|
18
|
+
# 同时也会影响 `.apitestgen/plans/test_strategy.json` 中的默认测试深度描述
|
|
19
|
+
# - minimal: 1 正向 + 1 必填缺失 + 1 安全(无 token),共 3 条;仅用于冒烟
|
|
20
|
+
# - balanced: 每字段最多 1 条负向 + 1 边界,去重(默认,适合日常回归)
|
|
21
|
+
# - full: 所有组合,包括全部边界/枚举负向用例
|
|
22
|
+
test_strategy: balanced
|
|
23
|
+
|
|
24
|
+
# 说明:
|
|
25
|
+
# - `record-analyze` / `json_converter` 会自动生成 `.apitestgen/*.json`
|
|
26
|
+
# - 同时自动刷新 `.apitestgen/plans/requirement_matrix.json`
|
|
27
|
+
# `.apitestgen/plans/test_strategy.json`
|
|
28
|
+
# `.apitestgen/plans/quality_review.json`
|
|
29
|
+
# - 这些 sidecar 不参与运行时请求发送,但会被后续人工审查和校验脚本使用
|
|
30
|
+
|
|
31
|
+
# 代理录制配置(可选,使用 /apitestgen-record 时生效)
|
|
32
|
+
proxy:
|
|
33
|
+
port: 8888
|
|
34
|
+
filter_paths:
|
|
35
|
+
- "/admin-api/"
|
|
36
|
+
# 排除的路径模式
|
|
37
|
+
# exclude_patterns:
|
|
38
|
+
# - "/ws/"
|
|
39
|
+
|
|
40
|
+
# 依赖检测算法配置(P2-2)
|
|
41
|
+
dependency_detector:
|
|
42
|
+
# 高频值过滤阈值:值出现在超过此比例的响应中则视为噪音(0~1)
|
|
43
|
+
high_freq_threshold: 0.5
|
|
44
|
+
# 最低置信度阈值:低于此值的关联不写入数据库
|
|
45
|
+
min_confidence: 0.5
|
|
46
|
+
# 时间窗(秒):依赖必须在目标请求之前 N 秒内出现;0 = 不限制
|
|
47
|
+
time_window_seconds: 0
|
|
48
|
+
|
|
49
|
+
# 测试用例文案语言:zh-CN(默认)或 en
|
|
50
|
+
# 影响 `_generate_analysis_test_cases` 的 testpoint / expectation / precondition 字面值
|
|
51
|
+
locale: zh-CN
|
|
52
|
+
|
|
53
|
+
# CloudTest 平台对接配置(供 tools/analyzer/output_formatter.py --format cloudtest
|
|
54
|
+
# 及 tools/integrations/cloudtest_pusher.py 读取)
|
|
55
|
+
# cloudtest:
|
|
56
|
+
# # 创建用例接口的 featureUri 默认值;后续也可按项目自定义成真实特性目录 URI
|
|
57
|
+
# default_feature_uri: REPLACE_WITH_FEATURE_URI
|
|
58
|
+
# # 可选:CloudTest/SVN 中脚本路径的仓库前缀;不配时使用 api_tests/... 相对路径
|
|
59
|
+
# # svn_script_path_prefix: /svn/your-project
|
|
60
|
+
# # CSV 列顺序(可选,默认按下方 headers 字面量输出)
|
|
61
|
+
# # columns:
|
|
62
|
+
# # - expectOutput
|
|
63
|
+
# # - featureUri
|
|
64
|
+
# # - interfaceName
|
|
65
|
+
# # - name
|
|
66
|
+
# # - number
|
|
67
|
+
# # - preparation
|
|
68
|
+
# # - svnScriptPath
|
|
69
|
+
# # - testStep
|
|
70
|
+
#
|
|
71
|
+
# # REST 推送契约(dry-run 时可留空;--apply 前必须填完)
|
|
72
|
+
# api:
|
|
73
|
+
# base_url: https://cloudtest.example.com
|
|
74
|
+
# # 认证:CloudTest 走 Cookie + csrftoken(Django Session 风格)
|
|
75
|
+
# # 浏览器登录 CloudTest 后从 DevTools 复制;rotating,过期后需手动刷新。
|
|
76
|
+
# headers:
|
|
77
|
+
# Cookie: "sessionid=...; csrftoken=..."
|
|
78
|
+
# csrftoken: REPLACE_WITH_CSRF_TOKEN_VALUE
|
|
79
|
+
# X-Project-Id: your-project-id # 如平台需要再加
|
|
80
|
+
# external_id_column: number # CSV 列名,默认就是 number
|
|
81
|
+
# search_query_field: number # 查询时用的 query 参数名
|
|
82
|
+
# case_search_path: /api/testcases # GET 此地址 + ?search_query_field=xxx
|
|
83
|
+
# case_create_path: /api/testcases
|
|
84
|
+
# case_create_method: POST
|
|
85
|
+
# case_update_path: /api/testcases/{id}
|
|
86
|
+
# case_update_method: PUT
|
|
87
|
+
# # 默认 CSV 列已对齐创建用例接口参数;只有自定义 columns 时才需要 field_map
|
|
88
|
+
# # field_map 的 value 语义:
|
|
89
|
+
# # - 若是 CSV 列名 → 取该行该列的值
|
|
90
|
+
# # - 若不是任何列名 → 作为字面值原样写进 body(保留 YAML 类型:int / bool / null …)
|
|
91
|
+
# # 用后者来传 CSV 里没有的固定参数,例如 stage / type 之类的枚举值。
|
|
92
|
+
# field_map:
|
|
93
|
+
# expectOutput: expectOutput
|
|
94
|
+
# featureUri: featureUri
|
|
95
|
+
# interfaceName: interfaceName
|
|
96
|
+
# name: name
|
|
97
|
+
# number: number
|
|
98
|
+
# preparation: preparation
|
|
99
|
+
# svnScriptPath: svnScriptPath
|
|
100
|
+
# testStep: testStep
|
|
101
|
+
# stage: 2 # 字面值示例:body 里发 "stage": 2(数字,不是 "2")
|