Repository: relekang/python-semantic-release
Pull request #352 (2021-06-24 16:12:44)

fix: use release-api for gitlab
Used the release api from python-gitlab instead of the outdated tag api.

Solves this [issue](https://github.com/relekang/python-semantic-release/issues/350)

relekang: Nice, can you please fix the broken tests?
FirstSanny: > 
> 
> Nice, can you please fix the broken tests?

fixed them. but since the test is only running against mocks i am not sure how safe these tests are. 😄 
relekang: Hehe, valid point 😅 
The mocks for these integrations should probably be at the http layer.
FirstSanny: > 
> 
> Hehe, valid point 😅
> The mocks for these integrations should probably be at the http layer.

But i guess this would be a new issue 😉 

Do need to provide anything else?
relekang: Of course, did not mean that it should be done here. I forgot to check back after running the github action.

Thanks for fixing this 🙌 

Patch:
diff --git a/semantic_release/hvcs.py b/semantic_release/hvcs.py
index fd52cf7..98c971e 100644
--- a/semantic_release/hvcs.py
+++ b/semantic_release/hvcs.py
@@ -5,7 +5,7 @@ import mimetypes
 import os
 from typing import Optional, Union
 
-import gitlab
+from gitlab import gitlab, exceptions
 from requests import HTTPError, Session
 from requests.auth import AuthBase
 from urllib3 import Retry
@@ -138,7 +138,8 @@ class Github(Base):
     def session(
         raise_for_status=True, retry: Union[Retry, bool, int] = True
     ) -> Session:
-        session = build_requests_session(raise_for_status=raise_for_status, retry=retry)
+        session = build_requests_session(
+            raise_for_status=raise_for_status, retry=retry)
         session.auth = Github.auth()
         return session
 
@@ -158,7 +159,8 @@ class Github(Base):
         url = "{domain}/repos/{owner}/{repo}/commits/{ref}/status"
         try:
             response = Github.session().get(
-                url.format(domain=Github.api_url(), owner=owner, repo=repo, ref=ref)
+                url.format(domain=Github.api_url(),
+                           owner=owner, repo=repo, ref=ref)
             )
             return response.json().get("state") == "success"
         except HTTPError as e:
@@ -261,11 +263,13 @@ class Github(Base):
         success = Github.create_release(owner, repo, tag, changelog)
 
         if not success:
-            logger.debug("Unsuccessful, looking for an existing release to update")
+            logger.debug(
+                "Unsuccessful, looking for an existing release to update")
             release_id = Github.get_release(owner, repo, tag)
             if release_id:
                 logger.debug(f"Updating release {release_id}")
-                success = Github.edit_release(owner, repo, release_id, changelog)
+                success = Github.edit_release(
+                    owner, repo, release_id, changelog)
             else:
                 logger.debug(f"Existing release not found")
 
@@ -383,7 +387,8 @@ class Gitlab(Base):
         """
         gl = gitlab.Gitlab(Gitlab.api_url(), private_token=Gitlab.token())
         gl.auth()
-        jobs = gl.projects.get(owner + "/" + repo).commits.get(ref).statuses.list()
+        jobs = gl.projects.get(
+            owner + "/" + repo).commits.get(ref).statuses.list()
         for job in jobs:
             if job["status"] not in ["success", "skipped"]:
                 if job["status"] == "pending":
@@ -392,7 +397,8 @@ class Gitlab(Base):
                     )
                     return False
                 elif job["status"] == "failed" and not job["allow_failure"]:
-                    logger.debug(f"check_build_status: job {job['name']} failed")
+                    logger.debug(
+                        f"check_build_status: job {job['name']} failed")
                     return False
         return True
 
@@ -414,13 +420,11 @@ class Gitlab(Base):
         gl = gitlab.Gitlab(Gitlab.api_url(), private_token=Gitlab.token())
         gl.auth()
         try:
-            tag = gl.projects.get(owner + "/" + repo).tags.get(ref)
-            tag.set_release_description(changelog)
-        except gitlab.exceptions.GitlabGetError:
-            logger.debug(f"Tag {ref} was not found for project {owner}/{repo}")
-            return False
-        except gitlab.exceptions.GitlabUpdateError:
-            logger.debug(f"Failed to update tag {ref} for project {owner}/{repo}")
+            gl.projects.get(owner + "/" + repo).releases.create(
+                {'name': 'Release ' + version, 'tag_name': ref, 'description': changelog})
+        except exceptions.GitlabCreateError:
+            logger.debug(
+                f"Release {ref} could not be created for project {owner}/{repo}")
             return False
 
         return True
@@ -436,7 +440,8 @@ def get_hvcs() -> Base:
     try:
         return globals()[hvcs.capitalize()]
     except KeyError:
-        raise ImproperConfigurationError('"{0}" is not a valid option for hvcs.')
+        raise ImproperConfigurationError(
+            '"{0}" is not a valid option for hvcs.')
 
 
 def check_build_status(owner: str, repository: str, ref: str) -> bool:
@@ -462,7 +467,8 @@ def post_changelog(owner: str, repository: str, version: str, changelog: str) ->
     :param changelog: A string with the changelog in correct format
     :return: a tuple with success status and payload from hvcs
     """
-    logger.debug(f"Posting release changelog for {owner}/{repository} {version}")
+    logger.debug(
+        f"Posting release changelog for {owner}/{repository} {version}")
     return get_hvcs().post_release_changelog(owner, repository, version, changelog)
 
 


Test patch:
diff --git a/tests/mocks/mock_gitlab.py b/tests/mocks/mock_gitlab.py
index 2afac94..536102c 100644
--- a/tests/mocks/mock_gitlab.py
+++ b/tests/mocks/mock_gitlab.py
@@ -9,6 +9,7 @@ class _GitlabProject:
     def __init__(self, status):
         self.commits = {"my_ref": self._Commit(status)}
         self.tags = self._Tags()
+        self.releases = self._Releases()
 
     class _Commit:
         def __init__(self, status):
@@ -36,7 +37,8 @@ class _GitlabProject:
                             "status": "passed",
                             "allow_failure": False,
                         },
