xai-review 0.37.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 +6 -6
- 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/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/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.37.0.dist-info → xai_review-0.38.0.dist-info}/METADATA +9 -7
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/RECORD +55 -33
- 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
- /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.37.0.dist-info → xai_review-0.38.0.dist-info}/WHEEL +0 -0
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.37.0.dist-info → xai_review-0.38.0.dist-info}/top_level.txt +0 -0
|
@@ -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)
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pydantic import HttpUrl, SecretStr
|
|
3
|
+
|
|
4
|
+
from ai_review.clients.bitbucket_server.pr.schema.changes import (
|
|
5
|
+
BitbucketServerChangeSchema,
|
|
6
|
+
BitbucketServerChangePathSchema,
|
|
7
|
+
BitbucketServerGetPRChangesResponseSchema,
|
|
8
|
+
)
|
|
9
|
+
from ai_review.clients.bitbucket_server.pr.schema.comments import (
|
|
10
|
+
BitbucketServerCommentSchema,
|
|
11
|
+
BitbucketServerCommentAnchorSchema,
|
|
12
|
+
BitbucketServerGetPRCommentsResponseSchema,
|
|
13
|
+
BitbucketServerCreatePRCommentRequestSchema,
|
|
14
|
+
BitbucketServerCreatePRCommentResponseSchema,
|
|
15
|
+
)
|
|
16
|
+
from ai_review.clients.bitbucket_server.pr.schema.pull_request import (
|
|
17
|
+
BitbucketServerRefSchema,
|
|
18
|
+
BitbucketServerUserSchema,
|
|
19
|
+
BitbucketServerProjectSchema,
|
|
20
|
+
BitbucketServerRepositorySchema,
|
|
21
|
+
BitbucketServerParticipantSchema,
|
|
22
|
+
BitbucketServerGetPRResponseSchema,
|
|
23
|
+
)
|
|
24
|
+
from ai_review.clients.bitbucket_server.pr.types import BitbucketServerPullRequestsHTTPClientProtocol
|
|
25
|
+
from ai_review.config import settings
|
|
26
|
+
from ai_review.libs.config.vcs.base import BitbucketServerVCSConfig
|
|
27
|
+
from ai_review.libs.config.vcs.bitbucket_server import BitbucketServerPipelineConfig, BitbucketServerHTTPClientConfig
|
|
28
|
+
from ai_review.libs.constants.vcs_provider import VCSProvider
|
|
29
|
+
from ai_review.services.vcs.bitbucket_server.client import BitbucketServerVCSClient
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FakeBitbucketServerPullRequestsHTTPClient(BitbucketServerPullRequestsHTTPClientProtocol):
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self.calls: list[tuple[str, dict]] = []
|
|
35
|
+
|
|
36
|
+
async def get_pull_request(
|
|
37
|
+
self,
|
|
38
|
+
project_key: str,
|
|
39
|
+
repo_slug: str,
|
|
40
|
+
pull_request_id: int
|
|
41
|
+
) -> BitbucketServerGetPRResponseSchema:
|
|
42
|
+
self.calls.append(
|
|
43
|
+
(
|
|
44
|
+
"get_pull_request",
|
|
45
|
+
{
|
|
46
|
+
"project_key": project_key,
|
|
47
|
+
"repo_slug": repo_slug,
|
|
48
|
+
"pull_request_id": pull_request_id
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
return BitbucketServerGetPRResponseSchema(
|
|
53
|
+
id=1,
|
|
54
|
+
version=1,
|
|
55
|
+
title="Fake Bitbucket Server PR",
|
|
56
|
+
description="PR for testing server client",
|
|
57
|
+
state="OPEN",
|
|
58
|
+
open=True,
|
|
59
|
+
locked=False,
|
|
60
|
+
author=BitbucketServerParticipantSchema(
|
|
61
|
+
user=BitbucketServerUserSchema(
|
|
62
|
+
id=100,
|
|
63
|
+
name="author",
|
|
64
|
+
slug="author",
|
|
65
|
+
display_name="Author User",
|
|
66
|
+
),
|
|
67
|
+
role="AUTHOR",
|
|
68
|
+
),
|
|
69
|
+
reviewers=[
|
|
70
|
+
BitbucketServerParticipantSchema(
|
|
71
|
+
user=BitbucketServerUserSchema(
|
|
72
|
+
id=101,
|
|
73
|
+
name="reviewer",
|
|
74
|
+
slug="reviewer",
|
|
75
|
+
display_name="Reviewer User",
|
|
76
|
+
),
|
|
77
|
+
role="REVIEWER",
|
|
78
|
+
)
|
|
79
|
+
],
|
|
80
|
+
from_ref=BitbucketServerRefSchema(
|
|
81
|
+
id="refs/heads/feature/test",
|
|
82
|
+
display_id="feature/test",
|
|
83
|
+
latest_commit="def456",
|
|
84
|
+
repository=BitbucketServerRepositorySchema(
|
|
85
|
+
slug="repo",
|
|
86
|
+
name="Repo Name",
|
|
87
|
+
project=BitbucketServerProjectSchema(key="PRJ"),
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
to_ref=BitbucketServerRefSchema(
|
|
91
|
+
id="refs/heads/main",
|
|
92
|
+
display_id="main",
|
|
93
|
+
latest_commit="abc123",
|
|
94
|
+
repository=BitbucketServerRepositorySchema(
|
|
95
|
+
slug="repo",
|
|
96
|
+
name="Repo Name",
|
|
97
|
+
project=BitbucketServerProjectSchema(key="PRJ"),
|
|
98
|
+
),
|
|
99
|
+
),
|
|
100
|
+
created_date=1690000000,
|
|
101
|
+
updated_date=1690000100,
|
|
102
|
+
links={},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
async def get_changes(
|
|
106
|
+
self,
|
|
107
|
+
project_key: str,
|
|
108
|
+
repo_slug: str,
|
|
109
|
+
pull_request_id: int
|
|
110
|
+
) -> BitbucketServerGetPRChangesResponseSchema:
|
|
111
|
+
self.calls.append(
|
|
112
|
+
(
|
|
113
|
+
"get_changes", {
|
|
114
|
+
"project_key": project_key,
|
|
115
|
+
"repo_slug": repo_slug,
|
|
116
|
+
"pull_request_id": pull_request_id
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
return BitbucketServerGetPRChangesResponseSchema(
|
|
121
|
+
size=1,
|
|
122
|
+
start=0,
|
|
123
|
+
limit=100,
|
|
124
|
+
is_last_page=True,
|
|
125
|
+
next_page_start=None,
|
|
126
|
+
values=[
|
|
127
|
+
BitbucketServerChangeSchema(
|
|
128
|
+
path=BitbucketServerChangePathSchema(to_string="src/main.py"),
|
|
129
|
+
type="MODIFY",
|
|
130
|
+
src_path=None,
|
|
131
|
+
node_type="FILE",
|
|
132
|
+
executable=False,
|
|
133
|
+
)
|
|
134
|
+
],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
async def get_comments(
|
|
138
|
+
self,
|
|
139
|
+
project_key: str,
|
|
140
|
+
repo_slug: str,
|
|
141
|
+
pull_request_id: int
|
|
142
|
+
) -> BitbucketServerGetPRCommentsResponseSchema:
|
|
143
|
+
self.calls.append(
|
|
144
|
+
(
|
|
145
|
+
"get_comments",
|
|
146
|
+
{"project_key": project_key, "repo_slug": repo_slug, "pull_request_id": pull_request_id}
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
return BitbucketServerGetPRCommentsResponseSchema(
|
|
150
|
+
size=2,
|
|
151
|
+
start=0,
|
|
152
|
+
limit=100,
|
|
153
|
+
is_last_page=True,
|
|
154
|
+
next_page_start=None,
|
|
155
|
+
values=[
|
|
156
|
+
BitbucketServerCommentSchema(
|
|
157
|
+
id=1,
|
|
158
|
+
text="General comment",
|
|
159
|
+
author=BitbucketServerUserSchema(
|
|
160
|
+
id=100,
|
|
161
|
+
name="user1",
|
|
162
|
+
slug="user1",
|
|
163
|
+
display_name="User One",
|
|
164
|
+
),
|
|
165
|
+
anchor=None,
|
|
166
|
+
comments=[],
|
|
167
|
+
created_date=1690000000,
|
|
168
|
+
updated_date=1690000000,
|
|
169
|
+
),
|
|
170
|
+
BitbucketServerCommentSchema(
|
|
171
|
+
id=2,
|
|
172
|
+
text="Inline comment",
|
|
173
|
+
author=BitbucketServerUserSchema(
|
|
174
|
+
id=101,
|
|
175
|
+
name="user2",
|
|
176
|
+
slug="user2",
|
|
177
|
+
display_name="User Two",
|
|
178
|
+
),
|
|
179
|
+
anchor=BitbucketServerCommentAnchorSchema(path="src/main.py", line=5, line_type="ADDED"),
|
|
180
|
+
comments=[],
|
|
181
|
+
created_date=1690000001,
|
|
182
|
+
updated_date=1690000001,
|
|
183
|
+
),
|
|
184
|
+
],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
async def create_comment(
|
|
188
|
+
self,
|
|
189
|
+
project_key: str,
|
|
190
|
+
repo_slug: str,
|
|
191
|
+
pull_request_id: int,
|
|
192
|
+
request: BitbucketServerCreatePRCommentRequestSchema
|
|
193
|
+
) -> BitbucketServerCreatePRCommentResponseSchema:
|
|
194
|
+
self.calls.append(
|
|
195
|
+
(
|
|
196
|
+
"create_comment",
|
|
197
|
+
{
|
|
198
|
+
"project_key": project_key,
|
|
199
|
+
"repo_slug": repo_slug,
|
|
200
|
+
"pull_request_id": pull_request_id,
|
|
201
|
+
**request.model_dump(by_alias=True)
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
return BitbucketServerCreatePRCommentResponseSchema(
|
|
206
|
+
id=10,
|
|
207
|
+
text=request.text,
|
|
208
|
+
author=BitbucketServerUserSchema(
|
|
209
|
+
id=999,
|
|
210
|
+
name="bot",
|
|
211
|
+
slug="bot",
|
|
212
|
+
display_name="Automation Bot",
|
|
213
|
+
),
|
|
214
|
+
anchor=request.anchor,
|
|
215
|
+
comments=[],
|
|
216
|
+
created_date=1690000200,
|
|
217
|
+
updated_date=1690000200,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class FakeBitbucketServerHTTPClient:
|
|
222
|
+
def __init__(self, pull_requests_client: BitbucketServerPullRequestsHTTPClientProtocol):
|
|
223
|
+
self.pr = pull_requests_client
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@pytest.fixture
|
|
227
|
+
def fake_bitbucket_server_pull_requests_http_client() -> FakeBitbucketServerPullRequestsHTTPClient:
|
|
228
|
+
return FakeBitbucketServerPullRequestsHTTPClient()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@pytest.fixture
|
|
232
|
+
def fake_bitbucket_server_http_client(
|
|
233
|
+
fake_bitbucket_server_pull_requests_http_client: FakeBitbucketServerPullRequestsHTTPClient
|
|
234
|
+
) -> FakeBitbucketServerHTTPClient:
|
|
235
|
+
return FakeBitbucketServerHTTPClient(pull_requests_client=fake_bitbucket_server_pull_requests_http_client)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@pytest.fixture
|
|
239
|
+
def bitbucket_server_vcs_client(
|
|
240
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
241
|
+
fake_bitbucket_server_http_client: FakeBitbucketServerHTTPClient
|
|
242
|
+
) -> BitbucketServerVCSClient:
|
|
243
|
+
monkeypatch.setattr(
|
|
244
|
+
"ai_review.services.vcs.bitbucket_server.client.get_bitbucket_server_http_client",
|
|
245
|
+
lambda: fake_bitbucket_server_http_client,
|
|
246
|
+
)
|
|
247
|
+
return BitbucketServerVCSClient()
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@pytest.fixture
|
|
251
|
+
def bitbucket_server_http_client_config(monkeypatch: pytest.MonkeyPatch):
|
|
252
|
+
fake_config = BitbucketServerVCSConfig(
|
|
253
|
+
provider=VCSProvider.BITBUCKET_SERVER,
|
|
254
|
+
pipeline=BitbucketServerPipelineConfig(
|
|
255
|
+
project_key="PRJ",
|
|
256
|
+
repo_slug="repo",
|
|
257
|
+
pull_request_id=1,
|
|
258
|
+
),
|
|
259
|
+
http_client=BitbucketServerHTTPClientConfig(
|
|
260
|
+
timeout=10,
|
|
261
|
+
api_url=HttpUrl("https://bitbucket.server.local/rest/api/1.0"),
|
|
262
|
+
api_token=SecretStr("fake-token"),
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
monkeypatch.setattr(settings, "vcs", fake_config)
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from httpx import AsyncClient
|
|
3
|
+
|
|
4
|
+
from ai_review.clients.bitbucket_cloud.client import get_bitbucket_cloud_http_client, BitbucketCloudHTTPClient
|
|
5
|
+
from ai_review.clients.bitbucket_cloud.pr.client import BitbucketCloudPullRequestsHTTPClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.usefixtures("bitbucket_cloud_http_client_config")
|
|
9
|
+
def test_get_bitbucket_cloud_http_client_builds_ok():
|
|
10
|
+
bitbucket_http_client = get_bitbucket_cloud_http_client()
|
|
11
|
+
|
|
12
|
+
assert isinstance(bitbucket_http_client, BitbucketCloudHTTPClient)
|
|
13
|
+
assert isinstance(bitbucket_http_client.pr, BitbucketCloudPullRequestsHTTPClient)
|
|
14
|
+
assert isinstance(bitbucket_http_client.pr.client, AsyncClient)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from httpx import Response, Request
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.bitbucket_cloud.tools import bitbucket_cloud_has_next_page
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def make_response(data: dict) -> Response:
|
|
7
|
+
return Response(
|
|
8
|
+
json=data,
|
|
9
|
+
request=Request("GET", "http://bitbucket.test"),
|
|
10
|
+
status_code=200,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_bitbucket_cloud_has_next_page_true():
|
|
15
|
+
resp = make_response({"next": "https://api.bitbucket.org/2.0/repositories/test/repo?page=2"})
|
|
16
|
+
assert bitbucket_cloud_has_next_page(resp) is True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_bitbucket_cloud_has_next_page_false_none():
|
|
20
|
+
resp = make_response({"next": None})
|
|
21
|
+
assert bitbucket_cloud_has_next_page(resp) is False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_bitbucket_cloud_has_next_page_false_missing():
|
|
25
|
+
resp = make_response({})
|
|
26
|
+
assert bitbucket_cloud_has_next_page(resp) is False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_bitbucket_cloud_has_next_page_false_empty_string():
|
|
30
|
+
resp = make_response({"next": ""})
|
|
31
|
+
assert bitbucket_cloud_has_next_page(resp) is False
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from httpx import AsyncClient
|
|
3
|
+
|
|
4
|
+
from ai_review.clients.bitbucket_server.client import get_bitbucket_server_http_client, BitbucketServerHTTPClient
|
|
5
|
+
from ai_review.clients.bitbucket_server.pr.client import BitbucketServerPullRequestsHTTPClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.usefixtures("bitbucket_server_http_client_config")
|
|
9
|
+
def test_get_bitbucket_server_http_client_builds_ok():
|
|
10
|
+
bitbucket_server_http_client = get_bitbucket_server_http_client()
|
|
11
|
+
|
|
12
|
+
assert isinstance(bitbucket_server_http_client, BitbucketServerHTTPClient)
|
|
13
|
+
assert isinstance(bitbucket_server_http_client.pr, BitbucketServerPullRequestsHTTPClient)
|
|
14
|
+
assert isinstance(bitbucket_server_http_client.pr.client, AsyncClient)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from httpx import Response, Request
|
|
4
|
+
|
|
5
|
+
from ai_review.clients.bitbucket_server.tools import bitbucket_server_has_next_page
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def make_response(data: dict) -> Response:
|
|
9
|
+
"""Helper to create a fake HTTPX Response with given JSON data."""
|
|
10
|
+
return Response(
|
|
11
|
+
status_code=200,
|
|
12
|
+
content=json.dumps(data).encode(),
|
|
13
|
+
request=Request("GET", "http://bitbucket-server.test"),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_bitbucket_server_has_next_page_true_when_not_last():
|
|
18
|
+
"""Should return True if isLastPage=False (i.e., more pages exist)."""
|
|
19
|
+
resp = make_response({"isLastPage": False})
|
|
20
|
+
assert bitbucket_server_has_next_page(resp) is True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_bitbucket_server_has_next_page_false_when_last_page():
|
|
24
|
+
"""Should return False if isLastPage=True (last page reached)."""
|
|
25
|
+
resp = make_response({"isLastPage": True})
|
|
26
|
+
assert bitbucket_server_has_next_page(resp) is False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_bitbucket_server_has_next_page_false_when_missing():
|
|
30
|
+
"""Should default to False if isLastPage key is missing (treated as last page)."""
|
|
31
|
+
resp = make_response({})
|
|
32
|
+
assert bitbucket_server_has_next_page(resp) is False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_bitbucket_server_has_next_page_false_when_invalid_type():
|
|
36
|
+
"""Should handle invalid non-bool values gracefully."""
|
|
37
|
+
resp = make_response({"isLastPage": "not-a-bool"})
|
|
38
|
+
assert bitbucket_server_has_next_page(resp) is False
|
|
File without changes
|