"""CLI 层(parser.py + app.py)单元测试。 运行: python tests/unit/test_cli.py """ from __future__ import annotations import os import sys import unittest from unittest.mock import patch sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "assets")) from sapcli.cli.parser import build_parser from sapcli.types import all_type_keys class TestParserBuild(unittest.TestCase): """test_parser_build: build_parser() 返回 ArgumentParser""" def test_returns_argument_parser(self): import argparse parser = build_parser() self.assertIsInstance(parser, argparse.ArgumentParser) def test_parser_has_description(self): parser = build_parser() self.assertIn("sap-cli", parser.description) class TestParserDownload(unittest.TestCase): """test_parser_download: 解析 download 命令""" def test_download_full_args(self): parser = build_parser() args = parser.parse_args([ "download", "--name", "ZTEST", "--type", "report", "--path", "./out", ]) self.assertEqual(args.command, "download") self.assertEqual(args.name, "ZTEST") self.assertEqual(args.type, "report") self.assertEqual(args.path, "./out") def test_download_class_type(self): parser = build_parser() args = parser.parse_args([ "download", "--name", "ZCL_TEST", "--type", "class", "--path", "./out", ]) self.assertEqual(args.command, "download") self.assertEqual(args.type, "class") class TestParserSyncSingle(unittest.TestCase): """test_parser_sync_single: 解析 sync 单对象模式""" def test_sync_single(self): parser = build_parser() args = parser.parse_args([ "sync", "--name", "ZTEST", "--type", "report", "--path", "./file.abap", ]) self.assertEqual(args.command, "sync") self.assertEqual(args.name, "ZTEST") self.assertEqual(args.type, "report") self.assertEqual(args.path, "./file.abap") self.assertFalse(getattr(args, "all", False)) class TestParserSyncAll(unittest.TestCase): """test_parser_sync_all: 解析 sync --all 批量模式""" def test_sync_all(self): parser = build_parser() args = parser.parse_args([ "sync", "--all", "--path", "./project", ]) self.assertEqual(args.command, "sync") self.assertTrue(args.all) self.assertEqual(args.path, "./project") self.assertIsNone(args.name) self.assertIsNone(args.type) def test_sync_all_dry_run(self): parser = build_parser() args = parser.parse_args([ "sync", "--all", "--path", "./project", "--dry-run", ]) self.assertTrue(args.all) self.assertTrue(args.dry_run) class TestParserCreate(unittest.TestCase): """test_parser_create: 解析 create 命令""" def test_create_full_args(self): parser = build_parser() args = parser.parse_args([ "create", "--name", "ZTEST", "--type", "report", "--description", "test", "--package", "$TMP", ]) self.assertEqual(args.command, "create") self.assertEqual(args.name, "ZTEST") self.assertEqual(args.type, "report") self.assertEqual(args.description, "test") self.assertEqual(args.package, "$TMP") def test_create_default_package(self): parser = build_parser() args = parser.parse_args([ "create", "--name", "ZTEST", "--type", "report", ]) self.assertEqual(args.package, "$TMP") self.assertIsNone(args.description) class TestParserNoCommand(unittest.TestCase): """test_parser_no_command: 无命令时 args.command 为 None""" def test_no_command(self): parser = build_parser() args = parser.parse_args([]) self.assertIsNone(args.command) class TestParserConfig(unittest.TestCase): """test_parser_config: 解析 config 子命令""" def test_config_show(self): parser = build_parser() args = parser.parse_args(["config", "show"]) self.assertEqual(args.command, "config") self.assertEqual(args.config_action, "show") def test_config_set(self): parser = build_parser() args = parser.parse_args(["config", "set", "host", "http://sap:8000"]) self.assertEqual(args.command, "config") self.assertEqual(args.config_action, "set") self.assertEqual(args.key, "host") self.assertEqual(args.value, "http://sap:8000") class TestParserAuth(unittest.TestCase): """test_parser_auth: 解析 auth 子命令""" def test_auth_login(self): parser = build_parser() args = parser.parse_args(["auth", "login"]) self.assertEqual(args.command, "auth") self.assertEqual(args.auth_action, "login") def test_auth_logout(self): parser = build_parser() args = parser.parse_args(["auth", "logout"]) self.assertEqual(args.command, "auth") self.assertEqual(args.auth_action, "logout") def test_auth_status(self): parser = build_parser() args = parser.parse_args(["auth", "status"]) self.assertEqual(args.command, "auth") self.assertEqual(args.auth_action, "status") class TestParserTransport(unittest.TestCase): """test_parser_transport: 解析 transport 子命令""" def test_transport_info(self): parser = build_parser() args = parser.parse_args(["transport", "info", "--corr_nr", "DEVK001"]) self.assertEqual(args.command, "transport") self.assertEqual(args.transport_action, "info") self.assertEqual(args.corr_nr, "DEVK001") def test_transport_list(self): parser = build_parser() args = parser.parse_args(["transport", "list"]) self.assertEqual(args.command, "transport") self.assertEqual(args.transport_action, "list") class TestParserInvalidType(unittest.TestCase): """test_parser_invalid_type: --type invalid 应报错""" def test_invalid_type_download(self): parser = build_parser() with self.assertRaises(SystemExit): parser.parse_args([ "download", "--name", "ZTEST", "--type", "invalid", "--path", "./out", ]) def test_invalid_type_create(self): parser = build_parser() with self.assertRaises(SystemExit): parser.parse_args([ "create", "--name", "ZTEST", "--type", "invalid", ]) class TestParserScaffold(unittest.TestCase): """test_parser_scaffold: 解析 scaffold 命令""" def test_scaffold_with_template(self): parser = build_parser() args = parser.parse_args([ "scaffold", "--name", "ZTEST", "--template", "alv-report", ]) self.assertEqual(args.command, "scaffold") self.assertEqual(args.name, "ZTEST") self.assertEqual(args.template, "alv-report") def test_scaffold_invalid_template(self): parser = build_parser() with self.assertRaises(SystemExit): parser.parse_args([ "scaffold", "--name", "ZTEST", "--template", "nonexistent", ]) class TestMainNoCommandExits(unittest.TestCase): """test_main_no_command_exits: main() 无参数时 exit code 1""" @patch("sapcli.cli.app.sys.argv", ["sap-cli"]) @patch("sapcli.cli.app._setup_encoding") @patch("sapcli.cli.app._setup_logging") @patch("sapcli.cli.app._suppress_ssl_warnings") def test_main_no_command_exits(self, mock_ssl, mock_log, mock_enc): from sapcli.cli.app import main with self.assertRaises(SystemExit) as ctx: main() self.assertEqual(ctx.exception.code, 1) class TestParserEdgeCases(unittest.TestCase): """补充边界情况测试。""" def test_all_type_keys_accepted(self): """确保 all_type_keys() 返回的每个类型都能被 parser 接受。""" parser = build_parser() for t in all_type_keys(): args = parser.parse_args([ "download", "--name", "ZTEST", "--type", t, "--path", "./out", ]) self.assertEqual(args.type, t, f"type={t} 应被 parser 接受") def test_sync_fail_fast(self): parser = build_parser() args = parser.parse_args([ "sync", "--name", "ZTEST", "--type", "report", "--path", "./file.abap", "--fail-fast", ]) self.assertTrue(args.fail_fast) def test_delete_with_path(self): parser = build_parser() args = parser.parse_args([ "delete", "--name", "ZTEST", "--type", "report", "--path", "./project", ]) self.assertEqual(args.command, "delete") self.assertEqual(args.path, "./project") def test_info_command(self): parser = build_parser() args = parser.parse_args([ "info", "--name", "ZCL_TEST", "--type", "class", ]) self.assertEqual(args.command, "info") self.assertEqual(args.name, "ZCL_TEST") def test_init_command(self): parser = build_parser() args = parser.parse_args(["init", "--path", "./project"]) self.assertEqual(args.command, "init") self.assertEqual(args.path, "./project") def test_refresh_command(self): parser = build_parser() args = parser.parse_args(["refresh", "--path", "./project"]) self.assertEqual(args.command, "refresh") def test_list_command(self): parser = build_parser() args = parser.parse_args([ "list", "--type", "report", "--prefix", "Z*", ]) self.assertEqual(args.command, "list") self.assertEqual(args.prefix, "Z*") def test_whereused_command(self): parser = build_parser() args = parser.parse_args([ "whereused", "--name", "ZTEST", "--type", "report", ]) self.assertEqual(args.command, "whereused") def test_search_command(self): parser = build_parser() args = parser.parse_args(["search", "--query", "CALL FUNCTION"]) self.assertEqual(args.command, "search") self.assertEqual(args.query, "CALL FUNCTION") def test_check_command(self): parser = build_parser() args = parser.parse_args([ "check", "--name", "ZTEST", "--type", "report", ]) self.assertEqual(args.command, "check") def test_format_command(self): parser = build_parser() args = parser.parse_args([ "format", "--name", "ZTEST", "--type", "report", ]) self.assertEqual(args.command, "format") def test_diff_command(self): parser = build_parser() args = parser.parse_args([ "diff", "--name", "ZTEST", "--type", "report", "--path", "./ztest.abap", ]) self.assertEqual(args.command, "diff") def test_analyze_command(self): parser = build_parser() args = parser.parse_args(["analyze", "--path", "./project"]) self.assertEqual(args.command, "analyze") def test_package_create(self): parser = build_parser() args = parser.parse_args([ "package", "create", "--name", "ZMY_PKG", "--description", "test pkg", ]) self.assertEqual(args.command, "package") self.assertEqual(args.package_action, "create") self.assertEqual(args.name, "ZMY_PKG") def test_cds_download(self): parser = build_parser() args = parser.parse_args([ "cds", "download", "--name", "ZMY_CDS", "--path", "./out", ]) self.assertEqual(args.command, "cds") self.assertEqual(args.cds_action, "download") def test_transport_release(self): parser = build_parser() args = parser.parse_args([ "transport", "release", "--corr_nr", "DEVK001", ]) self.assertEqual(args.transport_action, "release") def test_transport_objects(self): parser = build_parser() args = parser.parse_args([ "transport", "objects", "--corr_nr", "DEVK001", ]) self.assertEqual(args.transport_action, "objects") def test_transport_create(self): parser = build_parser() args = parser.parse_args([ "transport", "create", "--description", "CEM BIP 传输请求", ]) self.assertEqual(args.transport_action, "create") self.assertEqual(args.description, "CEM BIP 传输请求") def test_global_config_option(self): parser = build_parser() args = parser.parse_args([ "--config", "./my_config.ini", "download", "--name", "ZTEST", "--type", "report", "--path", "./out", ]) self.assertEqual(args.config, "./my_config.ini") def test_global_profile_option(self): parser = build_parser() args = parser.parse_args([ "--profile", "DEV", "download", "--name", "ZTEST", "--type", "report", "--path", "./out", ]) self.assertEqual(args.profile, "DEV") if __name__ == "__main__": unittest.main()