-                        {"name": "bad_job", "status": "failed", "allow_failure": False},
+                        {"name": "bad_job", "status": "failed",
+                            "allow_failure": False},
                     ]
                 elif status == "allow_failure":
                     self.jobs = [
@@ -88,12 +90,27 @@ class _GitlabProject:
                 if self.locked:
                     raise gitlab.exceptions.GitlabUpdateError
 
+    class _Releases:
+        def __init__(self):
+            pass
+
+        def create(self, input):
+            if input['name'] and input['tag_name']:
+                if input['tag_name'] == "vmy_good_tag" or input['tag_name'] == "vmy_locked_tag":
+                    return self._Release()
+            raise gitlab.exceptions.GitlabCreateError
+
+        class _Release:
+            def __init__(self, locked=False):
+                pass
+
 
 def mock_gitlab(status="success"):
     mocks = [
         mock.patch("os.environ", {"GL_TOKEN": "token"}),
         mock.patch(
-            "semantic_release.hvcs.config.get", wrapped_config_get(hvcs="gitlab")
+            "semantic_release.hvcs.config.get", wrapped_config_get(
+                hvcs="gitlab")
         ),
         mock.patch("gitlab.Gitlab.auth"),
         mock.patch(
diff --git a/tests/test_hvcs.py b/tests/test_hvcs.py
index 00d8c18..ea13d79 100644
--- a/tests/test_hvcs.py
+++ b/tests/test_hvcs.py
@@ -320,10 +320,12 @@ class GithubReleaseTests(TestCase):
             dummy_file.write(dummy_content)
 
         def request_callback(request):
-            self.assertEqual(request.body.decode().replace("\r\n", "\n"), dummy_content)
+            self.assertEqual(request.body.decode().replace(
+                "\r\n", "\n"), dummy_content)
             self.assertEqual(request.url, self.asset_url_params)
             self.assertEqual(request.headers["Content-Type"], "text/markdown")
-            self.assertEqual("token super-token", request.headers.get("Authorization"))
+            self.assertEqual("token super-token",
+                             request.headers.get("Authorization"))
 
             return 201, {}, json.dumps({})
 
@@ -351,12 +353,14 @@ class GithubReleaseTests(TestCase):
             dummy_file.write(dummy_content)
 
         def request_callback(request):
-            self.assertEqual(request.body.decode().replace("\r\n", "\n"), dummy_content)
+            self.assertEqual(request.body.decode().replace(
+                "\r\n", "\n"), dummy_content)
             self.assertEqual(request.url, self.asset_no_extension_url_params)
             self.assertEqual(
                 request.headers["Content-Type"], "application/octet-stream"
             )
-            self.assertEqual("token super-token", request.headers["Authorization"])
+            self.assertEqual("token super-token",
+                             request.headers["Authorization"])
 
             return 201, {}, json.dumps({})
 
@@ -382,10 +386,12 @@ class GithubReleaseTests(TestCase):
             dummy_file.write(dummy_content)
 
         def request_callback(request):
-            self.assertEqual(request.body.decode().replace("\r\n", "\n"), dummy_content)
+            self.assertEqual(request.body.decode().replace(
+                "\r\n", "\n"), dummy_content)
             self.assertEqual(request.url, self.dist_asset_url_params)
             self.assertEqual(request.headers["Content-Type"], "text/markdown")
-            self.assertEqual("token super-token", request.headers.get("Authorization"))
+            self.assertEqual("token super-token",
+                             request.headers.get("Authorization"))
 
             return 201, {}, json.dumps({})
 
@@ -411,15 +417,18 @@ class GithubReleaseTests(TestCase):
 class GitlabReleaseTests(TestCase):
     @mock_gitlab()
     def test_should_return_true_if_success(self, mock_auth, mock_project):
-        self.assertTrue(post_changelog("owner", "repo", "my_good_tag", "changelog"))
+        self.assertTrue(post_changelog(
+            "owner", "repo", "my_good_tag", "changelog"))
 
     @mock_gitlab()
     def test_should_return_false_if_bad_tag(self, mock_auth, mock_project):
-        self.assertFalse(post_changelog("owner", "repo", "my_bad_tag", "changelog"))
+        self.assertFalse(post_changelog(
+            "owner", "repo", "my_bad_tag", "changelog"))
 
     @mock_gitlab()
-    def test_should_return_false_if_failed_update(self, mock_auth, mock_project):
-        self.assertFalse(post_changelog("owner", "repo", "my_locked_tag", "changelog"))
+    def test_should_return_true_for_locked_tags(self, mock_auth, mock_project):
+        self.assertTrue(post_changelog(
+            "owner", "repo", "my_locked_tag", "changelog"))
 
 
 def test_github_token():