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(
[