"""info 命令 DDIC 描述/开发包补全 — 单元测试。 覆盖四项行为(协调者 TDD 要求): 1. ADT 已返回描述/开发包时不得补查 2. 补查成功填充描述/开发包 3. 补查失败保持原样且不抛 4. 描述文本语言回退顺序(登录语言 -> 'E' -> '1' -> 首行) """ from __future__ import annotations import argparse import os import sys import unittest from unittest.mock import MagicMock, patch sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "assets")) from sapcli.client import ADTClient from sapcli.commands.crud import ( _pick_description_text, _query_ddic_description, _query_ddic_package, _sap_language_key, ) 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(): client = MagicMock(spec=ADTClient) client.host = "https://sap.example.com" client.sap_client = "100" client.csrf_token = "test-csrf-token" client.session = MagicMock() 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": "structure", "name": "ZMM_BIP_001_HEADER", "path": ".", "config": None} defaults.update(kwargs) return argparse.Namespace(**defaults) def _ddic_xml(name="ZMM_BIP_001_HEADER", otype="TABL/DS", version="active", description="", package=None, master_language="ZH"): attrs = ( f' adtcore:name="{name}"' f' adtcore:type="{otype}"' f' adtcore:version="{version}"' f' adtcore:masterLanguage="{master_language}"' ) if description: attrs += f' adtcore:description="{description}"' head = ( b'' b'' return ( head + b'>' b'' b'' ) class TestLanguageFallback(unittest.TestCase): def test_prefers_login_language(self): rows = [("1", "中文描述"), ("E", "English desc")] self.assertEqual(_pick_description_text(rows, "1"), "中文描述") def test_prefers_E_when_login_language_missing(self): rows = [("1", "中文描述"), ("E", "English desc")] self.assertEqual(_pick_description_text(rows, ""), "English desc") def test_E_before_1(self): rows = [("E", "English desc"), ("1", "中文描述")] self.assertEqual(_pick_description_text(rows, "D"), "English desc") def test_falls_back_to_first_row_when_no_priority_match(self): rows = [("F", "French"), ("D", "Deutsch")] self.assertEqual(_pick_description_text(rows, ""), "French") def test_empty_rows_returns_empty(self): self.assertEqual(_pick_description_text([], "E"), "") self.assertEqual(_pick_description_text(None, "E"), "") def test_sap_language_key_mapping(self): self.assertEqual(_sap_language_key("ZH"), "1") self.assertEqual(_sap_language_key("EN"), "E") self.assertEqual(_sap_language_key(""), "") self.assertEqual(_sap_language_key("E"), "E") class TestQueryDdicDescription(unittest.TestCase): def test_structure_queries_dd02t_and_fills(self): client = _make_client() client.query_table_data.return_value = { "columns": ["DDLANGUAGE", "DDTEXT"], "rows": [["E", "BIP vendor header"]], } result = _query_ddic_description(client, "structure", "ZMM_BIP_001_HEADER", "") self.assertEqual(result, "BIP vendor header") sql = client.query_table_data.call_args[0][0] self.assertIn("DD02T", sql) self.assertIn("TABNAME IN ('ZMM_BIP_001_HEADER')", sql) def test_tabletype_queries_dd40t(self): client = _make_client() client.query_table_data.return_value = { "columns": ["DDLANGUAGE", "DDTEXT"], "rows": [["1", "BIP bank rows"]], } result = _query_ddic_description(client, "tabletype", "ZMM_BIP_001_BANK_T", "") self.assertEqual(result, "BIP bank rows") sql = client.query_table_data.call_args[0][0] self.assertIn("DD40T", sql) self.assertIn("TYPENAME IN ('ZMM_BIP_001_BANK_T')", sql) def test_non_ddic_type_returns_empty_without_query(self): client = _make_client() self.assertEqual(_query_ddic_description(client, "class", "ZCL_X", ""), "") client.query_table_data.assert_not_called() def test_failure_returns_empty(self): client = _make_client() client.query_table_data.side_effect = Exception("boom") self.assertEqual(_query_ddic_description(client, "structure", "X", ""), "") class TestQueryDdicPackage(unittest.TestCase): def test_structure_queries_tadir(self): client = _make_client() client.query_table_data.return_value = { "columns": ["DEVCLASS"], "rows": [["ZMM"]], } result = _query_ddic_package(client, "structure", "ZMM_BIP_001_HEADER") self.assertEqual(result, "ZMM") sql = client.query_table_data.call_args[0][0] self.assertIn("TADIR", sql) self.assertIn("OBJECT IN ('TABL')", sql) self.assertIn("OBJ_NAME IN ('ZMM_BIP_001_HEADER')", sql) def test_no_rows_returns_empty(self): client = _make_client() client.query_table_data.return_value = {"columns": ["DEVCLASS"], "rows": []} self.assertEqual(_query_ddic_package(client, "structure", "ZMM_BIP_001_HEADER"), "") def test_failure_returns_empty(self): client = _make_client() client.query_table_data.side_effect = Exception("boom") self.assertEqual(_query_ddic_package(client, "structure", "X"), "") class TestCmdInfoSupplement(unittest.TestCase): def test_no_supplement_when_values_present(self): from sapcli.commands.crud import cmd_info client = _make_client() client.session.get.return_value = _mock_resp( 200, content=_ddic_xml(description="My desc", package="ZPKG") ) client.query_table_data.return_value = {"columns": [], "rows": []} with patch("builtins.print"): cmd_info(_args(type="structure", name="ZMM_BIP_001_HEADER"), client) sqls = [str(c[0][0]) for c in client.query_table_data.call_args_list] self.assertFalse(any(t in s for t in ("DD02T", "DD40T", "DD04T", "DD01T") for s in sqls)) self.assertFalse(any("TADIR" in s for s in sqls)) def test_supplements_description_and_package(self): from sapcli.commands.crud import cmd_info client = _make_client() client.session.get.return_value = _mock_resp(200, content=_ddic_xml()) def fake_query(sql, max_rows=200): if "DD02T" in sql: return {"columns": ["DDLANGUAGE", "DDTEXT"], "rows": [["E", "BIP vendor header"]]} if "TADIR" in sql: return {"columns": ["DEVCLASS"], "rows": [["ZX_TEST_PKG"]]} return {"columns": [], "rows": []} client.query_table_data.side_effect = fake_query with patch("builtins.print") as mock_print: cmd_info(_args(type="structure", name="ZMM_BIP_001_HEADER"), client) printed = " ".join(str(c) for c, _ in mock_print.call_args_list) self.assertIn("BIP vendor header", printed) self.assertIn("ZX_TEST_PKG", printed) def test_supplement_failure_keeps_original(self): from sapcli.commands.crud import cmd_info client = _make_client() client.session.get.return_value = _mock_resp(200, content=_ddic_xml()) def fake_query(sql, max_rows=200): if "DD02T" in sql or "TADIR" in sql: raise Exception("boom") return {"columns": [], "rows": []} client.query_table_data.side_effect = fake_query with patch("builtins.print") as mock_print: cmd_info(_args(type="structure", name="ZMM_BIP_001_HEADER"), client) printed = " ".join(str(c) for c, _ in mock_print.call_args_list) self.assertIn("(未指定)", printed) if __name__ == "__main__": unittest.main()