feat: bgRFC/FM 支撑能力 + ADT 后缀剥离与 CLAS 激活修复(B1 配套)
新增能力: - remote-enable 子命令:设置函数模块处理类型为远程启用(bgRFC/RFC 执行体必需)。 实现要点均按实机验证:PUT fmodule:processingType="rfc"(枚举值仅小写 rfc 有效, remoteEnabled/remote/RFC 等均 400);lockHandle 走 query 参数(放 header 报 ParameterNotFound);PUT 后 GET 复核回显;DDIC 结构参数才可远程启用(类本地类型 报"针对 RFC 不允许使用类或接口的类型")。 - create --type functiongroup 透传 --package / --corr_nr(此前 package 被静默写成 $TMP, 导致函数组无法归目标包)。 修复: - activate 请求体补重复引用:ADT CLAS 激活端点对 objectReferences 只有 1 个引用时 返回 HTTP 200 + 空 body 且不执行激活;引用数 ≥2 才真正激活(同对象重复亦可)。 - scanner 剥离 ADT 文件后缀(xxx.clas.abap 解析错误)。 守卫测试: - tests/unit/test_bgrfc_support.py(17 例):函数组 package 透传、processingType 助手、 remote-enable 命令(含 lockHandle 走 query/小写 rfc/错误透传/复核失配判失败)、 函数 URI 组装不得用组名冒充模块名。 - test_activate.py 增加激活请求体引用数守卫;test_repo_guards.py 增加后缀剥离守卫。 测试:tests/unit 676 + tests/test_sapcli.py 69 = 745 全绿 (PYTHONPATH=assets python -m unittest discover -s tests/unit -t tests)
This commit is contained in:
@@ -167,3 +167,53 @@ class TestCmdActivateNoneMode(unittest.TestCase):
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
class TestActivateRequestBodyHasTwoRefs(unittest.TestCase):
|
||||
"""守卫:activation 请求体必须含 ≥2 个 objectReference 引用。
|
||||
|
||||
实测(SAP_BASIS 752,2026-09-15 受控实验):objectReferences 里只有 1 个
|
||||
objectReference 时,服务端返回 HTTP 200 + 空 body(无 content-type)且
|
||||
根本不执行激活;引用数为 2 及以上才真正激活(同一对象重复 2 次亦可)。
|
||||
单对象 activate 因此必须补重复引用,否则「激活成功」是假象。
|
||||
"""
|
||||
|
||||
def _capture_body(self, name, uri, corr=None):
|
||||
from sapcli.client._source import SourceMixin
|
||||
|
||||
class _Client(SourceMixin):
|
||||
def __init__(self):
|
||||
self.host = "https://sap.example.com"
|
||||
self._stateful = False
|
||||
self.session = MagicMock()
|
||||
self._headers = MagicMock(return_value={})
|
||||
resp = MagicMock()
|
||||
resp.status_code = 200
|
||||
resp.content = b""
|
||||
resp.text = ""
|
||||
resp.headers = {"content-type": "text/plain"}
|
||||
self.session.post.return_value = resp
|
||||
self.session.get.return_value = resp
|
||||
|
||||
c = _Client()
|
||||
try:
|
||||
c.activate(name, uri, corr)
|
||||
except Exception:
|
||||
pass
|
||||
body = c.session.post.call_args[1].get("data", "")
|
||||
return body
|
||||
|
||||
def test_single_object_request_has_two_refs(self):
|
||||
body = self._capture_body(
|
||||
"ZINT_CL_LOG_API", "/sap/bc/adt/oo/classes/zint_cl_log_api")
|
||||
n = body.count("<adtcore:objectReference ")
|
||||
self.assertGreaterEqual(
|
||||
n, 2,
|
||||
f"单对象 activate 请求体只有 {n} 个引用 —— 服务端将返回空响应且不激活")
|
||||
|
||||
def test_request_includes_target_object(self):
|
||||
body = self._capture_body(
|
||||
"ZINT_CL_LOG_API", "/sap/bc/adt/oo/classes/zint_cl_log_api")
|
||||
self.assertIn("zint_cl_log_api", body)
|
||||
self.assertIn("ZINT_CL_LOG_API", body)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user