Files
吴让宇 c5905a5b1e refactor: 本仓升为唯一源(原 sap-cli 源码仓归档)
方向反转:此前 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 重写为单源开发流程。
2026-09-11 00:40:15 +08:00

242 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 头 |