Skip to content

Commit cf61005

Browse files
jonasz-lasut-baloisejonasz-lasut
authored andcommitted
Fix ADO integration and update unit tests to be compliant with python>3.13
1 parent 9378c89 commit cf61005

File tree

5 files changed

+41
-85
lines changed

5 files changed

+41
-85
lines changed

gitopscli/git_api/azure_devops_git_repo_api_adapter.py

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
from collections.abc import Callable
12
from typing import Any, Literal
23

34
from azure.devops.connection import Connection
45
from azure.devops.credentials import BasicAuthentication
5-
from azure.devops.v7_1.git.models import (
6+
from azure.devops.v7_0.git.models import (
67
Comment,
78
GitPullRequest,
89
GitPullRequestCommentThread,
910
GitPullRequestCompletionOptions,
10-
GitRefUpdate,
1111
)
1212
from msrest.exceptions import ClientException
1313

@@ -26,6 +26,7 @@ def __init__(
2626
password: str | None,
2727
organisation: str,
2828
repository_name: str,
29+
sleep_func: Callable[[int], None] | None,
2930
) -> None:
3031
# In Azure DevOps:
3132
# git_provider_url = https://dev.azure.com/organization (e.g. https://dev.azure.com/org)
@@ -37,6 +38,11 @@ def __init__(
3738
self.__project_name = organisation # In Azure DevOps, "organisation" param is actually the project
3839
self.__repository_name = repository_name
3940

41+
if not sleep_func:
42+
raise GitOpsException("Sleep function is required for Azure DevOps")
43+
44+
self.__sleep_func = sleep_func
45+
4046
if not password:
4147
raise GitOpsException("Password (Personal Access Token) is required for Azure DevOps")
4248

@@ -108,13 +114,23 @@ def merge_pull_request(
108114
merge_parameters: dict[str, Any] | None = None,
109115
) -> None:
110116
try:
117+
# Required because of race-condition before PullRequest is first properly created+queued
118+
# and the PullRequest completion can be requested
119+
self.__sleep_func(3)
120+
111121
pr = self.__git_client.get_pull_request(
112122
repository_id=self.__repository_name,
113123
pull_request_id=pr_id,
114124
project=self.__project_name,
115125
)
116126

117-
completion_options = GitPullRequestCompletionOptions()
127+
# Handle deletion of a branch with completion_options instead of delete_pull_request call
128+
completion_options = GitPullRequestCompletionOptions(
129+
bypass_policy=False,
130+
delete_source_branch=True,
131+
transition_work_items=False,
132+
)
133+
118134
if merge_method == "squash":
119135
completion_options.merge_strategy = "squash"
120136
elif merge_method == "rebase":
@@ -177,46 +193,9 @@ def add_pull_request_comment(self, pr_id: int, text: str, parent_id: int | None
177193
except Exception as ex:
178194
raise GitOpsException(f"Error connecting to '{self.__base_url}'") from ex
179195

180-
def delete_branch(self, branch: str) -> None:
181-
def _raise_branch_not_found() -> None:
182-
raise GitOpsException(f"Branch '{branch}' does not exist")
183-
184-
try:
185-
refs = self.__git_client.get_refs(
186-
repository_id=self.__repository_name,
187-
project=self.__project_name,
188-
filter=f"heads/{branch}",
189-
)
190-
191-
if not refs:
192-
_raise_branch_not_found()
193-
194-
branch_ref = refs[0]
195-
196-
# Create ref update to delete the branch
197-
ref_update = GitRefUpdate(
198-
name=f"refs/heads/{branch}",
199-
old_object_id=branch_ref.object_id,
200-
new_object_id="0000000000000000000000000000000000000000",
201-
)
202-
203-
self.__git_client.update_refs(
204-
ref_updates=[ref_update],
205-
repository_id=self.__repository_name,
206-
project=self.__project_name,
207-
)
208-
209-
except GitOpsException:
210-
raise
211-
except ClientException as ex:
212-
error_msg = str(ex)
213-
if "401" in error_msg:
214-
raise GitOpsException("Bad credentials") from ex
215-
if "404" in error_msg:
216-
raise GitOpsException(f"Branch '{branch}' does not exist") from ex
217-
raise GitOpsException(f"Error deleting branch: {error_msg}") from ex
218-
except Exception as ex:
219-
raise GitOpsException(f"Error connecting to '{self.__base_url}'") from ex
196+
def delete_branch(self, _: str) -> None:
197+
# branch deletion is set in merge completion_options
198+
return
220199

221200
def get_branch_head_hash(self, branch: str) -> str:
222201
def _raise_branch_not_found() -> None:

gitopscli/git_api/git_repo_api_factory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from time import sleep
2+
13
from gitopscli.gitops_exception import GitOpsException
24

35
from .azure_devops_git_repo_api_adapter import AzureDevOpsGitRepoApiAdapter
@@ -51,5 +53,6 @@ def create(config: GitApiConfig, organisation: str, repository_name: str) -> Git
5153
password=config.password,
5254
organisation=organisation,
5355
repository_name=repository_name,
56+
sleep_func=sleep,
5457
)
5558
return GitRepoApiLoggingProxy(git_repo_api)

tests/git_api/test_azure_devops_git_repo_api_adapter.py

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
from gitopscli.gitops_exception import GitOpsException
99

1010

11+
def mock_sleep_func(_: int) -> None:
12+
return
13+
14+
1115
class AzureDevOpsGitRepoApiAdapterTest(unittest.TestCase):
1216
def setUp(self):
1317
with patch("gitopscli.git_api.azure_devops_git_repo_api_adapter.Connection"):
@@ -17,6 +21,7 @@ def setUp(self):
1721
password="testtoken",
1822
organisation="testproject",
1923
repository_name="testrepo",
24+
sleep_func=mock_sleep_func,
2025
)
2126

2227
@patch("gitopscli.git_api.azure_devops_git_repo_api_adapter.Connection")
@@ -30,6 +35,7 @@ def test_init_success(self, mock_connection):
3035
password="token",
3136
organisation="project",
3237
repository_name="repo",
38+
sleep_func=mock_sleep_func,
3339
)
3440

