方向反转:此前 SKILL.md 是「模板渲染产物」、sap-cli 是源;现 sap-cli 归档, sap-cli-skill 承接开发与分发,SKILL.md 回归手工维护的正本。 迁移(来自 sap-cli,共 104 文件): - tests/ 692 例测试(15 个文件的内联 sys.path 改指 assets/) - openspec/ SDD 规格与归档变更(42 文件) - docs/ 开发文档与 ADT 原理(含 dev/CLAUDE.md、AGENTS.md) - .claude/ rules 副本 + settings.json(供 Claude Code) - .github/ .hermes/ .pre-commit-config.yaml .editorconfig CLAUDE.md - scripts/ 保持仅 setup.py(pack_skill.py 已随旧仓归档,不迁) 修复(迁移暴露的真实缺陷): - assets/pyproject.toml 的 build-backend 写作 `setuptools.backends._legacy:_Backend`, 该模块在 setuptools 中不存在 → `pip install -e` 从来装不上。改为 build_meta。 实测:临时 venv 安装成功,sap-cli --help 正常列出 31 个命令 - pyproject readme 指向不存在的 assets/README.md(editable 安装会失败)→ 改内联文本 - pyproject urls 改指 sap-cli-skill 机制调整: - .github/workflows/ci.yml 适配 assets/ 布局;顶部注明该工作流仅 GitHub 执行, 本仓在 Gitee 不会自动跑 - pre-commit 增本地测试门禁(Gitee 上真正生效的那道) - .gitignore 合并旧仓完整规则(保留 log/ 下 md 知识库入库,只忽略运行日志) - 大文件上限 100KB→1MB(架构图 512KB) 守卫测试 tests/unit/test_repo_guards.py(10 → 18 例): - SKILL.md 须记录 parser 全部 CLI 命令 / 铁律 1-5 须为真实小节标题 / 示例不得违反铁律 5 - references/ 规则齐备;.claude/rules 与 references 必须一致(实测抓到一次真实漂移) - VERSION == sapcli.__version__ == README 版本 - 仓内不得再出现 pack_skill.py / skill-src(防废弃流程回潮) 698 tests OK;editable 安装与 CLI 入口经临时 venv 实测通过。 docs/RELEASING.md 重写为单源开发流程。
242 lines
13 KiB
Markdown
242 lines
13 KiB
Markdown
# ADT 工具集测试报告
|
||
|
||
> 测试目标:验证 `main.py` 的 create / info / download / sync / delete 五大功能在各种场景下的正确性。
|
||
>
|
||
> 测试日期:2026-05-20 | 测试脚本:[test_main.py](file:///d:/codeSpace/Gitee/Projects2026/sap-cli/test_main.py)
|
||
|
||
## 前置条件
|
||
|
||
- [ ] SAP 系统可连接(配置文件 `config.ini` 正确)
|
||
- [ ] Python 3.10+ 和 `requests` 库已安装
|
||
- [ ] 系统中存在至少一个 Modifiable 状态的传输请求
|
||
- [ ] 系统中存在以下已有对象用于查询测试:
|
||
- class: `ZCL_SAP_MCP_HANDLER`
|
||
- report: `ZIDTR_IMPORT_BOM`
|
||
- domain: `ZSAPILOT_OBJECT_TYPE`
|
||
- dataelement: `ZSAPILOT_OBJECT_NAME`
|
||
- table: `ZSAPILOT_OBJ`
|
||
|
||
---
|
||
|
||
## 测试覆盖矩阵
|
||
|
||
> 操作(行) × 对象类型(列) 的交叉覆盖情况。
|
||
> ✅ = 已测试通过 | ⬜ = 不适用 | ➖ = 未覆盖(系统限制)
|
||
|
||
| 操作 \ 对象类型 | report | class | function | functiongroup | interface | domain | dataelement | table | tabletype |
|
||
|:---------------|:------:|:-----:|:--------:|:-------------:|:---------:|:------:|:-----------:|:-----:|:---------:|
|
||
| **create** | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ SC3 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
|
||
| **info** | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ | ✅ SC4 | ✅ SC9 | ✅ SC9 | ✅ SC9| ✅ SC9 |
|
||
| **download** | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ SC13 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
|
||
| **sync** | ✅ SC1,SC5 | ✅ SC2 | ✅ SC3 | ✅ SC13 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
|
||
| **delete** | ✅ SC1 | ✅ SC2 | ✅ SC3 | ✅ SC3 | ✅ SC4 | 🔒 | 🔒 | 🔒 | ⬜ |
|
||
|
||
> **图例:**
|
||
> - ✅ 已测试通过
|
||
> - ⬜ 不适用(VIT 端点不支持源码读写和锁定操作)
|
||
> - 🔒 SAP 系统限制(当前目标系统 NW 7.40 的 ADT API 不支持 DDIC 对象的源码读写和创建/删除)
|
||
|
||
> **DDIC 对象(🔒)说明:**
|
||
> - `info` 可用(使用 `Accept: */*` 头),已在 SC9 验证
|
||
> - `create/download/sync/delete` 不可用 — 经实际探测,SAP NetWeaver 7.40 的 ADT 框架对 DDIC 对象(domain/dataelement/table)不提供源码读写端点(返回 404/406)
|
||
> - 在 SAP S/4HANA 或更高版本 NetWeaver 系统上,这些操作可能可用
|
||
> - 代码层面已实现完整支持(URI 映射、创建模板、argparse choices 均已配置),仅受目标系统 API 限制
|
||
|
||
> **tabletype(⬜)说明:**
|
||
> - `info` 已可用 — 通过 VIT 端点(`/sap/bc/adt/vit/wb/object_type/ttypda/object_name/{name}`)查询元数据,已在 SC9 验证
|
||
> - `download/sync/delete` 不可用 — VIT 端点为只读元数据接口,不提供源码读写和锁定操作
|
||
> - `create` 不可用 — 无可用的集合端点用于创建新对象
|
||
|
||
---
|
||
|
||
## 场景测试详情
|
||
|
||
### 场景组 happy — 正向完整流程
|
||
|
||
> 核心流程:创建 → 查询 → 下载 → 修改 → 同步激活 → 验证修改 → 删除 → 验证不存在
|
||
|
||
#### 场景 1: 正向完整流程 — report
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC1-1 | 创建 report | `create --name ZTEST_SC1_RPT_xxx --type report` | rc == 0 | ✅ |
|
||
| SC1-2 | 查询验证 | `info --name ZTEST_SC1_RPT_xxx --type report` | rc == 0 且名称匹配 | ✅ |
|
||
| SC1-3 | 下载源码 | `download --name ZTEST_SC1_RPT_xxx --type report` | rc == 0 且文件存在 | ✅ |
|
||
| SC1-4 | 修改后同步激活 | 修改源码 → `sync` | 语法检查通过 + 激活成功 | ✅ |
|
||
| SC1-5 | 验证修改持久化 | 再次 `download` | 源码包含修改标记 | ✅ |
|
||
| SC1-6 | 删除对象 | `delete --name ZTEST_SC1_RPT_xxx` | 删除成功 | ✅ |
|
||
| SC1-7 | 验证已不存在 | `info` | rc != 0 | ✅ |
|
||
|
||
#### 场景 2: 正向完整流程 — class
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC2-1 | 创建 class | `create --name ZTEST_SC2_CLS_xxx --type class` | rc == 0 | ✅ |
|
||
| SC2-2 | 查询验证 | `info --name ZTEST_SC2_CLS_xxx --type class` | rc == 0 且名称匹配 | ✅ |
|
||
| SC2-3 | 下载源码 | `download --name ZTEST_SC2_CLS_xxx --type class` | rc == 0 且文件存在 | ✅ |
|
||
| SC2-4 | 修改后同步激活 | 修改源码 → `sync` | 语法检查通过 + 激活成功 | ✅ |
|
||
| SC2-5 | 验证修改持久化 | 再次 `download` | 源码包含修改标记 | ✅ |
|
||
| SC2-6 | 删除对象 | `delete --name ZTEST_SC2_CLS_xxx` | 删除成功 | ✅ |
|
||
| SC2-7 | 验证已不存在 | `info` | rc != 0 | ✅ |
|
||
|
||
#### 场景 3: 正向完整流程 — function + functiongroup
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC3-1 | 创建函数组 | `create --name ZTEST_SC3_FG_xxx --type functiongroup` | rc == 0 | ✅ |
|
||
| SC3-2 | info 验证函数组存在 | `info --name ZTEST_SC3_FG_xxx --type functiongroup` | rc == 0 且名称匹配 | ✅ |
|
||
| SC3-3 | 创建函数 | `create --name ZTEST_SC3_FG_xxx/ZTEST_SC3_FM_xxx --type function` | rc == 0 | ✅ |
|
||
| SC3-4 | info 验证函数存在 | `info` | rc == 0 且名称匹配 | ✅ |
|
||
| SC3-5 | 下载函数源码 | `download` | rc == 0 且文件存在 | ✅ |
|
||
| SC3-6 | 首次修改 → 同步激活 | 修改源码 → `sync` | 语法检查通过 + 激活成功 | ✅ |
|
||
| SC3-7 | 再次下载验证修改持久化 | `download` | rc == 0 且文件包含修改标记 | ✅ |
|
||
| SC3-7b | 验证修改内容已持久化 | 读取下载文件 | 包含 "SC3 TEST MARKER" | ✅ |
|
||
| SC3-8 | 二次修改 → 同步激活 | 替换标记 → `sync` | 语法检查通过 + 激活成功 | ✅ |
|
||
| SC3-9 | info 验证函数仍存在 | `info --name ... --type function` | rc == 0 且名称匹配 | ✅ |
|
||
| SC3-10 | info 验证函数组仍存在 | `info --name ... --type functiongroup` | rc == 0 且名称匹配 | ✅ |
|
||
| SC3-11 | 删除函数 | `delete` 函数模块 | 删除成功 | ✅ |
|
||
| SC3-12 | info 验证函数组在函数删除后仍存在 | `info --name ... --type functiongroup` | rc == 0(函数组独立于函数) | ✅ |
|
||
| SC3-13 | 删除函数组 | `delete` 函数组 | 删除成功 | ✅ |
|
||
| SC3-14 | info 验证函数组已不存在 | `info` | rc != 0 | ✅ |
|
||
|
||
#### 场景 4: 正向完整流程 — interface
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC4-1 | 创建 interface | `create --name ZTEST_SC4_INT_xxx --type interface` | rc == 0 | ✅ |
|
||
| SC4-2 | 查询验证 | `info --name ZTEST_SC4_INT_xxx --type interface` | rc == 0 且名称匹配 | ✅ |
|
||
| SC4-3 | 下载源码 | `download --name ZTEST_SC4_INT_xxx --type interface` | rc == 0 且文件存在 | ✅ |
|
||
| SC4-4 | 修改后同步激活 | 修改源码 → `sync` | 语法检查通过 + 激活成功 | ✅ |
|
||
| SC4-5 | 验证修改持久化 | 再次 `download` | 源码包含修改标记 | ✅ |
|
||
| SC4-6 | 删除对象 | `delete --name ZTEST_SC4_INT_xxx` | 删除成功 | ✅ |
|
||
| SC4-7 | 验证已不存在 | `info` | rc != 0 | ✅ |
|
||
|
||
---
|
||
|
||
### 场景组 error — 逆向错误处理
|
||
|
||
#### 场景 5: 逆向 — 语法错误
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC5-1 | 创建 report | `create` | rc == 0 | ✅ |
|
||
| SC5-2 | 下载源码 | `download` | rc == 0 且文件存在 | ✅ |
|
||
| SC5-3 | 注入无效语法 → 同步 | 添加 `INVALID_SYNTAX_HERE.` → `sync` | rc != 0 且包含错误信息 | ✅ |
|
||
| SC5-4 | 恢复源码 → 重新同步 | 还原源码 → `sync` | rc == 0 + 激活成功 | ✅ |
|
||
| SC5-5 | 清理删除 | `delete` | 删除成功 | ✅ |
|
||
|
||
#### 场景 6: 逆向 — 对象不存在
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC6-1 | 查询不存在的 class | `info --name Z_NOT_EXIST_99999 --type class` | rc != 0 且包含"不存在" | ✅ |
|
||
| SC6-2 | 下载不存在的 report | `download --name Z_NOT_EXIST_99999` | rc != 0 | ✅ |
|
||
| SC6-3 | 删除不存在的 report | `delete --name Z_NOT_EXIST_99999` | rc != 0 | ✅ |
|
||
|
||
#### 场景 7: 逆向 — 重复创建
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC7-1 | 首次创建 | `create --name ZTEST_SC7_RPT_xxx` | rc == 0 | ✅ |
|
||
| SC7-2 | 重复创建 → 报错 | 再次 `create` 同名对象 | rc != 0 | ✅ |
|
||
| SC7-3 | 清理删除 | `delete` | 删除成功 | ✅ |
|
||
|
||
---
|
||
|
||
### 场景组 edge — 边界场景
|
||
|
||
#### 场景 8: 边界 — 同步自动创建
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC8-1 | 准备本地源码 | 创建 .abap 文件 | 文件存在 | ✅ |
|
||
| SC8-2 | sync 自动创建 | `sync` 不存在的对象 | rc == 0(自动创建) | ✅ |
|
||
| SC8-3 | info 验证存在 | `info` | rc == 0 且名称匹配 | ✅ |
|
||
| SC8-4 | 清理删除 | `delete` | 删除成功 | ✅ |
|
||
|
||
#### 场景 9: 边界 — DDIC 对象查询
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC9-1 | info domain | `info --name ZSAPILOT_OBJECT_TYPE --type domain` | rc == 0 且名称匹配 | ✅ |
|
||
| SC9-2 | info dataelement | `info --name ZSAPILOT_OBJECT_NAME --type dataelement` | rc == 0 | ✅ |
|
||
| SC9-3 | info table | `info --name ZSAPILOT_OBJ --type table` | rc == 0 | ✅ |
|
||
| SC9-4 | info tabletype | `info --name ZSAPILOT_OBJECT_TT --type tabletype` | rc == 0 且名称匹配 | ✅ |
|
||
|
||
#### 场景 10: 边界 — 删除安全确认
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC10-1 | 创建 report | `create` | rc == 0 | ✅ |
|
||
| SC10-2 | delete 输入 no → 取消 | `delete` stdin="no" | 输出包含"已取消" | ✅ |
|
||
| SC10-3 | info 验证仍存在 | `info` | rc == 0 且名称匹配 | ✅ |
|
||
| SC10-4 | delete 输入 yes → 成功 | `delete` stdin="yes" | 删除成功 | ✅ |
|
||
| SC10-5 | info 验证已不存在 | `info` | rc != 0 | ✅ |
|
||
|
||
#### 场景 13: 边界 — functiongroup 不支持源码操作
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC13-1 | download functiongroup → 报错 | `download --name ZIDT_MCP_TOOL --type functiongroup` | rc != 0 且包含"不支持" | ✅ |
|
||
| SC13-2 | sync functiongroup → 报错 | `sync --name ZIDT_MCP_TOOL --type functiongroup` | rc != 0 且包含"不支持" | ✅ |
|
||
|
||
---
|
||
|
||
### 场景组 existing — 已有对象操作
|
||
|
||
#### 场景 11: 已有对象查询
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC11-1 | info 已有 class | `info --name ZCL_SAP_MCP_HANDLER --type class` | rc == 0 且名称匹配 | ✅ |
|
||
| SC11-2 | 下载已有 class | `download --name ZCL_SAP_MCP_HANDLER` | rc == 0 且文件存在 | ✅ |
|
||
| SC11-3 | 同步无修改 → 成功 | `sync`(源码未修改) | 语法检查通过 + 激活成功 | ✅ |
|
||
|
||
---
|
||
|
||
### 场景组 config — 配置与环境
|
||
|
||
#### 场景 12: 配置与环境
|
||
|
||
| 编号 | 步骤 | 操作 | 验证条件 | 结果 |
|
||
|:----:|------|------|---------|:----:|
|
||
| SC12-1 | 默认配置运行 | `download`(使用 config.ini) | rc == 0 | ✅ |
|
||
| SC12-2 | 无效 SAP_HOST | 设置 `SAP_HOST=http://invalid:9999` | rc != 0 | ✅ |
|
||
|
||
---
|
||
|
||
## 测试统计
|
||
|
||
### 按场景组统计
|
||
|
||
| 场景组 | 场景数 | 步骤总数 | ✅ 通过 | ❌ 失败 | 通过率 |
|
||
|--------|:------:|:--------:|:-------:|:-------:|:------:|
|
||
| happy(正向完整流程) | 4 | 36 | 36 | 0 | 100% |
|
||
| error(逆向错误处理) | 3 | 11 | 11 | 0 | 100% |
|
||
| edge(边界场景) | 4 | 14 | 14 | 0 | 100% |
|
||
| existing(已有对象) | 1 | 3 | 3 | 0 | 100% |
|
||
| config(配置环境) | 1 | 2 | 2 | 0 | 100% |
|
||
| **合计** | **13** | **66** | **66** | **0** | **100%** |
|
||
|
||
### 失败项分析
|
||
|
||
本次测试全部通过,无失败项。
|
||
|
||
### 测试过程中发现并修复的问题
|
||
|
||
| # | 问题 | 修复内容 |
|
||
|---|------|---------|
|
||
| 1 | 下载文件含 `\r\r\n` 双重回车 | 保存前统一换行符 `source.replace("\r\n", "\n")` |
|
||
| 2 | `lock()` 未传 corrNr 导致写入冲突 | `lock()` 增加 `corr_nr` 参数 |
|
||
| 3 | `activate()` 误判 inactiveObjects 为成功 | 新增 Content-Type 检测,正确处理 `inactiveCtsObjects` 响应 |
|
||
| 4 | `activate()` 使用 `preauditRequested=true` 导致激活失败 | 移除该参数 |
|
||
| 5 | `_headers()` 硬编码 `Accept-Language: EN` | 移除硬编码,使用 SAP 系统登录语言 |
|
||
| 6 | `cmd_sync` 交互式等待用户输入 | 改为非交互式,报错直接退出 |
|
||
| 7 | `cmd_sync` 缺少语法检查步骤 | 新增 `syntax_check()` 方法,激活前先做语法检查 |
|
||
| 8 | `cmd_sync` 对象不存在时直接退出 | 改为自动创建空对象后继续同步流程 |
|
||
| 9 | `create` 缺少 interface 默认模板 | 新增 interface 模板到 `DEFAULT_TEMPLATES` |
|
||
| 10 | `parse_object_name` 对 functiongroup 崩溃(src_uri 为 None) | 增加 `if config["src_uri_template"] else None` |
|
||
| 11 | `cmd_info` 缺少名称校验 | 新增查询结果名称与请求名称的对比校验 |
|
||
| 12 | `cmd_info` function 名称校验失败 | 用 `split("/")[-1]` 取实际对象名进行校验 |
|
||
| 13 | DDIC 对象 info 查询 Accept 头不正确 | 改用 `*/*` Accept 头 |
|