方向反转:此前 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 重写为单源开发流程。
601 lines
26 KiB
Python
601 lines
26 KiB
Python
"""commands/ 模块单元测试
|
||
|
||
运行: python tests/unit/test_commands.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
import tempfile
|
||
import unittest
|
||
from unittest.mock import MagicMock, patch, PropertyMock
|
||
|
||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "assets"))
|
||
|
||
from sapcli.client import ADTClient
|
||
|
||
|
||
def _mock_resp(status_code=200, text="", content=b"", headers=None):
|
||
r = MagicMock()
|
||
r.status_code = status_code
|
||
r.text = text
|
||
r.content = content
|
||
r.headers = headers or {}
|
||
r.raise_for_status = MagicMock()
|
||
return r
|
||
|
||
|
||
def _make_client():
|
||
"""创建完整 mock 的 ADTClient。"""
|
||
client = MagicMock()
|
||
client.host = "https://sap.example.com"
|
||
client.sap_client = "100"
|
||
client.csrf_token = "test-csrf-token"
|
||
client.user = "TESTUSER"
|
||
# client.session 用于直接 HTTP 调用
|
||
client.session = MagicMock()
|
||
client._headers.return_value = {
|
||
"content-type": "application/xml",
|
||
"x-csrf-token": "test-csrf-token",
|
||
}
|
||
return client
|
||
|
||
|
||
def _args(**kwargs):
|
||
defaults = {"type": "report", "name": "ZTEST", "path": ".", "config": None}
|
||
defaults.update(kwargs)
|
||
return argparse.Namespace(**defaults)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# crud.py — download
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdDownload(unittest.TestCase):
|
||
|
||
def test_download_report_success(self):
|
||
from sapcli.commands.crud import cmd_download
|
||
client = _make_client()
|
||
client.object_exists.return_value = True
|
||
client.get_source.return_value = "REPORT ztest.\nWRITE: / 'hello'."
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_download(_args(path=td), client)
|
||
self.assertTrue(os.path.isfile(os.path.join(td, "ztest.abap")))
|
||
|
||
def test_download_object_not_found(self):
|
||
from sapcli.commands.crud import cmd_download
|
||
from sapcli.exceptions import ObjectNotFoundError
|
||
client = _make_client()
|
||
client.object_exists.return_value = False
|
||
with tempfile.TemporaryDirectory() as td:
|
||
with self.assertRaises(ObjectNotFoundError):
|
||
cmd_download(_args(path=td), client)
|
||
|
||
def test_download_creates_dir(self):
|
||
from sapcli.commands.crud import cmd_download
|
||
client = _make_client()
|
||
client.object_exists.return_value = True
|
||
client.get_source.return_value = "REPORT ztest."
|
||
with tempfile.TemporaryDirectory() as td:
|
||
subdir = os.path.join(td, "sub", "dir")
|
||
cmd_download(_args(path=subdir), client)
|
||
self.assertTrue(os.path.isfile(os.path.join(subdir, "ztest.abap")))
|
||
|
||
def test_download_class(self):
|
||
from sapcli.commands.crud import cmd_download
|
||
client = _make_client()
|
||
client.object_exists.return_value = True
|
||
client.get_source.return_value = "CLASS zcl_test DEFINITION."
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_download(_args(type="class", name="ZCL_TEST", path=td), client)
|
||
self.assertTrue(os.path.isfile(os.path.join(td, "zcl_test.abap")))
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# crud.py — sync
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdSync(unittest.TestCase):
|
||
|
||
def test_sync_report_success(self):
|
||
from sapcli.commands.crud import cmd_sync
|
||
client = _make_client()
|
||
client.lock.return_value = ("lh_1", "DEVK001")
|
||
client.set_source.return_value = True
|
||
client.unlock.return_value = True
|
||
client.activate.return_value = (True, [])
|
||
with tempfile.NamedTemporaryFile(mode="w", suffix=".abap", delete=False, encoding="utf-8") as f:
|
||
f.write("REPORT ztest.\nWRITE: / 'hello'.")
|
||
tmpfile = f.name
|
||
try:
|
||
cmd_sync(_args(path=tmpfile, project_path=None), client)
|
||
client.lock.assert_called_once()
|
||
client.set_source.assert_called_once()
|
||
finally:
|
||
os.unlink(tmpfile)
|
||
|
||
def test_sync_file_not_found(self):
|
||
from sapcli.commands.crud import cmd_sync
|
||
from sapcli.exceptions import ConfigError
|
||
client = _make_client()
|
||
with self.assertRaises(ConfigError):
|
||
cmd_sync(_args(path="/nonexistent/file.abap"), client)
|
||
|
||
def test_sync_with_check(self):
|
||
from sapcli.commands.crud import cmd_sync
|
||
client = _make_client()
|
||
client.lock.return_value = ("lh_1", "DEVK001")
|
||
client.set_source.return_value = True
|
||
client.unlock.return_value = True
|
||
client.activate.return_value = (True, [])
|
||
client.syntax_check.return_value = (True, [])
|
||
with tempfile.NamedTemporaryFile(mode="w", suffix=".abap", delete=False, encoding="utf-8") as f:
|
||
f.write("REPORT ztest.")
|
||
tmpfile = f.name
|
||
try:
|
||
cmd_sync(_args(path=tmpfile, check=True, project_path=None), client)
|
||
client.syntax_check.assert_called_once()
|
||
finally:
|
||
os.unlink(tmpfile)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# crud.py — info(直接用 client.session.get)
|
||
# ═══════════════════════════════════════════
|
||
|
||
_INFO_XML = (
|
||
b'<?xml version="1.0"?>'
|
||
b'<adtcore:object xmlns:adtcore="http://www.sap.com/adt/core"'
|
||
b' adtcore:name="ZTEST" adtcore:type="PROG/P"'
|
||
b' adtcore:version="active" adtcore:description="Test report"'
|
||
b' adtcore:masterLanguage="EN"/>'
|
||
)
|
||
|
||
class TestCmdInfo(unittest.TestCase):
|
||
|
||
def test_info_success(self):
|
||
from sapcli.commands.crud import cmd_info
|
||
client = _make_client()
|
||
client.session.get.return_value = _mock_resp(200, content=_INFO_XML)
|
||
cmd_info(_args(), client)
|
||
client.session.get.assert_called()
|
||
|
||
def test_info_not_exists(self):
|
||
from sapcli.commands.crud import cmd_info
|
||
from sapcli.exceptions import ObjectNotFoundError
|
||
client = _make_client()
|
||
client.session.get.return_value = _mock_resp(404)
|
||
with self.assertRaises(ObjectNotFoundError):
|
||
cmd_info(_args(), client)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# crud.py — delete
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdDelete(unittest.TestCase):
|
||
|
||
@patch("builtins.input", return_value="yes")
|
||
@patch("sapcli.commands.crud.Manifest")
|
||
def test_delete_success(self, MockManifest, mock_input):
|
||
from sapcli.commands.crud import cmd_delete
|
||
client = _make_client()
|
||
client.object_exists.return_value = True
|
||
client.delete_object.return_value = (True, "")
|
||
cmd_delete(_args(), client)
|
||
client.delete_object.assert_called_once()
|
||
|
||
def test_delete_failure(self):
|
||
from sapcli.commands.crud import cmd_delete
|
||
from sapcli.exceptions import DeleteError
|
||
client = _make_client()
|
||
client.object_exists.return_value = True
|
||
client.delete_object.side_effect = DeleteError("删除失败")
|
||
with self.assertRaises(DeleteError), patch("builtins.input", return_value="yes"):
|
||
cmd_delete(_args(), client)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# crud.py — create
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdCreate(unittest.TestCase):
|
||
|
||
@patch("sapcli.commands.crud._select_transport_request", return_value="DEVK001")
|
||
@patch("sapcli.commands.crud.Manifest")
|
||
def test_create_report_no_source(self, MockManifest, mock_transport):
|
||
from sapcli.commands.crud import cmd_create
|
||
client = _make_client()
|
||
client.object_exists.return_value = False
|
||
client.create_object.return_value = ("/uri/ztest", "/uri/ztest/source/main")
|
||
cmd_create(_args(source=None, description="Test", package="$TMP", corr_nr=None), client)
|
||
client.create_object.assert_called_once()
|
||
|
||
@patch("sapcli.commands.crud._select_transport_request", return_value="DEVK001")
|
||
@patch("sapcli.commands.crud.Manifest")
|
||
def test_create_with_source_file(self, MockManifest, mock_transport):
|
||
from sapcli.commands.crud import cmd_create
|
||
client = _make_client()
|
||
client.object_exists.return_value = False
|
||
client.create_object.return_value = ("/uri/ztest", "/uri/ztest/source/main")
|
||
client.lock.return_value = ("lh_1", "DEVK001")
|
||
client.set_source.return_value = True
|
||
client.unlock.return_value = True
|
||
client.activate.return_value = (True, [])
|
||
with tempfile.NamedTemporaryFile(mode="w", suffix=".abap", delete=False, encoding="utf-8") as f:
|
||
f.write("REPORT ztest.")
|
||
tmpfile = f.name
|
||
try:
|
||
cmd_create(_args(source=tmpfile, description="Test", package="$TMP", corr_nr=None), client)
|
||
finally:
|
||
os.unlink(tmpfile)
|
||
|
||
@patch("sapcli.commands.crud._select_transport_request", return_value="DEVK001")
|
||
@patch("sapcli.commands.crud.Manifest")
|
||
def test_create_ddic_type(self, MockManifest, mock_transport):
|
||
from sapcli.commands.crud import cmd_create
|
||
client = _make_client()
|
||
client.object_exists.return_value = False
|
||
client.create_ddic_object.return_value = ("/uri/ztest_dom", "/uri/ztest_dom/source/main")
|
||
cmd_create(_args(type="domain", name="ZTEST_DOM", source=None, description="Domain", package="$TMP", corr_nr=None), client)
|
||
client.create_ddic_object.assert_called_once()
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# crud.py — print_source_preview
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestPrintSourcePreview(unittest.TestCase):
|
||
|
||
def test_preview_short(self):
|
||
from sapcli.commands.crud import print_source_preview
|
||
with patch("builtins.print"):
|
||
print_source_preview("REPORT ztest.\nWRITE: / 'hello'.")
|
||
|
||
def test_preview_long(self):
|
||
from sapcli.commands.crud import print_source_preview
|
||
long_src = "\n".join(f"LINE {i}" for i in range(100))
|
||
with patch("builtins.print"):
|
||
print_source_preview(long_src, max_lines=5)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# batch.py — init
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdInit(unittest.TestCase):
|
||
|
||
def test_init_empty_dir_no_crash(self):
|
||
"""空目录不崩溃(不扫描到 .abap 文件则提前返回)。"""
|
||
from sapcli.commands.batch import cmd_init
|
||
client = _make_client()
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_init(argparse.Namespace(name="ZTEST", type="report", path=td, config=None), client)
|
||
# 空目录无 .abap 文件 → 不创建 manifest → 不崩溃即可
|
||
|
||
def test_init_with_abap_file(self):
|
||
"""有 .abap 文件时,初始化应创建 manifest。"""
|
||
from sapcli.commands.batch import cmd_init
|
||
client = _make_client()
|
||
client.get_object_status.return_value = {"exists": True, "status": "active", "corr_nr": None}
|
||
with tempfile.TemporaryDirectory() as td:
|
||
report_dir = os.path.join(td, "reports")
|
||
os.makedirs(report_dir)
|
||
with open(os.path.join(report_dir, "ztest.abap"), "w", encoding="utf-8") as f:
|
||
f.write("REPORT ztest.")
|
||
cmd_init(argparse.Namespace(name="ZTEST", type="report", path=td, config=None), client)
|
||
self.assertTrue(os.path.isfile(os.path.join(td, "manifest.json")))
|
||
|
||
|
||
class TestCmdRefresh(unittest.TestCase):
|
||
|
||
def test_refresh_success(self):
|
||
from sapcli.commands.batch import cmd_refresh
|
||
from sapcli.manifest import Manifest, ManifestEntry
|
||
client = _make_client()
|
||
client.get_object_status.return_value = {
|
||
"exists": True, "status": "active", "corr_nr": None
|
||
}
|
||
with tempfile.TemporaryDirectory() as td:
|
||
# 直接写 manifest.json 文件,供 Manifest.load() 读取
|
||
entry = ManifestEntry(
|
||
name="ZTEST", type="report",
|
||
file="reports/ztest.abap",
|
||
system_status="not_exists",
|
||
)
|
||
import json
|
||
manifest_data = {
|
||
"version": 2,
|
||
"objects": {"ZTEST": entry.to_dict()},
|
||
}
|
||
manifest_path = os.path.join(td, "manifest.json")
|
||
with open(manifest_path, "w", encoding="utf-8") as f:
|
||
json.dump(manifest_data, f)
|
||
os.makedirs(os.path.join(td, "reports"))
|
||
cmd_refresh(argparse.Namespace(path=td, config=None), client)
|
||
client.get_object_status.assert_called()
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# cds.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdCds(unittest.TestCase):
|
||
|
||
def test_cds_download(self):
|
||
from sapcli.commands.cds import cmd_cds
|
||
client = _make_client()
|
||
client.get_cds_source.return_value = "define view Z_TEST as select from mara { mara.matnr };"
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_cds(argparse.Namespace(cds_action="download", name="Z_TEST", path=td, config=None), client)
|
||
client.get_cds_source.assert_called_once()
|
||
|
||
def test_cds_create(self):
|
||
from sapcli.commands.cds import cmd_cds
|
||
client = _make_client()
|
||
client.create_cds.return_value = ("/uri/z_test", "/uri/z_test/source/main")
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_cds(argparse.Namespace(
|
||
cds_action="create", name="Z_TEST", description="Test CDS",
|
||
source=None, path=td, config=None
|
||
), client)
|
||
client.create_cds.assert_called_once()
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# config_cmd.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdConfig(unittest.TestCase):
|
||
|
||
def test_config_show(self):
|
||
from sapcli.commands.config_cmd import cmd_config
|
||
with patch("sapcli.commands.config_cmd._config_show"):
|
||
cmd_config(argparse.Namespace(config_action="show", config=None))
|
||
|
||
def test_config_list_profiles(self):
|
||
from sapcli.commands.config_cmd import cmd_config
|
||
with patch("sapcli.commands.config_cmd._config_list_profiles"):
|
||
cmd_config(argparse.Namespace(config_action="list-profiles", config=None))
|
||
|
||
def test_config_set(self):
|
||
from sapcli.commands.config_cmd import cmd_config
|
||
with patch("sapcli.commands.config_cmd._config_set"):
|
||
cmd_config(argparse.Namespace(config_action="set", key="host", value="http://new:8000", config=None))
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# diff_cmd.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdDiff(unittest.TestCase):
|
||
|
||
def test_diff_success(self):
|
||
from sapcli.commands.diff_cmd import cmd_diff
|
||
client = _make_client()
|
||
client.read_source_for_diff.return_value = "REPORT zremote."
|
||
with tempfile.NamedTemporaryFile(mode="w", suffix=".abap", delete=False, encoding="utf-8") as f:
|
||
f.write("REPORT zlocal.")
|
||
tmpfile = f.name
|
||
try:
|
||
cmd_diff(argparse.Namespace(name="ZTEST", type="report", path=tmpfile, config=None), client)
|
||
client.read_source_for_diff.assert_called_once()
|
||
finally:
|
||
os.unlink(tmpfile)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# package_cmd.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdPackage(unittest.TestCase):
|
||
|
||
def test_package_info(self):
|
||
from sapcli.commands.package_cmd import cmd_package
|
||
client = _make_client()
|
||
client.get_package_info.return_value = {"name": "Z_TEST", "description": "Test"}
|
||
cmd_package(argparse.Namespace(package_action="info", name="Z_TEST", config=None), client)
|
||
client.get_package_info.assert_called_once()
|
||
|
||
def test_package_create(self):
|
||
from sapcli.commands.package_cmd import cmd_package
|
||
client = _make_client()
|
||
client.create_package.return_value = True
|
||
cmd_package(argparse.Namespace(package_action="create", name="Z_TEST_PKG", description="Test", superpackage=None, config=None), client)
|
||
client.create_package.assert_called_once()
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# quality.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdCheck(unittest.TestCase):
|
||
|
||
def test_check_clean(self):
|
||
from sapcli.commands.quality import cmd_check
|
||
client = _make_client()
|
||
client.atc_check.return_value = (True, [])
|
||
cmd_check(_args(), client)
|
||
client.atc_check.assert_called_once()
|
||
|
||
def test_check_with_errors(self):
|
||
from sapcli.commands.quality import cmd_check
|
||
client = _make_client()
|
||
client.atc_check.return_value = (False, [{"type": "E", "line": "1", "text": "Error"}])
|
||
cmd_check(_args(), client) # 打印错误但不抛异常
|
||
|
||
|
||
class TestCmdFormat(unittest.TestCase):
|
||
|
||
def test_format_success(self):
|
||
from sapcli.commands.quality import cmd_format
|
||
client = _make_client()
|
||
client.pretty_print.return_value = "FORMATTED CODE"
|
||
with tempfile.NamedTemporaryFile(mode="w", suffix=".abap", delete=False, encoding="utf-8") as f:
|
||
f.write("REPORT ztest.")
|
||
tmpfile = f.name
|
||
try:
|
||
cmd_format(argparse.Namespace(name="ZTEST", type="report", path=tmpfile, config=None), client)
|
||
client.pretty_print.assert_called_once()
|
||
finally:
|
||
os.unlink(tmpfile)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# search.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdList(unittest.TestCase):
|
||
|
||
def test_list_found(self):
|
||
from sapcli.commands.search import cmd_list
|
||
client = _make_client()
|
||
client.list_objects.return_value = [{"name": "Z_TEST", "type": "PROG/P", "package": "$TMP"}]
|
||
cmd_list(argparse.Namespace(type="report", package=None, prefix=None, config=None), client)
|
||
client.list_objects.assert_called_once()
|
||
|
||
def test_list_empty(self):
|
||
from sapcli.commands.search import cmd_list
|
||
client = _make_client()
|
||
client.list_objects.return_value = []
|
||
cmd_list(argparse.Namespace(type="report", package=None, prefix=None, config=None), client)
|
||
|
||
|
||
class TestCmdWhereUsed(unittest.TestCase):
|
||
|
||
def test_whereused_found(self):
|
||
from sapcli.commands.search import cmd_whereused
|
||
client = _make_client()
|
||
client.where_used.return_value = [{"name": "Z_USER", "type": "PROG/P"}]
|
||
cmd_whereused(_args(), client)
|
||
client.where_used.assert_called_once()
|
||
|
||
|
||
class TestCmdSearch(unittest.TestCase):
|
||
|
||
def test_search_found(self):
|
||
from sapcli.commands.search import cmd_search
|
||
client = _make_client()
|
||
client.search_code.return_value = [{"name": "Z_RESULT", "type": "PROG/P"}]
|
||
cmd_search(argparse.Namespace(query="SELECT", type=None, config=None), client)
|
||
client.search_code.assert_called_once()
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# transport.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdTransport(unittest.TestCase):
|
||
|
||
def test_transport_list(self):
|
||
from sapcli.commands.transport import cmd_transport
|
||
client = _make_client()
|
||
client.list_transport_requests.return_value = [
|
||
{"number": "DEVK001", "description": "Test", "owner": "TESTUSER", "status": "D"}
|
||
]
|
||
cmd_transport(argparse.Namespace(transport_action="list", config=None), client)
|
||
client.list_transport_requests.assert_called_once()
|
||
|
||
def test_transport_info(self):
|
||
from sapcli.commands.transport import cmd_transport
|
||
client = _make_client()
|
||
client.transport_info.return_value = {"number": "DEVK001", "description": "Test", "status": "D"}
|
||
cmd_transport(argparse.Namespace(transport_action="info", number="DEVK001", corr_nr="DEVK001", config=None), client)
|
||
client.transport_info.assert_called_once()
|
||
|
||
def test_transport_release(self):
|
||
from sapcli.commands.transport import cmd_transport
|
||
client = _make_client()
|
||
client.transport_release.return_value = True
|
||
with patch("builtins.input", return_value="yes"):
|
||
cmd_transport(argparse.Namespace(transport_action="release", number="DEVK001", corr_nr="DEVK001", config=None), client)
|
||
client.transport_release.assert_called_once()
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# analyze.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdAnalyze(unittest.TestCase):
|
||
|
||
def test_analyze_success(self):
|
||
from sapcli.commands.analyze import cmd_analyze
|
||
client = _make_client()
|
||
client.get_source.return_value = "REPORT ztest.\nCALL FUNCTION 'BAPI_TEST'."
|
||
with tempfile.NamedTemporaryFile(mode="w", suffix=".abap", delete=False, encoding="utf-8") as f:
|
||
f.write("REPORT ztest.")
|
||
tmpfile = f.name
|
||
try:
|
||
cmd_analyze(argparse.Namespace(name="ZTEST", type="report", path=tmpfile, config=None), client)
|
||
finally:
|
||
os.unlink(tmpfile)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# scaffold.py
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestCmdScaffold(unittest.TestCase):
|
||
|
||
def test_scaffold_no_template_lists(self):
|
||
from sapcli.commands.scaffold import cmd_scaffold
|
||
with patch("builtins.print") as mock_print:
|
||
cmd_scaffold(argparse.Namespace(template=None, name="ZTEST", path=".", config=None, package="$TMP"), None)
|
||
self.assertTrue(mock_print.called)
|
||
|
||
def test_scaffold_alv_report(self):
|
||
from sapcli.commands.scaffold import cmd_scaffold
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_scaffold(argparse.Namespace(template="alv-report", name="ZALV_TEST", path=td, config=None, package="$TMP"), None)
|
||
self.assertTrue(len(os.listdir(td)) > 0)
|
||
|
||
def test_scaffold_bapi_wrapper(self):
|
||
from sapcli.commands.scaffold import cmd_scaffold
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_scaffold(argparse.Namespace(template="bapi-wrapper", name="ZBAPI_TEST", path=td, config=None, package="$TMP"), None)
|
||
self.assertTrue(len(os.listdir(td)) > 0)
|
||
|
||
def test_scaffold_interface_class(self):
|
||
from sapcli.commands.scaffold import cmd_scaffold
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_scaffold(argparse.Namespace(template="interface-class", name="ZIF_TEST", path=td, config=None, package="$TMP"), None)
|
||
self.assertTrue(len(os.listdir(td)) > 0)
|
||
|
||
def test_scaffold_data_model(self):
|
||
from sapcli.commands.scaffold import cmd_scaffold
|
||
with tempfile.TemporaryDirectory() as td:
|
||
cmd_scaffold(argparse.Namespace(template="data-model", name="ZMODEL", path=td, config=None, package="$TMP"), None)
|
||
self.assertTrue(len(os.listdir(td)) > 0)
|
||
|
||
|
||
# ═══════════════════════════════════════════
|
||
# auth.py — 需 mock getpass + load_config
|
||
# ═══════════════════════════════════════════
|
||
|
||
class TestAuth(unittest.TestCase):
|
||
|
||
@patch("sapcli.auth.getpass")
|
||
@patch("sapcli.auth.set_password", return_value=True)
|
||
@patch("sapcli.config.load_config")
|
||
def test_cmd_auth_login(self, mock_load, mock_set, mock_getpass):
|
||
from sapcli.auth import cmd_auth_login
|
||
mock_cfg = MagicMock()
|
||
mock_cfg.host = "http://sap:8000"
|
||
mock_cfg.client = "100"
|
||
mock_cfg.user = "TESTUSER"
|
||
mock_load.return_value = (mock_cfg, None)
|
||
mock_getpass.getpass.return_value = "SECRET"
|
||
cmd_auth_login(argparse.Namespace(config=None))
|
||
mock_set.assert_called_once()
|
||
|
||
def test_cmd_auth_status_no_keyring(self):
|
||
from sapcli.auth import cmd_auth_status
|
||
with patch("sapcli.auth._KEYRING_AVAILABLE", False):
|
||
cmd_auth_status(argparse.Namespace()) # 不抛异常
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main(verbosity=2)
|