3541
self.assertEqual(adapter.get_username(), "user")
@@ -44,6 +50,7 @@ def test_init_no_password_raises_exception(self):
4450
password=None,
4551
organisation="project",
4652
repository_name="repo",
53+
sleep_func=mock_sleep_func,
4754
)
4855
self.assertEqual(str(context.value), "Password (Personal Access Token) is required for Azure DevOps")
4956

@@ -239,41 +246,6 @@ def test_get_pull_request_branch_without_refs_prefix(self):
239246

240247
self.assertEqual(result, "feature-branch")
241248

242-
def test_delete_branch_success(self):
243-
# Mock get refs
244-
mock_ref = MagicMock()
245-
mock_ref.object_id = "abc123def456"
246-
self.adapter._AzureDevOpsGitRepoApiAdapter__git_client.get_refs.return_value = [mock_ref]
247-
248-
# Mock update refs
249-
self.adapter._AzureDevOpsGitRepoApiAdapter__git_client.update_refs.return_value = None
250-
251-
self.adapter.delete_branch("feature-branch")
252-
253-
# Verify get_refs was called
254-
self.adapter._AzureDevOpsGitRepoApiAdapter__git_client.get_refs.assert_called_once_with(
255-
repository_id="testrepo", project="testproject", filter="heads/feature-branch"
256-
)
257-
258-
# Verify update_refs was called
259-
call_args = self.adapter._AzureDevOpsGitRepoApiAdapter__git_client.update_refs.call_args
260-
self.assertEqual(call_args.kwargs["repository_id"], "testrepo")
261-
self.assertEqual(call_args.kwargs["project"], "testproject")
262-
263-
ref_updates = call_args.kwargs["ref_updates"]
264-
self.assertEqual(len(ref_updates), 1)
265-
self.assertEqual(ref_updates[0].name, "refs/heads/feature-branch")
266-
self.assertEqual(ref_updates[0].old_object_id, "abc123def456")
267-
self.assertEqual(ref_updates[0].new_object_id, "0000000000000000000000000000000000000000")
268-
269-
def test_delete_branch_not_found(self):
270-
self.adapter._AzureDevOpsGitRepoApiAdapter__git_client.get_refs.return_value = []
271-
272-
with pytest.raises(GitOpsException) as context:
273-
self.adapter.delete_branch("nonexistent")
274-
275-
self.assertEqual(str(context.value), "Branch 'nonexistent' does not exist")
276-
277249
def test_add_pull_request_label_does_nothing(self):
278250
# Labels aren't supported in the SDK implementation, should not raise exception
279251
try:

