xai-review 0.36.0__py3-none-any.whl → 0.38.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xai-review might be problematic. Click here for more details.
- ai_review/clients/{bitbucket → bitbucket_cloud}/client.py +8 -7
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/client.py +51 -39
- ai_review/clients/bitbucket_cloud/pr/schema/comments.py +59 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/files.py +7 -7
- ai_review/clients/bitbucket_cloud/pr/schema/pull_request.py +34 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/user.py +1 -1
- ai_review/clients/bitbucket_cloud/pr/types.py +44 -0
- ai_review/clients/{bitbucket → bitbucket_cloud}/tools.py +1 -1
- ai_review/clients/bitbucket_server/client.py +32 -0
- ai_review/clients/bitbucket_server/pr/client.py +163 -0
- ai_review/clients/bitbucket_server/pr/schema/changes.py +36 -0
- ai_review/clients/bitbucket_server/pr/schema/comments.py +55 -0
- ai_review/clients/bitbucket_server/pr/schema/pull_request.py +48 -0
- ai_review/clients/bitbucket_server/pr/schema/user.py +13 -0
- ai_review/clients/bitbucket_server/pr/types.py +44 -0
- ai_review/clients/bitbucket_server/tools.py +6 -0
- ai_review/clients/claude/client.py +1 -0
- ai_review/clients/gemini/client.py +1 -0
- ai_review/clients/gitea/client.py +4 -3
- ai_review/clients/github/client.py +2 -1
- ai_review/clients/gitlab/client.py +2 -1
- ai_review/clients/ollama/client.py +1 -0
- ai_review/clients/openai/v1/client.py +1 -0
- ai_review/clients/openai/v2/client.py +1 -0
- ai_review/clients/openrouter/client.py +1 -0
- ai_review/libs/config/http.py +2 -1
- ai_review/libs/config/vcs/base.py +23 -6
- ai_review/libs/config/vcs/{bitbucket.py → bitbucket_cloud.py} +2 -2
- ai_review/libs/config/vcs/bitbucket_server.py +13 -0
- ai_review/libs/constants/vcs_provider.py +2 -1
- ai_review/libs/http/client.py +1 -1
- ai_review/resources/pricing.yaml +4 -0
- ai_review/services/vcs/bitbucket_cloud/__init__.py +0 -0
- ai_review/services/vcs/{bitbucket → bitbucket_cloud}/adapter.py +2 -2
- ai_review/services/vcs/{bitbucket → bitbucket_cloud}/client.py +24 -21
- ai_review/services/vcs/bitbucket_server/__init__.py +0 -0
- ai_review/services/vcs/bitbucket_server/adapter.py +27 -0
- ai_review/services/vcs/bitbucket_server/client.py +263 -0
- ai_review/services/vcs/factory.py +6 -3
- ai_review/tests/fixtures/clients/bitbucket_cloud.py +207 -0
- ai_review/tests/fixtures/clients/bitbucket_server.py +265 -0
- ai_review/tests/suites/clients/bitbucket_cloud/__init__.py +0 -0
- ai_review/tests/suites/clients/bitbucket_cloud/test_client.py +14 -0
- ai_review/tests/suites/clients/bitbucket_cloud/test_tools.py +31 -0
- ai_review/tests/suites/clients/bitbucket_server/__init__.py +0 -0
- ai_review/tests/suites/clients/bitbucket_server/test_client.py +14 -0
- ai_review/tests/suites/clients/bitbucket_server/test_tools.py +38 -0
- ai_review/tests/suites/services/vcs/bitbucket_cloud/__init__.py +0 -0
- ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_adapter.py +24 -24
- ai_review/tests/suites/services/vcs/{bitbucket → bitbucket_cloud}/test_client.py +51 -51
- ai_review/tests/suites/services/vcs/bitbucket_server/__init__.py +0 -0
- ai_review/tests/suites/services/vcs/bitbucket_server/test_adapter.py +115 -0
- ai_review/tests/suites/services/vcs/bitbucket_server/test_client.py +201 -0
- ai_review/tests/suites/services/vcs/test_factory.py +11 -4
- xai_review-0.38.0.dist-info/METADATA +519 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/RECORD +66 -44
- ai_review/clients/bitbucket/pr/schema/comments.py +0 -63
- ai_review/clients/bitbucket/pr/schema/pull_request.py +0 -34
- ai_review/clients/bitbucket/pr/types.py +0 -44
- ai_review/tests/fixtures/clients/bitbucket.py +0 -204
- ai_review/tests/suites/clients/bitbucket/test_client.py +0 -14
- ai_review/tests/suites/clients/bitbucket/test_tools.py +0 -31
- xai_review-0.36.0.dist-info/METADATA +0 -317
- /ai_review/clients/{bitbucket → bitbucket_cloud}/__init__.py +0 -0
- /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/__init__.py +0 -0
- /ai_review/clients/{bitbucket → bitbucket_cloud}/pr/schema/__init__.py +0 -0
- /ai_review/{services/vcs/bitbucket → clients/bitbucket_server}/__init__.py +0 -0
- /ai_review/{tests/suites/clients/bitbucket → clients/bitbucket_server/pr}/__init__.py +0 -0
- /ai_review/{tests/suites/services/vcs/bitbucket → clients/bitbucket_server/pr/schema}/__init__.py +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/WHEEL +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.36.0.dist-info → xai_review-0.38.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.bitbucket_server.client import get_bitbucket_server_http_client
|
|
4
|
+
from ai_review.clients.bitbucket_server.pr.schema.comments import (
|
|
5
|
+
BitbucketServerCommentAnchorSchema,
|
|
6
|
+
BitbucketServerCommentParentSchema,
|
|
7
|
+
BitbucketServerCreatePRCommentRequestSchema
|
|
8
|
+
)
|
|
9
|
+
from ai_review.config import settings
|
|
10
|
+
from ai_review.libs.logger import get_logger
|
|
11
|
+
from ai_review.services.vcs.bitbucket_server.adapter import get_review_comment_from_bitbucket_server_comment
|
|
12
|
+
from ai_review.services.vcs.types import (
|
|
13
|
+
VCSClientProtocol,
|
|
14
|
+
ThreadKind,
|
|
15
|
+
UserSchema,
|
|
16
|
+
BranchRefSchema,
|
|
17
|
+
ReviewInfoSchema,
|
|
18
|
+
ReviewThreadSchema,
|
|
19
|
+
ReviewCommentSchema,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = get_logger("BITBUCKET_SERVER_VCS_CLIENT")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BitbucketServerVCSClient(VCSClientProtocol):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.http_client = get_bitbucket_server_http_client()
|
|
28
|
+
self.project_key = settings.vcs.pipeline.project_key
|
|
29
|
+
self.repo_slug = settings.vcs.pipeline.repo_slug
|
|
30
|
+
self.pull_request_id = settings.vcs.pipeline.pull_request_id
|
|
31
|
+
self.pull_request_ref = f"{self.project_key}/{self.repo_slug}#{self.pull_request_id}"
|
|
32
|
+
|
|
33
|
+
# --- Review info ---
|
|
34
|
+
async def get_review_info(self) -> ReviewInfoSchema:
|
|
35
|
+
try:
|
|
36
|
+
pr = await self.http_client.pr.get_pull_request(
|
|
37
|
+
project_key=self.project_key,
|
|
38
|
+
repo_slug=self.repo_slug,
|
|
39
|
+
pull_request_id=self.pull_request_id,
|
|
40
|
+
)
|
|
41
|
+
changes = await self.http_client.pr.get_changes(
|
|
42
|
+
project_key=self.project_key,
|
|
43
|
+
repo_slug=self.repo_slug,
|
|
44
|
+
pull_request_id=self.pull_request_id,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
logger.info(f"Fetched PR info for {self.pull_request_ref}")
|
|
48
|
+
|
|
49
|
+
return ReviewInfoSchema(
|
|
50
|
+
id=pr.id,
|
|
51
|
+
title=pr.title,
|
|
52
|
+
description=pr.description or "",
|
|
53
|
+
author=UserSchema(
|
|
54
|
+
id=pr.author.user.id,
|
|
55
|
+
name=pr.author.user.display_name,
|
|
56
|
+
username=pr.author.user.slug or pr.author.user.name,
|
|
57
|
+
),
|
|
58
|
+
labels=[], # Bitbucket Server не поддерживает labels напрямую
|
|
59
|
+
base_sha=pr.to_ref.latest_commit,
|
|
60
|
+
head_sha=pr.from_ref.latest_commit,
|
|
61
|
+
assignees=[], # нет отдельного поля в Bitbucket Server API
|
|
62
|
+
reviewers=[
|
|
63
|
+
UserSchema(
|
|
64
|
+
id=user.user.id,
|
|
65
|
+
name=user.user.display_name,
|
|
66
|
+
username=user.user.slug or user.user.name,
|
|
67
|
+
)
|
|
68
|
+
for user in pr.reviewers
|
|
69
|
+
],
|
|
70
|
+
source_branch=BranchRefSchema(
|
|
71
|
+
ref=pr.from_ref.display_id,
|
|
72
|
+
sha=pr.from_ref.latest_commit,
|
|
73
|
+
),
|
|
74
|
+
target_branch=BranchRefSchema(
|
|
75
|
+
ref=pr.to_ref.display_id,
|
|
76
|
+
sha=pr.to_ref.latest_commit,
|
|
77
|
+
),
|
|
78
|
+
changed_files=[
|
|
79
|
+
change.path.to_string
|
|
80
|
+
for change in changes.values
|
|
81
|
+
if change.path and change.path.to_string
|
|
82
|
+
],
|
|
83
|
+
)
|
|
84
|
+
except Exception as error:
|
|
85
|
+
logger.exception(f"Failed to fetch PR info {self.pull_request_ref}: {error}")
|
|
86
|
+
return ReviewInfoSchema()
|
|
87
|
+
|
|
88
|
+
# --- Comments ---
|
|
89
|
+
async def get_general_comments(self) -> list[ReviewCommentSchema]:
|
|
90
|
+
try:
|
|
91
|
+
response = await self.http_client.pr.get_comments(
|
|
92
|
+
project_key=self.project_key,
|
|
93
|
+
repo_slug=self.repo_slug,
|
|
94
|
+
pull_request_id=self.pull_request_id,
|
|
95
|
+
)
|
|
96
|
+
logger.info(f"Fetched general comments for {self.pull_request_ref}")
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
get_review_comment_from_bitbucket_server_comment(comment)
|
|
100
|
+
for comment in response.values
|
|
101
|
+
if comment.anchor is None # нет привязки к файлу/строке — значит общий комментарий
|
|
102
|
+
]
|
|
103
|
+
except Exception as error:
|
|
104
|
+
logger.exception(f"Failed to fetch general comments for {self.pull_request_ref}: {error}")
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
async def get_inline_comments(self) -> list[ReviewCommentSchema]:
|
|
108
|
+
try:
|
|
109
|
+
response = await self.http_client.pr.get_comments(
|
|
110
|
+
project_key=self.project_key,
|
|
111
|
+
repo_slug=self.repo_slug,
|
|
112
|
+
pull_request_id=self.pull_request_id,
|
|
113
|
+
)
|
|
114
|
+
logger.info(f"Fetched inline comments for {self.pull_request_ref}")
|
|
115
|
+
|
|
116
|
+
return [
|
|
117
|
+
get_review_comment_from_bitbucket_server_comment(comment)
|
|
118
|
+
for comment in response.values
|
|
119
|
+
if comment.anchor is not None and comment.anchor.path is not None
|
|
120
|
+
]
|
|
121
|
+
except Exception as error:
|
|
122
|
+
logger.exception(f"Failed to fetch inline comments for {self.pull_request_ref}: {error}")
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
async def create_general_comment(self, message: str) -> None:
|
|
126
|
+
try:
|
|
127
|
+
logger.info(f"Posting general comment to PR {self.pull_request_ref}: {message}")
|
|
128
|
+
|
|
129
|
+
request = BitbucketServerCreatePRCommentRequestSchema(text=message)
|
|
130
|
+
|
|
131
|
+
await self.http_client.pr.create_comment(
|
|
132
|
+
project_key=self.project_key,
|
|
133
|
+
repo_slug=self.repo_slug,
|
|
134
|
+
pull_request_id=self.pull_request_id,
|
|
135
|
+
request=request,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
logger.info(f"Created general comment in PR {self.pull_request_ref}")
|
|
139
|
+
|
|
140
|
+
except Exception as error:
|
|
141
|
+
logger.exception(f"Failed to create general comment in PR {self.pull_request_ref}: {error}")
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
async def create_inline_comment(self, file: str, line: int, message: str) -> None:
|
|
145
|
+
try:
|
|
146
|
+
logger.info(f"Posting inline comment in {self.pull_request_ref} at {file}:{line}: {message}")
|
|
147
|
+
|
|
148
|
+
anchor = BitbucketServerCommentAnchorSchema(path=file, line=line, line_type="ADDED")
|
|
149
|
+
request = BitbucketServerCreatePRCommentRequestSchema(text=message, anchor=anchor)
|
|
150
|
+
|
|
151
|
+
await self.http_client.pr.create_comment(
|
|
152
|
+
project_key=self.project_key,
|
|
153
|
+
repo_slug=self.repo_slug,
|
|
154
|
+
pull_request_id=self.pull_request_id,
|
|
155
|
+
request=request,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
logger.info(f"Created inline comment in {self.pull_request_ref} at {file}:{line}")
|
|
159
|
+
|
|
160
|
+
except Exception as error:
|
|
161
|
+
logger.exception(
|
|
162
|
+
f"Failed to create inline comment in {self.pull_request_ref} at {file}:{line}: {error}")
|
|
163
|
+
raise
|
|
164
|
+
|
|
165
|
+
# --- Replies ---
|
|
166
|
+
async def create_inline_reply(self, thread_id: int | str, message: str) -> None:
|
|
167
|
+
try:
|
|
168
|
+
logger.info(f"Replying to inline thread {thread_id=} in PR {self.pull_request_ref}")
|
|
169
|
+
request = BitbucketServerCreatePRCommentRequestSchema(
|
|
170
|
+
text=message,
|
|
171
|
+
parent=BitbucketServerCommentParentSchema(id=int(thread_id)),
|
|
172
|
+
)
|
|
173
|
+
await self.http_client.pr.create_comment(
|
|
174
|
+
project_key=self.project_key,
|
|
175
|
+
repo_slug=self.repo_slug,
|
|
176
|
+
pull_request_id=self.pull_request_id,
|
|
177
|
+
request=request,
|
|
178
|
+
)
|
|
179
|
+
logger.info(f"Created inline reply to thread {thread_id=} in PR {self.pull_request_ref}")
|
|
180
|
+
except Exception as error:
|
|
181
|
+
logger.exception(
|
|
182
|
+
f"Failed to create inline reply to thread {thread_id=} in PR {self.pull_request_ref}: {error}"
|
|
183
|
+
)
|
|
184
|
+
raise
|
|
185
|
+
|
|
186
|
+
async def create_summary_reply(self, thread_id: int | str, message: str) -> None:
|
|
187
|
+
try:
|
|
188
|
+
logger.info(f"Replying to summary thread {thread_id=} in PR {self.pull_request_ref}")
|
|
189
|
+
request = BitbucketServerCreatePRCommentRequestSchema(
|
|
190
|
+
text=message,
|
|
191
|
+
parent=BitbucketServerCommentParentSchema(id=int(thread_id)),
|
|
192
|
+
)
|
|
193
|
+
await self.http_client.pr.create_comment(
|
|
194
|
+
project_key=self.project_key,
|
|
195
|
+
repo_slug=self.repo_slug,
|
|
196
|
+
pull_request_id=self.pull_request_id,
|
|
197
|
+
request=request,
|
|
198
|
+
)
|
|
199
|
+
logger.info(f"Created summary reply to thread {thread_id=} in PR {self.pull_request_ref}")
|
|
200
|
+
except Exception as error:
|
|
201
|
+
logger.exception(
|
|
202
|
+
f"Failed to create summary reply to thread {thread_id=} in PR {self.pull_request_ref}: {error}"
|
|
203
|
+
)
|
|
204
|
+
raise
|
|
205
|
+
|
|
206
|
+
# --- Threads ---
|
|
207
|
+
async def get_inline_threads(self) -> list[ReviewThreadSchema]:
|
|
208
|
+
try:
|
|
209
|
+
comments = await self.get_inline_comments()
|
|
210
|
+
|
|
211
|
+
threads_by_id: dict[str | int, list[ReviewCommentSchema]] = defaultdict(list)
|
|
212
|
+
for comment in comments:
|
|
213
|
+
if not comment.file:
|
|
214
|
+
continue
|
|
215
|
+
threads_by_id[comment.thread_id].append(comment)
|
|
216
|
+
|
|
217
|
+
logger.info(f"Built {len(threads_by_id)} inline threads for {self.pull_request_ref}")
|
|
218
|
+
|
|
219
|
+
threads: list[ReviewThreadSchema] = []
|
|
220
|
+
for thread_id, thread in threads_by_id.items():
|
|
221
|
+
file = thread[0].file
|
|
222
|
+
line = thread[0].line
|
|
223
|
+
if not file:
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
threads.append(
|
|
227
|
+
ReviewThreadSchema(
|
|
228
|
+
id=thread_id,
|
|
229
|
+
kind=ThreadKind.INLINE,
|
|
230
|
+
file=file,
|
|
231
|
+
line=line,
|
|
232
|
+
comments=sorted(thread, key=lambda c: int(c.id)),
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return threads
|
|
237
|
+
|
|
238
|
+
except Exception as error:
|
|
239
|
+
logger.exception(f"Failed to fetch inline threads for {self.pull_request_ref}: {error}")
|
|
240
|
+
return []
|
|
241
|
+
|
|
242
|
+
async def get_general_threads(self) -> list[ReviewThreadSchema]:
|
|
243
|
+
try:
|
|
244
|
+
comments = await self.get_general_comments()
|
|
245
|
+
|
|
246
|
+
threads_by_id: dict[str | int, list[ReviewCommentSchema]] = defaultdict(list)
|
|
247
|
+
for comment in comments:
|
|
248
|
+
threads_by_id[comment.thread_id].append(comment)
|
|
249
|
+
|
|
250
|
+
logger.info(f"Built {len(threads_by_id)} general threads for {self.pull_request_ref}")
|
|
251
|
+
|
|
252
|
+
return [
|
|
253
|
+
ReviewThreadSchema(
|
|
254
|
+
id=thread_id,
|
|
255
|
+
kind=ThreadKind.SUMMARY,
|
|
256
|
+
comments=sorted(thread, key=lambda c: int(c.id)),
|
|
257
|
+
)
|
|
258
|
+
for thread_id, thread in threads_by_id.items()
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
except Exception as error:
|
|
262
|
+
logger.exception(f"Failed to fetch general threads for {self.pull_request_ref}: {error}")
|
|
263
|
+
return []
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from ai_review.config import settings
|
|
2
2
|
from ai_review.libs.constants.vcs_provider import VCSProvider
|
|
3
|
-
from ai_review.services.vcs.
|
|
3
|
+
from ai_review.services.vcs.bitbucket_cloud.client import BitbucketCloudVCSClient
|
|
4
|
+
from ai_review.services.vcs.bitbucket_server.client import BitbucketServerVCSClient
|
|
4
5
|
from ai_review.services.vcs.gitea.client import GiteaVCSClient
|
|
5
6
|
from ai_review.services.vcs.github.client import GitHubVCSClient
|
|
6
7
|
from ai_review.services.vcs.gitlab.client import GitLabVCSClient
|
|
@@ -15,7 +16,9 @@ def get_vcs_client() -> VCSClientProtocol:
|
|
|
15
16
|
return GitLabVCSClient()
|
|
16
17
|
case VCSProvider.GITHUB:
|
|
17
18
|
return GitHubVCSClient()
|
|
18
|
-
case VCSProvider.
|
|
19
|
-
return
|
|
19
|
+
case VCSProvider.BITBUCKET_CLOUD:
|
|
20
|
+
return BitbucketCloudVCSClient()
|
|
21
|
+
case VCSProvider.BITBUCKET_SERVER:
|
|
22
|
+
return BitbucketServerVCSClient()
|
|
20
23
|
case _:
|
|
21
24
|
raise ValueError(f"Unsupported VCS provider: {settings.vcs.provider}")
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pydantic import HttpUrl, SecretStr
|
|
3
|
+
|
|
4
|
+
from ai_review.clients.bitbucket_cloud.pr.schema.comments import (
|
|
5
|
+
BitbucketCloudPRCommentSchema,
|
|
6
|
+
BitbucketCloudCommentContentSchema,
|
|
7
|
+
BitbucketCloudCommentInlineSchema,
|
|
8
|
+
BitbucketCloudGetPRCommentsResponseSchema,
|
|
9
|
+
BitbucketCloudCreatePRCommentRequestSchema,
|
|
10
|
+
BitbucketCloudCreatePRCommentResponseSchema,
|
|
11
|
+
)
|
|
12
|
+
from ai_review.clients.bitbucket_cloud.pr.schema.files import (
|
|
13
|
+
BitbucketCloudGetPRFilesResponseSchema,
|
|
14
|
+
BitbucketCloudPRFileSchema,
|
|
15
|
+
BitbucketCloudPRFilePathSchema,
|
|
16
|
+
)
|
|
17
|
+
from ai_review.clients.bitbucket_cloud.pr.schema.pull_request import (
|
|
18
|
+
BitbucketCloudUserSchema,
|
|
19
|
+
BitbucketCloudBranchSchema,
|
|
20
|
+
BitbucketCloudCommitSchema,
|
|
21
|
+
BitbucketCloudRepositorySchema,
|
|
22
|
+
BitbucketCloudPRLocationSchema,
|
|
23
|
+
BitbucketCloudGetPRResponseSchema,
|
|
24
|
+
)
|
|
25
|
+
from ai_review.clients.bitbucket_cloud.pr.types import BitbucketCloudPullRequestsHTTPClientProtocol
|
|
26
|
+
from ai_review.config import settings
|
|
27
|
+
from ai_review.libs.config.vcs.base import BitbucketCloudVCSConfig
|
|
28
|
+
from ai_review.libs.config.vcs.bitbucket_cloud import (
|
|
29
|
+
BitbucketCloudPipelineConfig,
|
|
30
|
+
BitbucketCloudHTTPClientConfig
|
|
31
|
+
)
|
|
32
|
+
from ai_review.libs.constants.vcs_provider import VCSProvider
|
|
33
|
+
from ai_review.services.vcs.bitbucket_cloud.client import BitbucketCloudVCSClient
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FakeBitbucketCloudPullRequestsHTTPClient(BitbucketCloudPullRequestsHTTPClientProtocol):
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.calls: list[tuple[str, dict]] = []
|
|
39
|
+
|
|
40
|
+
async def get_pull_request(
|
|
41
|
+
self,
|
|
42
|
+
workspace: str,
|
|
43
|
+
repo_slug: str,
|
|
44
|
+
pull_request_id: str
|
|
45
|
+
) -> BitbucketCloudGetPRResponseSchema:
|
|
46
|
+
self.calls.append(
|
|
47
|
+
(
|
|
48
|
+
"get_pull_request",
|
|
49
|
+
{"workspace": workspace, "repo_slug": repo_slug, "pull_request_id": pull_request_id}
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
return BitbucketCloudGetPRResponseSchema(
|
|
53
|
+
id=1,
|
|
54
|
+
title="Fake Bitbucket PR",
|
|
55
|
+
description="This is a fake PR for testing",
|
|
56
|
+
state="OPEN",
|
|
57
|
+
author=BitbucketCloudUserSchema(uuid="u1", display_name="Tester", nickname="tester"),
|
|
58
|
+
source=BitbucketCloudPRLocationSchema(
|
|
59
|
+
commit=BitbucketCloudCommitSchema(hash="def456"),
|
|
60
|
+
branch=BitbucketCloudBranchSchema(name="feature/test"),
|
|
61
|
+
repository=BitbucketCloudRepositorySchema(uuid="r1", full_name="workspace/repo"),
|
|
62
|
+
),
|
|
63
|
+
destination=BitbucketCloudPRLocationSchema(
|
|
64
|
+
commit=BitbucketCloudCommitSchema(hash="abc123"),
|
|
65
|
+
branch=BitbucketCloudBranchSchema(name="main"),
|
|
66
|
+
repository=BitbucketCloudRepositorySchema(uuid="r1", full_name="workspace/repo"),
|
|
67
|
+
),
|
|
68
|
+
reviewers=[BitbucketCloudUserSchema(uuid="u2", display_name="Reviewer", nickname="reviewer")],
|
|
69
|
+
participants=[BitbucketCloudUserSchema(uuid="u3", display_name="Participant", nickname="participant")],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def get_files(
|
|
73
|
+
self,
|
|
74
|
+
workspace: str,
|
|
75
|
+
repo_slug: str,
|
|
76
|
+
pull_request_id: str
|
|
77
|
+
) -> BitbucketCloudGetPRFilesResponseSchema:
|
|
78
|
+
self.calls.append(
|
|
79
|
+
(
|
|
80
|
+
"get_files",
|
|
81
|
+
{"workspace": workspace, "repo_slug": repo_slug, "pull_request_id": pull_request_id}
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
return BitbucketCloudGetPRFilesResponseSchema(
|
|
85
|
+
size=2,
|
|
86
|
+
page=1,
|
|
87
|
+
page_len=100,
|
|
88
|
+
next=None,
|
|
89
|
+
values=[
|
|
90
|
+
BitbucketCloudPRFileSchema(
|
|
91
|
+
new=BitbucketCloudPRFilePathSchema(path="app/main.py"),
|
|
92
|
+
old=None,
|
|
93
|
+
status="modified",
|
|
94
|
+
lines_added=10,
|
|
95
|
+
lines_removed=2,
|
|
96
|
+
),
|
|
97
|
+
BitbucketCloudPRFileSchema(
|
|
98
|
+
new=BitbucketCloudPRFilePathSchema(path="utils/helper.py"),
|
|
99
|
+
old=None,
|
|
100
|
+
status="added",
|
|
101
|
+
lines_added=5,
|
|
102
|
+
lines_removed=0,
|
|
103
|
+
),
|
|
104
|
+
],
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
async def get_comments(
|
|
108
|
+
self,
|
|
109
|
+
workspace: str,
|
|
110
|
+
repo_slug: str,
|
|
111
|
+
pull_request_id: str
|
|
112
|
+
) -> BitbucketCloudGetPRCommentsResponseSchema:
|
|
113
|
+
self.calls.append(
|
|
114
|
+
(
|
|
115
|
+
"get_comments",
|
|
116
|
+
{"workspace": workspace, "repo_slug": repo_slug, "pull_request_id": pull_request_id}
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
return BitbucketCloudGetPRCommentsResponseSchema(
|
|
120
|
+
size=2,
|
|
121
|
+
page=1,
|
|
122
|
+
next=None,
|
|
123
|
+
values=[
|
|
124
|
+
BitbucketCloudPRCommentSchema(
|
|
125
|
+
id=1,
|
|
126
|
+
inline=None,
|
|
127
|
+
content=BitbucketCloudCommentContentSchema(raw="General comment"),
|
|
128
|
+
),
|
|
129
|
+
BitbucketCloudPRCommentSchema(
|
|
130
|
+
id=2,
|
|
131
|
+
inline=BitbucketCloudCommentInlineSchema(path="file.py", to_line=5),
|
|
132
|
+
content=BitbucketCloudCommentContentSchema(raw="Inline comment"),
|
|
133
|
+
),
|
|
134
|
+
],
|
|
135
|
+
page_len=100,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def create_comment(
|
|
139
|
+
self,
|
|
140
|
+
workspace: str,
|
|
141
|
+
repo_slug: str,
|
|
142
|
+
pull_request_id: str,
|
|
143
|
+
request: BitbucketCloudCreatePRCommentRequestSchema
|
|
144
|
+
) -> BitbucketCloudCreatePRCommentResponseSchema:
|
|
145
|
+
self.calls.append(
|
|
146
|
+
(
|
|
147
|
+
"create_comment",
|
|
148
|
+
{
|
|
149
|
+
"workspace": workspace,
|
|
150
|
+
"repo_slug": repo_slug,
|
|
151
|
+
"pull_request_id": pull_request_id,
|
|
152
|
+
**request.model_dump(by_alias=True)
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
return BitbucketCloudCreatePRCommentResponseSchema(
|
|
157
|
+
id=10,
|
|
158
|
+
content=request.content,
|
|
159
|
+
inline=request.inline,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class FakeBitbucketCloudHTTPClient:
|
|
164
|
+
def __init__(self, pull_requests_client: BitbucketCloudPullRequestsHTTPClientProtocol):
|
|
165
|
+
self.pr = pull_requests_client
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.fixture
|
|
169
|
+
def fake_bitbucket_cloud_pull_requests_http_client() -> FakeBitbucketCloudPullRequestsHTTPClient:
|
|
170
|
+
return FakeBitbucketCloudPullRequestsHTTPClient()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@pytest.fixture
|
|
174
|
+
def fake_bitbucket_cloud_http_client(
|
|
175
|
+
fake_bitbucket_cloud_pull_requests_http_client: FakeBitbucketCloudPullRequestsHTTPClient
|
|
176
|
+
) -> FakeBitbucketCloudHTTPClient:
|
|
177
|
+
return FakeBitbucketCloudHTTPClient(pull_requests_client=fake_bitbucket_cloud_pull_requests_http_client)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@pytest.fixture
|
|
181
|
+
def bitbucket_cloud_vcs_client(
|
|
182
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
183
|
+
fake_bitbucket_cloud_http_client: FakeBitbucketCloudHTTPClient
|
|
184
|
+
) -> BitbucketCloudVCSClient:
|
|
185
|
+
monkeypatch.setattr(
|
|
186
|
+
"ai_review.services.vcs.bitbucket_cloud.client.get_bitbucket_cloud_http_client",
|
|
187
|
+
lambda: fake_bitbucket_cloud_http_client,
|
|
188
|
+
)
|
|
189
|
+
return BitbucketCloudVCSClient()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.fixture
|
|
193
|
+
def bitbucket_cloud_http_client_config(monkeypatch: pytest.MonkeyPatch):
|
|
194
|
+
fake_config = BitbucketCloudVCSConfig(
|
|
195
|
+
provider=VCSProvider.BITBUCKET_CLOUD,
|
|
196
|
+
pipeline=BitbucketCloudPipelineConfig(
|
|
197
|
+
workspace="workspace",
|
|
198
|
+
repo_slug="repo",
|
|
199
|
+
pull_request_id="123",
|
|
200
|
+
),
|
|
201
|
+
http_client=BitbucketCloudHTTPClientConfig(
|
|
202
|
+
timeout=10,
|
|
203
|
+
api_url=HttpUrl("https://api.bitbucket.org/2.0"),
|
|
204
|
+
api_token=SecretStr("fake-token"),
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
monkeypatch.setattr(settings, "vcs", fake_config)
|