diff --git a/sap/adt/errors.py b/sap/adt/errors.py index 5e6787dc..59f1bd73 100644 --- a/sap/adt/errors.py +++ b/sap/adt/errors.py @@ -67,6 +67,16 @@ def __str__(self): return f'{self.message}' +class ExceptionResourceSaveFailure(ADTError): + """Thin wrapper for the class type of ADTErrors""" + + def __init__(self, message): + super().__init__('com.sap.adt', self.__class__.__name__, message) + + def __str__(self): + return f'{self.message}' + + def new_adt_error_from_xml(xmldata): """Parses the xml data and create the correct instance. diff --git a/sap/adt/objects.py b/sap/adt/objects.py index 86081418..628f9d79 100644 --- a/sap/adt/objects.py +++ b/sap/adt/objects.py @@ -783,6 +783,53 @@ def get_headers(self): return headers +class ClassIncludeEditor(ADTObjectSourceEditorWithResponse): + """Source Code Editor""" + + def __init__(self, include_type, instance, lock_handle=None, corrnr=None): + super().__init__(instance, lock_handle=lock_handle, corrnr=corrnr) + + self.include_type = include_type + + def generate_includes(self): + """Generates the corresponding Class Inlcude""" + + self.connection.execute( + 'POST', + self.uri + '/includes', + headers={'content-type': 'application/vnd.sap.adt.oo.classincludes+xml'}, + params=modify_object_params(self.lock_handle, self.corrnr), + body='' + '' + ) + + def write(self, content): + """Tries to write twice. If the first attempt fails, the method blindly + tries to generate the includes (because that is the most commont + root cause of failed writes) and repeats the write attempt. + """ + + try: + super().write(content) + # Hooray, the class include exists!!! + return + except sap.adt.errors.ExceptionResourceSaveFailure as ex: + # Oops, the class include most probably does not exist. + mod_log().debug("Writing of the class include has failed: %s", str(ex)) + # But we do not give up as that is expected. + # When you create an ABAP class in SAPGUI (or pull by abapGit) + # and then you try to write its test classes over ADT API + # you get the caught error because the test classes include + # was not generated automatically. + + # So lets try to generate the class include ... + self.generate_includes() + # and give it one more try - this time unprotected to die on errors + super().write(content) + + class ADTObjectPropertyEditor(ADTObjectEditor): """Object property modification actions - for objects which have only the XML and other resource associated. @@ -1209,7 +1256,7 @@ def open_editor(self, lock_handle=None, corrnr=None): if lock_handle is None: lock_handle = self.lock() - return self._clas.objtype.open_editor(self, lock_handle=lock_handle, corrnr=corrnr) + return ClassIncludeEditor(self.objtype.include_type, self, lock_handle=lock_handle, corrnr=corrnr) def fetch(self): """Retrieve data from ADT""" diff --git a/test/unit/fixtures_adt_clas.py b/test/unit/fixtures_adt_clas.py index 3d0c8be4..3266bc65 100644 --- a/test/unit/fixtures_adt_clas.py +++ b/test/unit/fixtures_adt_clas.py @@ -46,3 +46,7 @@ ''' + +WRITE_INCLUDE_ERROR_XML = '''TEST=====CCAU does not have any inactive version''' + +GENERATE_INCLUDE_REQUEST_XML = '''''' diff --git a/test/unit/test_sap_adt_class.py b/test/unit/test_sap_adt_class.py index e490249a..b66c70ec 100644 --- a/test/unit/test_sap_adt_class.py +++ b/test/unit/test_sap_adt_class.py @@ -11,7 +11,8 @@ from fixtures_adt import (LOCK_RESPONSE_OK, EMPTY_RESPONSE_OK, TEST_CLASSES_READ_RESPONSE_OK, DEFINITIONS_READ_RESPONSE_OK, IMPLEMENTATIONS_READ_RESPONSE_OK) -from fixtures_adt_clas import CREATE_CLASS_ADT_XML, GET_CLASS_ADT_XML +from fixtures_adt_clas import (CREATE_CLASS_ADT_XML, GET_CLASS_ADT_XML, WRITE_INCLUDE_ERROR_XML, + GENERATE_INCLUDE_REQUEST_XML) FIXTURE_CLASS_MAIN_CODE='''class zcl_hello_world definition public. @@ -135,6 +136,24 @@ def test_adt_class_write_implementations(self): def test_adt_class_write_tests(self): self.include_write_test(lambda clas: clas.test_classes, 'includes/testclasses') + def test_adt_class_write_include_with_generate(self): + GENERATE_INCLUDE_REQUEST_ID = 2 + GENERATE_INCLUDE_ADT_URI = '/sap/bc/adt/oo/classes/zcl_hello_world/includes' + WRITE_GENERATED_INCLUDE_REQUEST_ID = 3 + + conn = Connection([LOCK_RESPONSE_OK, + Response(status_code=405, headers={'content-type': 'application/xml'}, text=WRITE_INCLUDE_ERROR_XML), + EMPTY_RESPONSE_OK, # generate test class + EMPTY_RESPONSE_OK, # write test class + None]) + clas = sap.adt.Class(conn, 'ZCL_HELLO_WORLD') + with clas.test_classes.open_editor() as editor: + editor.write('* new test') + + self.assertEqual(conn.execs[GENERATE_INCLUDE_REQUEST_ID].adt_uri, GENERATE_INCLUDE_ADT_URI) + self.assertEqual(conn.execs[GENERATE_INCLUDE_REQUEST_ID].body, GENERATE_INCLUDE_REQUEST_XML) + self.assertEqual(conn.execs[WRITE_GENERATED_INCLUDE_REQUEST_ID].body, b'* new test') + def include_activate_test(self, getter, includes_uri): conn = Connection( [