From bd15041c1d4e39981abcb41dbb9b943ea61cc753 Mon Sep 17 00:00:00 2001 From: Libor Bucek Date: Thu, 28 Sep 2023 09:03:14 +0200 Subject: [PATCH] tree-wide: add support for Function Group Include CRUD --- doc/commands/functiongroup.md | 59 +++++++++++++-- sap/cli/function.py | 57 +++++++++++++++ test/unit/fixtures_adt_function.py | 24 +++++++ test/unit/test_sap_cli_function.py | 112 ++++++++++++++++++++++++++++- 4 files changed, 244 insertions(+), 8 deletions(-) diff --git a/doc/commands/functiongroup.md b/doc/commands/functiongroup.md index 738394ba..bbd6fb54 100644 --- a/doc/commands/functiongroup.md +++ b/doc/commands/functiongroup.md @@ -1,9 +1,15 @@ # Function Group -1. [create](#create) -2. [write](#write) -3. [activate](#activate) -4. [read](#read) +- [Function Group](#function-group) + - [create](#create) + - [write](#write) + - [activate](#activate) + - [read group](#read-group) + - [Function Group Include](#function-group-include) + - [create](#create-1) + - [write](#write-1) + - [activate](#activate-1) + - [read group](#read-group-1) ## create @@ -37,10 +43,53 @@ Activates the given function group. sapcli functiongroup activate ZFG_PARENT ``` -### read group +## read group Download main source codes of the given function group ```bash sapcli functiongroup read ZFG_PARENT ``` + +## Function Group Include + +### create + +Creates a function group of the given name with the given description in the +given package. + +```bash +sapcli functiongroup include create ZFG_PARENT ZFGI_HELLO_WORLD "Function Group Include description" +``` + +### write + +Changes main source code of the given function group. + +``` +sapcli functiongroup include write [FUNCTION_GROUP_NAME] [FUNCTION_GROUP_INCLUDE_NAME] [FILE_PATH|-] [--corrnr TRANSPORT] [--activate] [--ignore-errors] [--warning-errors] +``` + +* _FUNCITON\_GROUP\_NAME_ function group name +* _FUNCITON\_GROUP\_INCLUDE\_NAME_ function group include name +* _FILE\_PATH_ a single file path or - for reading _stdin_ +* _--corrnr TRANSPORT_ specifies CTS Transport Request Number if needed +* _--activate_ activate after finishing the write operation +* _--ignore-errors_ continue activating objects ignoring errors +* _--warning-errors_ treat activation warnings as errors + +### activate + +Activates the given function group. + +```bash +sapcli functiongroup include activate ZFG_PARENT ZFGI_HELLO_WORLD +``` + +### read group + +Download main source codes of the given function group + +```bash +sapcli functiongroup include read ZFG_PARENT ZFGI_HELLO_WORLD +``` diff --git a/sap/cli/function.py b/sap/cli/function.py index b2da6e52..5049aab4 100644 --- a/sap/cli/function.py +++ b/sap/cli/function.py @@ -5,6 +5,55 @@ import sap.adt import sap.cli.object +class CommandGroupFunctionGroupInclude(sap.cli.object.CommandGroupObjectTemplate): + """Container for definition commands.""" + + def __init__(self): + super().__init__('include') + + self.define() + + def build_new_metadata(self, connection, args): + return sap.adt.ADTCoreData(language='EN', master_language='EN', + responsible=connection.user.upper()) + + def build_new_object(self, connection, args, metadata): + return sap.adt.FunctionInclude(connection, args.name.upper(), args.group, metadata=metadata) + + def instance(self, connection, name, args, metadata=None): + return sap.adt.FunctionInclude(connection, name.upper(), args.group, metadata=metadata) + + def define_create(self, commands): + create_cmd = super().define_create(commands) + + # Function Modules belong to a function group and not into a package! + create_cmd.insert_argument(0, 'group') + + return create_cmd + + def define_read(self, commands): + read_cmd = super().define_read(commands) + + # Function Modules belong to a function group and not into a package! + read_cmd.insert_argument(0, 'group') + + return read_cmd + + def define_write(self, commands): + write_cmd = super().define_write(commands) + + # Function Modules belong to a function group and not into a package! + write_cmd.insert_argument(0, 'group') + + return write_cmd + + def define_activate(self, commands): + activate_cmd = super().define_activate(commands) + + # Function Modules belong to a function group and not into a package! + activate_cmd.insert_argument(0, 'group') + + return activate_cmd class CommandGroupFunctionGroup(sap.cli.object.CommandGroupObjectMaster): """Commands for Function Groups""" @@ -12,8 +61,16 @@ class CommandGroupFunctionGroup(sap.cli.object.CommandGroupObjectMaster): def __init__(self): super().__init__('functiongroup') + self.function_group_include_grp = CommandGroupFunctionGroupInclude() + self.define() + def install_parser(self, arg_parser): + activation_group = super().install_parser(arg_parser) + + function_group_include_parser = activation_group.add_parser(self.function_group_include_grp.name) + self.function_group_include_grp.install_parser(function_group_include_parser) + def build_new_object(self, connection, args, metadata): return sap.adt.FunctionGroup(connection, args.name.upper(), package=args.package, metadata=metadata) diff --git a/test/unit/fixtures_adt_function.py b/test/unit/fixtures_adt_function.py index e0f0816d..64f35871 100644 --- a/test/unit/fixtures_adt_function.py +++ b/test/unit/fixtures_adt_function.py @@ -103,3 +103,27 @@ ENDFUNCTION. ''' + +FAKE_LOCK_HANDLE = 'lock_handle' + +FUNCTION_GROUP_INCLUDE_ADT_XML=''' + + +''' + +CREATE_FUNCTION_GROUP_INCLUDE_ADT_XML=''' + + +''' + +WRITE_FUNCTION_GROUP_INCLUDE_BODY='''DATA: test_write TYPE string.''' + +READ_FUNCTION_GROUP_INCLUDE_BODY='''DATA: test_read TYPE string.''' + +ACTIVATE_FUNCTION_GROUP_INCLUDE_BODY = ''' + + +''' \ No newline at end of file diff --git a/test/unit/test_sap_cli_function.py b/test/unit/test_sap_cli_function.py index 84630ce4..a7b7ec14 100755 --- a/test/unit/test_sap_cli_function.py +++ b/test/unit/test_sap_cli_function.py @@ -7,14 +7,21 @@ import sap.cli.function -from mock import Connection, Response +from mock import Connection, Response, Request from fixtures_adt import EMPTY_RESPONSE_OK, LOCK_RESPONSE_OK +from fixtures_adt_wb import RESPONSE_ACTIVATION_OK from fixtures_adt_function import ( CLI_CREATE_FUNCTION_GROUP_ADT_XML, CREATE_FUNCTION_MODULE_ADT_XML, GET_FUNCTION_MODULE_ADT_XML, - PUT_FUNCITON_MODULE_ADT_XML) - + PUT_FUNCITON_MODULE_ADT_XML, + FAKE_LOCK_HANDLE, + CREATE_FUNCTION_GROUP_INCLUDE_ADT_XML, + WRITE_FUNCTION_GROUP_INCLUDE_BODY, + READ_FUNCTION_GROUP_INCLUDE_BODY, + FUNCTION_GROUP_INCLUDE_ADT_XML, + ACTIVATE_FUNCTION_GROUP_INCLUDE_BODY + ) fugr_parser = ArgumentParser() sap.cli.function.CommandGroupFunctionGroup().install_parser(fugr_parser) @@ -22,6 +29,9 @@ fm_parser = ArgumentParser() sap.cli.function.CommandGroupFunctionModule().install_parser(fm_parser) +fugri_parser = ArgumentParser() +sap.cli.function.CommandGroupFunctionGroupInclude().install_parser(fugri_parser) + def fugr_parse_args(*argv): global fugr_parser @@ -33,6 +43,11 @@ def fm_parse_args(*argv): return fm_parser.parse_args(argv) +def fugri_parse_args(*argv): + global fugri_parser + return fugri_parser.parse_args(argv) + + class TestFunctionGroupCreate(unittest.TestCase): def test_create_fugr_defaults(self): @@ -144,5 +159,96 @@ def test_fmod_chattr_rfc(self): self.assertEqual(put_req.body.decode('utf-8'), PUT_FUNCITON_MODULE_ADT_XML) +class TestFunctionGroupIncludeCreate(unittest.TestCase): + + def test_create_fugr_include_defaults(self): + connection = Connection([EMPTY_RESPONSE_OK], user='FILAK') + + args = fugri_parse_args('create', 'ZFG_HELLO_WORLD', 'ZFG_I_HELLO_WORLD', 'Hello FUGR/I!') + args.execute(connection, args) + + self.assertEqual([(e.method, e.adt_uri) for e in connection.execs], [('POST', '/sap/bc/adt/functions/groups/zfg_hello_world/includes')]) + + post_req = connection.execs[0] + + self.assertEqual(sorted(post_req.headers.keys()), ['Content-Type']) + self.assertEqual(post_req.headers['Content-Type'], 'application/vnd.sap.adt.functions.fincludes.v2+xml; charset=utf-8') + + self.assertIsNone(post_req.params) + + self.maxDiff = None + self.assertEqual(post_req.body.decode('utf-8'), CREATE_FUNCTION_GROUP_INCLUDE_ADT_XML) + + +class TestFunctionGroupIncludeWrite(unittest.TestCase): + + @patch('sap.adt.objects.ADTObject.lock', return_value=FAKE_LOCK_HANDLE) + @patch('sap.adt.objects.ADTObject.unlock') + def test_write(self, fake_lock, fake_unlock): + connection = Connection() + + args = fugri_parse_args('write', 'ZFG_HELLO_WORLD', 'ZFG_I_HELLO_WORLD', '-') + with patch('sys.stdin', StringIO(WRITE_FUNCTION_GROUP_INCLUDE_BODY)): + args.execute(connection, args) + + fake_lock.assert_called_once() + fake_unlock.assert_called_once() + + expected_request = Request( + adt_uri='/sap/bc/adt/functions/groups/zfg_hello_world/includes/zfg_i_hello_world/source/main', + method='PUT', + headers={'Content-Type': 'text/plain; charset=utf-8', 'Accept': 'text/plain'}, + body=bytes(WRITE_FUNCTION_GROUP_INCLUDE_BODY, 'utf-8'), + params={'lockHandle': FAKE_LOCK_HANDLE} + ) + + self.assertEqual(connection.execs[0], expected_request) + + +class TestFunctionGroupIncludeRead(unittest.TestCase): + + def test_read(self): + connection = Connection([ + ( + Response( + text=READ_FUNCTION_GROUP_INCLUDE_BODY, + status_code=200, + headers={'Content-Type': 'text/plain; charset=utf-8'} + ), + Request.get( + adt_uri='functions/groups/zfg_hello_world/includes/zfg_i_hello_world/source/main', + accept='text/plain' + ) + ) + ], asserter=self) + + args = fugri_parse_args('read', 'ZFG_HELLO_WORLD', 'ZFG_I_HELLO_WORLD') + with patch('sys.stdin', StringIO(READ_FUNCTION_GROUP_INCLUDE_BODY)) as mock_print: + args.execute(connection, args) + + expected_stdout = READ_FUNCTION_GROUP_INCLUDE_BODY + self.assertEqual(mock_print.getvalue(), expected_stdout) + + +class TestFunctionGroupIncludeActivate(unittest.TestCase): + + def test_activate(self): + connection = Connection([ + RESPONSE_ACTIVATION_OK, + Response(text=FUNCTION_GROUP_INCLUDE_ADT_XML, status_code=200, headers={}) + ]) + + args = fugri_parse_args('activate', 'ZFG_HELLO_WORLD', 'ZFG_I_HELLO_WORLD') + args.execute(connection, args) + + expected_request = Request( + adt_uri='/sap/bc/adt/activation', + method='POST', + headers={'Accept': 'application/xml', 'Content-Type': 'application/xml'}, + body=ACTIVATE_FUNCTION_GROUP_INCLUDE_BODY, + params={'method': 'activate', 'preauditRequested': 'true'} + ) + + self.assertEqual(connection.execs[0], expected_request) if __name__ == '__main__': unittest.main()