xai-review 0.27.0__py3-none-any.whl → 0.29.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/cli/commands/run_inline_reply_review.py +7 -0
- ai_review/cli/commands/run_summary_reply_review.py +7 -0
- ai_review/cli/main.py +17 -0
- ai_review/clients/bitbucket/pr/schema/comments.py +14 -0
- ai_review/clients/bitbucket/pr/schema/pull_request.py +1 -5
- ai_review/clients/bitbucket/pr/schema/user.py +7 -0
- ai_review/clients/github/pr/client.py +35 -4
- ai_review/clients/github/pr/schema/comments.py +21 -0
- ai_review/clients/github/pr/schema/pull_request.py +1 -4
- ai_review/clients/github/pr/schema/user.py +6 -0
- ai_review/clients/github/pr/types.py +11 -1
- ai_review/clients/gitlab/mr/client.py +32 -1
- ai_review/clients/gitlab/mr/schema/changes.py +1 -5
- ai_review/clients/gitlab/mr/schema/discussions.py +14 -12
- ai_review/clients/gitlab/mr/schema/notes.py +5 -0
- ai_review/clients/gitlab/mr/schema/position.py +13 -0
- ai_review/clients/gitlab/mr/schema/user.py +7 -0
- ai_review/clients/gitlab/mr/types.py +16 -7
- ai_review/libs/asynchronous/gather.py +8 -1
- ai_review/libs/config/prompt.py +96 -64
- ai_review/libs/config/review.py +2 -0
- ai_review/libs/llm/output_json_parser.py +60 -0
- ai_review/prompts/default_inline_reply.md +10 -0
- ai_review/prompts/default_summary_reply.md +14 -0
- ai_review/prompts/default_system_inline_reply.md +31 -0
- ai_review/prompts/default_system_summary_reply.md +13 -0
- ai_review/services/artifacts/schema.py +2 -2
- ai_review/services/git/service.py +42 -11
- ai_review/services/hook/constants.py +14 -0
- ai_review/services/hook/service.py +95 -4
- ai_review/services/hook/types.py +18 -2
- ai_review/services/prompt/adapter.py +1 -1
- ai_review/services/prompt/service.py +49 -3
- ai_review/services/prompt/tools.py +21 -0
- ai_review/services/prompt/types.py +23 -0
- ai_review/services/review/gateway/comment.py +45 -6
- ai_review/services/review/gateway/llm.py +2 -1
- ai_review/services/review/gateway/types.py +50 -0
- ai_review/services/review/internal/inline/service.py +40 -0
- ai_review/services/review/internal/inline/types.py +8 -0
- ai_review/services/review/internal/inline_reply/schema.py +23 -0
- ai_review/services/review/internal/inline_reply/service.py +20 -0
- ai_review/services/review/internal/inline_reply/types.py +8 -0
- ai_review/services/review/{policy → internal/policy}/service.py +2 -1
- ai_review/services/review/internal/policy/types.py +15 -0
- ai_review/services/review/{summary → internal/summary}/service.py +2 -2
- ai_review/services/review/{summary → internal/summary}/types.py +1 -1
- ai_review/services/review/internal/summary_reply/__init__.py +0 -0
- ai_review/services/review/internal/summary_reply/schema.py +8 -0
- ai_review/services/review/internal/summary_reply/service.py +15 -0
- ai_review/services/review/internal/summary_reply/types.py +8 -0
- ai_review/services/review/runner/__init__.py +0 -0
- ai_review/services/review/runner/context.py +72 -0
- ai_review/services/review/runner/inline.py +80 -0
- ai_review/services/review/runner/inline_reply.py +80 -0
- ai_review/services/review/runner/summary.py +71 -0
- ai_review/services/review/runner/summary_reply.py +79 -0
- ai_review/services/review/runner/types.py +6 -0
- ai_review/services/review/service.py +78 -110
- ai_review/services/vcs/bitbucket/adapter.py +27 -0
- ai_review/services/vcs/bitbucket/client.py +118 -42
- ai_review/services/vcs/github/adapter.py +35 -0
- ai_review/services/vcs/github/client.py +105 -44
- ai_review/services/vcs/gitlab/adapter.py +28 -0
- ai_review/services/vcs/gitlab/client.py +103 -43
- ai_review/services/vcs/types.py +34 -0
- ai_review/tests/fixtures/clients/bitbucket.py +2 -2
- ai_review/tests/fixtures/clients/github.py +35 -6
- ai_review/tests/fixtures/clients/gitlab.py +71 -6
- ai_review/tests/fixtures/libs/__init__.py +0 -0
- ai_review/tests/fixtures/libs/llm/__init__.py +0 -0
- ai_review/tests/fixtures/libs/llm/output_json_parser.py +13 -0
- ai_review/tests/fixtures/services/hook.py +8 -0
- ai_review/tests/fixtures/services/llm.py +8 -5
- ai_review/tests/fixtures/services/prompt.py +70 -0
- ai_review/tests/fixtures/services/review/base.py +41 -0
- ai_review/tests/fixtures/services/review/gateway/__init__.py +0 -0
- ai_review/tests/fixtures/services/review/gateway/comment.py +98 -0
- ai_review/tests/fixtures/services/review/gateway/llm.py +17 -0
- ai_review/tests/fixtures/services/review/internal/__init__.py +0 -0
- ai_review/tests/fixtures/services/review/{inline.py → internal/inline.py} +8 -6
- ai_review/tests/fixtures/services/review/internal/inline_reply.py +25 -0
- ai_review/tests/fixtures/services/review/internal/policy.py +28 -0
- ai_review/tests/fixtures/services/review/internal/summary.py +21 -0
- ai_review/tests/fixtures/services/review/internal/summary_reply.py +19 -0
- ai_review/tests/fixtures/services/review/runner/__init__.py +0 -0
- ai_review/tests/fixtures/services/review/runner/context.py +50 -0
- ai_review/tests/fixtures/services/review/runner/inline.py +50 -0
- ai_review/tests/fixtures/services/review/runner/inline_reply.py +50 -0
- ai_review/tests/fixtures/services/review/runner/summary.py +50 -0
- ai_review/tests/fixtures/services/review/runner/summary_reply.py +50 -0
- ai_review/tests/fixtures/services/vcs.py +23 -0
- ai_review/tests/suites/cli/__init__.py +0 -0
- ai_review/tests/suites/cli/test_main.py +54 -0
- ai_review/tests/suites/libs/config/test_prompt.py +108 -28
- ai_review/tests/suites/libs/llm/__init__.py +0 -0
- ai_review/tests/suites/libs/llm/test_output_json_parser.py +155 -0
- ai_review/tests/suites/services/hook/test_service.py +88 -4
- ai_review/tests/suites/services/prompt/test_adapter.py +3 -3
- ai_review/tests/suites/services/prompt/test_service.py +102 -58
- ai_review/tests/suites/services/prompt/test_tools.py +86 -1
- ai_review/tests/suites/services/review/gateway/__init__.py +0 -0
- ai_review/tests/suites/services/review/gateway/test_comment.py +253 -0
- ai_review/tests/suites/services/review/gateway/test_llm.py +82 -0
- ai_review/tests/suites/services/review/internal/__init__.py +0 -0
- ai_review/tests/suites/services/review/internal/inline/__init__.py +0 -0
- ai_review/tests/suites/services/review/{inline → internal/inline}/test_schema.py +1 -1
- ai_review/tests/suites/services/review/internal/inline/test_service.py +81 -0
- ai_review/tests/suites/services/review/internal/inline_reply/__init__.py +0 -0
- ai_review/tests/suites/services/review/internal/inline_reply/test_schema.py +57 -0
- ai_review/tests/suites/services/review/internal/inline_reply/test_service.py +72 -0
- ai_review/tests/suites/services/review/internal/policy/__init__.py +0 -0
- ai_review/tests/suites/services/review/{policy → internal/policy}/test_service.py +1 -1
- ai_review/tests/suites/services/review/internal/summary/__init__.py +0 -0
- ai_review/tests/suites/services/review/{summary → internal/summary}/test_schema.py +1 -1
- ai_review/tests/suites/services/review/{summary → internal/summary}/test_service.py +2 -2
- ai_review/tests/suites/services/review/internal/summary_reply/__init__.py +0 -0
- ai_review/tests/suites/services/review/internal/summary_reply/test_schema.py +19 -0
- ai_review/tests/suites/services/review/internal/summary_reply/test_service.py +21 -0
- ai_review/tests/suites/services/review/runner/__init__.py +0 -0
- ai_review/tests/suites/services/review/runner/test_context.py +89 -0
- ai_review/tests/suites/services/review/runner/test_inline.py +100 -0
- ai_review/tests/suites/services/review/runner/test_inline_reply.py +109 -0
- ai_review/tests/suites/services/review/runner/test_summary.py +87 -0
- ai_review/tests/suites/services/review/runner/test_summary_reply.py +97 -0
- ai_review/tests/suites/services/review/test_service.py +64 -97
- ai_review/tests/suites/services/vcs/bitbucket/test_adapter.py +109 -0
- ai_review/tests/suites/services/vcs/bitbucket/{test_service.py → test_client.py} +88 -1
- ai_review/tests/suites/services/vcs/github/test_adapter.py +162 -0
- ai_review/tests/suites/services/vcs/github/{test_service.py → test_client.py} +102 -2
- ai_review/tests/suites/services/vcs/gitlab/test_adapter.py +134 -0
- ai_review/tests/suites/services/vcs/gitlab/{test_service.py → test_client.py} +113 -3
- {xai_review-0.27.0.dist-info → xai_review-0.29.0.dist-info}/METADATA +8 -5
- {xai_review-0.27.0.dist-info → xai_review-0.29.0.dist-info}/RECORD +146 -72
- ai_review/services/review/inline/service.py +0 -54
- ai_review/services/review/inline/types.py +0 -11
- ai_review/tests/fixtures/services/review/summary.py +0 -19
- ai_review/tests/suites/services/review/inline/test_service.py +0 -107
- /ai_review/{services/review/inline → libs/llm}/__init__.py +0 -0
- /ai_review/services/review/{policy → internal}/__init__.py +0 -0
- /ai_review/services/review/{summary → internal/inline}/__init__.py +0 -0
- /ai_review/services/review/{inline → internal/inline}/schema.py +0 -0
- /ai_review/{tests/suites/services/review/inline → services/review/internal/inline_reply}/__init__.py +0 -0
- /ai_review/{tests/suites/services/review → services/review/internal}/policy/__init__.py +0 -0
- /ai_review/{tests/suites/services/review → services/review/internal}/summary/__init__.py +0 -0
- /ai_review/services/review/{summary → internal/summary}/schema.py +0 -0
- {xai_review-0.27.0.dist-info → xai_review-0.29.0.dist-info}/WHEEL +0 -0
- {xai_review-0.27.0.dist-info → xai_review-0.29.0.dist-info}/entry_points.txt +0 -0
- {xai_review-0.27.0.dist-info → xai_review-0.29.0.dist-info}/licenses/LICENSE +0 -0
- {xai_review-0.27.0.dist-info → xai_review-0.29.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.cost.types import CostServiceProtocol
|
|
4
|
+
from ai_review.services.review.runner.types import ReviewRunnerProtocol
|
|
5
|
+
from ai_review.services.review.service import ReviewService
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def review_service(
|
|
10
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
11
|
+
fake_cost_service: CostServiceProtocol,
|
|
12
|
+
fake_inline_review_runner: ReviewRunnerProtocol,
|
|
13
|
+
fake_context_review_runner: ReviewRunnerProtocol,
|
|
14
|
+
fake_summary_review_runner: ReviewRunnerProtocol,
|
|
15
|
+
fake_inline_reply_review_runner: ReviewRunnerProtocol,
|
|
16
|
+
fake_summary_reply_review_runner: ReviewRunnerProtocol,
|
|
17
|
+
):
|
|
18
|
+
monkeypatch.setattr("ai_review.services.review.service.CostService", lambda: fake_cost_service)
|
|
19
|
+
|
|
20
|
+
monkeypatch.setattr(
|
|
21
|
+
"ai_review.services.review.service.InlineReviewRunner",
|
|
22
|
+
lambda **_: fake_inline_review_runner
|
|
23
|
+
)
|
|
24
|
+
monkeypatch.setattr(
|
|
25
|
+
"ai_review.services.review.service.ContextReviewRunner",
|
|
26
|
+
lambda **_: fake_context_review_runner
|
|
27
|
+
)
|
|
28
|
+
monkeypatch.setattr(
|
|
29
|
+
"ai_review.services.review.service.SummaryReviewRunner",
|
|
30
|
+
lambda **_: fake_summary_review_runner
|
|
31
|
+
)
|
|
32
|
+
monkeypatch.setattr(
|
|
33
|
+
"ai_review.services.review.service.InlineReplyReviewRunner",
|
|
34
|
+
lambda **_: fake_inline_reply_review_runner
|
|
35
|
+
)
|
|
36
|
+
monkeypatch.setattr(
|
|
37
|
+
"ai_review.services.review.service.SummaryReplyReviewRunner",
|
|
38
|
+
lambda **_: fake_summary_reply_review_runner
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
return ReviewService()
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from ai_review.services.review.gateway.comment import ReviewCommentGateway
|
|
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 FakeReviewCommentGateway(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=5,
|
|
31
|
+
comments=[
|
|
32
|
+
ReviewCommentSchema(
|
|
33
|
+
id="c1",
|
|
34
|
+
body="AI inline comment <!--AI-->",
|
|
35
|
+
file="file.py",
|
|
36
|
+
line=5,
|
|
37
|
+
author=fake_user
|
|
38
|
+
),
|
|
39
|
+
ReviewCommentSchema(id="c2", body="Developer reply", file="file.py", line=5, author=fake_user),
|
|
40
|
+
],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
fake_summary_thread = ReviewThreadSchema(
|
|
44
|
+
id="t2",
|
|
45
|
+
kind=ThreadKind.SUMMARY,
|
|
46
|
+
comments=[
|
|
47
|
+
ReviewCommentSchema(id="c3", body="AI summary comment <!--AI-->", author=fake_user),
|
|
48
|
+
ReviewCommentSchema(id="c4", body="Developer reply", author=fake_user),
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.responses = responses or {
|
|
53
|
+
"get_inline_threads": [fake_inline_thread],
|
|
54
|
+
"get_summary_threads": [fake_summary_thread],
|
|
55
|
+
"has_existing_inline_comments": False,
|
|
56
|
+
"has_existing_summary_comments": False,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async def get_inline_threads(self) -> list[ReviewThreadSchema]:
|
|
60
|
+
self.calls.append(("get_inline_threads", {}))
|
|
61
|
+
return self.responses.get("get_inline_threads", [])
|
|
62
|
+
|
|
63
|
+
async def get_summary_threads(self) -> list[ReviewThreadSchema]:
|
|
64
|
+
self.calls.append(("get_summary_threads", {}))
|
|
65
|
+
return self.responses.get("get_summary_threads", [])
|
|
66
|
+
|
|
67
|
+
async def has_existing_inline_comments(self) -> bool:
|
|
68
|
+
self.calls.append(("has_existing_inline_comments", {}))
|
|
69
|
+
return self.responses.get("has_existing_inline_comments", False)
|
|
70
|
+
|
|
71
|
+
async def has_existing_summary_comments(self) -> bool:
|
|
72
|
+
self.calls.append(("has_existing_summary_comments", {}))
|
|
73
|
+
return self.responses.get("has_existing_summary_comments", False)
|
|
74
|
+
|
|
75
|
+
async def process_inline_reply(self, thread_id: str, reply: InlineCommentReplySchema) -> None:
|
|
76
|
+
self.calls.append(("process_inline_reply", {"thread_id": thread_id, "reply": reply}))
|
|
77
|
+
|
|
78
|
+
async def process_summary_reply(self, thread_id: str, reply: SummaryCommentReplySchema) -> None:
|
|
79
|
+
self.calls.append(("process_summary_reply", {"thread_id": thread_id, "reply": reply}))
|
|
80
|
+
|
|
81
|
+
async def process_inline_comment(self, comment: InlineCommentSchema) -> None:
|
|
82
|
+
self.calls.append(("process_inline_comment", {"comment": comment}))
|
|
83
|
+
|
|
84
|
+
async def process_summary_comment(self, comment: SummaryCommentSchema) -> None:
|
|
85
|
+
self.calls.append(("process_summary_comment", {"comment": comment}))
|
|
86
|
+
|
|
87
|
+
async def process_inline_comments(self, comments: InlineCommentListSchema) -> None:
|
|
88
|
+
self.calls.append(("process_inline_comments", {"comments": comments}))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.fixture
|
|
92
|
+
def fake_review_comment_gateway() -> FakeReviewCommentGateway:
|
|
93
|
+
return FakeReviewCommentGateway()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.fixture
|
|
97
|
+
def review_comment_gateway(fake_vcs_client: VCSClientProtocol) -> ReviewCommentGateway:
|
|
98
|
+
return ReviewCommentGateway(vcs=fake_vcs_client)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.review.gateway.types import ReviewLLMGatewayProtocol
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FakeReviewLLMGateway(ReviewLLMGatewayProtocol):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.calls: list[tuple[str, dict]] = []
|
|
9
|
+
|
|
10
|
+
async def ask(self, prompt: str, prompt_system: str) -> str:
|
|
11
|
+
self.calls.append(("ask", {"prompt": prompt, "prompt_system": prompt_system}))
|
|
12
|
+
return "FAKE_LLM_RESPONSE"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def fake_review_llm_gateway() -> FakeReviewLLMGateway:
|
|
17
|
+
return FakeReviewLLMGateway()
|
|
File without changes
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from ai_review.services.review.inline.schema import InlineCommentListSchema, InlineCommentSchema
|
|
4
|
-
from ai_review.services.review.inline.
|
|
3
|
+
from ai_review.services.review.internal.inline.schema import InlineCommentListSchema, InlineCommentSchema
|
|
4
|
+
from ai_review.services.review.internal.inline.service import InlineCommentService
|
|
5
|
+
from ai_review.services.review.internal.inline.types import InlineCommentServiceProtocol
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class FakeInlineCommentService(InlineCommentServiceProtocol):
|
|
@@ -15,11 +16,12 @@ class FakeInlineCommentService(InlineCommentServiceProtocol):
|
|
|
15
16
|
self.calls.append(("parse_model_output", {"output": output}))
|
|
16
17
|
return InlineCommentListSchema(root=self.comments)
|
|
17
18
|
|
|
18
|
-
def try_parse_model_output(self, raw: str) -> InlineCommentListSchema | None:
|
|
19
|
-
self.calls.append(("try_parse_model_output", {"raw": raw}))
|
|
20
|
-
return InlineCommentListSchema(root=self.comments)
|
|
21
|
-
|
|
22
19
|
|
|
23
20
|
@pytest.fixture
|
|
24
21
|
def fake_inline_comment_service() -> FakeInlineCommentService:
|
|
25
22
|
return FakeInlineCommentService()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def inline_comment_service() -> InlineCommentService:
|
|
27
|
+
return InlineCommentService()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.review.internal.inline_reply.schema import InlineCommentReplySchema
|
|
4
|
+
from ai_review.services.review.internal.inline_reply.service import InlineCommentReplyService
|
|
5
|
+
from ai_review.services.review.internal.inline_reply.types import InlineCommentReplyServiceProtocol
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FakeInlineCommentReplyService(InlineCommentReplyServiceProtocol):
|
|
9
|
+
def __init__(self, reply: InlineCommentReplySchema | None = None):
|
|
10
|
+
self.calls: list[tuple[str, dict]] = []
|
|
11
|
+
self.reply = reply or InlineCommentReplySchema(message="Looks good!", suggestion="use const instead of var")
|
|
12
|
+
|
|
13
|
+
def parse_model_output(self, output: str) -> InlineCommentReplySchema | None:
|
|
14
|
+
self.calls.append(("parse_model_output", {"output": output}))
|
|
15
|
+
return self.reply
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def fake_inline_comment_reply_service() -> FakeInlineCommentReplyService:
|
|
20
|
+
return FakeInlineCommentReplyService()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def inline_comment_reply_service() -> InlineCommentReplyService:
|
|
25
|
+
return InlineCommentReplyService()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from ai_review.services.review.internal.policy.types import ReviewPolicyServiceProtocol
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FakeReviewPolicyService(ReviewPolicyServiceProtocol):
|
|
9
|
+
def __init__(self, responses: dict[str, Any] | None = None):
|
|
10
|
+
self.calls: list[tuple[str, dict]] = []
|
|
11
|
+
self.responses = responses or {}
|
|
12
|
+
|
|
13
|
+
def apply_for_files(self, files: list[str]) -> list[str]:
|
|
14
|
+
self.calls.append(("apply_for_files", {"files": files}))
|
|
15
|
+
return self.responses.get("apply_for_files", files)
|
|
16
|
+
|
|
17
|
+
def apply_for_inline_comments(self, comments: list) -> list:
|
|
18
|
+
self.calls.append(("apply_for_inline_comments", {"comments": comments}))
|
|
19
|
+
return self.responses.get("apply_for_inline_comments", comments)
|
|
20
|
+
|
|
21
|
+
def apply_for_context_comments(self, comments: list) -> list:
|
|
22
|
+
self.calls.append(("apply_for_context_comments", {"comments": comments}))
|
|
23
|
+
return self.responses.get("apply_for_context_comments", comments)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def fake_review_policy_service() -> FakeReviewPolicyService:
|
|
28
|
+
return FakeReviewPolicyService()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from ai_review.services.review.internal.summary.schema import SummaryCommentSchema
|
|
6
|
+
from ai_review.services.review.internal.summary.types import SummaryCommentServiceProtocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FakeSummaryCommentService(SummaryCommentServiceProtocol):
|
|
10
|
+
def __init__(self, responses: dict[str, Any] | None = None):
|
|
11
|
+
self.calls: list[tuple[str, dict]] = []
|
|
12
|
+
self.responses = responses or {}
|
|
13
|
+
|
|
14
|
+
def parse_model_output(self, output: str) -> SummaryCommentSchema:
|
|
15
|
+
self.calls.append(("parse_model_output", {"output": output}))
|
|
16
|
+
return self.responses.get("parse_model_output", SummaryCommentSchema(text="This is a summary comment"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def fake_summary_comment_service() -> FakeSummaryCommentService:
|
|
21
|
+
return FakeSummaryCommentService()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.review.internal.summary_reply.schema import SummaryCommentReplySchema
|
|
4
|
+
from ai_review.services.review.internal.summary_reply.types import SummaryCommentReplyServiceProtocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FakeSummaryCommentReplyService(SummaryCommentReplyServiceProtocol):
|
|
8
|
+
def __init__(self, reply: SummaryCommentReplySchema | None = None):
|
|
9
|
+
self.calls: list[tuple[str, dict]] = []
|
|
10
|
+
self.reply = reply or SummaryCommentReplySchema(text="Overall, the code looks clean and efficient.")
|
|
11
|
+
|
|
12
|
+
def parse_model_output(self, output: str) -> SummaryCommentReplySchema:
|
|
13
|
+
self.calls.append(("parse_model_output", {"output": output}))
|
|
14
|
+
return self.reply
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def fake_summary_comment_reply_service() -> FakeSummaryCommentReplyService:
|
|
19
|
+
return FakeSummaryCommentReplyService()
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.cost.types import CostServiceProtocol
|
|
4
|
+
from ai_review.services.diff.types import DiffServiceProtocol
|
|
5
|
+
from ai_review.services.git.types import GitServiceProtocol
|
|
6
|
+
from ai_review.services.prompt.types import PromptServiceProtocol
|
|
7
|
+
from ai_review.services.review.gateway.types import ReviewLLMGatewayProtocol, ReviewCommentGatewayProtocol
|
|
8
|
+
from ai_review.services.review.internal.inline.types import InlineCommentServiceProtocol
|
|
9
|
+
from ai_review.services.review.internal.policy.types import ReviewPolicyServiceProtocol
|
|
10
|
+
from ai_review.services.review.runner.context import ContextReviewRunner
|
|
11
|
+
from ai_review.services.review.runner.types import ReviewRunnerProtocol
|
|
12
|
+
from ai_review.services.vcs.types import VCSClientProtocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeContextReviewRunner(ReviewRunnerProtocol):
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.calls = []
|
|
18
|
+
|
|
19
|
+
async def run(self) -> None:
|
|
20
|
+
self.calls.append(("run", {}))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def fake_context_review_runner() -> FakeContextReviewRunner:
|
|
25
|
+
return FakeContextReviewRunner()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def context_review_runner(
|
|
30
|
+
fake_vcs_client: VCSClientProtocol,
|
|
31
|
+
fake_git_service: GitServiceProtocol,
|
|
32
|
+
fake_diff_service: DiffServiceProtocol,
|
|
33
|
+
fake_cost_service: CostServiceProtocol,
|
|
34
|
+
fake_prompt_service: PromptServiceProtocol,
|
|
35
|
+
fake_review_llm_gateway: ReviewLLMGatewayProtocol,
|
|
36
|
+
fake_review_policy_service: ReviewPolicyServiceProtocol,
|
|
37
|
+
fake_review_comment_gateway: ReviewCommentGatewayProtocol,
|
|
38
|
+
fake_inline_comment_service: InlineCommentServiceProtocol,
|
|
39
|
+
) -> ContextReviewRunner:
|
|
40
|
+
return ContextReviewRunner(
|
|
41
|
+
vcs=fake_vcs_client,
|
|
42
|
+
git=fake_git_service,
|
|
43
|
+
diff=fake_diff_service,
|
|
44
|
+
cost=fake_cost_service,
|
|
45
|
+
prompt=fake_prompt_service,
|
|
46
|
+
review_policy=fake_review_policy_service,
|
|
47
|
+
inline_comment=fake_inline_comment_service,
|
|
48
|
+
review_llm_gateway=fake_review_llm_gateway,
|
|
49
|
+
review_comment_gateway=fake_review_comment_gateway,
|
|
50
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.cost.types import CostServiceProtocol
|
|
4
|
+
from ai_review.services.diff.types import DiffServiceProtocol
|
|
5
|
+
from ai_review.services.git.types import GitServiceProtocol
|
|
6
|
+
from ai_review.services.prompt.types import PromptServiceProtocol
|
|
7
|
+
from ai_review.services.review.gateway.types import ReviewLLMGatewayProtocol, ReviewCommentGatewayProtocol
|
|
8
|
+
from ai_review.services.review.internal.inline.types import InlineCommentServiceProtocol
|
|
9
|
+
from ai_review.services.review.internal.policy.types import ReviewPolicyServiceProtocol
|
|
10
|
+
from ai_review.services.review.runner.inline import InlineReviewRunner
|
|
11
|
+
from ai_review.services.review.runner.types import ReviewRunnerProtocol
|
|
12
|
+
from ai_review.services.vcs.types import VCSClientProtocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeInlineReviewRunner(ReviewRunnerProtocol):
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.calls = []
|
|
18
|
+
|
|
19
|
+
async def run(self) -> None:
|
|
20
|
+
self.calls.append(("run", {}))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def fake_inline_review_runner() -> FakeInlineReviewRunner:
|
|
25
|
+
return FakeInlineReviewRunner()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def inline_review_runner(
|
|
30
|
+
fake_vcs_client: VCSClientProtocol,
|
|
31
|
+
fake_git_service: GitServiceProtocol,
|
|
32
|
+
fake_diff_service: DiffServiceProtocol,
|
|
33
|
+
fake_cost_service: CostServiceProtocol,
|
|
34
|
+
fake_prompt_service: PromptServiceProtocol,
|
|
35
|
+
fake_review_llm_gateway: ReviewLLMGatewayProtocol,
|
|
36
|
+
fake_review_policy_service: ReviewPolicyServiceProtocol,
|
|
37
|
+
fake_review_comment_gateway: ReviewCommentGatewayProtocol,
|
|
38
|
+
fake_inline_comment_service: InlineCommentServiceProtocol,
|
|
39
|
+
) -> InlineReviewRunner:
|
|
40
|
+
return InlineReviewRunner(
|
|
41
|
+
vcs=fake_vcs_client,
|
|
42
|
+
git=fake_git_service,
|
|
43
|
+
diff=fake_diff_service,
|
|
44
|
+
cost=fake_cost_service,
|
|
45
|
+
prompt=fake_prompt_service,
|
|
46
|
+
review_policy=fake_review_policy_service,
|
|
47
|
+
inline_comment=fake_inline_comment_service,
|
|
48
|
+
review_llm_gateway=fake_review_llm_gateway,
|
|
49
|
+
review_comment_gateway=fake_review_comment_gateway,
|
|
50
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.cost.types import CostServiceProtocol
|
|
4
|
+
from ai_review.services.diff.types import DiffServiceProtocol
|
|
5
|
+
from ai_review.services.git.types import GitServiceProtocol
|
|
6
|
+
from ai_review.services.prompt.types import PromptServiceProtocol
|
|
7
|
+
from ai_review.services.review.gateway.types import ReviewLLMGatewayProtocol, ReviewCommentGatewayProtocol
|
|
8
|
+
from ai_review.services.review.internal.inline_reply.types import InlineCommentReplyServiceProtocol
|
|
9
|
+
from ai_review.services.review.internal.policy.types import ReviewPolicyServiceProtocol
|
|
10
|
+
from ai_review.services.review.runner.inline_reply import InlineReplyReviewRunner
|
|
11
|
+
from ai_review.services.review.runner.types import ReviewRunnerProtocol
|
|
12
|
+
from ai_review.services.vcs.types import VCSClientProtocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeInlineReplyReviewRunner(ReviewRunnerProtocol):
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.calls = []
|
|
18
|
+
|
|
19
|
+
async def run(self) -> None:
|
|
20
|
+
self.calls.append(("run", {}))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def fake_inline_reply_review_runner() -> FakeInlineReplyReviewRunner:
|
|
25
|
+
return FakeInlineReplyReviewRunner()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def inline_reply_review_runner(
|
|
30
|
+
fake_vcs_client: VCSClientProtocol,
|
|
31
|
+
fake_git_service: GitServiceProtocol,
|
|
32
|
+
fake_diff_service: DiffServiceProtocol,
|
|
33
|
+
fake_cost_service: CostServiceProtocol,
|
|
34
|
+
fake_prompt_service: PromptServiceProtocol,
|
|
35
|
+
fake_review_llm_gateway: ReviewLLMGatewayProtocol,
|
|
36
|
+
fake_review_policy_service: ReviewPolicyServiceProtocol,
|
|
37
|
+
fake_review_comment_gateway: ReviewCommentGatewayProtocol,
|
|
38
|
+
fake_inline_comment_reply_service: InlineCommentReplyServiceProtocol,
|
|
39
|
+
) -> InlineReplyReviewRunner:
|
|
40
|
+
return InlineReplyReviewRunner(
|
|
41
|
+
vcs=fake_vcs_client,
|
|
42
|
+
git=fake_git_service,
|
|
43
|
+
diff=fake_diff_service,
|
|
44
|
+
cost=fake_cost_service,
|
|
45
|
+
prompt=fake_prompt_service,
|
|
46
|
+
review_policy=fake_review_policy_service,
|
|
47
|
+
review_llm_gateway=fake_review_llm_gateway,
|
|
48
|
+
inline_comment_reply=fake_inline_comment_reply_service,
|
|
49
|
+
review_comment_gateway=fake_review_comment_gateway,
|
|
50
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.cost.types import CostServiceProtocol
|
|
4
|
+
from ai_review.services.diff.types import DiffServiceProtocol
|
|
5
|
+
from ai_review.services.git.types import GitServiceProtocol
|
|
6
|
+
from ai_review.services.prompt.types import PromptServiceProtocol
|
|
7
|
+
from ai_review.services.review.gateway.types import ReviewCommentGatewayProtocol, ReviewLLMGatewayProtocol
|
|
8
|
+
from ai_review.services.review.internal.policy.types import ReviewPolicyServiceProtocol
|
|
9
|
+
from ai_review.services.review.internal.summary.types import SummaryCommentServiceProtocol
|
|
10
|
+
from ai_review.services.review.runner.summary import SummaryReviewRunner
|
|
11
|
+
from ai_review.services.review.runner.types import ReviewRunnerProtocol
|
|
12
|
+
from ai_review.services.vcs.types import VCSClientProtocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeSummaryReviewRunner(ReviewRunnerProtocol):
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.calls = []
|
|
18
|
+
|
|
19
|
+
async def run(self) -> None:
|
|
20
|
+
self.calls.append(("run", {}))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def fake_summary_review_runner() -> FakeSummaryReviewRunner:
|
|
25
|
+
return FakeSummaryReviewRunner()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def summary_review_runner(
|
|
30
|
+
fake_vcs_client: VCSClientProtocol,
|
|
31
|
+
fake_git_service: GitServiceProtocol,
|
|
32
|
+
fake_diff_service: DiffServiceProtocol,
|
|
33
|
+
fake_cost_service: CostServiceProtocol,
|
|
34
|
+
fake_prompt_service: PromptServiceProtocol,
|
|
35
|
+
fake_review_llm_gateway: ReviewLLMGatewayProtocol,
|
|
36
|
+
fake_review_policy_service: ReviewPolicyServiceProtocol,
|
|
37
|
+
fake_review_comment_gateway: ReviewCommentGatewayProtocol,
|
|
38
|
+
fake_summary_comment_service: SummaryCommentServiceProtocol,
|
|
39
|
+
) -> SummaryReviewRunner:
|
|
40
|
+
return SummaryReviewRunner(
|
|
41
|
+
vcs=fake_vcs_client,
|
|
42
|
+
git=fake_git_service,
|
|
43
|
+
diff=fake_diff_service,
|
|
44
|
+
cost=fake_cost_service,
|
|
45
|
+
prompt=fake_prompt_service,
|
|
46
|
+
review_policy=fake_review_policy_service,
|
|
47
|
+
summary_comment=fake_summary_comment_service,
|
|
48
|
+
review_llm_gateway=fake_review_llm_gateway,
|
|
49
|
+
review_comment_gateway=fake_review_comment_gateway,
|
|
50
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from ai_review.services.cost.types import CostServiceProtocol
|
|
4
|
+
from ai_review.services.diff.types import DiffServiceProtocol
|
|
5
|
+
from ai_review.services.git.types import GitServiceProtocol
|
|
6
|
+
from ai_review.services.prompt.types import PromptServiceProtocol
|
|
7
|
+
from ai_review.services.review.gateway.types import ReviewLLMGatewayProtocol, ReviewCommentGatewayProtocol
|
|
8
|
+
from ai_review.services.review.internal.policy.types import ReviewPolicyServiceProtocol
|
|
9
|
+
from ai_review.services.review.internal.summary_reply.types import SummaryCommentReplyServiceProtocol
|
|
10
|
+
from ai_review.services.review.runner.summary_reply import SummaryReplyReviewRunner
|
|
11
|
+
from ai_review.services.review.runner.types import ReviewRunnerProtocol
|
|
12
|
+
from ai_review.services.vcs.types import VCSClientProtocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FakeSummaryReplyReviewRunner(ReviewRunnerProtocol):
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.calls = []
|
|
18
|
+
|
|
19
|
+
async def run(self) -> None:
|
|
20
|
+
self.calls.append(("run", {}))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def fake_summary_reply_review_runner() -> FakeSummaryReplyReviewRunner:
|
|
25
|
+
return FakeSummaryReplyReviewRunner()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture
|
|
29
|
+
def summary_reply_review_runner(
|
|
30
|
+
fake_vcs_client: VCSClientProtocol,
|
|
31
|
+
fake_git_service: GitServiceProtocol,
|
|
32
|
+
fake_diff_service: DiffServiceProtocol,
|
|
33
|
+
fake_cost_service: CostServiceProtocol,
|
|
34
|
+
fake_prompt_service: PromptServiceProtocol,
|
|
35
|
+
fake_review_llm_gateway: ReviewLLMGatewayProtocol,
|
|
36
|
+
fake_review_policy_service: ReviewPolicyServiceProtocol,
|
|
37
|
+
fake_review_comment_gateway: ReviewCommentGatewayProtocol,
|
|
38
|
+
fake_summary_comment_reply_service: SummaryCommentReplyServiceProtocol,
|
|
39
|
+
) -> SummaryReplyReviewRunner:
|
|
40
|
+
return SummaryReplyReviewRunner(
|
|
41
|
+
vcs=fake_vcs_client,
|
|
42
|
+
git=fake_git_service,
|
|
43
|
+
diff=fake_diff_service,
|
|
44
|
+
cost=fake_cost_service,
|
|
45
|
+
prompt=fake_prompt_service,
|
|
46
|
+
review_policy=fake_review_policy_service,
|
|
47
|
+
review_llm_gateway=fake_review_llm_gateway,
|
|
48
|
+
summary_comment_reply=fake_summary_comment_reply_service,
|
|
49
|
+
review_comment_gateway=fake_review_comment_gateway,
|
|
50
|
+
)
|
|
@@ -5,6 +5,7 @@ import pytest
|
|
|
5
5
|
from ai_review.services.vcs.types import (
|
|
6
6
|
VCSClientProtocol,
|
|
7
7
|
ReviewInfoSchema,
|
|
8
|
+
ReviewThreadSchema,
|
|
8
9
|
ReviewCommentSchema,
|
|
9
10
|
)
|
|
10
11
|
|
|
@@ -43,6 +44,28 @@ class FakeVCSClient(VCSClientProtocol):
|
|
|
43
44
|
|
|
44
45
|
return self.responses.get("create_inline_comment_result", None)
|
|
45
46
|
|
|
47
|
+
async def create_inline_reply(self, thread_id: int | str, message: str) -> None:
|
|
48
|
+
self.calls.append(("create_inline_reply", (thread_id, message), {}))
|
|
49
|
+
if error := self.responses.get("create_inline_reply_error"):
|
|
50
|
+
raise error
|
|
51
|
+
|
|
52
|
+
return self.responses.get("create_inline_reply_result", None)
|
|
53
|
+
|
|
54
|
+
async def create_summary_reply(self, thread_id: int | str, message: str) -> None:
|
|
55
|
+
self.calls.append(("create_summary_reply", (thread_id, message), {}))
|
|
56
|
+
if error := self.responses.get("create_summary_reply_error"):
|
|
57
|
+
raise error
|
|
58
|
+
|
|
59
|
+
return self.responses.get("create_summary_reply_result", None)
|
|
60
|
+
|
|
61
|
+
async def get_inline_threads(self) -> list[ReviewThreadSchema]:
|
|
62
|
+
self.calls.append(("get_inline_threads", (), {}))
|
|
63
|
+
return self.responses.get("get_inline_threads", [])
|
|
64
|
+
|
|
65
|
+
async def get_general_threads(self) -> list[ReviewThreadSchema]:
|
|
66
|
+
self.calls.append(("get_general_threads", (), {}))
|
|
67
|
+
return self.responses.get("get_general_threads", [])
|
|
68
|
+
|
|
46
69
|
|
|
47
70
|
@pytest.fixture
|
|
48
71
|
def fake_vcs_client() -> FakeVCSClient:
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from typer.testing import CliRunner
|
|
3
|
+
|
|
4
|
+
from ai_review.cli.main import app
|
|
5
|
+
from ai_review.services.review.service import ReviewService
|
|
6
|
+
|
|
7
|
+
runner = CliRunner()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def dummy_review_service(monkeypatch: pytest.MonkeyPatch, review_service: ReviewService):
|
|
12
|
+
monkeypatch.setattr("ai_review.cli.commands.run_review.ReviewService", lambda: review_service)
|
|
13
|
+
monkeypatch.setattr("ai_review.cli.commands.run_inline_review.ReviewService", lambda: review_service)
|
|
14
|
+
monkeypatch.setattr("ai_review.cli.commands.run_context_review.ReviewService", lambda: review_service)
|
|
15
|
+
monkeypatch.setattr("ai_review.cli.commands.run_summary_review.ReviewService", lambda: review_service)
|
|
16
|
+
monkeypatch.setattr("ai_review.cli.commands.run_inline_reply_review.ReviewService", lambda: review_service)
|
|
17
|
+
monkeypatch.setattr("ai_review.cli.commands.run_summary_reply_review.ReviewService", lambda: review_service)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.parametrize(
|
|
21
|
+
"args, expected_output",
|
|
22
|
+
[
|
|
23
|
+
(["run"], "Starting full AI review..."),
|
|
24
|
+
(["run-inline"], "Starting inline AI review..."),
|
|
25
|
+
(["run-context"], "Starting context AI review..."),
|
|
26
|
+
(["run-summary"], "Starting summary AI review..."),
|
|
27
|
+
(["run-inline-reply"], "Starting inline reply AI review..."),
|
|
28
|
+
(["run-summary-reply"], "Starting summary reply AI review..."),
|
|
29
|
+
],
|
|
30
|
+
)
|
|
31
|
+
def test_cli_commands_invoke_review_service_successfully(args: list[str], expected_output: str):
|
|
32
|
+
"""
|
|
33
|
+
Ensure CLI commands correctly call the ReviewService with fake dependencies.
|
|
34
|
+
"""
|
|
35
|
+
result = runner.invoke(app, args)
|
|
36
|
+
|
|
37
|
+
assert result.exit_code == 0
|
|
38
|
+
assert expected_output in result.output
|
|
39
|
+
assert "AI review completed successfully!" in result.output
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_show_config_outputs_json(monkeypatch: pytest.MonkeyPatch):
|
|
43
|
+
"""
|
|
44
|
+
Validate that the 'show-config' command prints settings as JSON.
|
|
45
|
+
"""
|
|
46
|
+
monkeypatch.setattr(
|
|
47
|
+
"ai_review.cli.main.settings.model_dump_json",
|
|
48
|
+
lambda **_: '{"debug": true}'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
result = runner.invoke(app, ["show-config"])
|
|
52
|
+
assert result.exit_code == 0
|
|
53
|
+
assert "Loaded AI Review configuration" in result.output
|
|
54
|
+
assert '{"debug": true}' in result.output
|