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)