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 重写为单源开发流程。
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# AGENTS.md — AI 代理协作指南
|
||||
|
||||
## 项目概述
|
||||
|
||||
sap-cli 是 Python CLI 工具,通过 SAP ADT REST API 管理 ABAP 开发对象。
|
||||
修改代码前请先阅读 `CLAUDE.md` 了解项目结构和开发规范。
|
||||
|
||||
## OpenSpec 规范驱动开发
|
||||
|
||||
本项目使用 OpenSpec 进行规范驱动开发。**修改任何功能前,必须先查阅对应的 spec 文件。**
|
||||
|
||||
### Spec 文件位置
|
||||
|
||||
所有系统行为的 source of truth 位于 `openspec/specs/` 下:
|
||||
|
||||
| 域 | 文件 | 内容 |
|
||||
|----|------|------|
|
||||
| 连接与认证 | `openspec/specs/connection/spec.md` | 登录、认证、会话管理 |
|
||||
| 对象生命周期 | `openspec/specs/object-lifecycle/spec.md` | 16 种对象类型的 CRUD |
|
||||
| 批量操作 | `openspec/specs/batch-ops/spec.md` | 初始化、批量同步、清单刷新 |
|
||||
| 搜索与浏览 | `openspec/specs/search-browse/spec.md` | 对象列表、where-used、源码搜索 |
|
||||
| 传输管理 | `openspec/specs/transport/spec.md` | 传输请求管理、传输对象列表 |
|
||||
| 代码质量 | `openspec/specs/quality/spec.md` | ATC 检查、代码格式化、Diff |
|
||||
| 配置管理 | `openspec/specs/config/spec.md` | 多系统配置、凭据存储、Profile |
|
||||
|
||||
### 变更流程
|
||||
|
||||
1. **查阅 spec**: 在 `openspec/specs/` 中找到相关的 domain spec
|
||||
2. **创建 change**: 在 `openspec/changes/<name>/` 下创建提案
|
||||
3. **编写 proposal.md**: 描述变更内容、影响范围
|
||||
4. **编写 design.md**: 技术设计,包含 ADT 端点详情
|
||||
5. **编写 tasks.md**: 任务列表,每个任务独立可测试
|
||||
6. **实现**: 按 tasks.md 逐步编写代码
|
||||
7. **更新 spec**: 完成后将变更合并回 specs/
|
||||
|
||||
### Spec 编写规则
|
||||
|
||||
- 使用 Given/When/Then 格式编写 Scenario
|
||||
- 每个 Requirement MUST 指定 ADT API 端点
|
||||
- 使用 RFC 2119 关键词: SHALL(必须)、MUST(强制)、SHOULD(推荐)
|
||||
- 中文编写说明文字,英文编写 Requirement 名称和 Scenario 关键词
|
||||
|
||||
## 代码修改指南
|
||||
|
||||
### 修改前检查清单
|
||||
|
||||
- [ ] 阅读了相关的 spec 文件
|
||||
- [ ] 理解了现有的代码结构
|
||||
- [ ] 已创建 change proposal(如果涉及功能变更)
|
||||
- [ ] 了解受影响的 ADT 端点
|
||||
|
||||
### 代码风格
|
||||
|
||||
- PEP 8 编码规范
|
||||
- 使用 type hints(`from __future__ import annotations`)
|
||||
- 使用 f-strings 格式化字符串
|
||||
- 函数和类使用 docstring
|
||||
- logging 使用 `logging.getLogger("sapcli.module")`
|
||||
|
||||
### 新增对象类型
|
||||
|
||||
如需添加新的 ABAP 对象类型支持:
|
||||
|
||||
1. 在 `sapcli/types.py` 中定义 `ObjectTypeConfig` 并 `_register()`
|
||||
2. 更新 `openspec/specs/core/spec.md` 的类型注册表
|
||||
3. 如有特殊的 URI 模板或创建逻辑,在 `sapcli/client.py` 中处理
|
||||
4. 在 `sapcli/scanner.py` 的 `DIRECTORY_TYPE_MAP` 中添加目录映射
|
||||
5. 编写测试用例
|
||||
|
||||
### 新增 CLI 命令
|
||||
|
||||
1. 在 `sapcli/cli/parser.py` 的 `build_parser()` 中添加 subparser
|
||||
2. 在 `sapcli/commands/` 下实现 `cmd_xxx()` 函数(或添加到现有模块)
|
||||
3. 在 `sapcli/commands/__init__.py` 中导出
|
||||
4. 在 `sapcli/cli/app.py` 的 `command_map` 中注册
|
||||
5. 更新相关 spec 文件
|
||||
6. 编写测试用例
|
||||
|
||||
### 错误处理
|
||||
|
||||
- 所有自定义异常继承自 `SapCliError`
|
||||
- 新增异常类型在 `sapcli/exceptions.py` 中定义
|
||||
- CLI 主循环捕获 `SapCliError`,打印友好信息后 exit(1)
|
||||
|
||||
## 测试
|
||||
|
||||
```bash
|
||||
python -m pytest tests/test_sapcli.py -v
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- SAP ADT REST API 使用 HTTP Basic Auth + CSRF Token
|
||||
- 大部分 ADT 端点需要 stateful 会话(锁定/写入时)
|
||||
- DDIC 对象通过 XML body 创建(非纯文本)
|
||||
- function 类型名称格式: `组名/模块名`
|
||||
- tabletype 使用 VIT 端点,需要大写名称
|
||||
@@ -0,0 +1,180 @@
|
||||
# sap-cli — AI 协作指南
|
||||
|
||||
> [!danger] 核心约束:文档优先,禁止擅自读取源代码
|
||||
> 本项目提供了完整的技术文档体系,AI 在协作时**必须优先阅读文档**获取所需信息,**严禁未经许可直接读取 Python 源代码文件**(`sapcli/` 包内的 `.py` 文件、`main.py`、`test/` 目录)。
|
||||
>
|
||||
> 如果发现文档描述与实际行为不一致,**必须先向用户说明差异并征得同意**,才能读取源代码进行校验。
|
||||
|
||||
## 项目简介
|
||||
|
||||
sap-cli 是一个命令行工具,通过 SAP ADT REST API 实现 ABAP 开发对象的远程管理(创建、同步、下载、查询、删除),让开发者可以在本地编辑器中编写 ABAP 代码,一行命令同步到 SAP 系统。
|
||||
|
||||
## 文档体系(AI 必读)
|
||||
|
||||
> [!important] 文档覆盖了项目的全部设计细节,AI 应按需查阅以下文档,而非阅读源代码。
|
||||
|
||||
| 文档 | 用途 | 文件路径 |
|
||||
| 文档 | 说明 | 路径 |
|
||||
|------|------|------|
|
||||
| 使用指南 | 命令详解、参数说明、输出示例 | `docs/guide.md` |
|
||||
| 技术说明 | 架构、模块职责、通信流程、异常体系 | `docs/dev/architecture.md` |
|
||||
| 批量同步设计 | manifest 清单、拓扑排序、批量 sync | `docs/dev/batch-design.md` |
|
||||
| 测试报告 | 测试场景覆盖 | `docs/dev/test-report.md` |
|
||||
| ADT 学习笔记总览 | ADT 原理系列索引 | `docs/adt/README.md` |
|
||||
|
||||
### 按需求查阅
|
||||
|
||||
| 需求 | 文档 |
|
||||
|------|------|
|
||||
| 了解某个命令怎么用 | `docs/guide.md` |
|
||||
| 了解模块接口 / API 调用链 | `docs/dev/architecture.md` |
|
||||
| 了解批量同步 / manifest | `docs/dev/batch-design.md` |
|
||||
| 了解 ADT REST API 原理 | `docs/adt/` 目录下的系列笔记 |
|
||||
| 了解支持哪些对象类型 | `README.md` 功能矩阵 |
|
||||
|
||||
## AI 行为规范
|
||||
|
||||
### 1. 文档优先原则
|
||||
|
||||
- **所有问题先查文档** — 项目文档完整覆盖了架构、命令、API、模块接口、异常体系等全部信息
|
||||
- **文档即真相** — 以文档描述为准进行操作,不需要通过读代码"验证"
|
||||
- **引用文档回答** — 回答用户问题时引用具体文档路径,方便用户追溯
|
||||
|
||||
### 2. 源代码访问限制
|
||||
|
||||
> [!warning] 以下文件属于源代码,未经用户许可**禁止读取**:
|
||||
> - `main.py` — CLI 入口
|
||||
> - `sapcli/*.py` — 核心包(config / types / client / commands / exceptions / manifest / scanner / sorter)
|
||||
> - `test/**/*.py` — 测试套件
|
||||
|
||||
**允许读取的文件:**
|
||||
- `*.md` — 所有文档文件
|
||||
- `*.abap` — ABAP 源代码文件(这是用户要同步到 SAP 的业务代码)
|
||||
- `config.ini` — 连接配置(注意不要泄露密码)
|
||||
- `manifest.json` — 项目清单文件
|
||||
- `*.drawio` / `*.png` — 架构图等资源文件
|
||||
|
||||
### 3. 源代码校验流程
|
||||
|
||||
当文档描述与实际执行效果不一致时:
|
||||
|
||||
```
|
||||
1. 向用户报告:"文档描述 XXX,但实际表现为 YYY,差异点为 ZZZ"
|
||||
2. 说明需要读取哪些源文件、读取目的是什么
|
||||
3. 等待用户明确同意后,方可读取源代码
|
||||
4. 读取后只关注差异点,不要全量阅读无关代码
|
||||
```
|
||||
|
||||
### 4. 典型操作流程
|
||||
|
||||
#### 帮助用户同步代码到 SAP
|
||||
|
||||
```
|
||||
1. 确认用户的 ABAP 文件路径和对象信息(名称、类型)
|
||||
2. 执行: python main.py sync --name <名称> --type <类型> --path <文件路径>
|
||||
3. 如果成功 → 告知用户
|
||||
4. 如果语法检查失败 → 展示错误信息,提示用户修改后重试
|
||||
5. 如果激活失败 → 展示错误信息,提示用户修改后重试
|
||||
```
|
||||
|
||||
#### 帮助用户创建新对象
|
||||
|
||||
```
|
||||
1. 确认对象名称、类型、描述
|
||||
2. 执行: python main.py create --name <名称> --type <类型> --description "<描述>"
|
||||
3. 创建成功后提示用户下载模板或直接编辑
|
||||
```
|
||||
|
||||
#### 帮助用户批量同步
|
||||
|
||||
```
|
||||
1. 确认项目根目录路径
|
||||
2. 如果没有 manifest.json,先执行: python main.py init --path <项目目录>
|
||||
3. 预览执行计划: python main.py sync --all --path <项目目录> --dry-run
|
||||
4. 确认后执行(推荐指定 --corr_nr 避免交互中断):
|
||||
python main.py sync --all --path <项目目录> --corr_nr <传输请求号>
|
||||
5. 如果不指定 --corr_nr,会在启动时弹出一次传输请求选择,后续自动复用
|
||||
```
|
||||
|
||||
## 命令速查
|
||||
|
||||
```bash
|
||||
# 单对象操作
|
||||
python main.py create --name <名称> --type <类型> [--description <描述>] [--corr_nr <请求号>]
|
||||
python main.py info --name <名称> --type <类型>
|
||||
python main.py download --name <名称> --type <类型> --path <保存目录>
|
||||
python main.py sync --name <名称> --type <类型> --path <.abap文件> [--corr_nr <请求号>]
|
||||
python main.py delete --name <名称> --type <类型>
|
||||
|
||||
# 批量操作(基于 manifest 清单)
|
||||
python main.py init --path <项目目录>
|
||||
python main.py sync --all --path <项目目录> [--dry-run] [--fail-fast]
|
||||
python main.py refresh --path <项目目录>
|
||||
|
||||
# 搜索与浏览
|
||||
python main.py list --type <类型> [--package <包名>] [--prefix <前缀>]
|
||||
python main.py whereused --name <名称> --type <类型>
|
||||
python main.py search --query <关键词> [--type <类型>]
|
||||
|
||||
# 传输管理
|
||||
python main.py transport list
|
||||
python main.py transport info --corr_nr <请求号>
|
||||
python main.py transport release --corr_nr <请求号>
|
||||
python main.py transport objects --corr_nr <请求号>
|
||||
|
||||
# 代码质量
|
||||
python main.py check --name <名称> --type <类型>
|
||||
python main.py format --name <名称> --type <类型>
|
||||
python main.py diff --name <名称> --type <类型> [--path <本地文件>]
|
||||
|
||||
# 包管理
|
||||
python main.py package create --name <包名> [--description <描述>]
|
||||
python main.py package info --name <包名>
|
||||
python main.py package list --name <包名>
|
||||
|
||||
# CDS View
|
||||
python main.py cds download --name <CDS名> --path <目录>
|
||||
python main.py cds sync --name <CDS名> --path <DDL文件>
|
||||
python main.py cds create --name <CDS名> [--description <描述>]
|
||||
|
||||
# 辅助工具
|
||||
python main.py analyze --path <项目目录>
|
||||
python main.py scaffold --name <名称> --template <模板> [--package <包名>]
|
||||
python main.py config show
|
||||
python main.py config list-profiles
|
||||
```
|
||||
|
||||
## 对象类型速查
|
||||
|
||||
| 类型参数 | 说明 | 名称格式 |
|
||||
|---------|------|---------|
|
||||
| `report` | 程序/报表 | 直接使用程序名 |
|
||||
| `class` | ABAP 类 | 直接使用类名 |
|
||||
| `interface` | ABAP 接口 | 直接使用接口名 |
|
||||
| `function` | 函数模块 | `函数组名/函数模块名` |
|
||||
| `functiongroup` | 函数组 | 直接使用函数组名 |
|
||||
| `include` | Include 程序 | 直接使用程序名 |
|
||||
| `domain` | 域 | 直接使用域名 |
|
||||
| `dataelement` | 数据元素 | 直接使用元素名 |
|
||||
| `table` | 透明表 | 直接使用表名 |
|
||||
| `structure` | 结构 | 直接使用结构名 |
|
||||
| `tabletype` | 表类型 | 直接使用类型名 |
|
||||
| `cdsview` | CDS View | 直接使用 CDS 名 |
|
||||
| `messageclass` | 消息类 | 直接使用消息类名 |
|
||||
| `view` | 数据库视图 | 直接使用视图名 |
|
||||
| `searchhelp` | 搜索帮助 | 直接使用搜索帮助名 |
|
||||
| `lockobject` | 锁对象 | 直接使用锁对象名 |
|
||||
|
||||
## 依赖排序优先级(批量同步)
|
||||
|
||||
```
|
||||
domain(10) → dataelement(20) → structure(25) → table(30) → tabletype(40) → view(42) → lockobject(44) → searchhelp(46) → messageclass(48) → interface(50) → class(60) → function(70) → functiongroup(75) → include(78) → cdsview(79) → report(80)
|
||||
```
|
||||
|
||||
## 关键技术要点
|
||||
|
||||
- **认证**: HTTP Basic Auth + CSRF Token
|
||||
- **sync 流程**: 检查/创建 → 锁定 → 写入 → 解锁 → 语法检查 → 激活
|
||||
- **锁定机制**: stateful session,自动检测传输请求绑定
|
||||
- **配置加载优先级**: 环境变量 > --config 指定文件 > 工作目录 config.ini > 脚本目录 config.ini
|
||||
- **唯一第三方依赖**: `requests`
|
||||
@@ -0,0 +1,51 @@
|
||||
# ADT 对象类型码映射(来源:abap-adt-api objectcreator.ts)
|
||||
|
||||
## 已确认的 ADT 可创建类型
|
||||
|
||||
| TypeId | 对象类型 | ADT 端点路径 |
|
||||
|--------|---------|-------------|
|
||||
| CLAS/OC | Class | /oo/classes |
|
||||
| INTF/OI | Interface | /oo/interfaces |
|
||||
| PROG/P | Program/Report | /programs/programs |
|
||||
| PROG/I | Include | /programs/includes |
|
||||
| FUGR/F | Function Module | /functions/groups/{grp}/fmodules |
|
||||
| FUGR/FF | Function Group | /functions/groups |
|
||||
| DOMA/DD | Domain | /ddic/domains |
|
||||
| DTEL/DE | Data Element | /ddic/dataelements |
|
||||
| TABL/DT | Table | /ddic/tables |
|
||||
| TABL/DS | Structure | /ddic/structures |
|
||||
| MSAG/N | Message Class | /oo/t100/messages/classes |
|
||||
| DDLS/DF | CDS View (DDL Source) | /ddic/ddlsources |
|
||||
| DCLS/DL | CDS Access Control (DCL) | /ddic/dclsources |
|
||||
| DDLX/EX | Metadata Extension | /ddic/metadataextensions |
|
||||
| DDLA/ADF | Abstract Entity | /ddic/ddlabstractentities (推测) |
|
||||
| SRVD/SRV | Service Definition | /ddic/servicedefinitions (推测) |
|
||||
| AUTH | Authorization Object | (需验证) |
|
||||
| SUSO/B | Auth Check Object | (需验证) |
|
||||
|
||||
## 确认不在 ADT 标准创建列表中的类型
|
||||
|
||||
| 类型 | 原因 | 替代方案 |
|
||||
|------|------|---------|
|
||||
| Type Group (TYPE-POOL) | ADT 可能归入 programs | 通过 /programs/typegroups 尝试 |
|
||||
| Number Range Object | 无 ADT 创建端点 | 需 RFC(NUMBER_RANGE_GET_INFO) |
|
||||
| Enhancement Spot | 使用专用 /enhancements 端点 | 需验证 |
|
||||
| Behavior Definition (BDEF) | RAP 专用,S/4HANA Cloud 才有 | NW 7.40 可能不支持 |
|
||||
| SICF Service | 使用 /discovery/services | 需验证 |
|
||||
| Table Type | 通过 VIT 端点 | 已有部分支持 |
|
||||
|
||||
## Phase 2 调整建议
|
||||
|
||||
### 可直接实现(ADT 有标准端点)
|
||||
- ✅ DCL (CDS Access Control) — /ddic/dclsources,类似 CDS View
|
||||
- ✅ DDLX (Metadata Extension) — /ddic/metadataextensions,类似 CDS View
|
||||
- ✅ DDLA (Abstract Entity) — 类似 CDS View
|
||||
|
||||
### 需要验证端点后决定
|
||||
- ⚠️ Type Group — 可能在 /programs/typegroups
|
||||
- ⚠️ Enhancement Spot — 可能在 /enhancements
|
||||
- ⚠️ SICF Service — 可能通过 /discovery/services
|
||||
|
||||
### NW 7.40 不支持(需移到远期)
|
||||
- ❌ Behavior Definition (BDEF) — RAP 专用,NW 7.40 无此功能
|
||||
- ❌ Number Range Object — 无 ADT 端点,需 RFC
|
||||
@@ -0,0 +1,379 @@
|
||||
# sap-cli — 技术说明
|
||||
|
||||
## 目录
|
||||
|
||||
- [项目定位](#项目定位)
|
||||
- [架构概览](#架构概览)
|
||||
- [包结构](#包结构)
|
||||
- [模块职责](#模块职责)
|
||||
- [通信流程](#通信流程)
|
||||
- [对象类型映射](#对象类型映射)
|
||||
- [异常体系](#异常体系)
|
||||
- [配置加载策略](#配置加载策略)
|
||||
- [日志机制](#日志机制)
|
||||
- [错误处理策略](#错误处理策略)
|
||||
- [测试架构](#测试架构)
|
||||
- [依赖关系](#依赖关系)
|
||||
|
||||
---
|
||||
|
||||
## 项目定位
|
||||
|
||||
sap-cli 是一个轻量级命令行工具,通过 SAP ADT (ABAP Development Tools) REST API 与 SAP NetWeaver 系统交互,实现 ABAP 开发对象的远程管理(创建、同步、下载、查询、删除)。
|
||||
|
||||
**核心设计原则:**
|
||||
|
||||
- 零 SAP 专有客户端依赖,仅基于标准 HTTP 协议
|
||||
- 唯一第三方依赖为 `requests`
|
||||
- 所有通信均通过 ADT REST API 完成,数据格式为 XML / Plain Text
|
||||
|
||||
---
|
||||
|
||||
## 架构概览
|
||||
|
||||
![[sap-cli/doc/assets/architecture.drawio.png]]
|
||||
|
||||
架构分为三层:
|
||||
|
||||
| 层级 | 组件 | 说明 |
|
||||
|------|------|------|
|
||||
| **用户终端** | `main.py` | CLI 入口,argparse 参数解析与命令分发 |
|
||||
| **核心包** | `sapcli/` | 5 个模块:config / types / exceptions / client / commands |
|
||||
| **外部系统** | SAP NetWeaver | ADT REST API 端点(Programs / OO Objects / DDIC / CTS) |
|
||||
|
||||
左侧外部文件通过虚线连接至核心包:
|
||||
|
||||
- `config.ini` — 连接配置(读取)
|
||||
- `.abap 文件` — 本地源代码(读写)
|
||||
- `log/` — 运行日志(写入)
|
||||
|
||||
核心通信链路:`client.py (ADTClient)` 通过 HTTP/REST(Basic Auth + CSRF Token)与 SAP 系统交互。
|
||||
|
||||
## 包结构
|
||||
|
||||
```
|
||||
sap-cli/
|
||||
├── main.py # CLI 入口(argparse + 命令分发)
|
||||
├── config.ini # 默认连接配置文件
|
||||
├── sapcli/ # 核心功能包
|
||||
│ ├── __init__.py
|
||||
│ ├── config.py # 配置加载(INI / 环境变量)
|
||||
│ ├── types.py # 对象类型定义与名称解析
|
||||
│ ├── client.py # ADT REST API 客户端
|
||||
│ ├── commands.py # 5 个业务命令的实现
|
||||
│ └── exceptions.py # 自定义异常层级
|
||||
├── test/
|
||||
│ └── script/
|
||||
│ └── test_main.py # 集成测试套件(子进程调用)
|
||||
├── doc/ # 文档
|
||||
│ ├── ADT/ # ADT 技术原理系列
|
||||
│ ├── 技术说明.md # 本文档
|
||||
│ ├── sap-cli使用指南.md # 用户操作指南
|
||||
│ └── 测试报告.md # 测试覆盖报告
|
||||
└── log/ # 运行时日志目录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块职责
|
||||
|
||||
### main.py — CLI 入口
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| 参数定义 | 定义 5 个子命令(download / sync / delete / info / create)及其参数 |
|
||||
| 配置加载 | 调用 `sapcli.config.load_config()` 获取连接参数 |
|
||||
| 客户端初始化 | 创建 `ADTClient` 实例并执行登录(获取 CSRF Token) |
|
||||
| 命令分发 | 根据子命令名称映射到对应的 `cmd_*()` 函数 |
|
||||
|
||||
### sapcli/config.py — 配置管理
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| `SAPConfig` | 连接参数数据类(host / client / user / password) |
|
||||
| `load_config()` | 按优先级链加载配置:环境变量 → 指定文件 → 工作目录 → 脚本目录 |
|
||||
|
||||
### sapcli/types.py — 对象类型系统
|
||||
|
||||
| 组件 | 职责 |
|
||||
|------|------|
|
||||
| `ObjectTypeConfig` | 单个 ABAP 对象类型的 URI 模板与元数据(数据类) |
|
||||
| `OBJECT_TYPE_CONFIG` | 9 种 ABAP 对象类型的注册表 |
|
||||
| `ParsedName` | 解析后的对象名组件(数据类) |
|
||||
| `parse_object_name()` | 将用户输入解析为 ADT URI 所需的路径片段 |
|
||||
| `get_type_config()` | 按类型键查询对象类型配置 |
|
||||
| `all_type_keys()` | 返回所有受支持的对象类型列表 |
|
||||
|
||||
### sapcli/client.py — ADT 通信层
|
||||
|
||||
| 方法 | 职责 |
|
||||
|------|------|
|
||||
| `login()` | Basic Auth 认证 + 获取 CSRF Token |
|
||||
| `object_exists()` | 检查对象是否存在于 SAP 系统 |
|
||||
| `get_source()` | 下载对象源代码 |
|
||||
| `set_source()` | 上传(写入)对象源代码 |
|
||||
| `lock()` / `unlock()` | 对象锁定 / 解锁(stateful session) |
|
||||
| `get_transport_request()` | 获取可用的传输请求号 |
|
||||
| `activate()` | 激活对象 |
|
||||
| `syntax_check()` | 语法检查 |
|
||||
| `create_object()` | 创建新对象(支持 9 种类型) |
|
||||
| `delete_object()` | 删除对象 |
|
||||
| `function_group_exists()` | 检查函数组是否存在 |
|
||||
| `create_function_group()` | 创建函数组容器 |
|
||||
|
||||
### sapcli/commands.py — 命令处理
|
||||
|
||||
| 函数 | 职责 |
|
||||
|------|------|
|
||||
| `cmd_download()` | 下载对象源码到本地 `.abap` 文件 |
|
||||
| `cmd_sync()` | 同步本地源码到 SAP(含自动创建、锁定、写入、检查、激活) |
|
||||
| `cmd_info()` | 查询并展示对象元数据 |
|
||||
| `cmd_delete()` | 从 SAP 系统删除对象(需用户确认) |
|
||||
| `cmd_create()` | 在 SAP 系统创建新对象(支持模板预填充) |
|
||||
|
||||
### sapcli/exceptions.py — 异常层级
|
||||
|
||||
| 异常类 | 触发场景 |
|
||||
|--------|---------|
|
||||
| `SapCliError` | 所有自定义异常的基类 |
|
||||
| `ConfigError` | 配置文件缺失或格式错误 |
|
||||
| `LoginError` | SAP 登录失败(认证错误或网络不通) |
|
||||
| `ObjectNotFoundError` | 请求的对象在 SAP 系统中不存在 |
|
||||
| `ObjectAlreadyExistsError` | 创建时对象已存在 |
|
||||
| `LockError` | 对象锁定冲突 |
|
||||
| `ActivationError` | 对象激活失败 |
|
||||
| `SyntaxCheckError` | 语法检查未通过 |
|
||||
| `DeleteError` | 删除操作失败 |
|
||||
| `CreateError` | 创建操作失败 |
|
||||
| `InvalidNameError` | 对象名称格式不合法 |
|
||||
|
||||
---
|
||||
|
||||
## 通信流程
|
||||
|
||||
### 认证机制
|
||||
|
||||
采用 HTTP Basic Auth + CSRF Token 双重验证:
|
||||
|
||||
```
|
||||
┌────────┐ ┌────────┐
|
||||
│ Client │ │ SAP │
|
||||
└───┬────┘ └───┬────┘
|
||||
│ GET /sap/bc/adt/compatibility/graph │
|
||||
│ Header: x-csrf-token: fetch │
|
||||
│ Header: Authorization: Basic *** │
|
||||
│ ──────────────────────────────────────────► │
|
||||
│ │
|
||||
│ 200 OK │
|
||||
│ Header: x-csrf-token: <TOKEN> │
|
||||
│ ◄────────────────────────────────────────── │
|
||||
│ │
|
||||
│ 后续所有请求携带 CSRF Token │
|
||||
│ (POST / PUT / DELETE 必须携带) │
|
||||
│ ──────────────────────────────────────────► │
|
||||
```
|
||||
|
||||
### sync 命令 — 完整 API 调用链
|
||||
|
||||
`sync` 是最核心的命令,执行以下完整流程:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Step 1: 登录获取 CSRF Token │
|
||||
│ GET /sap/bc/adt/compatibility/graph │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Step 2: 检查对象存在性(不存在则自动创建) │
|
||||
│ GET /sap/bc/adt/programs/programs/{name} │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Step 3: 获取传输请求号 │
|
||||
│ GET /sap/bc/adt/cts/transportrequests │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Step 4: 锁定对象(stateful session) │
|
||||
│ POST /sap/bc/adt/programs/programs/{name} │
|
||||
│ ?_action=LOCK&accessMode=MODIFY │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Step 5: 写入源代码 │
|
||||
│ PUT /sap/bc/adt/programs/programs/{name}/source/main │
|
||||
│ ?lockHandle={handle} │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Step 6: 解锁对象 │
|
||||
│ POST /sap/bc/adt/programs/programs/{name} │
|
||||
│ ?_action=UNLOCK&lockHandle={handle} │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Step 7: 语法检查(可选) │
|
||||
│ POST /sap/bc/adt/activation?method=check │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Step 8: 激活对象 │
|
||||
│ POST /sap/bc/adt/activation?method=activate │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 锁定与传输请求 — 容错机制
|
||||
|
||||
工具内置了两层自动重试策略:
|
||||
|
||||
1. **锁定重试** — 若对象已在其他传输请求中被锁定,从错误响应中提取已有的传输请求号,使用该传输号重新锁定
|
||||
2. **写入重试** — 若写入源码时因传输号不匹配而失败,自动使用正确的传输号重试
|
||||
|
||||
### Session 管理
|
||||
|
||||
| 操作类型 | Session 模式 | 说明 |
|
||||
|---------|-------------|------|
|
||||
| 锁定 / 写入 / 解锁 | **stateful** | 通过 `X-sap-adt-sessiontype: stateful` 头维持会话状态 |
|
||||
| 读取 / 激活 | **stateless** | 无状态请求,独立响应 |
|
||||
| 解锁后 | 自动切换回 **stateless** | 确保会话资源释放 |
|
||||
|
||||
---
|
||||
|
||||
## 对象类型映射
|
||||
|
||||
每种 ABAP 对象类型对应不同的 ADT URI 模板:
|
||||
|
||||
| 类型 | 对象 URI | 源码 URI | 备注 |
|
||||
|------|---------|---------|------|
|
||||
| `report` | `/sap/bc/adt/programs/programs/{name}` | `.../source/main` | ABAP 报表程序 |
|
||||
| `class` | `/sap/bc/adt/oo/classes/{name}` | `.../source/main` | ABAP 类 |
|
||||
| `interface` | `/sap/bc/adt/oo/interfaces/{name}` | `.../source/main` | ABAP 接口 |
|
||||
| `function` | `/sap/bc/adt/functions/groups/{group}/fmodules/{name}` | `.../source/main` | 需 `函数组/函数名` 格式 |
|
||||
| `functiongroup` | `/sap/bc/adt/functions/groups/{name}` | 无源码 | 仅容器,不含源码 |
|
||||
| `domain` | `/sap/bc/adt/ddic/domains/{name}` | `.../source/main` | ABAP 域 |
|
||||
| `dataelement` | `/sap/bc/adt/ddic/dataelements/{name}` | `.../source/main` | ABAP 数据元素 |
|
||||
| `table` | `/sap/bc/adt/ddic/tables/{name}` | `.../source/main` | ABAP 透明表 |
|
||||
| `tabletype` | `/sap/bc/adt/vit/wb/object_type/ttypda/object_name/{name}` | 无源码 (VIT 端点) | ABAP 表类型,仅支持元数据查询 |
|
||||
|
||||
> [!note] function 类型的特殊性
|
||||
> `function` 类型需要 `函数组名/函数名` 格式输入(如 `ZGROUP/Z_MY_FUNC`),URI 中包含两层路径参数(`{group}` 和 `{name}`)。
|
||||
|
||||
---
|
||||
|
||||
## 配置加载策略
|
||||
|
||||
配置按以下优先级链加载,高优先级源覆盖低优先级源:
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ 环境变量 (SAP_HOST 等) │ ← 最高优先级
|
||||
└──────────────┬───────────────┘
|
||||
│ 覆盖
|
||||
┌──────────────▼───────────────┐
|
||||
│ --config 指定的 INI 文件 │
|
||||
└──────────────┬───────────────┘
|
||||
│ 回退
|
||||
┌──────────────▼───────────────┐
|
||||
│ 当前工作目录 / config.ini │
|
||||
└──────────────┬───────────────┘
|
||||
│ 回退
|
||||
┌──────────────▼───────────────┐
|
||||
│ 脚本同目录 / config.ini │ ← 最低优先级
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
支持的环境变量映射:
|
||||
|
||||
| INI 字段 | 环境变量 |
|
||||
|---------|---------|
|
||||
| `host` | `SAP_HOST` |
|
||||
| `client` | `SAP_CLIENT` |
|
||||
| `user` | `SAP_USER` |
|
||||
| `password` | `SAP_PASSWORD` |
|
||||
|
||||
---
|
||||
|
||||
## 日志机制
|
||||
|
||||
| 属性 | 值 |
|
||||
|------|-----|
|
||||
| 日志路径 | `log/adt_tools.log` |
|
||||
| 日志级别 | `DEBUG`(记录所有 HTTP 请求/响应详情) |
|
||||
| 日志格式 | `时间 │ 级别 │ 消息` |
|
||||
| 写入模式 | 追加写入(append) |
|
||||
|
||||
日志记录每次 API 调用的完整上下文:请求 URL、请求参数、响应状态码、响应体摘要,便于生产环境问题排查。
|
||||
|
||||
---
|
||||
|
||||
## 错误处理策略
|
||||
|
||||
### 按场景分类
|
||||
|
||||
| 场景 | 处理方式 | 对应异常 |
|
||||
|------|---------|---------|
|
||||
| 登录失败 | 终止并报错 | `LoginError` |
|
||||
| 对象不存在(download / info / delete) | 提示不存在并退出 | `ObjectNotFoundError` |
|
||||
| 对象不存在(sync) | 自动创建空对象后继续 | — |
|
||||
| 对象已存在(create) | 提示已存在并退出 | `ObjectAlreadyExistsError` |
|
||||
| 语法检查失败 | 显示错误详情,不执行激活 | `SyntaxCheckError` |
|
||||
| 激活失败 | 显示错误/警告详情并退出 | `ActivationError` |
|
||||
| 锁定冲突 | 自动提取已有传输号重试 | `LockError` |
|
||||
| 写入冲突 | 自动使用正确传输号重试 | — |
|
||||
| 删除操作 | 需用户输入 `yes` 确认 | — |
|
||||
|
||||
### 错误处理设计原则
|
||||
|
||||
1. **快速失败** — 非预期错误立即终止,避免静默失败
|
||||
2. **自动恢复** — 锁定冲突和写入冲突具备自动重试能力
|
||||
3. **信息透明** — 所有错误均输出详细信息,日志记录完整上下文
|
||||
|
||||
---
|
||||
|
||||
## 测试架构
|
||||
|
||||
测试套件位于 `test/script/test_main.py`,通过子进程调用 `main.py`,确保端到端验证。
|
||||
|
||||
### 测试设计
|
||||
|
||||
- **独立性** — 每个测试场景独立运行,使用时间戳生成唯一临时对象名
|
||||
- **完整性** — 每个场景覆盖完整生命周期(创建 → 验证 → 修改 → 验证 → 删除 → 验证)
|
||||
- **自清理** — 测试结束后自动清理临时文件(`test_output/` 目录)
|
||||
- **可分组** — 支持按场景组选择运行
|
||||
|
||||
### 测试场景组
|
||||
|
||||
| 组名 | 说明 | 包含场景 |
|
||||
|------|------|---------|
|
||||
| `happy` | 正常流程 | Report / Class / Function+FunctionGroup / Interface 生命周期 |
|
||||
| `error` | 错误处理 | 语法错误 / 对象不存在 / 重复创建 |
|
||||
| `edge` | 边界条件 | sync 自动创建 / 删除确认 / DDIC 查询 |
|
||||
| `existing` | 已有对象操作 | 对已有对象的下载和同步 |
|
||||
| `config` | 配置验证 | 配置文件加载测试 |
|
||||
|
||||
---
|
||||
|
||||
## 依赖关系
|
||||
|
||||
### 运行时依赖
|
||||
|
||||
```
|
||||
main.py
|
||||
├── sapcli/ # 内部包
|
||||
│ ├── config.py
|
||||
│ │ └── configparser # 解析 INI 配置文件
|
||||
│ ├── client.py
|
||||
│ │ ├── requests # HTTP 通信(唯一第三方依赖)
|
||||
│ │ └── xml.etree # 解析 ADT XML 响应
|
||||
│ ├── commands.py
|
||||
│ │ └── logging # 日志记录
|
||||
│ ├── types.py
|
||||
│ │ └── re # 对象名称解析
|
||||
│ └── exceptions.py
|
||||
│ └── (无外部依赖)
|
||||
├── argparse # 命令行参数解析
|
||||
└── logging # 日志配置
|
||||
```
|
||||
|
||||
### 测试时依赖
|
||||
|
||||
```
|
||||
test/script/test_main.py
|
||||
├── subprocess # 子进程调用 main.py
|
||||
└── 标准库(os, sys, time, shutil, re)
|
||||
```
|
||||
|
||||
### 第三方依赖汇总
|
||||
|
||||
| 依赖 | 用途 | 安装方式 |
|
||||
|------|------|---------|
|
||||
| `requests` | HTTP 通信 | `pip install requests` |
|
||||
|
||||
> [!tip] 零额外依赖
|
||||
> 除 `requests` 外,所有依赖均为 Python 标准库,无需额外安装。
|
||||
@@ -0,0 +1,684 @@
|
||||
---
|
||||
title: 批量同步设计 — 项目清单驱动
|
||||
created: 2026-05-25
|
||||
updated: 2026-05-25
|
||||
tags:
|
||||
- SAP
|
||||
- ADT
|
||||
- design
|
||||
parent: "[[sap-cli/doc/sap-cli使用指南|sap-cli使用指南]]"
|
||||
related:
|
||||
- "[[sap-cli/doc/技术说明|技术说明]]"
|
||||
- "[[sap-cli/doc/sap-cli使用指南#4.3 源代码同步激活 (`sync`)|sync 命令]]"
|
||||
---
|
||||
|
||||
# 批量同步设计 — 项目清单驱动
|
||||
|
||||
> [!abstract] 概述
|
||||
> 为 sap-cli 工具增加**项目清单**机制,支持批量源代码同步。核心思路:在源代码目录下维护 `manifest.json` 清单文件,记录每个开发对象在 SAP 系统中的状态,后续操作基于清单驱动,避免重复查询。
|
||||
|
||||
> [!info] 设计动机
|
||||
> 现有 `sync` 命令只支持单对象操作,缺少以下能力:
|
||||
> - 批量扫描本地文件并识别对象
|
||||
> - 预查询 SAP 系统状态,制定执行计划
|
||||
> - 按依赖关系排序,批量执行同步
|
||||
> - 缓存对象状态,避免每次重复查询
|
||||
|
||||
## 1. 目录约定
|
||||
|
||||
### 1.1 项目目录结构
|
||||
|
||||
项目根目录下按对象类型组织子目录,`manifest.json` 位于根目录:
|
||||
|
||||
```
|
||||
project/ ← --path 指向这里(项目根目录)
|
||||
├── manifest.json ← 状态清单(init 自动生成)
|
||||
├── reports/
|
||||
│ └── {对象名小写}.abap
|
||||
├── classes/
|
||||
│ └── {对象名小写}.abap
|
||||
├── interfaces/
|
||||
│ └── {对象名小写}.abap
|
||||
├── functions/
|
||||
│ └── {函数组名小写}/
|
||||
│ └── {函数模块名小写}.abap
|
||||
├── domains/
|
||||
│ └── {对象名小写}.abap
|
||||
├── dataelements/
|
||||
│ └── {对象名小写}.abap
|
||||
├── tables/
|
||||
│ └── {对象名小写}.abap
|
||||
└── tabletypes/
|
||||
└── {对象名小写}.abap
|
||||
```
|
||||
|
||||
### 1.2 目录名 → 类型映射
|
||||
|
||||
`init` 扫描时按以下映射表从目录名推断对象类型:
|
||||
|
||||
| 目录名 | 对象类型 | 文件 → 对象名规则 |
|
||||
|--------|---------|------------------|
|
||||
| `reports/` | `report` | `zmy_report.abap` → `ZMY_REPORT` |
|
||||
| `classes/` | `class` | `zcl_my_class.abap` → `ZCL_MY_CLASS` |
|
||||
| `interfaces/` | `interface` | `zif_my_interface.abap` → `ZIF_MY_INTERFACE` |
|
||||
| `functions/` | `function` | `zgroup/z_my_func.abap` → `ZGROUP/Z_MY_FUNC` |
|
||||
| `domains/` | `domain` | `z_status.abap` → `Z_STATUS` |
|
||||
| `dataelements/` | `dataelement` | `z_status.abap` → `Z_STATUS` |
|
||||
| `tables/` | `table` | `zmy_table.abap` → `ZMY_TABLE` |
|
||||
| `tabletypes/` | `tabletype` | `zty_table.abap` → `ZTY_TABLE` |
|
||||
|
||||
> [!note] 文件名 → 对象名规则
|
||||
> - 文件名去掉 `.abap` 后缀后转为大写即为对象名
|
||||
> - `functions/` 下的子目录名作为函数组名,子目录内的文件名作为函数模块名
|
||||
> - 对象名为 `函数组名大写/函数模块名大写` 格式
|
||||
|
||||
## 2. 清单文件 (`manifest.json`)
|
||||
|
||||
### 2.1 结构定义
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"last_init": "2026-05-25T10:00:00",
|
||||
"last_refresh": "2026-05-25T14:00:00",
|
||||
"objects": {
|
||||
"<对象名>": {
|
||||
"type": "<对象类型>",
|
||||
"file": "<相对文件路径>",
|
||||
"system_status": "<状态>",
|
||||
"corr_nr": "<传输请求号或null>",
|
||||
"depends_on": ["<依赖对象名>"],
|
||||
"last_sync": "<ISO时间戳或null>",
|
||||
"last_sync_result": "<结果>"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `version` | int | 清单格式版本号,当前为 `1` |
|
||||
| `last_init` | string | 最近一次 `init` 的时间戳 |
|
||||
| `last_refresh` | string | 最近一次 `refresh` 的时间戳 |
|
||||
| `objects` | object | 以对象名为 key 的字典 |
|
||||
| `objects.<name>.type` | string | 对象类型(report/class/interface/...) |
|
||||
| `objects.<name>.file` | string | 相对于项目根目录的文件路径 |
|
||||
| `objects.<name>.system_status` | string | `active` / `inactive` / `not_exists` |
|
||||
| `objects.<name>.corr_nr` | string\|null | 传输请求号,`null` 表示本地对象 `$TMP` |
|
||||
| `objects.<name>.depends_on` | string[] | 依赖的对象名列表 |
|
||||
| `objects.<name>.last_sync` | string\|null | 最近一次成功同步的时间 |
|
||||
| `objects.<name>.last_sync_result` | string | `success` / `failed` / `pending` |
|
||||
|
||||
### 2.3 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"last_init": "2026-05-25T10:00:00",
|
||||
"last_refresh": null,
|
||||
"objects": {
|
||||
"Z_STATUS": {
|
||||
"type": "domain",
|
||||
"file": "domains/z_status.abap",
|
||||
"system_status": "active",
|
||||
"corr_nr": "DEVK901362",
|
||||
"depends_on": [],
|
||||
"last_sync": "2026-05-25T10:30:00",
|
||||
"last_sync_result": "success"
|
||||
},
|
||||
"ZIF_MY_INTERFACE": {
|
||||
"type": "interface",
|
||||
"file": "interfaces/zif_my_interface.abap",
|
||||
"system_status": "active",
|
||||
"corr_nr": "DEVK901362",
|
||||
"depends_on": [],
|
||||
"last_sync": "2026-05-25T10:30:00",
|
||||
"last_sync_result": "success"
|
||||
},
|
||||
"ZCL_MY_CLASS": {
|
||||
"type": "class",
|
||||
"file": "classes/zcl_my_class.abap",
|
||||
"system_status": "active",
|
||||
"corr_nr": "DEVK901362",
|
||||
"depends_on": ["ZIF_MY_INTERFACE"],
|
||||
"last_sync": "2026-05-25T11:00:00",
|
||||
"last_sync_result": "success"
|
||||
},
|
||||
"ZMY_REPORT": {
|
||||
"type": "report",
|
||||
"file": "reports/zmy_report.abap",
|
||||
"system_status": "inactive",
|
||||
"corr_nr": null,
|
||||
"depends_on": ["ZCL_MY_CLASS"],
|
||||
"last_sync": null,
|
||||
"last_sync_result": "pending"
|
||||
},
|
||||
"ZGROUP/Z_MY_FUNC": {
|
||||
"type": "function",
|
||||
"file": "functions/zgroup/z_my_func.abap",
|
||||
"system_status": "active",
|
||||
"corr_nr": "DEVK901362",
|
||||
"depends_on": [],
|
||||
"last_sync": "2026-05-25T10:00:00",
|
||||
"last_sync_result": "success"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 命令变更一览
|
||||
|
||||
| 命令 | 变更类型 | 说明 |
|
||||
|------|---------|------|
|
||||
| `init` | **新增** | 扫描项目目录 → 查询 SAP → 生成 `manifest.json` |
|
||||
| `create` | **变更** | 新增 `--path` 参数;创建成功后自动写入清单 |
|
||||
| `sync` | **变更** | 新增 `--all` 批量模式;单对象模式操作后更新清单 |
|
||||
| `delete` | **变更** | 新增 `--path` 参数;删除成功后从清单移除 |
|
||||
| `refresh` | **新增** | 重新查询清单中所有对象的 SAP 状态 |
|
||||
|
||||
## 4. 命令详细设计
|
||||
|
||||
### 4.1 `init` — 项目初始化(新增)
|
||||
|
||||
#### 命令格式
|
||||
|
||||
```bash
|
||||
python main.py init --path <项目根目录>
|
||||
```
|
||||
|
||||
#### 执行流程
|
||||
|
||||
```
|
||||
扫描项目目录
|
||||
├─ 按目录映射表识别文件 → 生成 {对象名, 类型, 文件路径} 列表
|
||||
├─ 输出: "扫描到 N 个本地文件"
|
||||
└─ 忽略非 .abap 文件和隐藏文件
|
||||
|
||||
逐个查询 SAP 系统
|
||||
├─ 对象存在 → 查询 info → 获取 system_status + corr_nr
|
||||
├─ 对象不存在 → 标记 system_status: "not_exists"
|
||||
└─ 查询失败 → 标记 system_status: "unknown"
|
||||
|
||||
应用类型默认优先级生成初始 depends_on
|
||||
(init 时只设置类型层面的默认依赖,用户可后续手动编辑)
|
||||
domain/dataelement/table → 无依赖
|
||||
tabletype → 依赖同项目中的 table
|
||||
interface → 无依赖
|
||||
class → 依赖同项目中被引用的 interface
|
||||
function → 无显式依赖
|
||||
report → 无显式依赖
|
||||
|
||||
写入 manifest.json
|
||||
输出汇总报告
|
||||
```
|
||||
|
||||
#### 输出示例
|
||||
|
||||
```
|
||||
============================================================
|
||||
sap-cli 项目初始化
|
||||
============================================================
|
||||
项目路径: ./project
|
||||
扫描到 5 个本地文件
|
||||
|
||||
→ 正在查询 SAP 系统状态...
|
||||
✓ Z_STATUS (domain) active corr: DEVK901362
|
||||
✓ ZIF_MY_INTERFACE (interface) active corr: DEVK901362
|
||||
✓ ZCL_MY_CLASS (class) active corr: DEVK901362
|
||||
⚠ ZMY_REPORT (report) inactive corr: 本地对象
|
||||
✗ ZCL_NEW_CLASS (class) 不存在
|
||||
|
||||
✓ 清单已生成: ./project/manifest.json
|
||||
5 个对象: 3 已激活, 1 未激活, 1 不存在
|
||||
```
|
||||
|
||||
> [!tip] 清单可手动编辑
|
||||
> `manifest.json` 生成后,用户可以手动编辑 `depends_on` 字段来补充对象间的显式依赖关系。
|
||||
> `init` 只设置类型层面的默认依赖,具体的引用关系(如某个 class 实现了哪个 interface)需要用户自行标注。
|
||||
|
||||
---
|
||||
|
||||
### 4.2 `sync --all` — 批量同步(新增)
|
||||
|
||||
#### 命令格式
|
||||
|
||||
```bash
|
||||
python main.py sync --all --path <项目根目录> [--corr_nr <传输请求号>] [--fail-fast] [--dry-run]
|
||||
```
|
||||
|
||||
| 参数 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `--all` | 是 | 启用批量模式 |
|
||||
| `--path` | 是 | 项目根目录(包含 `manifest.json`) |
|
||||
| `--corr_nr` | 否 | 统一传输请求号(跳过交互选择,所有对象共用) |
|
||||
| `--fail-fast` | 否 | 遇到失败立即停止(默认:跳过并继续) |
|
||||
| `--dry-run` | 否 | 仅输出执行计划,不实际操作 |
|
||||
|
||||
#### 执行流程
|
||||
|
||||
```
|
||||
读取 manifest.json
|
||||
├─ 不存在 → 报错: "请先执行 init"
|
||||
└─ 存在 → 继续
|
||||
|
||||
扫描本地文件 vs 清单差异
|
||||
├─ 本地有但清单没有的文件 → 提示: "N 个未纳入清单的文件,建议先 create"
|
||||
└─ 清单有但本地被删的文件 → 警告,跳过
|
||||
|
||||
过滤待处理对象
|
||||
├─ system_status == "active" && last_sync_result == "success" → 跳过(已同步)
|
||||
├─ system_status == "not_exists" → 标记为 [create + sync]
|
||||
└─ 其他 → 标记为 [sync]
|
||||
|
||||
拓扑排序
|
||||
├─ 按 depends_on 建有向图
|
||||
├─ 无显式依赖的按类型默认优先级排序
|
||||
└─ 检测循环依赖 → 报错退出
|
||||
|
||||
--dry-run 模式:
|
||||
输出执行计划,不实际操作
|
||||
|
||||
传输请求预选择(一次性交互):
|
||||
├─ 扫描待处理对象,统计需要传输请求号的数量
|
||||
├─ 指定了 --corr_nr → 直接使用,无交互
|
||||
├─ 全部对象已绑定请求号或为本地对象 → 无交互
|
||||
└─ 有对象需要请求号 → 弹出一次选择,选定后全局复用:
|
||||
├─ 选择已有请求号 → 后续所有需要请求号的对象都使用此请求号
|
||||
├─ 新建请求号 → 同上
|
||||
└─ 不使用请求号 → 所需对象都作为本地对象处理
|
||||
|
||||
执行阶段(按排序顺序逐个处理,使用预选的传输请求号):
|
||||
对每个对象:
|
||||
├─ 本地文件不存在 → 跳过,警告
|
||||
├─ system_status == "not_exists" → 先执行 create(使用预选请求号)
|
||||
├─ 执行 sync 逻辑(使用预选请求号,跳过逐对象交互):
|
||||
│ ├─ 对象已绑定请求号 → 使用对象自身绑定的请求号
|
||||
│ ├─ 本地对象($TMP)→ 无需请求号
|
||||
│ └─ 未绑定 → 使用预选的统一请求号(不再弹出交互)
|
||||
├─ 成功 → 更新 manifest:
|
||||
│ system_status: "active"
|
||||
│ corr_nr: 实际使用的请求号
|
||||
│ last_sync: 当前时间
|
||||
│ last_sync_result: "success"
|
||||
└─ 失败 →
|
||||
├─ --fail-fast → 停止,输出已处理和未处理的对象
|
||||
└─ 默认 → 标记 last_sync_result: "failed",跳过,继续下一个
|
||||
后续依赖此对象的对象也自动跳过(标记 skipped)
|
||||
|
||||
输出汇总报告
|
||||
```
|
||||
|
||||
#### dry-run 输出示例
|
||||
|
||||
```
|
||||
============================================================
|
||||
sap-cli 批量同步 (dry-run)
|
||||
============================================================
|
||||
项目路径: ./project
|
||||
清单对象: 5
|
||||
|
||||
执行计划(按依赖排序):
|
||||
1. Z_STATUS (domain) → sync [inactive → active]
|
||||
2. ZIF_MY_INTERFACE (interface) → 跳过 [已是最新]
|
||||
3. ZCL_MY_CLASS (class) → sync [依赖: ZIF_MY_INTERFACE ✓]
|
||||
4. ZMY_REPORT (report) → sync [依赖: ZCL_MY_CLASS]
|
||||
5. ZGROUP/Z_MY_FUNC (function) → 跳过 [已是最新]
|
||||
|
||||
待处理: 3 个 | 跳过: 2 个
|
||||
```
|
||||
|
||||
#### 执行输出示例
|
||||
|
||||
```
|
||||
============================================================
|
||||
sap-cli 批量同步
|
||||
============================================================
|
||||
项目路径: ./project
|
||||
待处理: 3 个对象
|
||||
|
||||
→ 预检传输请求需求...
|
||||
2 个对象已绑定请求号: Z_STATUS(DEVK901362), ZCL_MY_CLASS(DEVK901362)
|
||||
1 个对象需要传输请求: ZMY_REPORT
|
||||
|
||||
→ 查询可用的传输请求...
|
||||
找到 2 个可修改的传输请求:
|
||||
|
||||
1. DEVK901362 Implement sales validation (所有者: ADMIN2)
|
||||
2. DEVK901365 Bugfix for invoice printing (所有者: ADMIN2)
|
||||
3. 新建传输请求
|
||||
0. 不使用传输请求(本地对象)
|
||||
|
||||
请选择 [0-3]: 1
|
||||
✓ 统一传输请求: DEVK901362(后续所有对象共用此请求号)
|
||||
|
||||
── [1/3] Z_STATUS (domain) ──
|
||||
✓ 锁定成功(对象已绑定传输请求: DEVK901362)
|
||||
✓ 源代码写入成功
|
||||
✓ 解锁成功
|
||||
✓ 语法检查通过
|
||||
✓ 激活成功
|
||||
|
||||
── [2/3] ZCL_MY_CLASS (class) ──
|
||||
✓ 锁定成功(对象已绑定传输请求: DEVK901362)
|
||||
✓ 源代码写入成功
|
||||
✓ 解锁成功
|
||||
✓ 语法检查通过
|
||||
✓ 激活成功
|
||||
|
||||
── [3/3] ZMY_REPORT (report) ──
|
||||
✓ 锁定成功(使用统一传输请求: DEVK901362)
|
||||
✓ 源代码写入成功
|
||||
✓ 解锁成功
|
||||
✗ 语法检查未通过! 1 个错误
|
||||
[错误] 行 10: Field "XXX" is unknown
|
||||
⊘ 跳过(标记为 failed)
|
||||
|
||||
============================================================
|
||||
批量同步完成
|
||||
============================================================
|
||||
✓ 成功: Z_STATUS, ZCL_MY_CLASS
|
||||
✗ 失败: ZMY_REPORT (语法错误,行 10)
|
||||
⊘ 跳过: (无)
|
||||
清单已更新: ./project/manifest.json
|
||||
```
|
||||
|
||||
> [!tip] 指定 `--corr_nr` 跳过交互
|
||||
> 如果已知传输请求号,使用 `--corr_nr` 参数可以直接跳过交互选择:
|
||||
> ```bash
|
||||
> python main.py sync --all --path ./project --corr_nr DEVK901362
|
||||
> ```
|
||||
> 此时所有未绑定请求号的对象都会使用指定的请求号,**全程零交互**。
|
||||
|
||||
---
|
||||
|
||||
### 4.3 `refresh` — 刷新清单状态(新增)
|
||||
|
||||
#### 命令格式
|
||||
|
||||
```bash
|
||||
python main.py refresh --path <项目根目录>
|
||||
```
|
||||
|
||||
#### 执行流程
|
||||
|
||||
```
|
||||
读取 manifest.json
|
||||
逐个重新查询 SAP info
|
||||
更新每个对象的 system_status 和 corr_nr
|
||||
(不修改 last_sync 和 last_sync_result)
|
||||
输出变更摘要
|
||||
```
|
||||
|
||||
#### 使用场景
|
||||
|
||||
- 其他人修改了 SAP 端的对象
|
||||
- 传输请求状态发生变化(释放、导入等)
|
||||
- 需要确认当前系统状态但不想执行同步
|
||||
|
||||
#### 输出示例
|
||||
|
||||
```
|
||||
============================================================
|
||||
sap-cli 清单刷新
|
||||
============================================================
|
||||
项目路径: ./project
|
||||
清单对象: 5
|
||||
|
||||
→ 正在查询 SAP 系统状态...
|
||||
✓ Z_STATUS (domain) active corr: DEVK901362 (未变化)
|
||||
✓ ZIF_MY_INTERFACE (interface) active corr: DEVK901362 (未变化)
|
||||
~ ZCL_MY_CLASS (class) inactive corr: DEVK901362 (状态变更: active → inactive)
|
||||
✓ ZMY_REPORT (report) inactive corr: null (未变化)
|
||||
✓ ZGROUP/Z_MY_FUNC (function) active corr: DEVK901362 (未变化)
|
||||
|
||||
✓ 刷新完成
|
||||
变更: 1 个 | 未变化: 4 个
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 `create` — 变更:自动写入清单
|
||||
|
||||
#### 变更内容
|
||||
|
||||
- 新增 `--path` 参数:指向项目根目录
|
||||
- 创建成功后:自动在 `manifest.json` 中新增一条记录
|
||||
|
||||
#### 新增的清单记录
|
||||
|
||||
```json
|
||||
{
|
||||
"ZCL_NEW_CLASS": {
|
||||
"type": "class",
|
||||
"file": "classes/zcl_new_class.abap",
|
||||
"system_status": "active",
|
||||
"corr_nr": "DEVK901362",
|
||||
"depends_on": [],
|
||||
"last_sync": "2026-05-25T14:00:00",
|
||||
"last_sync_result": "success"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 向后兼容
|
||||
|
||||
- 如果未指定 `--path`,或指定目录下没有 `manifest.json`:仅创建对象,不写清单
|
||||
- 现有不含 `--path` 的用法不受影响
|
||||
|
||||
---
|
||||
|
||||
### 4.5 `delete` — 变更:从清单移除
|
||||
|
||||
#### 变更内容
|
||||
|
||||
- 新增 `--path` 参数:指向项目根目录
|
||||
- 删除成功后:从 `manifest.json` 中移除该对象记录
|
||||
|
||||
#### 向后兼容
|
||||
|
||||
- 如果未指定 `--path`,或指定目录下没有 `manifest.json`:仅删除对象,不更新清单
|
||||
|
||||
---
|
||||
|
||||
### 4.6 `sync`(单对象) — 变更:操作后更新清单
|
||||
|
||||
#### 变更内容
|
||||
|
||||
- 新增 `--path` 参数:指向项目根目录(可选)
|
||||
- sync 成功或失败后:更新 `manifest.json` 中对应对象的记录
|
||||
|
||||
#### 向后兼容
|
||||
|
||||
- 如果未指定 `--path`,或指定目录下没有 `manifest.json`:行为与现有完全一致
|
||||
|
||||
## 5. 依赖排序策略
|
||||
|
||||
### 5.1 类型默认优先级
|
||||
|
||||
无显式 `depends_on` 时,按类型优先级排序:
|
||||
|
||||
| 优先级 | 数值 | 类型 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| 1 | 10 | `domain` | 基础域定义 |
|
||||
| 2 | 20 | `dataelement` | 依赖 domain |
|
||||
| 3 | 30 | `table` | 依赖 dataelement |
|
||||
| 4 | 40 | `tabletype` | 依赖 table |
|
||||
| 5 | 50 | `interface` | 接口定义 |
|
||||
| 6 | 60 | `class` | 可能实现 interface |
|
||||
| 7 | 70 | `function` | 函数模块 |
|
||||
| 8 | 80 | `report` | 程序,可能引用以上所有 |
|
||||
|
||||
### 5.2 排序算法
|
||||
|
||||
采用拓扑排序(Kahn 算法):
|
||||
|
||||
```
|
||||
1. 以 manifest 中所有对象为节点
|
||||
2. 建立有向边:
|
||||
- 显式依赖: depends_on 中列出的对象 → 当前对象
|
||||
- 类型默认优先级: 仅当两个对象之间没有显式依赖时作为 tie-breaker
|
||||
3. 执行 Kahn 拓扑排序
|
||||
4. 检测循环依赖 → 若存在未入队的节点则报错,输出循环链
|
||||
```
|
||||
|
||||
### 5.3 依赖失败传播
|
||||
|
||||
批量 `sync` 时,若对象 A 依赖对象 B,而 B 同步失败:
|
||||
|
||||
```
|
||||
A.depends_on = ["B"]
|
||||
B.last_sync_result = "failed"
|
||||
|
||||
→ A 自动跳过,标记 last_sync_result: "skipped"
|
||||
→ 依赖 A 的对象同样跳过(级联跳过)
|
||||
```
|
||||
|
||||
## 6. 边界情况处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 本地新增了 .abap 文件但清单中没有 | `sync --all` 时提示: "N 个未纳入清单的文件",建议先执行 `create` |
|
||||
| 清单中有对象但本地文件被删了 | `sync --all` 时警告并跳过该对象 |
|
||||
| `manifest.json` 不存在 | `sync --all` 报错,提示先执行 `init` |
|
||||
| 依赖对象 sync 失败 | 跳过所有依赖它的对象(级联标记 skipped) |
|
||||
| 循环依赖 | 检测到后报错,输出循环链中的对象名 |
|
||||
| 非项目目录(无子目录结构) | `init` 时只扫描当前目录下的 `.abap` 文件 |
|
||||
| SAP 连接中断 | 标记已处理的对象,输出中断位置,下次可从断点继续 |
|
||||
| `manifest.json` 版本不匹配 | 提示版本不兼容,建议重新 `init` |
|
||||
| 部分对象已绑定请求号,部分未绑定 | 已绑定的使用自身请求号,未绑定的使用预选的统一请求号 |
|
||||
| 全部对象都是本地对象($TMP) | 无需传输请求,直接执行,零交互 |
|
||||
| 指定了 `--corr_nr` 但部分对象已绑定其他请求号 | 已绑定的使用自身请求号(不受 `--corr_nr` 覆盖) |
|
||||
|
||||
## 7. 项目生命周期
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ init ────────────────────────────────────────────── │
|
||||
│ │ 扫描本地文件 → 查询 SAP → 生成 manifest.json │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ create(新增对象时) ──────────────────────────────── │
|
||||
│ │ 在 SAP 创建 → 自动写入 manifest │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ sync --all(日常同步)───────────────────────────── │
|
||||
│ │ 读 manifest → 拓扑排序 → 逐个 sync → 更新 manifest │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ refresh(SAP 端有变化时)──────────────────────────── │
|
||||
│ 重新查询 SAP → 更新 manifest 中的状态字段 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
> [!tip] 典型工作流
|
||||
> ```bash
|
||||
> # 1. 首次使用:初始化项目清单
|
||||
> python main.py init --path ./my_project
|
||||
>
|
||||
> # 2. 日常开发:新增对象
|
||||
> python main.py create --name ZCL_NEW --type class --path ./my_project --description "新类"
|
||||
>
|
||||
> # 3. 日常开发:修改本地 .abap 文件后,批量同步
|
||||
> python main.py sync --all --path ./my_project
|
||||
>
|
||||
> # 4. 如需查看执行计划(不实际操作)
|
||||
> python main.py sync --all --path ./my_project --dry-run
|
||||
>
|
||||
> # 5. 其他人修改了 SAP 端对象后,刷新清单
|
||||
> python main.py refresh --path ./my_project
|
||||
> ```
|
||||
|
||||
## 8. 模块设计(实现层面)
|
||||
|
||||
### 8.1 新增模块
|
||||
|
||||
| 模块 | 职责 |
|
||||
|------|------|
|
||||
| `sapcli/manifest.py` | 清单文件的读取、写入、查询、更新 |
|
||||
| `sapcli/scanner.py` | 扫描本地目录,识别对象名和类型 |
|
||||
| `sapcli/sorter.py` | 拓扑排序,依赖检测 |
|
||||
|
||||
### 8.2 清单模块 (`manifest.py`) 接口
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ManifestEntry:
|
||||
name: str
|
||||
type: str
|
||||
file: str
|
||||
system_status: str # "active" / "inactive" / "not_exists"
|
||||
corr_nr: str | None
|
||||
depends_on: list[str]
|
||||
last_sync: str | None # ISO timestamp
|
||||
last_sync_result: str # "success" / "failed" / "pending"
|
||||
|
||||
class Manifest:
|
||||
version: int
|
||||
last_init: str | None
|
||||
last_refresh: str | None
|
||||
objects: dict[str, ManifestEntry]
|
||||
|
||||
@classmethod
|
||||
def load(cls, project_path: str) -> "Manifest": ...
|
||||
def save(self) -> None: ...
|
||||
def upsert(self, entry: ManifestEntry) -> None: ...
|
||||
def remove(self, name: str) -> None: ...
|
||||
def get(self, name: str) -> ManifestEntry | None: ...
|
||||
def pending_objects(self) -> list[ManifestEntry]: ...
|
||||
```
|
||||
|
||||
### 8.3 扫描模块 (`scanner.py`) 接口
|
||||
|
||||
```python
|
||||
DIRECTORY_TYPE_MAP = {
|
||||
"reports": "report",
|
||||
"classes": "class",
|
||||
"interfaces": "interface",
|
||||
"functions": "function",
|
||||
"domains": "domain",
|
||||
"dataelements": "dataelement",
|
||||
"tables": "table",
|
||||
"tabletypes": "tabletype",
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class ScannedObject:
|
||||
name: str # 大写对象名,function 类型含 /
|
||||
type: str # 对象类型
|
||||
file: str # 相对于项目根目录的路径
|
||||
|
||||
def scan_project(project_path: str) -> list[ScannedObject]: ...
|
||||
```
|
||||
|
||||
### 8.4 排序模块 (`sorter.py`) 接口
|
||||
|
||||
```python
|
||||
TYPE_PRIORITY = {
|
||||
"domain": 10,
|
||||
"dataelement": 20,
|
||||
"table": 30,
|
||||
"tabletype": 40,
|
||||
"interface": 50,
|
||||
"class": 60,
|
||||
"function": 70,
|
||||
"report": 80,
|
||||
}
|
||||
|
||||
class CyclicDependencyError(SapCliError): ...
|
||||
|
||||
def topological_sort(
|
||||
objects: list[ManifestEntry],
|
||||
) -> list[ManifestEntry]: ...
|
||||
# → 按 depends_on + TYPE_PRIORITY 排序
|
||||
# → 检测循环依赖,抛出 CyclicDependencyError
|
||||
```
|
||||
|
||||
## 9. 相关笔记
|
||||
|
||||
- [[sap-cli/doc/sap-cli使用指南|sap-cli 使用指南]] — 现有命令的使用说明
|
||||
- [[sap-cli/doc/技术说明|技术说明]] — 技术架构与模块设计
|
||||
- [[sap-cli/doc/ADT/03.修改代码-原理|修改代码原理]] — 锁定/编辑/激活工作流
|
||||
@@ -0,0 +1,530 @@
|
||||
# sap-cli 功能扩展分析报告
|
||||
|
||||
> **项目**: sap-cli — 用你喜欢的编辑器写 ABAP
|
||||
> **版本**: 当前 (v1.0)
|
||||
> **编制日期**: 2026-06-09
|
||||
> **定位**: 基于 SAP ADT REST API 的命令行 ABAP 开发工具
|
||||
|
||||
---
|
||||
|
||||
## 一、现有功能全景
|
||||
|
||||
### 1.1 已实现的 7 个命令
|
||||
|
||||
| 命令 | 功能 | 适用对象 |
|
||||
|:-----|:-----|:---------|
|
||||
| `download` | 从 SAP 下载源码到本地 .abap 文件 | report / class / interface / function / domain / dataelement / table / structure |
|
||||
| `sync` | 本地代码 → SAP(锁定→写入→解锁→语法检查→激活) | 同上 |
|
||||
| `create` | 在 SAP 创建开发对象(支持模板和 JSON 定义) | 10 种类型(含 functiongroup、tabletype) |
|
||||
| `info` | 查询对象元数据(名称/类型/状态/负责人/修改时间等) | 10 种类型 |
|
||||
| `delete` | 从 SAP 系统删除对象(需确认) | 10 种类型 |
|
||||
| `init` | 扫描本地目录 → 查询 SAP → 生成 manifest.json | 批量 |
|
||||
| `refresh` | 刷新清单中对象的 SAP 状态 | 批量 |
|
||||
|
||||
### 1.2 已支持的对象类型
|
||||
|
||||
**ABAP 程序对象(5 种)**:report、class、interface、function、functiongroup
|
||||
|
||||
**ABAP 字典对象(5 种)**:domain、dataelement、table、structure、tabletype
|
||||
|
||||
### 1.3 核心能力
|
||||
|
||||
- ✅ 单对象 CRUD 全生命周期
|
||||
- ✅ 批量同步(基于 manifest.json + 拓扑排序)
|
||||
- ✅ 传输请求管理(list / create / select)
|
||||
- ✅ DDIC 对象通过 JSON 定义文件创建
|
||||
- ✅ 锁定-编辑-解锁-激活完整工作流
|
||||
- ✅ 语法检查集成
|
||||
|
||||
---
|
||||
|
||||
## 二、功能扩展方向
|
||||
|
||||
### 🔵 方向一:对象类型扩展
|
||||
|
||||
#### 2.1.1 ADT 已支持但工具未覆盖的对象类型
|
||||
|
||||
| 优先级 | 对象类型 | ADT 端点 | 使用场景 | 实现难度 |
|
||||
|:------:|:---------|:---------|:---------|:--------:|
|
||||
| ⭐⭐⭐ | **CDS View (DDL)** | `/sap/bc/adt/ddic/ddlsources` | S/4HANA 核心开发,CDS View 是现代 ABAP 开发的基础 | 中 |
|
||||
| ⭐⭐⭐ | **CDS Access Control (DCL)** | `/sap/bc/adt/authorization/dclsources` | CDS 角色权限控制,与 CDS View 配套 | 中 |
|
||||
| ⭐⭐⭐ | **Include 程序** | `/sap/bc/adt/programs/programs` | 大型报表拆分,复用代码片段 | 低 |
|
||||
| ⭐⭐ | **消息类 (Message Class)** | `/sap/bc/adt/messageclasses` | MESSAGE 语句依赖,项目必备 | 低 |
|
||||
| ⭐⭐ | **数据库视图 (View)** | `/sap/bc/adt/ddic/views` | 数据建模,查询优化 | 中 |
|
||||
| ⭐⭐ | **搜索帮助 (Search Help)** | `/sap/bc/adt/ddic/searchhelps` | ALV 屏幕、F4 帮助 | 中 |
|
||||
| ⭐⭐ | **锁对象 (Lock Object)** | `/sap/bc/adt/ddic/lockobjects` | 并发控制,多用户数据一致性 | 中 |
|
||||
| ⭐ | **类型组 (Type Pool)** | `/sap/bc/adt/programs/programs` | 常量/类型定义集中管理 | 低 |
|
||||
| ⭐ | **AMDP 类** | 复用 class 端点 | HANA 数据库过程调用 | 低 |
|
||||
| ⭐ | **Web Dynpro 组件** | 专用端点 | 传统 Web UI(逐渐淘汰) | 高 |
|
||||
|
||||
**实现建议**:
|
||||
- 第一批优先:**Include 程序**(改动最小,复用现有 report 逻辑)+ **CDS View**(战略价值最高)
|
||||
- CDS View 需要新增 `cdsview` 类型,源码是 DDL 语法(非 ABAP),激活流程与 DDIC 类似
|
||||
|
||||
#### 2.1.2 tabletype 的完整支持
|
||||
|
||||
当前 `tabletype` 使用 VIT 端点,不支持 download/sync/delete。可以探索:
|
||||
- 通过 DDIC 专用端点(`/sap/bc/adt/ddic/tabletypes`)的源码读写接口
|
||||
- 或使用 VIT 的 PUT 操作实现间接源码写入
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向二:代码浏览与搜索
|
||||
|
||||
#### 2.2.1 对象列表浏览(`list` 命令)
|
||||
|
||||
```
|
||||
python main.py list --type class --prefix ZCL_* --package ZFINANCE
|
||||
```
|
||||
|
||||
**ADT API**: `GET /sap/bc/adt/repository/informationsystem/search`
|
||||
**使用场景**:
|
||||
- 浏览某个包下的所有对象
|
||||
- 按名称前缀模糊搜索
|
||||
- 按类型/所有者/修改时间筛选
|
||||
|
||||
#### 2.2.2 Where-Used 引用查询
|
||||
|
||||
```
|
||||
python main.py whereused --name ZCL_MY_CLASS --type class
|
||||
```
|
||||
|
||||
**ADT API**: `GET /sap/bc/adt/whereused`
|
||||
**使用场景**:
|
||||
- 修改前评估影响范围
|
||||
- 自动填充 `manifest.json` 的 `depends_on` 字段(替代手动维护)
|
||||
- 批量同步时自动推导依赖关系
|
||||
|
||||
#### 2.2.3 源代码全文搜索
|
||||
|
||||
```
|
||||
python main.py search --query "SELECT * FROM ZMY_TABLE" --type report
|
||||
```
|
||||
|
||||
**ADT API**: `GET /sap/bc/adt/repository/informationsystem/search` (code search)
|
||||
**使用场景**: 快速定位引用特定表/函数/变量的代码位置
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向三:代码质量与检查
|
||||
|
||||
#### 2.3.1 ATC 代码检查集成
|
||||
|
||||
```
|
||||
python main.py check --name ZMY_REPORT --type report --variant SAP_ABA_CHECK
|
||||
```
|
||||
|
||||
**ADT API**: `POST /sap/bc/adt/qualitymanager/ats/checkruns`
|
||||
**使用场景**:
|
||||
- 同步前自动运行 ATC 检查(替代或补充现有语法检查)
|
||||
- CI/CD 流水线中的质量门禁
|
||||
- 支持自定义检查变体
|
||||
|
||||
#### 2.3.2 代码格式化(ABAP Pretty Printer)
|
||||
|
||||
```
|
||||
python main.py format --name ZMY_REPORT --type report
|
||||
```
|
||||
|
||||
**ADT API**: `POST /sap/bc/adt/prettyprinter`
|
||||
**使用场景**:
|
||||
- 下载后自动格式化(统一缩进、大小写风格)
|
||||
- sync 前自动格式化(保持 SAP 端代码风格一致)
|
||||
|
||||
#### 2.3.3 代码差异对比
|
||||
|
||||
```
|
||||
python main.py diff --name ZMY_REPORT --type report --path ./src/zmy_report.abap
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- sync 前预览即将上传的改动
|
||||
- 显示本地文件与 SAP 端源码的差异
|
||||
- 防止误覆盖他人的修改
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向四:传输管理与多系统
|
||||
|
||||
#### 2.4.1 传输请求高级管理
|
||||
|
||||
```
|
||||
python main.py transport list --status D # 查看可修改的传输请求
|
||||
python main.py transport info DEVK901362 # 查看传输请求详情
|
||||
python main.py transport release DEVK901362 # 释放传输请求
|
||||
python main.py transport objects DEVK901362 # 列出传输请求中的所有对象
|
||||
```
|
||||
|
||||
**ADT API**:
|
||||
- `GET /sap/bc/adt/cts/transportrequests/{id}` — 详情
|
||||
- `POST /sap/bc/adt/cts/transportrequests/{id}?method=release` — 释放
|
||||
|
||||
**使用场景**:
|
||||
- 批量同步后一键释放传输请求
|
||||
- 在命令行中管理传输请求,无需进入 SAP GUI(SE01/SE09)
|
||||
|
||||
#### 2.4.2 多系统配置与跨系统同步
|
||||
|
||||
```ini
|
||||
# config.ini
|
||||
[DEV]
|
||||
host = http://dev-sap:8000
|
||||
client = 100
|
||||
|
||||
[QAS]
|
||||
host = http://qas-sap:8000
|
||||
client = 200
|
||||
|
||||
[PRD]
|
||||
host = http://prd-sap:8000
|
||||
client = 300
|
||||
```
|
||||
|
||||
```
|
||||
python main.py --profile DEV download --name ZMY_REPORT --type report --path ./src
|
||||
python main.py --profile QAS info --name ZMY_REPORT --type report
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- 开发/测试/生产多环境切换
|
||||
- 跨系统对象对比(DEV vs QAS 代码差异)
|
||||
- 一键从 DEV 下载 → 修改 → 推送到 QAS
|
||||
|
||||
#### 2.4.3 传输导入监控
|
||||
|
||||
```
|
||||
python main.py transport import DEVK901362 --target QAS
|
||||
python main.py transport status DEVK901362
|
||||
```
|
||||
|
||||
**使用场景**: 跟踪传输请求在目标系统的导入状态
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向五:包管理与项目结构
|
||||
|
||||
#### 2.5.1 ABAP 包(Package)操作
|
||||
|
||||
```
|
||||
python main.py package create ZFINANCE --description "财务模块" --superpackage ZBUSINESS
|
||||
python main.py package list --superpackage ZBUSINESS
|
||||
python main.py package move --name ZMY_REPORT --type report --package ZFINANCE
|
||||
```
|
||||
|
||||
**ADT API**: `/sap/bc/adt/packages/{name}`
|
||||
**使用场景**:
|
||||
- 项目初始化时自动创建包结构
|
||||
- 将 $TMP 本地对象迁移到正式包中
|
||||
|
||||
#### 2.5.2 项目模板(Scaffolding)
|
||||
|
||||
```
|
||||
python main.py scaffold --name ZSALES_ORDER --template "ALV Report" --package ZSALES
|
||||
```
|
||||
|
||||
预置模板:
|
||||
- **ALV 报表** — 包含 ALV GRID、字段目录、布局管理
|
||||
- **BAPI 封装** — 函数组 + 函数模块 + 异常处理
|
||||
- **接口类** — 接口 + 实现类 + 工厂方法
|
||||
- **数据模型** — domain + dataelement + table + tabletype + CDS View
|
||||
- **增强实现** — BAdI 定义 + 实现(如果 ADT 支持)
|
||||
|
||||
**使用场景**: 快速创建符合项目规范的标准代码骨架
|
||||
|
||||
#### 2.5.3 依赖自动分析
|
||||
|
||||
```
|
||||
python main.py analyze --path ./project
|
||||
```
|
||||
|
||||
**功能**:
|
||||
- 解析本地 .abap 文件中的 `TYPE REF TO`、`CALL METHOD`、`PERFORM` 等语句
|
||||
- 自动生成 `depends_on` 关系
|
||||
- 替代当前手动维护 manifest.json 依赖的方式
|
||||
- 结合 Where-Used API 交叉验证
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向六:Git 集成与 DevOps
|
||||
|
||||
#### 2.6.1 Git Hooks 集成
|
||||
|
||||
```bash
|
||||
# pre-commit hook: sync 前自动语法检查
|
||||
# post-pull hook: 自动 download 远端最新代码
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- `git commit` 前自动触发 ATC 检查
|
||||
- `git pull` 后自动拉取 SAP 端最新代码
|
||||
- `git push` 后自动触发 CI/CD
|
||||
|
||||
#### 2.6.2 CI/CD Pipeline 集成
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml 示例
|
||||
abap-sync:
|
||||
stage: deploy
|
||||
script:
|
||||
- python main.py sync --all --path ./src --corr_nr $TR_NUMBER --fail-fast
|
||||
- python main.py check --all --path ./src
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- Git 提交后自动同步到 SAP 开发系统
|
||||
- ATC 检查作为质量门禁
|
||||
- 传输请求自动创建和释放
|
||||
|
||||
#### 2.6.3 代码版本对比(SAP 版本管理集成)
|
||||
|
||||
```
|
||||
python main.py history --name ZMY_REPORT --type report
|
||||
python main.py version --name ZMY_REPORT --type report --version 1.2
|
||||
```
|
||||
|
||||
**ADT API**: `/sap/bc/adt/programs/programs/{name}/versions`
|
||||
**使用场景**:
|
||||
- 查看 SAP 端的版本历史
|
||||
- 对比不同版本之间的代码差异
|
||||
- 回滚到之前的版本
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向七:交互体验优化
|
||||
|
||||
#### 2.7.1 交互式终端 UI(TUI)
|
||||
|
||||
```
|
||||
python main.py tui
|
||||
```
|
||||
|
||||
**功能**:
|
||||
- 基于 [Textual](https://github.com/Textualize/textual) 或 [Rich](https://github.com/Textualize/rich) 的终端 UI
|
||||
- 对象树状浏览器(按类型/包/状态分组)
|
||||
- 源码差异对比视图
|
||||
- 批量操作进度条
|
||||
|
||||
#### 2.7.2 全局配置与 Profile
|
||||
|
||||
```bash
|
||||
sap-cli config set default_host http://dev-sap:8000
|
||||
sap-cli config set default_client 100
|
||||
sap-cli config set auto_format true
|
||||
sap-cli config set atc_variant SAP_ABA_CHECK
|
||||
sap-cli config set timeout 30
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- 替代手动编辑 config.ini
|
||||
- 按项目/环境保存不同配置
|
||||
- 设置全局默认参数(超时、格式化、检查变体等)
|
||||
|
||||
#### 2.7.3 Shell 自动补全
|
||||
|
||||
```bash
|
||||
eval "$(sap-cli completion bash)"
|
||||
eval "$(sap-cli completion zsh)"
|
||||
```
|
||||
|
||||
**使用场景**: Tab 键自动补全命令、对象类型、对象名
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向八:AI 与智能化
|
||||
|
||||
#### 2.8.1 AI 辅助代码生成
|
||||
|
||||
```
|
||||
python main.py generate --type report --description "根据销售订单号查询交货明细的ALV报表" --output ./src/zdelivery_detail.abap
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- 结合 LLM API(如 Claude/GPT)根据自然语言描述生成 ABAP 代码
|
||||
- 自动识别所需的表、数据元素、函数模块
|
||||
- 生成的代码自动通过语法检查
|
||||
|
||||
#### 2.8.2 MCP 服务器模式
|
||||
|
||||
```
|
||||
python main.py mcp-server --port 8080
|
||||
```
|
||||
|
||||
**参考**: [erpl-adt](https://github.com/DataZooDE/erpl-adt) 已实现 MCP 服务器
|
||||
**使用场景**:
|
||||
- 作为 MCP 工具供 AI 助手(如 Claude Desktop)调用
|
||||
- AI 可以直接浏览 SAP 对象、下载/同步代码
|
||||
- 实现"用自然语言修改 SAP 系统"的终极目标
|
||||
|
||||
#### 2.8.3 智能代码审查
|
||||
|
||||
```
|
||||
python main.py review --name ZMY_REPORT --type report --rules performance,security
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- 基于规则的静态分析(SQL 注入、性能反模式、命名规范)
|
||||
- 结合 AI 的代码审查建议
|
||||
- 自动生成改进建议
|
||||
|
||||
---
|
||||
|
||||
### 🔵 方向九:安全与运维
|
||||
|
||||
#### 2.9.1 密码安全存储
|
||||
|
||||
```
|
||||
python main.py auth login # 交互式输入密码,存入系统 keyring
|
||||
python main.py auth status # 检查登录状态
|
||||
python main.py auth logout # 清除凭证
|
||||
```
|
||||
|
||||
**实现方案**:
|
||||
- 使用 [keyring](https://github.com/jaraco/keyring) 库
|
||||
- 支持 Windows Credential Manager / macOS Keychain / Linux Secret Service
|
||||
- 不再明文存储密码
|
||||
|
||||
#### 2.9.2 SSL 证书验证
|
||||
|
||||
```ini
|
||||
[DEV]
|
||||
host = https://dev-sap:44300
|
||||
verify_ssl = true
|
||||
ca_bundle = /path/to/corp-ca.pem
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- 生产环境安全要求
|
||||
- 企业内网 CA 证书支持
|
||||
- 当前硬编码 `verify=False`,存在中间人攻击风险
|
||||
|
||||
#### 2.9.3 审计日志
|
||||
|
||||
```
|
||||
python main.py audit --from 2026-06-01 --to 2026-06-09
|
||||
```
|
||||
|
||||
**使用场景**:
|
||||
- 记录所有 create/sync/delete 操作的审计日志
|
||||
- 支持按时间/用户/操作类型查询
|
||||
- 满足企业合规要求
|
||||
|
||||
---
|
||||
|
||||
## 三、扩展优先级矩阵
|
||||
|
||||
按照 **业务价值 × 实现难度** 排序:
|
||||
|
||||
| 优先级 | 扩展方向 | 业务价值 | 实现难度 | 建议版本 |
|
||||
|:------:|:---------|:--------:|:--------:|:--------:|
|
||||
| 🔴 P0 | Include 程序支持 | ⭐⭐⭐ | ⭐ | v1.1 |
|
||||
| 🔴 P0 | 对象列表浏览 (list) | ⭐⭐⭐ | ⭐⭐ | v1.1 |
|
||||
| 🔴 P0 | 多系统配置 (profile) | ⭐⭐⭐ | ⭐⭐ | v1.2 |
|
||||
| 🔴 P0 | 密码安全存储 (keyring) | ⭐⭐⭐ | ⭐ | v1.2 |
|
||||
| 🟠 P1 | CDS View 支持 | ⭐⭐⭐ | ⭐⭐⭐ | v1.3 |
|
||||
| 🟠 P1 | Where-Used 引用查询 | ⭐⭐⭐ | ⭐⭐ | v1.3 |
|
||||
| 🟠 P1 | 代码差异对比 (diff) | ⭐⭐⭐ | ⭐⭐ | v1.3 |
|
||||
| 🟠 P1 | 传输请求高级管理 | ⭐⭐⭐ | ⭐⭐ | v1.4 |
|
||||
| 🟠 P1 | ABAP 包操作 | ⭐⭐ | ⭐⭐ | v1.4 |
|
||||
| 🟡 P2 | ATC 代码检查集成 | ⭐⭐ | ⭐⭐⭐ | v2.0 |
|
||||
| 🟡 P2 | 代码格式化 (Pretty Printer) | ⭐⭐ | ⭐⭐ | v2.0 |
|
||||
| 🟡 P2 | 依赖自动分析 | ⭐⭐⭐ | ⭐⭐⭐ | v2.0 |
|
||||
| 🟡 P2 | 项目模板 (Scaffolding) | ⭐⭐ | ⭐⭐ | v2.0 |
|
||||
| 🟡 P2 | CI/CD Pipeline 集成 | ⭐⭐⭐ | ⭐⭐ | v2.1 |
|
||||
| 🟢 P3 | 消息类 / 视图 / 搜索帮助 / 锁对象 | ⭐⭐ | ⭐⭐ | v2.x |
|
||||
| 🟢 P3 | 源代码全文搜索 | ⭐⭐ | ⭐⭐ | v2.x |
|
||||
| 🟢 P3 | SAP 版本管理集成 | ⭐⭐ | ⭐⭐⭐ | v2.x |
|
||||
| 🟢 P3 | Shell 自动补全 | ⭐ | ⭐ | v2.x |
|
||||
| 🔵 P4 | 交互式终端 UI (TUI) | ⭐⭐ | ⭐⭐⭐ | v3.0 |
|
||||
| 🔵 P4 | AI 辅助代码生成 | ⭐⭐⭐ | ⭐⭐⭐ | v3.0 |
|
||||
| 🔵 P4 | MCP 服务器模式 | ⭐⭐⭐ | ⭐⭐ | v3.0 |
|
||||
| 🔵 P4 | 智能代码审查 | ⭐⭐ | ⭐⭐⭐ | v3.0 |
|
||||
|
||||
---
|
||||
|
||||
## 四、技术架构建议
|
||||
|
||||
### 4.1 近期架构优化(支持 v1.x 扩展)
|
||||
|
||||
```
|
||||
sapcli/
|
||||
├── __init__.py
|
||||
├── main.py # CLI 入口(拆分 argparse 到独立模块)
|
||||
├── cli/ # 命令注册与解析(新增)
|
||||
│ ├── parser.py # argparse 定义
|
||||
│ ├── completions.py # Shell 补全
|
||||
│ └── output.py # 格式化输出(替代散落各处的 print)
|
||||
├── client.py # ADT REST 客户端(保持)
|
||||
├── commands/ # 按功能拆分命令(新增目录)
|
||||
│ ├── crud.py # download / sync / create / delete / info
|
||||
│ ├── batch.py # init / refresh / sync-all
|
||||
│ ├── transport.py # 传输请求管理(新增)
|
||||
│ ├── search.py # list / whereused / search(新增)
|
||||
│ └── check.py # syntax-check / atc(新增)
|
||||
├── types.py # 对象类型注册(保持)
|
||||
├── ddic.py # DDIC 定义(保持)
|
||||
├── manifest.py # 清单管理(保持)
|
||||
├── scanner.py # 目录扫描(保持)
|
||||
├── sorter.py # 拓扑排序(保持)
|
||||
├── config.py # 配置管理(增强多 profile)
|
||||
├── auth.py # 凭证管理(新增:keyring 集成)
|
||||
├── exceptions.py # 异常定义(保持)
|
||||
└── utils/ # 工具函数(新增)
|
||||
├── xml_utils.py # XML 安全转义、解析
|
||||
├── diff.py # 代码差异对比
|
||||
└── format.py # ABAP 格式化
|
||||
```
|
||||
|
||||
### 4.2 新增对象类型的标准流程
|
||||
|
||||
每次新增一个对象类型,需要修改的文件:
|
||||
|
||||
| 步骤 | 文件 | 内容 |
|
||||
|:-----|:-----|:-----|
|
||||
| 1 | `types.py` | 注册 `ObjectTypeConfig`(URI 模板、content-type) |
|
||||
| 2 | `client.py` | `_build_create_body()` 新增分支(或数据驱动) |
|
||||
| 3 | `scanner.py` | `DIRECTORY_TYPE_MAP` 新增目录映射 |
|
||||
| 4 | `commands.py` | 特殊逻辑处理(如有) |
|
||||
| 5 | `ddic.py` | 如有 XML/DDL 定义需求 |
|
||||
|
||||
### 4.3 建议引入的依赖
|
||||
|
||||
| 依赖 | 用途 | 时机 |
|
||||
|:-----|:-----|:-----|
|
||||
| `keyring` | 安全凭证存储 | v1.2 |
|
||||
| `rich` | 终端美化输出(进度条、表格、语法高亮) | v1.3 |
|
||||
| `textual` | 终端 UI 框架 | v3.0(可选) |
|
||||
| `pydantic` | JSON 定义文件验证 | v2.0 |
|
||||
| `httpx` | 替代 requests(支持 async、超时、重试) | v2.0 |
|
||||
|
||||
---
|
||||
|
||||
## 五、竞品参考
|
||||
|
||||
| 项目 | 语言 | 特点 | 值得借鉴 |
|
||||
|:-----|:-----|:-----|:---------|
|
||||
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | TypeScript | 最全面的 ADT API 封装 | CDS View、AMDP、Where-Used、搜索 |
|
||||
| [erpl-adt](https://github.com/DataZooDE/erpl-adt) | TypeScript | CLI + MCP 服务器 | MCP 协议、对象浏览、AI 集成 |
|
||||
| [abapGit](https://github.com/abapGit/abapGit) | ABAP | SAP 端 Git 客户端 | 版本管理思想、serialize/deserialize |
|
||||
|
||||
---
|
||||
|
||||
## 六、总结
|
||||
|
||||
sap-cli 已经建立了一个坚实的核心:
|
||||
- **10 种对象类型**的完整 CRUD
|
||||
- **批量同步** + 拓扑排序 + 传输请求管理
|
||||
- **manifest.json** 状态驱动的项目管理
|
||||
|
||||
最自然、最高价值的扩展路径是:
|
||||
|
||||
```
|
||||
v1.1 Include + list + 消息类 → 补齐日常开发必需类型
|
||||
v1.2 多系统 + keyring → 企业级安全与多环境
|
||||
v1.3 CDS View + Where-Used → S/4HANA 现代化开发
|
||||
v1.4 传输管理 + 包操作 → 运维与管理能力
|
||||
v2.0 ATC + diff + 依赖分析 → 代码质量保障
|
||||
v2.x CI/CD + 模板 + 搜索 → DevOps 集成
|
||||
v3.0 TUI + AI + MCP → 智能化开发
|
||||
```
|
||||
|
||||
这个路径从 **"能用的工具"** 逐步演进到 **"不可替代的开发平台"**,每一步都有明确的用户价值。
|
||||
@@ -0,0 +1,103 @@
|
||||
# sap-cli 测试覆盖率报告
|
||||
|
||||
> 测试时间:2026-06-09
|
||||
> 测试用例:68(63 pass + 5 skip)
|
||||
> 覆盖率工具:coverage.py
|
||||
|
||||
---
|
||||
|
||||
## 总体概况
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 源码总行数(Stmts) | 3,018 |
|
||||
| 已覆盖行数 | 674 |
|
||||
| **总体覆盖率** | **22%** |
|
||||
|
||||
---
|
||||
|
||||
## 按模块覆盖率(从低到高)
|
||||
|
||||
### 🔴 严重不足(< 20%)— 占总代码量 71%
|
||||
|
||||
| 模块 | 代码行 | 覆盖率 | 未覆盖行 | 说明 |
|
||||
|------|--------|--------|---------|------|
|
||||
| `cli/parser.py` | 114 | **4%** | 110 | CLI 参数定义,仅被 import 触发 |
|
||||
| `commands/crud.py` | 560 | **5%** | 533 | **最大模块**,5 个核心命令零覆盖 |
|
||||
| `commands/batch.py` | 230 | **7%** | 215 | 批量操作 3 个命令零覆盖 |
|
||||
| `client.py` | 716 | **7%** | 665 | **第二大的**,30 个 API 方法零覆盖 |
|
||||
| `commands/cds.py` | 126 | **10%** | 114 | CDS 命令零覆盖 |
|
||||
| `commands/quality.py` | 94 | **10%** | 85 | check + format 零覆盖 |
|
||||
| `commands/search.py` | 100 | **10%** | 90 | list/whereused/search 零覆盖 |
|
||||
| `commands/transport.py` | 100 | **10%** | 90 | 传输管理零覆盖 |
|
||||
| `commands/config_cmd.py` | 75 | **11%** | 67 | config 命令零覆盖 |
|
||||
| `commands/package_cmd.py` | 78 | **12%** | 69 | package 命令零覆盖 |
|
||||
| `commands/diff_cmd.py` | 59 | **17%** | 49 | diff 命令零覆盖 |
|
||||
| `auth.py` | 99 | **31%** | 68 | keyring 方法被测,cmd_auth 未测 |
|
||||
|
||||
**小计:2,451 行未覆盖(占总量 81%)**
|
||||
|
||||
### 🟡 部分覆盖(20%–79%)
|
||||
|
||||
| 模块 | 代码行 | 覆盖率 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| `commands/analyze.py` | 62 | **34%** | 解析逻辑被测,命令入口未测 |
|
||||
| `commands/scaffold.py` | 64 | **34%** | 4 个模板生成被测,命令入口未测 |
|
||||
| `cli/output.py` | 25 | **60%** | print 函数被部分调用 |
|
||||
| `scanner.py` | 52 | **62%** | 基础扫描被测,函数目录未测 |
|
||||
| `manifest.py` | 82 | **70%** | CRUD 被测,文件 I/O 边界未测 |
|
||||
| `config.py` | 62 | **77%** | 环境变量被测,profile/load 未测 |
|
||||
|
||||
### 🟢 良好(≥ 80%)
|
||||
|
||||
| 模块 | 代码行 | 覆盖率 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| `exceptions.py` | 35 | **83%** | 核心异常被测 |
|
||||
| `ddic.py` | 128 | **84%** | XML/DDL 构建被测 |
|
||||
| `types.py` | 69 | **90%** | 注册+解析被测,新注册 6 种未测 |
|
||||
| `sorter.py` | 68 | **96%** | 几乎全覆盖 |
|
||||
| `xml_utils.py` | 5 | **100%** | 全覆盖 |
|
||||
| `__init__.py` 系列 | 15 | **100%** | 全覆盖 |
|
||||
|
||||
---
|
||||
|
||||
## 三层覆盖率分析
|
||||
|
||||
```
|
||||
项目架构 代码行 覆盖率 未覆盖
|
||||
──────────────────────────────────────────────
|
||||
CLI 层 parser+app 139 5% 132
|
||||
命令层 commands/* 1,548 8% 1,424
|
||||
API 层 client.py 716 7% 665
|
||||
基础层 其他模块 615 74% 159
|
||||
──────────────────────────────────────────────
|
||||
合计 3,018 22% 2,344
|
||||
```
|
||||
|
||||
### 关键发现
|
||||
|
||||
1. **CLI 层 + 命令层 + API 层 = 2,403 行,覆盖率仅 7%**
|
||||
— 这三层占总代码量的 80%,但几乎没有测试
|
||||
— 原因:现有 68 用例全部测的是基础层(types/config/ddic/manifest/sorter)
|
||||
|
||||
2. **基础层覆盖率 74%** — 已经不错
|
||||
— types.py 的 6 种新注册类型需要补充测试
|
||||
— config.py 的 `load_config(profile=...)` 需要补充测试
|
||||
|
||||
3. **单文件代码量 Top 3**:
|
||||
- `client.py` (716行) — 0 测试 ← 最大风险点
|
||||
- `commands/crud.py` (560行) — 0 测试
|
||||
- `commands/batch.py` (230行) — 0 测试
|
||||
|
||||
---
|
||||
|
||||
## 提升路径
|
||||
|
||||
| 目标覆盖率 | 需要新增测试 | 预计工时 |
|
||||
|-----------|-------------|---------|
|
||||
| 50% | client.py + commands 核心方法 (~130 用例) | 6h |
|
||||
| 70% | 所有命令 + cli 入口 (~90 用例) | 4h |
|
||||
| 80% | 边界条件 + 错误路径 (~50 用例) | 3h |
|
||||
| 90% | 极端场景 + 集成测试 (~40 用例) | 3h |
|
||||
|
||||
> HTML 详细报告已生成:`tests/coverage_html/index.html`
|
||||
@@ -0,0 +1,344 @@
|
||||
# sap-cli 测试方案
|
||||
|
||||
> 版本:2.1 | 20 命令 · 16 类型 · 30 API 方法
|
||||
> 日期:2026-06-09
|
||||
|
||||
---
|
||||
|
||||
## 一、现状分析
|
||||
|
||||
### 1.1 代码架构(四层)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ CLI 层 parser.py + app.py │ ← 参数解析、命令路由
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ 命令层 commands/*.py (11 模块, 20 命令) │ ← 业务编排
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ API 层 client.py (30 方法) │ ← ADT REST 通信
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ 基础层 types/config/auth/ddic/manifest/... │ ← 纯逻辑、无网络
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 现有测试
|
||||
|
||||
| 文件 | 行数 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `tests/test_sapcli.py` | 695 | ✅ 活跃 | 68 用例(63 pass + 5 skip) |
|
||||
| `tests/test_batch.py` | 840 | ⚠️ 过时 | 旧版批量测试,API 签名已变 |
|
||||
| `tests/test_main.py` | 833 | ⚠️ 过时 | 旧版 E2E,引用旧入口 |
|
||||
|
||||
**test_sapcli.py 覆盖的模块:**
|
||||
- ✅ types.py(注册、解析)— 6 用例
|
||||
- ✅ config.py(环境变量)— 2 用例
|
||||
- ✅ auth.py(keyring 降级)— 4 用例
|
||||
- ✅ ddic.py(XML/DDL 构建)— 7 用例
|
||||
- ✅ manifest.py(增删查)— 5 用例
|
||||
- ✅ scanner.py(目录扫描)— 2 用例
|
||||
- ✅ sorter.py(拓扑排序)— 4 用例
|
||||
- ✅ exceptions.py — 2 用例
|
||||
- ✅ scaffold.py(模板生成)— 4 用例
|
||||
- ✅ xml_utils.py — 4 用例
|
||||
- ✅ XML 注入防御 — 3 用例
|
||||
- ✅ 模块结构检查 — 5 用例
|
||||
- ✅ OpenSpec 文件检查 — 3 用例
|
||||
- ✅ output.py — 3 用例
|
||||
- ✅ analyze.py — 4 用例
|
||||
|
||||
**未覆盖的模块(缺口):**
|
||||
- ❌ client.py(30 个 API 方法)— 0 用例
|
||||
- ❌ commands/*.py(11 个命令模块)— 0 用例
|
||||
- ❌ cli/parser.py(参数校验)— 0 用例
|
||||
- ❌ cli/app.py(路由逻辑)— 0 用例
|
||||
|
||||
---
|
||||
|
||||
## 二、测试分层设计
|
||||
|
||||
### 分层策略
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ E2E 测试 │ ← 真实 SAP 系统
|
||||
│ (手动) │ 仅在上线前执行
|
||||
├─────────────┤
|
||||
│ 集成测试 │ ← Mock HTTP Server
|
||||
│ (自动化) │ 验证 client↔ADT 通信
|
||||
├─────────────┤
|
||||
│ 单元测试 │ ← unittest.mock
|
||||
│ (自动化) │ 验证各模块独立逻辑
|
||||
├─────────────┤
|
||||
│ 静态分析 │ ← pylint / mypy
|
||||
│ (CI 自动) │ 语法、类型、规范
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
| 层级 | 目标 | 技术 | 运行频率 | 依赖 |
|
||||
|------|------|------|---------|------|
|
||||
| 静态分析 | 语法/类型/规范 | pylint, mypy | 每次 commit | 无 |
|
||||
| 单元测试 | 模块独立逻辑 | unittest + mock | 每次 commit | 无 |
|
||||
| 集成测试 | API 通信正确性 | unittest + responses/mock HTTP | 每次 commit | 无 |
|
||||
| E2E 测试 | 端到端真实流程 | 手动执行 | 发布前 | SAP 系统 |
|
||||
|
||||
---
|
||||
|
||||
## 三、单元测试设计(优先级 P0)
|
||||
|
||||
> 目标:每个模块的纯逻辑都有测试,不依赖网络。
|
||||
|
||||
### 3.1 保留现有 68 用例
|
||||
|
||||
全部保留,但修复 5 个 skip:
|
||||
- `test_multi_profile` / `test_profile_fallback`:现在 config.py 已支持 `--profile`,应改为 pass
|
||||
- `test_list_objects` / `test_pretty_print` / `test_transport_info`:方法已加回 client.py,应取消 skip
|
||||
|
||||
### 3.2 新增用例清单
|
||||
|
||||
#### A. client.py — API 层(30 方法)
|
||||
|
||||
每个方法测 3 个场景:成功响应、HTTP 错误、XML 解析。
|
||||
|
||||
| 方法 | 正常用例 | 错误用例 | mock 要点 |
|
||||
|------|---------|---------|-----------|
|
||||
| `login()` | 返回有效 session | 401 → LoginError | mock session.get |
|
||||
| `lock()` | 返回 lock_handle + corr_nr | 403 → ObjectLockedError | mock session.post |
|
||||
| `unlock()` | 返回 True | 非 200 返回 False | mock session.post |
|
||||
| `set_source()` | 返回 True | 非 200 返回 False | mock session.put |
|
||||
| `get_source()` | 返回源码字符串 | 404 → ObjectNotFoundError | mock session.get |
|
||||
| `delete_object()` | 返回 (True, msg) | 非 200 (False, msg) | mock session.delete |
|
||||
| `create_object()` | 返回 (obj_uri, src_uri) | 409 → ObjectAlreadyExistsError | mock session.post |
|
||||
| `activate()` | 返回 (True, []) | 返回 (False, [errors]) | mock session.post |
|
||||
| `syntax_check()` | 返回 (True, []) | 返回 (False, [errors]) | mock session.post |
|
||||
| `get_object_status()` | 返回 status dict | 404 → ObjectNotFoundError | mock session.get |
|
||||
| `object_exists()` | 返回 True/False | — | mock session.get |
|
||||
| `list_objects()` | 返回 list[dict] | 空 → [] | mock session.get |
|
||||
| `where_used()` | 返回 list[dict] | 无引用 → [] | mock session.post |
|
||||
| `search_code()` | 返回 list[dict] | 无结果 → [] | mock session.post |
|
||||
| `read_source_for_diff()` | 委托 get_source | — | mock self.get_source |
|
||||
| `get_cds_source()` | 返回 DDL 字符串 | 404 → ObjectNotFoundError | mock session.get |
|
||||
| `create_cds()` | 创建+写入+返回 URI | 失败 → SapCliError | mock lock/set_source/unlock |
|
||||
| `create_package()` | 返回 True | 非 200 → False | mock session.post |
|
||||
| `get_package_info()` | 返回 dict | 404 → SapCliError | mock session.get |
|
||||
| `transport_info()` | 返回 dict | 404 → SapCliError | mock session.get |
|
||||
| `transport_release()` | 返回 True | 非 200 → False | mock session.post |
|
||||
| `transport_objects()` | 返回 list[dict] | 空 → [] | mock session.get |
|
||||
| `atc_check()` | 返回 (bool, list) | 无发现 → (True, []) | mock session.post |
|
||||
| `pretty_print()` | 返回格式化源码 | 失败 → SapCliError | mock session.post |
|
||||
| `list_transport_requests()` | 返回 list[dict] | 空 → [] | mock session.get |
|
||||
| `create_transport_request()` | 返回 corr_nr | 失败 → SapCliError | mock session.post |
|
||||
| `get_transport_request()` | 返回 corr_nr 或 None | 空 → None | mock session.get |
|
||||
| `create_function_group()` | 成功 | 失败 → SapCliError | mock session.post |
|
||||
| `function_group_exists()` | True/False | — | mock session.get |
|
||||
| `create_ddic_object()` | 成功写入 | 失败 → SapCliError | mock self._put_ddic_xml |
|
||||
|
||||
**小计:~90 用例**
|
||||
|
||||
#### B. commands/*.py — 命令层(11 模块)
|
||||
|
||||
每个命令测:正常流程 + 错误处理 + 参数缺失。
|
||||
|
||||
| 命令 | 模块 | 正常用例 | 错误用例 |
|
||||
|------|------|---------|---------|
|
||||
| `create` | crud.py | 创建成功(4种模板+--source+--definition) | 对象已存在、类型不支持 |
|
||||
| `download` | crud.py | 下载成功、文件名正确 | 对象不存在、无源码 URI |
|
||||
| `sync` | crud.py | 五步同步成功 | 语法检查失败、激活失败 |
|
||||
| `info` | crud.py | 显示元数据 | 对象不存在 |
|
||||
| `delete` | crud.py | 确认删除、取消删除 | 对象不存在 |
|
||||
| `init` | batch.py | 扫描→生成 manifest | 空目录、无 SAP 连接 |
|
||||
| `refresh` | batch.py | 更新状态 | — |
|
||||
| `sync --all` | batch.py | 批量同步+拓扑排序 | 循环依赖、fail-fast |
|
||||
| `list` | search.py | 列出对象 | 空 |
|
||||
| `whereused` | search.py | 找到引用 | 无引用 |
|
||||
| `search` | search.py | 搜索结果 | 无结果 |
|
||||
| `diff` | diff_cmd.py | 有差异/无差异 | — |
|
||||
| `cds` | cds.py | 下载DDL/创建CDS | — |
|
||||
| `package` | package_cmd.py | 创建/查询包 | — |
|
||||
| `transport` | transport.py | 列出/详情/释放 | — |
|
||||
| `check` | quality.py | ATC检查有/无发现 | — |
|
||||
| `format` | quality.py | 格式化成功 | — |
|
||||
| `analyze` | analyze.py | 分析依赖 | 无依赖 |
|
||||
| `scaffold` | scaffold.py | 4种模板+无模板列出 | — |
|
||||
| `config` | config_cmd.py | show/list-profiles/set | — |
|
||||
| `auth` | auth.py | login/logout/status | keyring 不可用 |
|
||||
|
||||
**小计:~60 用例**
|
||||
|
||||
#### C. cli/ — 入口层
|
||||
|
||||
| 测试场景 | 说明 |
|
||||
|---------|------|
|
||||
| 无参数 → exit(1) + 帮助 | `app.py` 检测 no command |
|
||||
| --profile DEV | 读 [DEV] section |
|
||||
| --config 自定义路径 | 读指定文件 |
|
||||
| config set host 1.2.3.4 | 写入 config.ini |
|
||||
| config set invalid_key | 报错 |
|
||||
| auth status | keyring 状态 |
|
||||
| scaffold 无 --template | 列出模板 |
|
||||
| scaffold --template xxx --name ZZZ | 生成模板 |
|
||||
|
||||
**小计:~10 用例**
|
||||
|
||||
### 3.3 单元测试总计
|
||||
|
||||
| 类别 | 现有 | 新增 | 合计 |
|
||||
|------|------|------|------|
|
||||
| 基础层 | 68 | 0 | 68 |
|
||||
| API 层 (client.py) | 0 | ~90 | 90 |
|
||||
| 命令层 (commands/) | 0 | ~60 | 60 |
|
||||
| 入口层 (cli/) | 0 | ~10 | 10 |
|
||||
| **总计** | **68** | **~160** | **~228** |
|
||||
|
||||
---
|
||||
|
||||
## 四、集成测试设计(优先级 P1)
|
||||
|
||||
> 目标:验证 client.py 与真实 ADT API 的通信协议是否正确。
|
||||
|
||||
### 4.1 技术方案:`responses` 库 mock HTTP
|
||||
|
||||
```python
|
||||
import responses
|
||||
|
||||
@responses.activate
|
||||
def test_login_success():
|
||||
responses.add(responses.GET, "https://sap.example.com/sap/bc/adt/...",
|
||||
status=200, headers={"x-csrf-token": "TOKEN123"})
|
||||
client = ADTClient("sap.example.com", "100", "USER", "PASS")
|
||||
client.login()
|
||||
assert client.csrf_token == "TOKEN123"
|
||||
```
|
||||
|
||||
### 4.2 集成测试用例
|
||||
|
||||
按 ADT API 端点分组,验证 HTTP 方法和 XML 请求体:
|
||||
|
||||
| 端点组 | 测试场景 | 数量 |
|
||||
|--------|---------|------|
|
||||
| 认证 | login → CSRF token 获取 | 2 |
|
||||
| 锁管理 | lock/unlock → 正确的 If-Match header | 4 |
|
||||
| 源码读写 | get/set_source → 正确的 Content-Type | 4 |
|
||||
| 对象 CRUD | create/delete → 正确的 XML body | 6 |
|
||||
| DDIC | create_ddic → 正确的 XML namespace | 4 |
|
||||
| 激活/检查 | activate/syntax_check → 正确的 XML 响应解析 | 4 |
|
||||
| 搜索 | list/search/whereused → URL 参数编码 | 4 |
|
||||
| 传输 | transport list/info/release → URL 拼接 | 4 |
|
||||
| 质量 | atc_check/pretty_print → 请求体格式 | 4 |
|
||||
| **合计** | | **~36** |
|
||||
|
||||
---
|
||||
|
||||
## 五、E2E 测试设计(优先级 P2)
|
||||
|
||||
> 目标:端到端验证完整用户场景。
|
||||
|
||||
### 5.1 前置条件
|
||||
|
||||
- 需要一台可连接的 SAP 系统
|
||||
- 使用测试用户和 `$TMP` 包
|
||||
- 所有操作可逆(删除测试对象)
|
||||
|
||||
### 5.2 测试场景
|
||||
|
||||
| # | 场景 | 步骤 | 验证点 |
|
||||
|---|------|------|--------|
|
||||
| E1 | 完整 CRUD | create → info → download → sync → delete | 每步输出正确,对象最终不存在 |
|
||||
| E2 | DDIC 对象 | create domain → create dataelement → create table | 依赖顺序正确 |
|
||||
| E3 | 批量同步 | init → 修改本地文件 → sync --all | 所有对象同步成功 |
|
||||
| E4 | 多 Profile | config set → --profile DEV → download | 读取正确配置 |
|
||||
| E5 | 传输管理 | create transport → create object → release | 传输号关联正确 |
|
||||
|
||||
**E2E 测试建议用手动执行**,因为:
|
||||
- 依赖真实 SAP 系统可用性
|
||||
- 测试数据需要隔离
|
||||
- 某些操作不可逆(如 transport release)
|
||||
|
||||
### 5.3 E2E 自动化(可选)
|
||||
|
||||
如果 SAP 测试系统长期可用,可用 `tests/e2e_project/` 现有的测试数据做半自动 E2E:
|
||||
|
||||
```bash
|
||||
python main.py init --path tests/e2e_project
|
||||
python main.py sync --all --path tests/e2e_project
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、现有测试处理
|
||||
|
||||
| 文件 | 处理 | 原因 |
|
||||
|------|------|------|
|
||||
| `tests/test_sapcli.py` | ✅ 保留并扩展 | 当前唯一活跃测试 |
|
||||
| `tests/test_batch.py` | ❌ 废弃 | 840 行,API 签名已变,与新架构不兼容 |
|
||||
| `tests/test_main.py` | ❌ 废弃 | 833 行,引用旧入口 `src/` 路径 |
|
||||
| `tests/test_e2e.md` | ✅ 保留 | E2E 手动测试说明仍有参考价值 |
|
||||
|
||||
---
|
||||
|
||||
## 七、测试文件组织
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # 共享 fixture(SAPConfig mock、临时目录)
|
||||
├── test_sapcli.py # 基础层测试(保留现有 68 → 修复 5 skip)
|
||||
├── unit/ # 新增:单元测试
|
||||
│ ├── test_client.py # client.py 30 方法
|
||||
│ ├── test_commands.py # commands/ 11 模块
|
||||
│ └── test_cli.py # parser + app 路由
|
||||
├── integration/ # 新增:集成测试
|
||||
│ ├── test_adt_auth.py # 认证通信
|
||||
│ ├── test_adt_crud.py # CRUD 通信
|
||||
│ ├── test_adt_search.py # 搜索通信
|
||||
│ ├── test_adt_transport.py # 传输通信
|
||||
│ └── test_adt_quality.py # 质量通信
|
||||
├── e2e/ # E2E 手动测试
|
||||
│ └── test_e2e.md # 手动测试说明
|
||||
├── e2e_project/ # E2E 测试数据
|
||||
│ ├── manifest.json
|
||||
│ ├── reports/
|
||||
│ ├── classes/
|
||||
│ └── functions/
|
||||
└── fixtures/ # 测试固件
|
||||
├── adt_responses/ # ADT XML 响应样本
|
||||
│ ├── login_success.xml
|
||||
│ ├── object_info.xml
|
||||
│ └── syntax_errors.xml
|
||||
└── sample_source/ # 示例 ABAP 源码
|
||||
├── zhello.abap
|
||||
└── zcl_class.abap
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、实施优先级
|
||||
|
||||
| 阶段 | 内容 | 用例数 | 预计工时 |
|
||||
|------|------|--------|---------|
|
||||
| **P0** | 修复 5 个 skip + 清理旧测试 | +5 pass, -2 文件 | 1h |
|
||||
| **P0** | client.py 单元测试 | ~90 | 4h |
|
||||
| **P1** | commands/ 单元测试 | ~60 | 3h |
|
||||
| **P1** | cli/ 入口测试 | ~10 | 1h |
|
||||
| **P2** | 集成测试(mock HTTP) | ~36 | 3h |
|
||||
| **P3** | E2E 手动测试 | 5 场景 | 2h |
|
||||
| | **合计** | **~228** | **~14h** |
|
||||
|
||||
### 立即可做(本次)
|
||||
|
||||
1. 修复 5 个 skip 用例 → 68 pass
|
||||
2. 清理旧测试文件 → 减少 1673 行过时代码
|
||||
3. 新建测试目录结构
|
||||
4. 从 client.py 核心方法开始写单元测试
|
||||
|
||||
---
|
||||
|
||||
## 九、关键技术选型
|
||||
|
||||
| 工具 | 用途 | 是否需要安装 |
|
||||
|------|------|-------------|
|
||||
| `unittest` | 测试框架 | ✅ 内置 |
|
||||
| `unittest.mock` | mock client/requests | ✅ 内置 |
|
||||
| `responses` | mock HTTP 响应(集成测试) | 需要 `pip install responses` |
|
||||
| `tempfile` | 临时文件/目录 | ✅ 内置 |
|
||||
| `pytest` | 可选:更简洁的断言 + fixture | 需要 `pip install pytest` |
|
||||
|
||||
**建议**:保持 `unittest` 框架(不引入 pytest),减少外部依赖。集成测试用 `unittest.mock.patch` 替代 `responses`。
|
||||
@@ -0,0 +1,241 @@
|
||||
# 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 头 |
|
||||
Reference in New Issue
Block a user