dd-apitest 0.2.0__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.
- dd_apitest-0.2.0/PKG-INFO +197 -0
- dd_apitest-0.2.0/README.md +174 -0
- dd_apitest-0.2.0/core/__init__.py +1 -0
- dd_apitest-0.2.0/core/__main__.py +99 -0
- dd_apitest-0.2.0/core/assert_engine.py +34 -0
- dd_apitest-0.2.0/core/bundled/__init__.py +1 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/cases/flows/post_then_get.json +23 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/cases/get-basic.json +6 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/cases/get-delay.json +6 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/cases/get-query.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/cases/post-form.json +8 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/cases/post-json.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/forms/post_form.json +6 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/groups/flows/group.json +9 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/groups/readonly/group.json +11 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/groups/write/group.json +10 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/payloads/get_query.json +4 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/payloads/post_json.json +4 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/payloads/post_seed.json +3 -0
- dd_apitest-0.2.0/core/bundled/suites/demo-ui/suite.json +48 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/agent-list.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/associate-by-mcp-name.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/associate.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/detail.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/disassociate.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/flows/create_then_delete.json +20 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/page.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/cases/restore.json +7 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/groups/readonly/group.json +11 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/groups/write/group.json +12 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/agent_list.json +3 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/associate.json +5 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/associate_by_mcp_name.json +4 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/create.json +12 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/delete.json +3 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/detail.json +3 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/disassociate.json +4 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/page.json +5 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/restore.json +4 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/token_bind.json +6 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/payloads/update.json +13 -0
- dd_apitest-0.2.0/core/bundled/suites/xsiam-credential/suite.json +30 -0
- dd_apitest-0.2.0/core/context.py +67 -0
- dd_apitest-0.2.0/core/errors.py +6 -0
- dd_apitest-0.2.0/core/extract.py +15 -0
- dd_apitest-0.2.0/core/hooks.py +70 -0
- dd_apitest-0.2.0/core/http.py +120 -0
- dd_apitest-0.2.0/core/init_cmd.py +25 -0
- dd_apitest-0.2.0/core/loader.py +330 -0
- dd_apitest-0.2.0/core/menu.py +131 -0
- dd_apitest-0.2.0/core/paths.py +84 -0
- dd_apitest-0.2.0/core/plugins/__init__.py +3 -0
- dd_apitest-0.2.0/core/plugins/auth_hengnao.py +28 -0
- dd_apitest-0.2.0/core/plugins/auth_none.py +10 -0
- dd_apitest-0.2.0/core/plugins/base.py +14 -0
- dd_apitest-0.2.0/core/plugins/registry.py +83 -0
- dd_apitest-0.2.0/core/report.py +111 -0
- dd_apitest-0.2.0/core/report_html.py +78 -0
- dd_apitest-0.2.0/core/runner.py +337 -0
- dd_apitest-0.2.0/core/setuptools_build.py +19 -0
- dd_apitest-0.2.0/core/template.py +34 -0
- dd_apitest-0.2.0/core/templates/report.html +112 -0
- dd_apitest-0.2.0/core/tui/__init__.py +3 -0
- dd_apitest-0.2.0/core/tui/app.py +690 -0
- dd_apitest-0.2.0/dd_apitest.egg-info/PKG-INFO +197 -0
- dd_apitest-0.2.0/dd_apitest.egg-info/SOURCES.txt +91 -0
- dd_apitest-0.2.0/dd_apitest.egg-info/dependency_links.txt +1 -0
- dd_apitest-0.2.0/dd_apitest.egg-info/entry_points.txt +3 -0
- dd_apitest-0.2.0/dd_apitest.egg-info/requires.txt +3 -0
- dd_apitest-0.2.0/dd_apitest.egg-info/top_level.txt +1 -0
- dd_apitest-0.2.0/pyproject.toml +52 -0
- dd_apitest-0.2.0/setup.cfg +4 -0
- dd_apitest-0.2.0/tests/test_apitest_import.py +4 -0
- dd_apitest-0.2.0/tests/test_assert_engine.py +34 -0
- dd_apitest-0.2.0/tests/test_auth_hengnao.py +41 -0
- dd_apitest-0.2.0/tests/test_cli_run.py +46 -0
- dd_apitest-0.2.0/tests/test_cli_smoke.py +16 -0
- dd_apitest-0.2.0/tests/test_client_request.py +13 -0
- dd_apitest-0.2.0/tests/test_context.py +51 -0
- dd_apitest-0.2.0/tests/test_demo_ui_suite.py +28 -0
- dd_apitest-0.2.0/tests/test_extract.py +17 -0
- dd_apitest-0.2.0/tests/test_http.py +37 -0
- dd_apitest-0.2.0/tests/test_init_cmd.py +40 -0
- dd_apitest-0.2.0/tests/test_loader.py +127 -0
- dd_apitest-0.2.0/tests/test_paths.py +56 -0
- dd_apitest-0.2.0/tests/test_plugin_discovery.py +68 -0
- dd_apitest-0.2.0/tests/test_report.py +102 -0
- dd_apitest-0.2.0/tests/test_report_html.py +99 -0
- dd_apitest-0.2.0/tests/test_runner.py +31 -0
- dd_apitest-0.2.0/tests/test_template.py +16 -0
- dd_apitest-0.2.0/tests/test_tui_smoke.py +238 -0
- dd_apitest-0.2.0/tests/test_xsiam_suite_layout.py +25 -0
- dd_apitest-0.2.0/tests/test_xsiam_suite_loader.py +27 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dd-apitest
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: JSON-driven OpenAPI integration test CLI (TUI + suites)
|
|
5
|
+
Author: DBApp Security
|
|
6
|
+
License-Expression: LicenseRef-Proprietary
|
|
7
|
+
Project-URL: Homepage, https://github.com/dbappsecurity/apitest-standalone
|
|
8
|
+
Project-URL: Documentation, https://github.com/dbappsecurity/apitest-standalone/blob/main/docs/2026-05-29-apitest-framework-usage.md
|
|
9
|
+
Project-URL: Issues, https://github.com/dbappsecurity/apitest-standalone/issues
|
|
10
|
+
Keywords: apitest,openapi,api-testing,dd-apitest
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Software Development :: Testing
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: requests>=2.32.0
|
|
21
|
+
Requires-Dist: python-dotenv>=1.0.1
|
|
22
|
+
Requires-Dist: textual>=0.80.0
|
|
23
|
+
|
|
24
|
+
# XSIAM Credential / apitest
|
|
25
|
+
|
|
26
|
+
## 环境准备
|
|
27
|
+
|
|
28
|
+
### 方式 A:从 PyPI / 内网 PyPI 运行(推荐分发)
|
|
29
|
+
|
|
30
|
+
PyPI **包名**为 `dd-apitest`(公网 [pypi.org/project/apitest](https://pypi.org/project/apitest/) 已被其他项目占用,命令行仍为 **`apitest`**)。
|
|
31
|
+
|
|
32
|
+
需已安装 [uv](https://docs.astral.sh/uv/)。**发布前**需维护者先执行 `uv publish`;用户侧:
|
|
33
|
+
|
|
34
|
+
```powershell
|
|
35
|
+
# 一次性运行(包名 dd-apitest,命令 apitest;Windows 需显式指定命令名)
|
|
36
|
+
uvx --from dd-apitest apitest --user-data init
|
|
37
|
+
uvx --from dd-apitest apitest --user-data
|
|
38
|
+
uvx --from dd-apitest apitest --user-data run --suite demo-ui --group readonly --env httpbin
|
|
39
|
+
|
|
40
|
+
# 内网 PyPI(示例)
|
|
41
|
+
# uvx --index-url https://pypi.your-company.com/simple/ --from dd-apitest apitest --user-data init
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
持久安装(装好后直接用 `apitest`):
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
uv tool install dd-apitest
|
|
48
|
+
apitest --user-data init
|
|
49
|
+
apitest --user-data
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
发布流程见 [`docs/publishing-pypi.md`](./docs/publishing-pypi.md)。
|
|
53
|
+
|
|
54
|
+
### 方式 A′:`uvx --from` 本地或 Git(未上 PyPI 时)
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
uvx --from "C:/Programs/dbapp-workspace/apitest-standalone" apitest --user-data init
|
|
58
|
+
uvx --from "C:/Programs/dbapp-workspace/apitest-standalone" apitest --user-data
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
说明:
|
|
62
|
+
|
|
63
|
+
- `uvx` 会临时安装到 uv 缓存,不污染当前目录;
|
|
64
|
+
- 包内已内置 suite 模板;`init --user-data` 写入 `~/.apitest/`;
|
|
65
|
+
- 已 `init` 且存在 `~/.apitest/suites` 时,可省略每次的 `--user-data`;
|
|
66
|
+
- 在**已 clone 的仓库根目录**内开发时,默认使用仓库下的 `suites/`。
|
|
67
|
+
|
|
68
|
+
### 方式 B:仓库内开发(仅 uv)
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
uv venv --python 3.12
|
|
72
|
+
.\.venv\Scripts\Activate.ps1
|
|
73
|
+
uv pip install -e .
|
|
74
|
+
uv pip install "pytest>=8.0.0" "responses>=0.25.0"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`.env` 示例(勿提交仓库):
|
|
78
|
+
|
|
79
|
+
```env
|
|
80
|
+
HENGNAO_APPKEY=your_key
|
|
81
|
+
HENGNAO_APPSECRET=your_secret
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## apitest(推荐)
|
|
85
|
+
|
|
86
|
+
JSON 配置驱动的 OpenAPI 联调框架。日常在仓库内用 `uv run apitest`;任意目录用 `uvx --from <源> apitest`(见上文方式 A)。
|
|
87
|
+
|
|
88
|
+
- **使用文档**:[`docs/2026-05-29-apitest-framework-usage.md`](./docs/2026-05-29-apitest-framework-usage.md)
|
|
89
|
+
- **设计文档**:[`docs/superpowers/specs/2026-05-29-apitest-framework-design.md`](./docs/superpowers/specs/2026-05-29-apitest-framework-design.md)
|
|
90
|
+
|
|
91
|
+
### 常用命令
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
# 交互 TUI(Textual):方向键选组/用例,快捷键见下
|
|
95
|
+
uv run apitest
|
|
96
|
+
|
|
97
|
+
| 键 | 作用 |
|
|
98
|
+
|----|------|
|
|
99
|
+
| `g` | 跑当前组 |
|
|
100
|
+
| `c` | 跑当前用例 |
|
|
101
|
+
| `a` | 跑全部组 |
|
|
102
|
+
| `e` | **选择环境**(列表点选,Enter 确认) |
|
|
103
|
+
| `n` | 下一环境(顺序轮换) |
|
|
104
|
+
| `s` / `b` | **返回 Suite 选择**(多个 suite 时;Esc 取消并回到当前 suite) |
|
|
105
|
+
| `q` | 退出(也可点底部 Footer 的 Quit) |
|
|
106
|
+
|
|
107
|
+
日志区使用 RichLog 彩色输出;跑完用例后会输出报告路径与 `file:///.../report.html` 链接。
|
|
108
|
+
|
|
109
|
+
启动时若存在多个 suite(如 `demo-ui`、`xsiam-credential`),会先弹出 Suite 列表;进入主界面后按 **`s`** 可退回 Suite 选择页重新选择(Esc 取消)。
|
|
110
|
+
|
|
111
|
+
# 只读冒烟
|
|
112
|
+
uv run apitest run --suite xsiam-credential --group readonly --env dev
|
|
113
|
+
|
|
114
|
+
# 写组(含 create-then-delete flow)
|
|
115
|
+
uv run apitest run --suite xsiam-credential --group write --env dev
|
|
116
|
+
|
|
117
|
+
# 全量(所有 group)
|
|
118
|
+
uv run apitest run --suite xsiam-credential --all --env dev
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
报告目录:`reports/{suite}/{timestamp}/`(已 gitignore)。
|
|
122
|
+
|
|
123
|
+
报告文件:
|
|
124
|
+
- `report.html`:主报告(可直接浏览器打开)
|
|
125
|
+
- `summary.json`:机器可读摘要
|
|
126
|
+
- `index.json`:索引(含 `reportHtml` 指针)
|
|
127
|
+
- `failures/{caseId}.json`:失败或 `--verbose` 详情(脱敏)
|
|
128
|
+
- `summary.md`:仅兼容模式(`legacy_md=True`)生成
|
|
129
|
+
|
|
130
|
+
非 TUI/菜单模式会同时打印:
|
|
131
|
+
- `Report: <本地路径>`
|
|
132
|
+
- `Report URL: file:///.../report.html`(多数终端可点击)
|
|
133
|
+
|
|
134
|
+
可选 data root 切换:
|
|
135
|
+
|
|
136
|
+
```powershell
|
|
137
|
+
# 使用用户空间 ~/.apitest
|
|
138
|
+
uv run apitest --user-data
|
|
139
|
+
|
|
140
|
+
# 指定自定义 data root
|
|
141
|
+
uv run apitest --data-dir "D:/apitest-data" run --suite xsiam-credential --all --env dev
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
默认 data root 为仓库根目录(`suites/`、`plugins/`、`.env` 所在目录)。
|
|
145
|
+
|
|
146
|
+
### 新增接口(检查清单)
|
|
147
|
+
|
|
148
|
+
1. 在 `suites/xsiam-credential/payloads/` 增加请求体或 query JSON
|
|
149
|
+
2. 在 `suites/xsiam-credential/cases/` 增加 case(或 flow 步骤)
|
|
150
|
+
3. 在 `groups/readonly/group.json` 或 `groups/write/group.json` 引用该 case
|
|
151
|
+
4. `uv run pytest -q`
|
|
152
|
+
|
|
153
|
+
Agent 配置用例:`.cursor/skills/apitest-test-data/SKILL.md`(流程)+ `reference.md`(字段/示例)。
|
|
154
|
+
|
|
155
|
+
表单上传:`bodyType` 为 `form-data` / `form-urlencoded`,见设计文档 §3.4.2。
|
|
156
|
+
|
|
157
|
+
### 演示 Suite(看 TUI 交互)
|
|
158
|
+
|
|
159
|
+
无需内网与 `HENGNAO_*` 密钥,走公网 [httpbin.org](https://httpbin.org):
|
|
160
|
+
|
|
161
|
+
```powershell
|
|
162
|
+
uv run apitest
|
|
163
|
+
# 启动后若有两个 suite,选 demo-ui
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
| 组 | 说明 |
|
|
167
|
+
|----|------|
|
|
168
|
+
| `readonly` | GET 连通、Query、2s 延迟 |
|
|
169
|
+
| `write` | POST JSON、form-urlencoded |
|
|
170
|
+
| `flows` | POST → extract → GET 变量传递 |
|
|
171
|
+
|
|
172
|
+
环境:`demo-ui` 含 `httpbin` / `httpbin-delay` / `playground` / `sandbox`。TUI 内按 **`e`** 弹出列表选择;按 **`n`** 可顺序切换到下一环境。
|
|
173
|
+
|
|
174
|
+
### 目录
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
core/ # 框架内核
|
|
178
|
+
suites/demo-ui/ # TUI 演示(httpbin)
|
|
179
|
+
suites/xsiam-credential/
|
|
180
|
+
suite.json
|
|
181
|
+
groups/readonly|write/group.json
|
|
182
|
+
cases/
|
|
183
|
+
payloads/
|
|
184
|
+
plugins/ # 项目级自定义 auth_*.py(可选)
|
|
185
|
+
reports/ # 执行产物
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 测试
|
|
189
|
+
|
|
190
|
+
```powershell
|
|
191
|
+
uv run pytest -q
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## 安全说明
|
|
195
|
+
|
|
196
|
+
- 日志与报告会对 `accessToken`、`refreshToken`、`clientSecret`、`appSecret`、`sign` 脱敏。
|
|
197
|
+
- 勿将 `.env` 提交到仓库。
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# XSIAM Credential / apitest
|
|
2
|
+
|
|
3
|
+
## 环境准备
|
|
4
|
+
|
|
5
|
+
### 方式 A:从 PyPI / 内网 PyPI 运行(推荐分发)
|
|
6
|
+
|
|
7
|
+
PyPI **包名**为 `dd-apitest`(公网 [pypi.org/project/apitest](https://pypi.org/project/apitest/) 已被其他项目占用,命令行仍为 **`apitest`**)。
|
|
8
|
+
|
|
9
|
+
需已安装 [uv](https://docs.astral.sh/uv/)。**发布前**需维护者先执行 `uv publish`;用户侧:
|
|
10
|
+
|
|
11
|
+
```powershell
|
|
12
|
+
# 一次性运行(包名 dd-apitest,命令 apitest;Windows 需显式指定命令名)
|
|
13
|
+
uvx --from dd-apitest apitest --user-data init
|
|
14
|
+
uvx --from dd-apitest apitest --user-data
|
|
15
|
+
uvx --from dd-apitest apitest --user-data run --suite demo-ui --group readonly --env httpbin
|
|
16
|
+
|
|
17
|
+
# 内网 PyPI(示例)
|
|
18
|
+
# uvx --index-url https://pypi.your-company.com/simple/ --from dd-apitest apitest --user-data init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
持久安装(装好后直接用 `apitest`):
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
uv tool install dd-apitest
|
|
25
|
+
apitest --user-data init
|
|
26
|
+
apitest --user-data
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
发布流程见 [`docs/publishing-pypi.md`](./docs/publishing-pypi.md)。
|
|
30
|
+
|
|
31
|
+
### 方式 A′:`uvx --from` 本地或 Git(未上 PyPI 时)
|
|
32
|
+
|
|
33
|
+
```powershell
|
|
34
|
+
uvx --from "C:/Programs/dbapp-workspace/apitest-standalone" apitest --user-data init
|
|
35
|
+
uvx --from "C:/Programs/dbapp-workspace/apitest-standalone" apitest --user-data
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
说明:
|
|
39
|
+
|
|
40
|
+
- `uvx` 会临时安装到 uv 缓存,不污染当前目录;
|
|
41
|
+
- 包内已内置 suite 模板;`init --user-data` 写入 `~/.apitest/`;
|
|
42
|
+
- 已 `init` 且存在 `~/.apitest/suites` 时,可省略每次的 `--user-data`;
|
|
43
|
+
- 在**已 clone 的仓库根目录**内开发时,默认使用仓库下的 `suites/`。
|
|
44
|
+
|
|
45
|
+
### 方式 B:仓库内开发(仅 uv)
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
uv venv --python 3.12
|
|
49
|
+
.\.venv\Scripts\Activate.ps1
|
|
50
|
+
uv pip install -e .
|
|
51
|
+
uv pip install "pytest>=8.0.0" "responses>=0.25.0"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`.env` 示例(勿提交仓库):
|
|
55
|
+
|
|
56
|
+
```env
|
|
57
|
+
HENGNAO_APPKEY=your_key
|
|
58
|
+
HENGNAO_APPSECRET=your_secret
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## apitest(推荐)
|
|
62
|
+
|
|
63
|
+
JSON 配置驱动的 OpenAPI 联调框架。日常在仓库内用 `uv run apitest`;任意目录用 `uvx --from <源> apitest`(见上文方式 A)。
|
|
64
|
+
|
|
65
|
+
- **使用文档**:[`docs/2026-05-29-apitest-framework-usage.md`](./docs/2026-05-29-apitest-framework-usage.md)
|
|
66
|
+
- **设计文档**:[`docs/superpowers/specs/2026-05-29-apitest-framework-design.md`](./docs/superpowers/specs/2026-05-29-apitest-framework-design.md)
|
|
67
|
+
|
|
68
|
+
### 常用命令
|
|
69
|
+
|
|
70
|
+
```powershell
|
|
71
|
+
# 交互 TUI(Textual):方向键选组/用例,快捷键见下
|
|
72
|
+
uv run apitest
|
|
73
|
+
|
|
74
|
+
| 键 | 作用 |
|
|
75
|
+
|----|------|
|
|
76
|
+
| `g` | 跑当前组 |
|
|
77
|
+
| `c` | 跑当前用例 |
|
|
78
|
+
| `a` | 跑全部组 |
|
|
79
|
+
| `e` | **选择环境**(列表点选,Enter 确认) |
|
|
80
|
+
| `n` | 下一环境(顺序轮换) |
|
|
81
|
+
| `s` / `b` | **返回 Suite 选择**(多个 suite 时;Esc 取消并回到当前 suite) |
|
|
82
|
+
| `q` | 退出(也可点底部 Footer 的 Quit) |
|
|
83
|
+
|
|
84
|
+
日志区使用 RichLog 彩色输出;跑完用例后会输出报告路径与 `file:///.../report.html` 链接。
|
|
85
|
+
|
|
86
|
+
启动时若存在多个 suite(如 `demo-ui`、`xsiam-credential`),会先弹出 Suite 列表;进入主界面后按 **`s`** 可退回 Suite 选择页重新选择(Esc 取消)。
|
|
87
|
+
|
|
88
|
+
# 只读冒烟
|
|
89
|
+
uv run apitest run --suite xsiam-credential --group readonly --env dev
|
|
90
|
+
|
|
91
|
+
# 写组(含 create-then-delete flow)
|
|
92
|
+
uv run apitest run --suite xsiam-credential --group write --env dev
|
|
93
|
+
|
|
94
|
+
# 全量(所有 group)
|
|
95
|
+
uv run apitest run --suite xsiam-credential --all --env dev
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
报告目录:`reports/{suite}/{timestamp}/`(已 gitignore)。
|
|
99
|
+
|
|
100
|
+
报告文件:
|
|
101
|
+
- `report.html`:主报告(可直接浏览器打开)
|
|
102
|
+
- `summary.json`:机器可读摘要
|
|
103
|
+
- `index.json`:索引(含 `reportHtml` 指针)
|
|
104
|
+
- `failures/{caseId}.json`:失败或 `--verbose` 详情(脱敏)
|
|
105
|
+
- `summary.md`:仅兼容模式(`legacy_md=True`)生成
|
|
106
|
+
|
|
107
|
+
非 TUI/菜单模式会同时打印:
|
|
108
|
+
- `Report: <本地路径>`
|
|
109
|
+
- `Report URL: file:///.../report.html`(多数终端可点击)
|
|
110
|
+
|
|
111
|
+
可选 data root 切换:
|
|
112
|
+
|
|
113
|
+
```powershell
|
|
114
|
+
# 使用用户空间 ~/.apitest
|
|
115
|
+
uv run apitest --user-data
|
|
116
|
+
|
|
117
|
+
# 指定自定义 data root
|
|
118
|
+
uv run apitest --data-dir "D:/apitest-data" run --suite xsiam-credential --all --env dev
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
默认 data root 为仓库根目录(`suites/`、`plugins/`、`.env` 所在目录)。
|
|
122
|
+
|
|
123
|
+
### 新增接口(检查清单)
|
|
124
|
+
|
|
125
|
+
1. 在 `suites/xsiam-credential/payloads/` 增加请求体或 query JSON
|
|
126
|
+
2. 在 `suites/xsiam-credential/cases/` 增加 case(或 flow 步骤)
|
|
127
|
+
3. 在 `groups/readonly/group.json` 或 `groups/write/group.json` 引用该 case
|
|
128
|
+
4. `uv run pytest -q`
|
|
129
|
+
|
|
130
|
+
Agent 配置用例:`.cursor/skills/apitest-test-data/SKILL.md`(流程)+ `reference.md`(字段/示例)。
|
|
131
|
+
|
|
132
|
+
表单上传:`bodyType` 为 `form-data` / `form-urlencoded`,见设计文档 §3.4.2。
|
|
133
|
+
|
|
134
|
+
### 演示 Suite(看 TUI 交互)
|
|
135
|
+
|
|
136
|
+
无需内网与 `HENGNAO_*` 密钥,走公网 [httpbin.org](https://httpbin.org):
|
|
137
|
+
|
|
138
|
+
```powershell
|
|
139
|
+
uv run apitest
|
|
140
|
+
# 启动后若有两个 suite,选 demo-ui
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| 组 | 说明 |
|
|
144
|
+
|----|------|
|
|
145
|
+
| `readonly` | GET 连通、Query、2s 延迟 |
|
|
146
|
+
| `write` | POST JSON、form-urlencoded |
|
|
147
|
+
| `flows` | POST → extract → GET 变量传递 |
|
|
148
|
+
|
|
149
|
+
环境:`demo-ui` 含 `httpbin` / `httpbin-delay` / `playground` / `sandbox`。TUI 内按 **`e`** 弹出列表选择;按 **`n`** 可顺序切换到下一环境。
|
|
150
|
+
|
|
151
|
+
### 目录
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
core/ # 框架内核
|
|
155
|
+
suites/demo-ui/ # TUI 演示(httpbin)
|
|
156
|
+
suites/xsiam-credential/
|
|
157
|
+
suite.json
|
|
158
|
+
groups/readonly|write/group.json
|
|
159
|
+
cases/
|
|
160
|
+
payloads/
|
|
161
|
+
plugins/ # 项目级自定义 auth_*.py(可选)
|
|
162
|
+
reports/ # 执行产物
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 测试
|
|
166
|
+
|
|
167
|
+
```powershell
|
|
168
|
+
uv run pytest -q
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 安全说明
|
|
172
|
+
|
|
173
|
+
- 日志与报告会对 `accessToken`、`refreshToken`、`clientSecret`、`appSecret`、`sign` 脱敏。
|
|
174
|
+
- 勿将 `.env` 提交到仓库。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
from core.errors import ConfigError
|
|
10
|
+
from core.init_cmd import init_user_data
|
|
11
|
+
from core.loader import discover_suites, find_suite_by_name
|
|
12
|
+
from core.menu import run_menu
|
|
13
|
+
from core.paths import resolve_data_root, script_root, set_runtime_data_root, suites_template_source
|
|
14
|
+
from core.report import write_run_report
|
|
15
|
+
from core.runner import run_all_groups, run_group
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _file_uri(path: Path) -> str:
|
|
19
|
+
return path.resolve().as_uri()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _cmd_run(args: argparse.Namespace) -> int:
|
|
23
|
+
suite = find_suite_by_name(args.suite)
|
|
24
|
+
session_env = args.env or suite.default_env
|
|
25
|
+
if args.all:
|
|
26
|
+
result = run_all_groups(suite, session_env=session_env)
|
|
27
|
+
else:
|
|
28
|
+
if not args.group:
|
|
29
|
+
print("--group or --all is required", file=sys.stderr)
|
|
30
|
+
return 2
|
|
31
|
+
result = run_group(suite, args.group, session_env=session_env)
|
|
32
|
+
out = write_run_report(suite_name=suite.name, session_env=session_env, result=result)
|
|
33
|
+
report_html = (out / "report.html").resolve()
|
|
34
|
+
total = result.total
|
|
35
|
+
rate = (result.passed / total * 100) if total else 0
|
|
36
|
+
print(f"Summary: passed={result.passed} failed={result.failed} total={total} ({rate:.0f}%)")
|
|
37
|
+
print(f"Report: {report_html}")
|
|
38
|
+
print(f"Report URL: {_file_uri(report_html)}")
|
|
39
|
+
return 0 if result.failed == 0 else 1
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _cmd_init(args: argparse.Namespace) -> int:
|
|
43
|
+
source = suites_template_source()
|
|
44
|
+
target = resolve_data_root(user_data=args.user_data, data_dir=args.data_dir)
|
|
45
|
+
initialized = init_user_data(source_data_root=source, target_data_root=target, force=args.force)
|
|
46
|
+
print(f"Initialized data root: {initialized}")
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main(argv: list[str] | None = None) -> int:
|
|
51
|
+
parser = argparse.ArgumentParser(prog="apitest")
|
|
52
|
+
parser.add_argument("--user-data", action="store_true", help="Use ~/.apitest as data root")
|
|
53
|
+
parser.add_argument("--data-dir", help="Custom data root directory")
|
|
54
|
+
sub = parser.add_subparsers(dest="command")
|
|
55
|
+
|
|
56
|
+
run_parser = sub.add_parser("run", help="Run suite groups non-interactively")
|
|
57
|
+
run_parser.add_argument("--suite", required=True)
|
|
58
|
+
run_parser.add_argument("--group")
|
|
59
|
+
run_parser.add_argument("--all", action="store_true")
|
|
60
|
+
run_parser.add_argument("--env")
|
|
61
|
+
run_parser.set_defaults(func=_cmd_run)
|
|
62
|
+
|
|
63
|
+
sub.add_parser("menu", help="Interactive menu")
|
|
64
|
+
init_parser = sub.add_parser("init", help="Initialize data root with suites template")
|
|
65
|
+
init_parser.add_argument("--force", action="store_true")
|
|
66
|
+
init_parser.set_defaults(func=_cmd_init)
|
|
67
|
+
|
|
68
|
+
args = parser.parse_args(argv)
|
|
69
|
+
root = resolve_data_root(user_data=args.user_data, data_dir=args.data_dir)
|
|
70
|
+
set_runtime_data_root(root)
|
|
71
|
+
# Keep compatibility with current repos; user-space .env should override script/.env.
|
|
72
|
+
load_dotenv(script_root() / ".env")
|
|
73
|
+
load_dotenv(root / ".env", override=True)
|
|
74
|
+
|
|
75
|
+
if args.command == "run":
|
|
76
|
+
try:
|
|
77
|
+
return _cmd_run(args)
|
|
78
|
+
except ConfigError as exc:
|
|
79
|
+
print(exc, file=sys.stderr)
|
|
80
|
+
return 1
|
|
81
|
+
if args.command == "init":
|
|
82
|
+
try:
|
|
83
|
+
return _cmd_init(args)
|
|
84
|
+
except ConfigError as exc:
|
|
85
|
+
print(exc, file=sys.stderr)
|
|
86
|
+
return 1
|
|
87
|
+
|
|
88
|
+
if args.command == "menu" or (args.command is None and sys.stdin.isatty()):
|
|
89
|
+
return run_menu()
|
|
90
|
+
|
|
91
|
+
if args.command is None:
|
|
92
|
+
parser.print_help()
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
return 0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _json_subset(expected: Any, actual: Any) -> bool:
|
|
7
|
+
if isinstance(expected, dict):
|
|
8
|
+
if not isinstance(actual, dict):
|
|
9
|
+
return False
|
|
10
|
+
return all(k in actual and _json_subset(expected[k], actual[k]) for k in expected)
|
|
11
|
+
return expected == actual
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def check_success(
|
|
15
|
+
http_status: int,
|
|
16
|
+
body: Any,
|
|
17
|
+
rules: dict[str, Any],
|
|
18
|
+
) -> tuple[bool, list[str]]:
|
|
19
|
+
reasons: list[str] = []
|
|
20
|
+
expected_status = rules.get("httpStatus")
|
|
21
|
+
if expected_status is not None and http_status not in expected_status:
|
|
22
|
+
reasons.append(f"httpStatus expected {expected_status}, got {http_status}")
|
|
23
|
+
|
|
24
|
+
json_rule = rules.get("json")
|
|
25
|
+
if json_rule is not None:
|
|
26
|
+
if isinstance(body, dict):
|
|
27
|
+
if not _json_subset(json_rule, body):
|
|
28
|
+
reasons.append(f"json subset mismatch: expected {json_rule}, got {body}")
|
|
29
|
+
elif body is not None and not isinstance(body, dict):
|
|
30
|
+
pass
|
|
31
|
+
else:
|
|
32
|
+
reasons.append("json rule configured but response body is not a JSON object")
|
|
33
|
+
|
|
34
|
+
return (len(reasons) == 0, reasons)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Bundled suite templates (populated at wheel build time)."""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "post-then-get",
|
|
3
|
+
"type": "flow",
|
|
4
|
+
"name": "POST 写入变量再 GET",
|
|
5
|
+
"steps": [
|
|
6
|
+
{
|
|
7
|
+
"name": "post-seed",
|
|
8
|
+
"method": "POST",
|
|
9
|
+
"path": "/post",
|
|
10
|
+
"bodyFile": "payloads/post_seed.json",
|
|
11
|
+
"extract": { "echoToken": "$.json.echoToken" }
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "get-echo",
|
|
15
|
+
"method": "GET",
|
|
16
|
+
"path": "/get",
|
|
17
|
+
"query": {
|
|
18
|
+
"echoToken": "{{echoToken}}",
|
|
19
|
+
"tag": "{{demoTag}}"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "readonly",
|
|
3
|
+
"tags": ["read-only", "demo"],
|
|
4
|
+
"env": "httpbin",
|
|
5
|
+
"inheritSuccess": true,
|
|
6
|
+
"cases": [
|
|
7
|
+
{ "id": "get-basic", "file": "cases/get-basic.json" },
|
|
8
|
+
{ "id": "get-query", "file": "cases/get-query.json" },
|
|
9
|
+
{ "id": "get-delay", "file": "cases/get-delay.json" }
|
|
10
|
+
]
|
|
11
|
+
}
|