xai-review 0.29.0__py3-none-any.whl → 0.31.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/gitea/__init__.py +0 -0
- ai_review/clients/gitea/client.py +31 -0
- ai_review/clients/gitea/pr/__init__.py +0 -0
- ai_review/clients/gitea/pr/client.py +118 -0
- ai_review/clients/gitea/pr/schema/__init__.py +0 -0
- ai_review/clients/gitea/pr/schema/comments.py +37 -0
- ai_review/clients/gitea/pr/schema/files.py +16 -0
- ai_review/clients/gitea/pr/schema/pull_request.py +18 -0
- ai_review/clients/gitea/pr/schema/user.py +6 -0
- ai_review/clients/gitea/pr/types.py +25 -0
- ai_review/clients/gitea/tools.py +6 -0
- ai_review/libs/config/review.py +1 -0
- ai_review/libs/config/vcs/base.py +8 -1
- ai_review/libs/config/vcs/gitea.py +13 -0
- ai_review/libs/constants/vcs_provider.py +1 -0
- ai_review/services/review/gateway/review_dry_run_comment_gateway.py +42 -0
- ai_review/services/review/service.py +9 -3
- ai_review/services/vcs/factory.py +3 -0
- ai_review/services/vcs/gitea/__init__.py +0 -0
- ai_review/services/vcs/gitea/adapter.py +22 -0
- ai_review/services/vcs/gitea/client.py +151 -0
- ai_review/tests/fixtures/clients/gitea.py +141 -0
- ai_review/tests/fixtures/services/review/gateway/{comment.py → review_comment_gateway.py} +1 -1
- ai_review/tests/fixtures/services/review/gateway/review_dry_run_comment_gateway.py +103 -0
- ai_review/tests/fixtures/services/review/gateway/review_llm_gateway.py +34 -0
- ai_review/tests/suites/clients/gitea/__init__.py +0 -0
- ai_review/tests/suites/clients/gitea/test_client.py +14 -0
- ai_review/tests/suites/clients/gitea/test_tools.py +26 -0
- ai_review/tests/suites/services/review/gateway/{test_comment.py → test_review_comment_gateway.py} +1 -1
- ai_review/tests/suites/services/review/gateway/test_review_dry_run_comment_gateway.py +93 -0
- ai_review/tests/suites/services/review/gateway/{test_llm.py → test_review_llm_gateway.py} +1 -15
- ai_review/tests/suites/services/review/runner/test_context.py +2 -2
- ai_review/tests/suites/services/review/runner/test_inline.py +2 -2
- ai_review/tests/suites/services/review/runner/test_inline_reply.py +2 -2
- ai_review/tests/suites/services/review/runner/test_summary.py +2 -2
- ai_review/tests/suites/services/review/runner/test_summary_reply.py +2 -2
- ai_review/tests/suites/services/review/test_service.py +18 -0
- ai_review/tests/suites/services/vcs/gitea/__init__.py +0 -0
- ai_review/tests/suites/services/vcs/gitea/test_adapter.py +52 -0
- ai_review/tests/suites/services/vcs/gitea/test_client.py +86 -0
- ai_review/tests/suites/services/vcs/test_factory.py +7 -0
- {xai_review-0.29.0.dist-info → xai_review-0.31.0.dist-info}/METADATA +21 -12
- {xai_review-0.29.0.dist-info → xai_review-0.31.0.dist-info}/RECORD +49 -24
- ai_review/tests/fixtures/services/review/gateway/llm.py +0 -17
- /ai_review/services/review/gateway/{comment.py → review_comment_gateway.py} +0 -0
- /ai_review/services/review/gateway/{llm.py → review_llm_gateway.py} +0 -0
- {xai_review-0.29.0.dist-info → xai_review-0.31.0.dist-info}/WHEEL +0 -0
- {xai_review-0.29.0.dist-info → xai_review-0.31.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.29.0.dist-info → xai_review-0.31.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.29.0.dist-info → xai_review-0.31.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pydantic import HttpUrl, SecretStr
|
|
3
|
+
|
|
4
|
+
from ai_review.clients.gitea.pr.schema.comments import (
|
|
5
|
+
GiteaPRCommentSchema,
|
|
6
|
+
GiteaGetPRCommentsResponseSchema,
|
|
7
|
+
GiteaCreateCommentRequestSchema,
|
|
8
|
+
GiteaCreateCommentResponseSchema,
|
|
9
|
+
)
|
|
10
|
+
from ai_review.clients.gitea.pr.schema.files import (
|
|
11
|
+
GiteaGetPRFilesResponseSchema,
|
|
12
|
+
GiteaPRFileSchema,
|
|
13
|
+
)
|
|
14
|
+
from ai_review.clients.gitea.pr.schema.pull_request import (
|
|
15
|
+
GiteaGetPRResponseSchema,
|
|
16
|
+
GiteaBranchSchema,
|
|
17
|
+
)
|
|
18
|
+
from ai_review.clients.gitea.pr.schema.user import GiteaUserSchema
|
|
19
|
+
from ai_review.clients.gitea.pr.types import GiteaPullRequestsHTTPClientProtocol
|
|
20
|
+
from ai_review.config import settings
|
|
21
|
+
from ai_review.libs.config.vcs.base import GiteaVCSConfig
|
|
22
|
+
from ai_review.libs.config.vcs.gitea import GiteaPipelineConfig, GiteaHTTPClientConfig
|
|
23
|
+
from ai_review.libs.constants.vcs_provider import VCSProvider
|
|
24
|
+
from ai_review.services.vcs.gitea.client import GiteaVCSClient
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FakeGiteaPullRequestsHTTPClient(GiteaPullRequestsHTTPClientProtocol):
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.calls: list[tuple[str, dict]] = []
|
|
30
|
+
|
|
31
|
+
async def get_pull_request(self, owner: str, repo: str, pull_number: str) -> GiteaGetPRResponseSchema:
|
|
32
|
+
self.calls.append(("get_pull_request", {"owner": owner, "repo": repo, "pull_number": pull_number}))
|
|
33
|
+
return GiteaGetPRResponseSchema(
|
|
34
|
+
id=1,
|
|
35
|
+
number=1,
|
|
36
|
+
title="Fake Gitea PR",
|
|
37
|
+
body="This is a fake PR for testing",
|
|
38
|
+
user=GiteaUserSchema(id=101, login="tester"),
|
|
39
|
+
base=GiteaBranchSchema(ref="main", sha="abc123"),
|
|
40
|
+
head=GiteaBranchSchema(ref="feature", sha="def456"),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
async def get_files(self, owner: str, repo: str, pull_number: str) -> GiteaGetPRFilesResponseSchema:
|
|
44
|
+
self.calls.append(("get_files", {"owner": owner, "repo": repo, "pull_number": pull_number}))
|
|
45
|
+
return GiteaGetPRFilesResponseSchema(
|
|
46
|
+
root=[
|
|
47
|
+
GiteaPRFileSchema(
|
|
48
|
+
sha="abc",
|
|
49
|
+
status="modified",
|
|
50
|
+
filename="src/main.py",
|
|
51
|
+
patch="@@ -1,2 +1,2 @@\n- old\n+ new",
|
|
52
|
+
),
|
|
53
|
+
GiteaPRFileSchema(
|
|
54
|
+
sha="def",
|
|
55
|
+
status="added",
|
|
56
|
+
filename="utils/helper.py",
|
|
57
|
+
patch="+ print('Hello')",
|
|
58
|
+
),
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def get_comments(self, owner: str, repo: str, pull_number: str) -> GiteaGetPRCommentsResponseSchema:
|
|
63
|
+
self.calls.append(("get_comments", {"owner": owner, "repo": repo, "pull_number": pull_number}))
|
|
64
|
+
return GiteaGetPRCommentsResponseSchema(
|
|
65
|
+
root=[
|
|
66
|
+
GiteaPRCommentSchema(
|
|
67
|
+
id=1,
|
|
68
|
+
body="General comment",
|
|
69
|
+
user=GiteaUserSchema(id=201, login="alice"),
|
|
70
|
+
),
|
|
71
|
+
GiteaPRCommentSchema(
|
|
72
|
+
id=2,
|
|
73
|
+
body="Inline comment",
|
|
74
|
+
path="file.py",
|
|
75
|
+
line=5,
|
|
76
|
+
user=GiteaUserSchema(id=202, login="bob"),
|
|
77
|
+
),
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
async def create_comment(
|
|
82
|
+
self,
|
|
83
|
+
owner: str,
|
|
84
|
+
repo: str,
|
|
85
|
+
pull_number: str,
|
|
86
|
+
request: GiteaCreateCommentRequestSchema
|
|
87
|
+
) -> GiteaCreateCommentResponseSchema:
|
|
88
|
+
self.calls.append(
|
|
89
|
+
(
|
|
90
|
+
"create_comment",
|
|
91
|
+
{"owner": owner, "repo": repo, "pull_number": pull_number, **request.model_dump()},
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
return GiteaCreateCommentResponseSchema(id=10, body=request.body)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class FakeGiteaHTTPClient:
|
|
98
|
+
def __init__(self, pull_requests_client: FakeGiteaPullRequestsHTTPClient):
|
|
99
|
+
self.pr = pull_requests_client
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.fixture
|
|
103
|
+
def fake_gitea_pull_requests_http_client() -> FakeGiteaPullRequestsHTTPClient:
|
|
104
|
+
return FakeGiteaPullRequestsHTTPClient()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@pytest.fixture
|
|
108
|
+
def fake_gitea_http_client(
|
|
109
|
+
fake_gitea_pull_requests_http_client: FakeGiteaPullRequestsHTTPClient
|
|
110
|
+
) -> FakeGiteaHTTPClient:
|
|
111
|
+
return FakeGiteaHTTPClient(pull_requests_client=fake_gitea_pull_requests_http_client)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@pytest.fixture
|
|
115
|
+
def gitea_vcs_client(
|
|
116
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
117
|
+
fake_gitea_http_client: FakeGiteaHTTPClient
|
|
118
|
+
) -> GiteaVCSClient:
|
|
119
|
+
monkeypatch.setattr(
|
|
120
|
+
"ai_review.services.vcs.gitea.client.get_gitea_http_client",
|
|
121
|
+
lambda: fake_gitea_http_client,
|
|
122
|
+
)
|
|
123
|
+
return GiteaVCSClient()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.fixture
|
|
127
|
+
def gitea_http_client_config(monkeypatch: pytest.MonkeyPatch):
|
|
128
|
+
fake_config = GiteaVCSConfig(
|
|
129
|
+
provider=VCSProvider.GITEA,
|
|
130
|
+
pipeline=GiteaPipelineConfig(
|
|
131
|
+
repo="repo",
|
|
132
|
+
owner="owner",
|
|
133
|
+
pull_number="1",
|
|
134
|
+
),
|
|
135
|
+
http_client=GiteaHTTPClientConfig(
|
|
136
|
+
timeout=10,
|
|
137
|
+
api_url=HttpUrl("https://gitea.example.com"),
|
|
138
|
+
api_token=SecretStr("fake-token"),
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
monkeypatch.setattr(settings, "vcs", fake_config)
|
|
@@ -2,7 +2,7 @@ from typing import Any
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from ai_review.services.review.gateway.
|
|
5
|
+
from ai_review.services.review.gateway.review_comment_gateway import ReviewCommentGateway
|
|
6
6
|
from ai_review.services.review.gateway.types import ReviewCommentGatewayProtocol
|
|
7
7
|
from ai_review.services.review.internal.inline.schema import InlineCommentSchema, InlineCommentListSchema
|
|
8
8
|
from ai_review.services.review.internal.inline_reply.schema import InlineCommentReplySchema
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from ai_review.services.review.gateway.review_dry_run_comment_gateway import ReviewDryRunCommentGateway
|
|
6
|
+
from ai_review.services.review.gateway.types import ReviewCommentGatewayProtocol
|
|
7
|
+
from ai_review.services.review.internal.inline.schema import InlineCommentSchema, InlineCommentListSchema
|
|
8
|
+
from ai_review.services.review.internal.inline_reply.schema import InlineCommentReplySchema
|
|
9
|
+
from ai_review.services.review.internal.summary.schema import SummaryCommentSchema
|
|
10
|
+
from ai_review.services.review.internal.summary_reply.schema import SummaryCommentReplySchema
|
|
11
|
+
from ai_review.services.vcs.types import (
|
|
12
|
+
UserSchema,
|
|
13
|
+
ThreadKind,
|
|
14
|
+
ReviewThreadSchema,
|
|
15
|
+
ReviewCommentSchema,
|
|
16
|
+
VCSClientProtocol,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FakeReviewDryRunCommentGateway(ReviewCommentGatewayProtocol):
|
|
21
|
+
def __init__(self, responses: dict[str, Any] | None = None):
|
|
22
|
+
self.calls: list[tuple[str, dict]] = []
|
|
23
|
+
|
|
24
|
+
fake_user = UserSchema(id="u1", username="tester", name="Tester")
|
|
25
|
+
|
|
26
|
+
fake_inline_thread = ReviewThreadSchema(
|
|
27
|
+
id="t1",
|
|
28
|
+
kind=ThreadKind.INLINE,
|
|
29
|
+
file="file.py",
|
|
30
|
+
line=10,
|
|
31
|
+
comments=[
|
|
32
|
+
ReviewCommentSchema(
|
|
33
|
+
id="c1",
|
|
34
|
+
body="#ai-review-inline some comment",
|
|
35
|
+
file="file.py",
|
|
36
|
+
line=10,
|
|
37
|
+
author=fake_user,
|
|
38
|
+
),
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
fake_summary_thread = ReviewThreadSchema(
|
|
43
|
+
id="t2",
|
|
44
|
+
kind=ThreadKind.SUMMARY,
|
|
45
|
+
comments=[
|
|
46
|
+
ReviewCommentSchema(
|
|
47
|
+
id="c2",
|
|
48
|
+
body="#ai-review-summary summary comment",
|
|
49
|
+
author=fake_user,
|
|
50
|
+
),
|
|
51
|
+
],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.responses = responses or {
|
|
55
|
+
"get_inline_threads": [fake_inline_thread],
|
|
56
|
+
"get_summary_threads": [fake_summary_thread],
|
|
57
|
+
"has_existing_inline_comments": False,
|
|
58
|
+
"has_existing_summary_comments": False,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# --- Методы чтения ---
|
|
62
|
+
async def get_inline_threads(self) -> list[ReviewThreadSchema]:
|
|
63
|
+
self.calls.append(("get_inline_threads", {}))
|
|
64
|
+
return self.responses["get_inline_threads"]
|
|
65
|
+
|
|
66
|
+
async def get_summary_threads(self) -> list[ReviewThreadSchema]:
|
|
67
|
+
self.calls.append(("get_summary_threads", {}))
|
|
68
|
+
return self.responses["get_summary_threads"]
|
|
69
|
+
|
|
70
|
+
async def has_existing_inline_comments(self) -> bool:
|
|
71
|
+
self.calls.append(("has_existing_inline_comments", {}))
|
|
72
|
+
return self.responses["has_existing_inline_comments"]
|
|
73
|
+
|
|
74
|
+
async def has_existing_summary_comments(self) -> bool:
|
|
75
|
+
self.calls.append(("has_existing_summary_comments", {}))
|
|
76
|
+
return self.responses["has_existing_summary_comments"]
|
|
77
|
+
|
|
78
|
+
async def process_inline_reply(self, thread_id: str, reply: InlineCommentReplySchema) -> None:
|
|
79
|
+
self.calls.append(("process_inline_reply", {"thread_id": thread_id, "reply": reply}))
|
|
80
|
+
|
|
81
|
+
async def process_summary_reply(self, thread_id: str, reply: SummaryCommentReplySchema) -> None:
|
|
82
|
+
self.calls.append(("process_summary_reply", {"thread_id": thread_id, "reply": reply}))
|
|
83
|
+
|
|
84
|
+
async def process_inline_comment(self, comment: InlineCommentSchema) -> None:
|
|
85
|
+
self.calls.append(("process_inline_comment", {"comment": comment}))
|
|
86
|
+
|
|
87
|
+
async def process_summary_comment(self, comment: SummaryCommentSchema) -> None:
|
|
88
|
+
self.calls.append(("process_summary_comment", {"comment": comment}))
|
|
89
|
+
|
|
90
|
+
async def process_inline_comments(self, comments: InlineCommentListSchema) -> None:
|
|
91
|
+
self.calls.append(("process_inline_comments", {"comments": comments}))
|
|
92
|
+
for comment in comments.root:
|
|
93
|
+
await self.process_inline_comment(comment)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.fixture
|
|
97
|
+
def fake_review_dry_run_comment_gateway() -> FakeReviewDryRunCommentGateway:
|
|
98
|
+
return FakeReviewDryRunCommentGateway()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@pytest.fixture
|
|
102
|
+
def review_dry_run_comment_gateway(fake_vcs_client: VCSClientProtocol) -> ReviewDryRunCommentGateway:
|
|
103
|
+
return ReviewDryRunCommentGateway(vcs=fake_vcs_client)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.review.gateway.review_llm_gateway import ReviewLLMGateway
|
|
4
|
+
from ai_review.services.review.gateway.types import ReviewLLMGatewayProtocol
|
|
5
|
+
from ai_review.tests.fixtures.services.artifacts import FakeArtifactsService
|
|
6
|
+
from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
7
|
+
from ai_review.tests.fixtures.services.llm import FakeLLMClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FakeReviewLLMGateway(ReviewLLMGatewayProtocol):
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.calls: list[tuple[str, dict]] = []
|
|
13
|
+
|
|
14
|
+
async def ask(self, prompt: str, prompt_system: str) -> str:
|
|
15
|
+
self.calls.append(("ask", {"prompt": prompt, "prompt_system": prompt_system}))
|
|
16
|
+
return "FAKE_LLM_RESPONSE"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def fake_review_llm_gateway() -> FakeReviewLLMGateway:
|
|
21
|
+
return FakeReviewLLMGateway()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def review_llm_gateway(
|
|
26
|
+
fake_llm_client: FakeLLMClient,
|
|
27
|
+
fake_cost_service: FakeCostService,
|
|
28
|
+
fake_artifacts_service: FakeArtifactsService,
|
|
29
|
+
) -> ReviewLLMGateway:
|
|
30
|
+
return ReviewLLMGateway(
|
|
31
|
+
llm=fake_llm_client,
|
|
32
|
+
cost=fake_cost_service,
|
|
33
|
+
artifacts=fake_artifacts_service,
|
|
34
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from httpx import AsyncClient
|
|
3
|
+
|
|
4
|
+
from ai_review.clients.gitea.client import get_gitea_http_client, GiteaHTTPClient
|
|
5
|
+
from ai_review.clients.gitea.pr.client import GiteaPullRequestsHTTPClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.usefixtures("gitea_http_client_config")
|
|
9
|
+
def test_get_gitea_http_client_builds_ok():
|
|
10
|
+
gitea_http_client = get_gitea_http_client()
|
|
11
|
+
|
|
12
|
+
assert isinstance(gitea_http_client, GiteaHTTPClient)
|
|
13
|
+
assert isinstance(gitea_http_client.pr, GiteaPullRequestsHTTPClient)
|
|
14
|
+
assert isinstance(gitea_http_client.pr.client, AsyncClient)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from httpx import Response, Request
|
|
2
|
+
|
|
3
|
+
from ai_review.clients.gitea.tools import gitea_has_next_page
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def make_response(headers: dict) -> Response:
|
|
7
|
+
return Response(
|
|
8
|
+
request=Request("GET", "http://gitea.test"),
|
|
9
|
+
headers=headers,
|
|
10
|
+
status_code=200,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_gitea_has_next_page_true():
|
|
15
|
+
resp = make_response({"Link": '<https://gitea.test?page=2>; rel="next"'})
|
|
16
|
+
assert gitea_has_next_page(resp) is True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_gitea_has_next_page_false_empty():
|
|
20
|
+
resp = make_response({"Link": ""})
|
|
21
|
+
assert gitea_has_next_page(resp) is False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_gitea_has_next_page_false_missing():
|
|
25
|
+
resp = make_response({})
|
|
26
|
+
assert gitea_has_next_page(resp) is False
|
ai_review/tests/suites/services/review/gateway/{test_comment.py → test_review_comment_gateway.py}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
3
|
from ai_review.config import settings
|
|
4
|
-
from ai_review.services.review.gateway.
|
|
4
|
+
from ai_review.services.review.gateway.review_comment_gateway import ReviewCommentGateway
|
|
5
5
|
from ai_review.services.review.internal.inline.schema import InlineCommentSchema, InlineCommentListSchema
|
|
6
6
|
from ai_review.services.review.internal.inline_reply.schema import InlineCommentReplySchema
|
|
7
7
|
from ai_review.services.review.internal.summary.schema import SummaryCommentSchema
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.review.gateway.review_dry_run_comment_gateway import ReviewDryRunCommentGateway
|
|
4
|
+
from ai_review.services.review.internal.inline.schema import InlineCommentSchema, InlineCommentListSchema
|
|
5
|
+
from ai_review.services.review.internal.inline_reply.schema import InlineCommentReplySchema
|
|
6
|
+
from ai_review.services.review.internal.summary.schema import SummaryCommentSchema
|
|
7
|
+
from ai_review.services.review.internal.summary_reply.schema import SummaryCommentReplySchema
|
|
8
|
+
from ai_review.tests.fixtures.services.vcs import FakeVCSClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.asyncio
|
|
12
|
+
async def test_process_inline_reply_dry_run_logs_and_no_vcs_calls(
|
|
13
|
+
capsys,
|
|
14
|
+
fake_vcs_client: FakeVCSClient,
|
|
15
|
+
review_dry_run_comment_gateway: ReviewDryRunCommentGateway
|
|
16
|
+
):
|
|
17
|
+
"""Dry-run: should log the inline reply but not call VCS."""
|
|
18
|
+
reply = InlineCommentReplySchema(message="AI reply dry-run")
|
|
19
|
+
await review_dry_run_comment_gateway.process_inline_reply("t1", reply)
|
|
20
|
+
output = capsys.readouterr().out
|
|
21
|
+
|
|
22
|
+
assert "[dry-run]" in output
|
|
23
|
+
assert "Would create inline reply" in output
|
|
24
|
+
assert not any(call[0].startswith("create_") for call in fake_vcs_client.calls)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.asyncio
|
|
28
|
+
async def test_process_summary_reply_dry_run_logs_and_no_vcs_calls(
|
|
29
|
+
capsys,
|
|
30
|
+
fake_vcs_client: FakeVCSClient,
|
|
31
|
+
review_dry_run_comment_gateway: ReviewDryRunCommentGateway
|
|
32
|
+
):
|
|
33
|
+
"""Dry-run: should log the summary reply but not call VCS."""
|
|
34
|
+
reply = SummaryCommentReplySchema(text="Dry-run summary reply")
|
|
35
|
+
await review_dry_run_comment_gateway.process_summary_reply("t2", reply)
|
|
36
|
+
output = capsys.readouterr().out
|
|
37
|
+
|
|
38
|
+
assert "[dry-run]" in output
|
|
39
|
+
assert "Would create summary reply" in output
|
|
40
|
+
assert not any(call[0].startswith("create_") for call in fake_vcs_client.calls)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_process_inline_comment_dry_run_logs_and_no_vcs_calls(
|
|
45
|
+
capsys,
|
|
46
|
+
fake_vcs_client: FakeVCSClient,
|
|
47
|
+
review_dry_run_comment_gateway: ReviewDryRunCommentGateway
|
|
48
|
+
):
|
|
49
|
+
"""Dry-run: should log inline comment without creating one."""
|
|
50
|
+
comment = InlineCommentSchema(file="a.py", line=10, message="Test comment")
|
|
51
|
+
await review_dry_run_comment_gateway.process_inline_comment(comment)
|
|
52
|
+
output = capsys.readouterr().out
|
|
53
|
+
|
|
54
|
+
assert "[dry-run]" in output
|
|
55
|
+
assert "Would create inline comment" in output
|
|
56
|
+
assert "a.py" in output
|
|
57
|
+
assert not any(call[0].startswith("create_") for call in fake_vcs_client.calls)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.asyncio
|
|
61
|
+
async def test_process_summary_comment_dry_run_logs_and_no_vcs_calls(
|
|
62
|
+
capsys,
|
|
63
|
+
fake_vcs_client: FakeVCSClient,
|
|
64
|
+
review_dry_run_comment_gateway: ReviewDryRunCommentGateway
|
|
65
|
+
):
|
|
66
|
+
"""Dry-run: should log summary comment but not send it."""
|
|
67
|
+
comment = SummaryCommentSchema(text="Dry-run summary comment")
|
|
68
|
+
await review_dry_run_comment_gateway.process_summary_comment(comment)
|
|
69
|
+
output = capsys.readouterr().out
|
|
70
|
+
|
|
71
|
+
assert "[dry-run]" in output
|
|
72
|
+
assert "Would create summary comment" in output
|
|
73
|
+
assert not any(call[0].startswith("create_") for call in fake_vcs_client.calls)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.mark.asyncio
|
|
77
|
+
async def test_process_inline_comments_iterates_all(
|
|
78
|
+
capsys,
|
|
79
|
+
fake_vcs_client: FakeVCSClient,
|
|
80
|
+
review_dry_run_comment_gateway: ReviewDryRunCommentGateway
|
|
81
|
+
):
|
|
82
|
+
"""Dry-run: should iterate through all inline comments and log each."""
|
|
83
|
+
comments = InlineCommentListSchema(root=[
|
|
84
|
+
InlineCommentSchema(file="a.py", line=1, message="C1"),
|
|
85
|
+
InlineCommentSchema(file="b.py", line=2, message="C2"),
|
|
86
|
+
])
|
|
87
|
+
await review_dry_run_comment_gateway.process_inline_comments(comments)
|
|
88
|
+
output = capsys.readouterr().out
|
|
89
|
+
|
|
90
|
+
assert "[dry-run]" in output
|
|
91
|
+
assert "a.py" in output
|
|
92
|
+
assert "b.py" in output
|
|
93
|
+
assert not any(call[0].startswith("create_") for call in fake_vcs_client.calls)
|
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
3
|
from ai_review.services.llm.types import ChatResultSchema
|
|
4
|
-
from ai_review.services.review.gateway.
|
|
4
|
+
from ai_review.services.review.gateway.review_llm_gateway import ReviewLLMGateway
|
|
5
5
|
from ai_review.tests.fixtures.services.artifacts import FakeArtifactsService
|
|
6
6
|
from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
7
7
|
from ai_review.tests.fixtures.services.llm import FakeLLMClient
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def review_llm_gateway(
|
|
12
|
-
fake_llm_client: FakeLLMClient,
|
|
13
|
-
fake_cost_service: FakeCostService,
|
|
14
|
-
fake_artifacts_service: FakeArtifactsService,
|
|
15
|
-
) -> ReviewLLMGateway:
|
|
16
|
-
"""Fixture providing ReviewLLMGateway with fake dependencies."""
|
|
17
|
-
return ReviewLLMGateway(
|
|
18
|
-
llm=fake_llm_client,
|
|
19
|
-
cost=fake_cost_service,
|
|
20
|
-
artifacts=fake_artifacts_service,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
10
|
@pytest.mark.asyncio
|
|
25
11
|
async def test_ask_happy_path(
|
|
26
12
|
review_llm_gateway: ReviewLLMGateway,
|
|
@@ -4,8 +4,8 @@ from ai_review.services.review.runner.context import ContextReviewRunner
|
|
|
4
4
|
from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
5
5
|
from ai_review.tests.fixtures.services.diff import FakeDiffService
|
|
6
6
|
from ai_review.tests.fixtures.services.prompt import FakePromptService
|
|
7
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
8
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
7
|
+
from ai_review.tests.fixtures.services.review.gateway.review_comment_gateway import FakeReviewCommentGateway
|
|
8
|
+
from ai_review.tests.fixtures.services.review.gateway.review_llm_gateway import FakeReviewLLMGateway
|
|
9
9
|
from ai_review.tests.fixtures.services.review.internal.inline import FakeInlineCommentService
|
|
10
10
|
from ai_review.tests.fixtures.services.review.internal.policy import FakeReviewPolicyService
|
|
11
11
|
from ai_review.tests.fixtures.services.vcs import FakeVCSClient
|
|
@@ -6,8 +6,8 @@ from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
|
6
6
|
from ai_review.tests.fixtures.services.diff import FakeDiffService
|
|
7
7
|
from ai_review.tests.fixtures.services.git import FakeGitService
|
|
8
8
|
from ai_review.tests.fixtures.services.prompt import FakePromptService
|
|
9
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
10
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
9
|
+
from ai_review.tests.fixtures.services.review.gateway.review_comment_gateway import FakeReviewCommentGateway
|
|
10
|
+
from ai_review.tests.fixtures.services.review.gateway.review_llm_gateway import FakeReviewLLMGateway
|
|
11
11
|
from ai_review.tests.fixtures.services.review.internal.inline import FakeInlineCommentService
|
|
12
12
|
from ai_review.tests.fixtures.services.review.internal.policy import FakeReviewPolicyService
|
|
13
13
|
from ai_review.tests.fixtures.services.vcs import FakeVCSClient
|
|
@@ -6,8 +6,8 @@ from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
|
6
6
|
from ai_review.tests.fixtures.services.diff import FakeDiffService
|
|
7
7
|
from ai_review.tests.fixtures.services.git import FakeGitService
|
|
8
8
|
from ai_review.tests.fixtures.services.prompt import FakePromptService
|
|
9
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
10
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
9
|
+
from ai_review.tests.fixtures.services.review.gateway.review_comment_gateway import FakeReviewCommentGateway
|
|
10
|
+
from ai_review.tests.fixtures.services.review.gateway.review_llm_gateway import FakeReviewLLMGateway
|
|
11
11
|
from ai_review.tests.fixtures.services.review.internal.inline_reply import FakeInlineCommentReplyService
|
|
12
12
|
from ai_review.tests.fixtures.services.vcs import FakeVCSClient
|
|
13
13
|
|
|
@@ -5,8 +5,8 @@ from ai_review.services.review.runner.summary import SummaryReviewRunner
|
|
|
5
5
|
from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
6
6
|
from ai_review.tests.fixtures.services.diff import FakeDiffService
|
|
7
7
|
from ai_review.tests.fixtures.services.prompt import FakePromptService
|
|
8
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
9
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
8
|
+
from ai_review.tests.fixtures.services.review.gateway.review_comment_gateway import FakeReviewCommentGateway
|
|
9
|
+
from ai_review.tests.fixtures.services.review.gateway.review_llm_gateway import FakeReviewLLMGateway
|
|
10
10
|
from ai_review.tests.fixtures.services.review.internal.policy import FakeReviewPolicyService
|
|
11
11
|
from ai_review.tests.fixtures.services.review.internal.summary import FakeSummaryCommentService
|
|
12
12
|
from ai_review.tests.fixtures.services.vcs import FakeVCSClient
|
|
@@ -5,8 +5,8 @@ from ai_review.services.vcs.types import ReviewInfoSchema, ReviewThreadSchema, R
|
|
|
5
5
|
from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
6
6
|
from ai_review.tests.fixtures.services.diff import FakeDiffService
|
|
7
7
|
from ai_review.tests.fixtures.services.prompt import FakePromptService
|
|
8
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
9
|
-
from ai_review.tests.fixtures.services.review.gateway.
|
|
8
|
+
from ai_review.tests.fixtures.services.review.gateway.review_comment_gateway import FakeReviewCommentGateway
|
|
9
|
+
from ai_review.tests.fixtures.services.review.gateway.review_llm_gateway import FakeReviewLLMGateway
|
|
10
10
|
from ai_review.tests.fixtures.services.review.internal.policy import FakeReviewPolicyService
|
|
11
11
|
from ai_review.tests.fixtures.services.review.internal.summary_reply import FakeSummaryCommentReplyService
|
|
12
12
|
from ai_review.tests.fixtures.services.vcs import FakeVCSClient
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
3
|
from ai_review.services.llm.types import ChatResultSchema
|
|
4
|
+
from ai_review.services.review.gateway.review_comment_gateway import ReviewCommentGateway
|
|
5
|
+
from ai_review.services.review.gateway.review_dry_run_comment_gateway import ReviewDryRunCommentGateway
|
|
4
6
|
from ai_review.services.review.service import ReviewService
|
|
5
7
|
from ai_review.tests.fixtures.services.cost import FakeCostService
|
|
6
8
|
from ai_review.tests.fixtures.services.review.runner.context import FakeContextReviewRunner
|
|
@@ -91,3 +93,19 @@ def test_report_total_cost_no_data(capsys, review_service: ReviewService):
|
|
|
91
93
|
output = capsys.readouterr().out
|
|
92
94
|
|
|
93
95
|
assert "No cost data collected" in output
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_review_service_uses_dry_run_comment_gateway(monkeypatch: pytest.MonkeyPatch):
|
|
99
|
+
"""Should use ReviewDryRunCommentGateway when settings.review.dry_run=True."""
|
|
100
|
+
monkeypatch.setattr("ai_review.config.settings.review.dry_run", True)
|
|
101
|
+
|
|
102
|
+
service = ReviewService()
|
|
103
|
+
assert type(service.review_comment_gateway) is ReviewDryRunCommentGateway
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_review_service_uses_real_comment_gateway(monkeypatch: pytest.MonkeyPatch):
|
|
107
|
+
"""Should use normal ReviewCommentGateway when dry_run=False."""
|
|
108
|
+
monkeypatch.setattr("ai_review.config.settings.review.dry_run", False)
|
|
109
|
+
|
|
110
|
+
service = ReviewService()
|
|
111
|
+
assert type(service.review_comment_gateway) is ReviewCommentGateway
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from ai_review.clients.gitea.pr.schema.comments import GiteaPRCommentSchema
|
|
2
|
+
from ai_review.clients.gitea.pr.schema.user import GiteaUserSchema
|
|
3
|
+
from ai_review.services.vcs.gitea.adapter import get_review_comment_from_gitea_comment, get_user_from_gitea_user
|
|
4
|
+
from ai_review.services.vcs.types import ReviewCommentSchema, UserSchema
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_get_user_from_gitea_user_maps_fields_correctly():
|
|
8
|
+
user = GiteaUserSchema(id=42, login="tester")
|
|
9
|
+
result = get_user_from_gitea_user(user)
|
|
10
|
+
|
|
11
|
+
assert isinstance(result, UserSchema)
|
|
12
|
+
assert result.id == 42
|
|
13
|
+
assert result.username == "tester"
|
|
14
|
+
assert result.name == "tester"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_get_user_from_gitea_user_handles_none():
|
|
18
|
+
result = get_user_from_gitea_user(None)
|
|
19
|
+
assert result.id is None
|
|
20
|
+
assert result.username == ""
|
|
21
|
+
assert result.name == ""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_get_review_comment_from_gitea_comment_maps_all_fields():
|
|
25
|
+
comment = GiteaPRCommentSchema(
|
|
26
|
+
id=10,
|
|
27
|
+
body="Inline comment",
|
|
28
|
+
path="src/main.py",
|
|
29
|
+
line=15,
|
|
30
|
+
user=GiteaUserSchema(id=1, login="dev"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
result = get_review_comment_from_gitea_comment(comment)
|
|
34
|
+
|
|
35
|
+
assert isinstance(result, ReviewCommentSchema)
|
|
36
|
+
assert result.id == 10
|
|
37
|
+
assert result.body == "Inline comment"
|
|
38
|
+
assert result.file == "src/main.py"
|
|
39
|
+
assert result.line == 15
|
|
40
|
+
assert result.thread_id == 10
|
|
41
|
+
assert isinstance(result.author, UserSchema)
|
|
42
|
+
assert result.author.username == "dev"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_get_review_comment_handles_missing_user_and_body():
|
|
46
|
+
comment = GiteaPRCommentSchema(id=11, body="", path=None, line=None, user=None)
|
|
47
|
+
|
|
48
|
+
result = get_review_comment_from_gitea_comment(comment)
|
|
49
|
+
assert result.body == ""
|
|
50
|
+
assert result.author.username == ""
|
|
51
|
+
assert result.file is None
|
|
52
|
+
assert result.line is None
|