tests/git_api/test_repo_api_factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from time import sleep
23
from unittest.mock import MagicMock, patch
34

45
from gitopscli.git_api import GitApiConfig, GitProvider, GitRepoApiFactory
@@ -155,6 +156,7 @@ def test_create_azure_devops(self, mock_azure_devops_adapter_constructor, mock_l
155156
password="PAT_TOKEN",
156157
organisation="ORG",
157158
repository_name="REPO",
159+
sleep_func=sleep,
158160
)
159161
mock_logging_proxy_constructor.assert_called_with(mock_azure_devops_adapter)
160162

tests/test_cliparser.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
--pr-id PR_ID the id of the pull request
7979
--parent-id PARENT_ID
8080
the id of the parent comment, in case of a reply
81-
-v [VERBOSE], --verbose [VERBOSE]
81+
-v, --verbose [VERBOSE]
8282
Verbose exception logging
8383
--text TEXT the text of the comment
8484
"""
@@ -146,7 +146,7 @@
146146
--git-hash GIT_HASH the git hash which should be deployed
147147
--preview-id PREVIEW_ID
148148
The user-defined preview ID
149-
-v [VERBOSE], --verbose [VERBOSE]
149+
-v, --verbose [VERBOSE]
150150
Verbose exception logging
151151
"""
152152

@@ -188,7 +188,7 @@
188188
--pr-id PR_ID the id of the pull request
189189
--parent-id PARENT_ID
190190
the id of the parent comment, in case of a reply
191-
-v [VERBOSE], --verbose [VERBOSE]
191+
-v, --verbose [VERBOSE]
192192
Verbose exception logging
193193
"""
194194

@@ -260,7 +260,7 @@
260260
The user-defined preview ID
261261
--expect-preview-exists [EXPECT_PREVIEW_EXISTS]
262262
Fail if preview does not exist
263-
-v [VERBOSE], --verbose [VERBOSE]
263+
-v, --verbose [VERBOSE]
264264
Verbose exception logging
265265
"""
266266

@@ -303,7 +303,7 @@
303303
--branch BRANCH The branch for which the preview was created for
304304
--expect-preview-exists [EXPECT_PREVIEW_EXISTS]
305305
Fail if preview does not exist
306-
-v [VERBOSE], --verbose [VERBOSE]
306+
-v, --verbose [VERBOSE]
307307
Verbose exception logging
308308
"""
309309

@@ -383,7 +383,7 @@
383383
JSON array pr labels (Gitlab, Github supported)
384384
--merge-parameters MERGE_PARAMETERS
385385
JSON object pr parameters (only Gitlab supported)
386-
-v [VERBOSE], --verbose [VERBOSE]
386+
-v, --verbose [VERBOSE]
387387
Verbose exception logging
388388
"""
389389

@@ -435,7 +435,7 @@
435435
--git-provider-url GIT_PROVIDER_URL
436436
Git provider base API URL (e.g.
437437
https://bitbucket.example.tld)
438-
-v [VERBOSE], --verbose [VERBOSE]
438+
-v, --verbose [VERBOSE]
439439
Verbose exception logging
440440
--root-organisation ROOT_ORGANISATION
441441
Root config repository organisation

0 commit comments

Comments
 (0)