`info` 在 NW 7.40 上对 DDIC 类型恒报「查询失败: HTTP 406」,实测根因:
回退分支把 Accept 从专属媒体类型换成 `application/xml`,而该值在本系统同样返回 406,
等于没回退。真实客户端 `_headers()` 的 Accept 默认值本就是 `*/*`,此处相当于
把可用默认值覆盖成了失效值。
实测(S4T / NW 7.40,structure ZMM_BIP_001_HEADER):
application/vnd.sap.adt.ddic.structures.v1|v2|v3+xml → 406
application/xml → 406
*/* → 200
修后真机验证:`info --name ZMM_BIP_001_HEADER --type structure` 查询成功。
已知限制(非本修复引入):NW 7.40 的 DDIC 端点在 */* 下只返回 blueSource,
不含 description/packageRef,故 structure/tabletype 等类型的「描述/开发包/负责人」
为空。需这些字段时用 read-table 查 DD02T(描述)/ TADIR(开发包)。
(objectproperties 端点在本系统返回 422,不可用。)
测试:
- 新增 test_info_406_fallback_accept_is_wildcard —— 断言回退 Accept 必须为 */*,
且明确排除 application/xml。**必须用 side_effect 在请求发生时快照 headers**:
cmd_info 重试时复用同一个 hdrs dict,直接读 call_args_list[i].kwargs["headers"]
会因别名而全部显示最后一次赋的值,令该断言永远无法成立。
- 三个测试文件的 _make_client() 由 `_headers.return_value={...}`(共享 dict)
改为 side_effect 返回新 dict,与真实实现一致——原写法会让被记录的 headers
全部指向同一对象,使 header 断言失真。
- 反向验证:把回退改回 application/xml → 测试报红 AssertionError('application/xml' != '*/*')。
699 tests OK。
605 lines
27 KiB
Python
605 lines
27 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()
|
||
# 真实 _headers() 每次返回**新** dict。用 return_value 会让所有被记录的
|
||
# kwargs["headers"] 指向同一对象——后续赋值会覆盖历史值,令 header 断言失真
|
||
# (实测:406 回退的 Accept 断言曾被此别名效应蒙过)。
|
||
client._headers.side_effect = lambda content_type="application/xml": {
|
||
"content-type": content_type,
|
||
"x-csrf-token": "test-csrf-token",
|
||
"Accept": "*/*",
|
||
}
|
||
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)
|