fix(info): 406 回退值改为 */* —— 原值 application/xml 本身也返回 406

`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。
This commit is contained in:
吴让宇
2026-09-11 00:53:02 +08:00
parent c5905a5b1e
commit 86519c51d6
4 changed files with 57 additions and 9 deletions
+41 -4
View File
@@ -37,9 +37,13 @@ def _make_client():
client.csrf_token = "test-csrf-token"
client.user = "TESTUSER"
client.session = MagicMock()
client._headers.return_value = {
"content-type": "application/xml",
# 真实 _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
@@ -397,7 +401,11 @@ class TestCmdInfoObjectNotFound(unittest.TestCase):
self.assertIn("不匹配", str(ctx.exception))
def test_info_http_406_fallback(self):
"""HTTP 406 时回退到 application/xml。"""
"""HTTP 406 时回退到 */*。
只断言「重试了一次」会漏掉回退值本身无效的情况——实测 NW 7.40 上
application/xml 同样返回 406,回退恒败。故必须断言回退用的 Accept 值。
"""
from sapcli.commands.crud import cmd_info
client = _make_client()
@@ -406,10 +414,39 @@ class TestCmdInfoObjectNotFound(unittest.TestCase):
_mock_resp(406),
_mock_resp(200, content=_INFO_XML),
]
# 应该不抛异常
cmd_info(_args(), client)
self.assertEqual(client.session.get.call_count, 2)
def test_info_406_fallback_accept_is_wildcard(self):
"""回退请求的 Accept 必须是 */*(NW 7.40 上唯一可用值)。
实测(S4T / NW 7.40):structure 专属头 → 406application/xml → 406*/* → 200。
即 application/xml 本身也 406,用它做回退等于不重试。
⚠️ 断言必须在**请求发生时快照** headerscmd_info 重试时复用同一个 hdrs dict,
直接读 call_args_list[i].kwargs["headers"] 会因别名而全部显示最后一次赋的值,
使「回退换了头」这类断言永远无法成立(也无法发现回退值无效)。
"""
from sapcli.commands.crud import cmd_info
client = _make_client()
seen = []
def _get(url, headers=None, **kw):
seen.append(dict(headers or {})) # 快照,断开别名
return _mock_resp(406) if len(seen) == 1 else _mock_resp(200, content=_INFO_XML)
client.session.get.side_effect = _get
with patch("builtins.print"):
cmd_info(_args(), client)
self.assertEqual(len(seen), 2, "应为「原始请求 + 回退请求」两次")
self.assertEqual(seen[1]["Accept"], "*/*")
self.assertNotEqual(seen[1]["Accept"], "application/xml",
"application/xml 在 NW 7.40 上同样返回 406,不能作为回退值")
self.assertNotEqual(seen[0]["Accept"], seen[1]["Accept"],
"回退请求必须换用不同的 Accept 头")
# ═══════════════════════════════════════════════════════════════
# crud.py — cmd_info — 传输请求号字段(纯只读查 E071)