diff --git a/sap/adt/objects.py b/sap/adt/objects.py
index 628f9d79..a192a046 100644
--- a/sap/adt/objects.py
+++ b/sap/adt/objects.py
@@ -608,6 +608,26 @@ def create(self, corrnr=None):
params=create_params(corrnr),
body=bytes(xml, 'utf-8'))
+ def create_delete_body(self, corrnr=None):
+ """Create XML body for deletion request"""
+
+ return f'''
+
+
+ {corrnr if corrnr else ''}
+
+'''
+
+ def delete(self, corrnr=None):
+ """Deletes ADT object
+ """
+
+ return self._connection.execute(
+ 'POST',
+ 'deletion/delete',
+ headers={'Content-Type': 'application/vnd.sap.adt.deletion.request.v1+xml'},
+ body=bytes(self.create_delete_body(corrnr), 'utf-8'))
+
def fetch(self):
"""Retrieve data from ADT"""
diff --git a/sap/cli/checkin.py b/sap/cli/checkin.py
index de940b63..25705f7b 100644
--- a/sap/cli/checkin.py
+++ b/sap/cli/checkin.py
@@ -326,8 +326,20 @@ def checkin_clas(connection, repo_obj, corrnr=None):
try:
clas.create(corrnr)
- except sap.adt.errors.ExceptionResourceAlreadyExists as err:
- mod_log().info(err.message)
+ except sap.adt.errors.ExceptionResourceAlreadyExists as exc:
+ mod_log().info('Class already exists. Recreating.')
+ clas.fetch()
+
+ # pylint: disable=no-member
+ if clas.reference.name != repo_obj.package.name:
+ raise sap.adt.errors.ExceptionCheckinFailure(f'Cannot checkin class {repo_obj.name} into package'
+ f' {repo_obj.package.name}. It already exists in package'
+ f' {clas.reference.name}.') from exc
+
+ clas.delete(corrnr)
+ # Recreate class object to avoid stale data, which causes create to fail
+ clas = sap.adt.Class(connection, repo_obj.name.upper(), package=repo_obj.package.name, metadata=metadata)
+ clas.create(corrnr)
for source_file in repo_obj.files:
if not source_file.endswith('.abap'):
diff --git a/test/unit/test_sap_adt_object.py b/test/unit/test_sap_adt_object.py
index d57000f7..bcba8bbe 100755
--- a/test/unit/test_sap_adt_object.py
+++ b/test/unit/test_sap_adt_object.py
@@ -237,6 +237,31 @@ def test_create_mime_not_found(self):
self.assertEqual(str(caught.exception), 'Not supported mimes: application/something.else+xml not in application/vnd.sap.super.cool.txt+xml;application/vnd.sap.super.cool.txt.v2+xml')
+ def test_delete(self):
+ conn = Connection([EMPTY_RESPONSE_OK])
+ DummyADTObject(connection=conn, name='dummy').delete()
+
+ self.assertEqual(conn.execs[0].method, 'POST')
+ self.assertEqual(conn.execs[0].adt_uri, '/sap/bc/adt/deletion/delete')
+ self.assertEqual(conn.execs[0].body, b'''
+
+
+
+
+''')
+
+ def test_delete_with_corrnr(self):
+ conn = Connection([EMPTY_RESPONSE_OK])
+ DummyADTObject(connection=conn, name='dummy').delete(corrnr='NPLK900000')
+
+ self.assertEqual(conn.execs[0].method, 'POST')
+ self.assertEqual(conn.execs[0].adt_uri, '/sap/bc/adt/deletion/delete')
+ self.assertEqual(conn.execs[0].body, b'''
+
+
+ NPLK900000
+
+''')
def test_properties(self):
victory = DummyADTObject()
diff --git a/test/unit/test_sap_cli_checkin.py b/test/unit/test_sap_cli_checkin.py
index 3bf192dd..220d6002 100644
--- a/test/unit/test_sap_cli_checkin.py
+++ b/test/unit/test_sap_cli_checkin.py
@@ -568,7 +568,10 @@ def setUp(self):
self.clas_editor = MagicMock()
self.clas_editor.__enter__.return_value = self.clas_editor
- self.clas = MagicMock()
+ self.package_reference = Mock()
+ self.package_reference.name = 'test_package'
+
+ self.clas = MagicMock(reference=self.package_reference)
self.clas.open_editor.return_value = self.clas_editor
self.clas.definitions.open_editor.return_value = self.clas_editor
self.clas.implementations.open_editor.return_value = self.clas_editor
@@ -618,6 +621,16 @@ def test_checkin_clas(self):
Writing Clas: {self.clas_object.name} testclasses
''')
+ def test_checkin_clas_already_exists(self):
+ self.fake_open.return_value = StringIOFile(CLAS_XML)
+ self.clas.create.side_effect = [ExceptionResourceAlreadyExists('Class already exists.'), None]
+
+ sap.cli.checkin.checkin_clas(self.connection, self.clas_object)
+
+ self.clas.fetch.assert_called_once()
+ self.clas.delete.assert_called_once()
+ self.clas.create.assert_has_calls([call(None), call(None)])
+
def test_checkin_clas_with_corrnr(self):
self.fake_open.return_value = StringIOFile(CLAS_XML)
@@ -626,15 +639,37 @@ def test_checkin_clas_with_corrnr(self):
self.clas.create.assert_called_once_with('corrnr')
self.assert_open_editor_calls([], corrnr='corrnr')
- @patch('sap.cli.checkin.mod_log')
- def test_checkin_clas_create_error(self, fake_mod_log):
+ def test_checkin_clas_already_exists_with_corrnr(self):
self.fake_open.return_value = StringIOFile(CLAS_XML)
- self.clas.create.side_effect = ExceptionResourceAlreadyExists('Clas already created.')
+ self.clas.create.side_effect = [ExceptionResourceAlreadyExists('Class already exists.'), None]
- sap.cli.checkin.checkin_clas(self.connection, self.clas_object)
+ sap.cli.checkin.checkin_clas(self.connection, self.clas_object, 'corrnr')
+
+ self.clas.fetch.assert_called_once()
+ self.clas.delete.assert_called_once_with('corrnr')
+ self.clas.create.assert_has_calls([call('corrnr'), call('corrnr')])
+
+ def test_checkin_clas_in_different_package(self):
+ self.fake_open.return_value = StringIOFile(CLAS_XML)
+ self.clas.create.side_effect = ExceptionResourceAlreadyExists('Class already exists.')
+ self.package_reference.name = 'different_package'
+
+ with self.assertRaises(ExceptionCheckinFailure) as cm:
+ sap.cli.checkin.checkin_clas(self.connection, self.clas_object)
+
+ self.assertEqual(str(cm.exception),
+ f'Cannot checkin class {self.clas_object.name} into package {self.package.name}.'
+ ' It already exists in package different_package.')
+
+ def test_checkin_clas_create_error(self):
+ self.fake_open.return_value = StringIOFile(CLAS_XML)
+ self.clas.create.side_effect = SAPCliError('Class creation failed.')
+
+ with self.assertRaises(SAPCliError) as cm:
+ sap.cli.checkin.checkin_clas(self.connection, self.clas_object)
- fake_mod_log.return_value.info.assert_called_once_with('Clas already created.')
- self.assert_open_editor_calls([])
+ self.assertEqual(str(cm.exception), 'Class creation failed.')
+ self.clas.object_editor.assert_not_called()
def test_checkin_clas_source_file_wrong_suffix(self):
self.fake_open.return_value = StringIOFile(CLAS_XML)