Skip to content

Commit 8384a1e

Browse files
committed
fix: handling of target for guides, tests using lgtm.toml
1 parent ad6a149 commit 8384a1e

7 files changed

Lines changed: 59 additions & 24 deletions

File tree

scripts/evaluate_review_quality.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from lgtm_ai.git_client.gitlab import GitlabClient
1616
from lgtm_ai.review import CodeReviewer
1717
from lgtm_ai.review.context import ContextRetriever
18-
from lgtm_ai.validators import parse_target
18+
from lgtm_ai.validators import _parse_target
1919
from rich.logging import RichHandler
2020

2121
PRS_FOR_EVALUATION = {
@@ -94,7 +94,7 @@ def get_git_branch() -> str:
9494
def perform_review(
9595
output_dir: str, pr_url: str, pr_name: str, sample: int, model: SupportedAIModels, git_api_key: str, ai_api_key: str
9696
) -> None:
97-
url = parse_target(mock.Mock(), "pr_url", pr_url)
97+
url = _parse_target(mock.Mock(), "pr_url", pr_url)
9898
git_client = GitlabClient(client=gitlab.Gitlab(private_token=git_api_key), formatter=MarkDownFormatter())
9999
code_reviewer = CodeReviewer(
100100
reviewer_agent=get_reviewer_agent_with_settings(),

src/lgtm_ai/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from lgtm_ai.validators import (
3333
IntOrNoLimitType,
3434
ModelChoice,
35-
parse_target,
35+
TargetParser,
3636
validate_model_url,
3737
)
3838
from rich.console import Console
@@ -57,7 +57,6 @@ def entry_point() -> None:
5757
def _common_options[**P, T](func: Callable[P, T]) -> Callable[P, T]:
5858
"""Wrap a click command and adds common options for lgtm commands."""
5959

60-
@click.argument("target", required=True, callback=parse_target)
6160
@click.option(
6261
"--model",
6362
type=ModelChoice(SupportedAIModelsList),
@@ -112,6 +111,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
112111
return wrapper
113112

114113

114+
@click.argument("target", required=True, callback=TargetParser(allow_git_repo=True))
115115
@entry_point.command()
116116
@_common_options
117117
@click.option(
@@ -216,6 +216,7 @@ def review(target: PRUrl | LocalRepository, config: str | None, verbose: int, **
216216
logger.info("Review published successfully")
217217

218218

219+
@click.argument("target", required=True, callback=TargetParser(allow_git_repo=False))
219220
@entry_point.command()
220221
@_common_options
221222
def guide(

src/lgtm_ai/validators.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ class AllowedSchemes(StrEnum):
1616
Http = "http"
1717

1818

19-
def parse_target(ctx: click.Context, param: str, value: object) -> PRUrl | LocalRepository:
19+
class TargetParser:
20+
"""Generate a click callback that parses the `TARGET` argument."""
21+
22+
def __init__(self, allow_git_repo: bool) -> None:
23+
self.allow_git_repo = allow_git_repo
24+
25+
def __call__(self, ctx: click.Context, param: str, value: object) -> PRUrl | LocalRepository:
26+
return _parse_target(ctx, param, value, allow_git_repo=self.allow_git_repo)
27+
28+
29+
def _parse_target(ctx: click.Context, param: str, value: object, *, allow_git_repo: bool) -> PRUrl | LocalRepository:
2030
"""Click callback that transforms a given URL into a dataclass for later use.
2131
2232
It validates it and raises click exceptions if the URL is not valid.
@@ -26,7 +36,9 @@ def parse_target(ctx: click.Context, param: str, value: object) -> PRUrl | Local
2636

2737
parsed = urlparse(value)
2838
if not parsed.netloc:
29-
# Check whether its a directory path
39+
if not allow_git_repo:
40+
raise click.BadParameter("The PR URL must be a valid URL")
41+
3042
try:
3143
resolved_path = pathlib.Path(value).resolve(strict=True) # just to ensure it's a valid path
3244
# Check whether it's a git repository

tests/config/conftest.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from collections.abc import Iterator
44
from contextlib import contextmanager
55
from pathlib import Path
6-
from unittest import mock
76

87
import pytest
98

@@ -144,11 +143,3 @@ def clean_env_secrets() -> Iterator[None]:
144143
# Restore original environment
145144
os.environ.clear()
146145
os.environ.update(original_env)
147-
148-
149-
@pytest.fixture(autouse=True)
150-
def mock_current_working_directory(tmp_path: Path) -> Iterator[None]:
151-
with mock.patch("lgtm_ai.config.handler.os.getcwd", return_value=str(tmp_path)):
152-
# We still mock the current directory because this is a python project,
153-
# and thus it has a pyproject.toml file that will be read during the test execution!
154-
yield

tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
1+
from collections.abc import Iterator
12
from copy import deepcopy
3+
from pathlib import Path
24
from typing import Any
5+
from unittest import mock
36
from unittest.mock import MagicMock
47

58
import pytest
69

710

11+
@pytest.fixture(autouse=True)
12+
def mock_current_working_directory(tmp_path: Path) -> Iterator[None]:
13+
"""Mock the current working directory for config handling in all tests.
14+
15+
This is done so that the `lgtm.toml` file of `lgtm` is not used in tests.
16+
"""
17+
with mock.patch("lgtm_ai.config.handler.os.getcwd", return_value=str(tmp_path)):
18+
yield
19+
20+
821
class CopyingMock(MagicMock):
922
"""Mock which copies arguments when mocks are called with mutable arguments.
1023

tests/test_main.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from pathlib import Path
23
from unittest import mock
34

45
import click
@@ -73,7 +74,14 @@ def test_review_cli_github(*args: mock.MagicMock) -> None:
7374
@mock.patch("lgtm_ai.__main__.CodeReviewer")
7475
@mock.patch("lgtm_ai.__main__.PrettyFormatter")
7576
@mock.patch("lgtm_ai.__main__.get_git_client")
76-
def test_review_cli_local(*args: mock.MagicMock) -> None:
77+
def test_review_cli_local(
78+
m_get_git_client: mock.MagicMock,
79+
m_formatter: mock.MagicMock,
80+
m_reviewer: mock.MagicMock,
81+
tmp_path: Path,
82+
) -> None:
83+
# Create a .git directory to simulate a git repository
84+
(tmp_path / ".git").mkdir()
7785
runner = CliRunner()
7886
result = runner.invoke(
7987
review,
@@ -90,14 +98,19 @@ def test_review_cli_local(*args: mock.MagicMock) -> None:
9098
@mock.patch("lgtm_ai.__main__.CodeReviewer")
9199
@mock.patch("lgtm_ai.__main__.PrettyFormatter")
92100
@mock.patch("lgtm_ai.__main__.get_git_client")
93-
def test_review_cli_local_does_not_exist(*args: mock.MagicMock) -> None:
101+
def test_review_cli_local_does_not_exist(
102+
m_get_git_client: mock.MagicMock,
103+
m_formatter: mock.MagicMock,
104+
m_reviewer: mock.MagicMock,
105+
tmp_path: Path,
106+
) -> None:
94107
runner = CliRunner()
95108
result = runner.invoke(
96109
review,
97110
[
98111
"--ai-api-key",
99112
"fake-token",
100-
"./foo/bar/baz",
113+
"./fake",
101114
],
102115
catch_exceptions=False,
103116
)
@@ -162,8 +175,8 @@ def test_guide_cli_local_fails(*args: mock.MagicMock) -> None:
162175
catch_exceptions=False,
163176
)
164177

165-
assert result.exit_code == 1
166-
assert "Aborted!" in result.stderr
178+
assert result.exit_code == 2
179+
assert "Invalid value for 'TARGET': The PR URL must be a valid URL" in result.stderr
167180

168181

169182
@pytest.mark.parametrize(

tests/test_validators.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import click
77
import pytest
88
from lgtm_ai.base.schemas import PRSource, PRUrl
9-
from lgtm_ai.validators import parse_target, validate_model_url
9+
from lgtm_ai.validators import TargetParser, validate_model_url
1010

1111

1212
@pytest.mark.parametrize(
@@ -34,12 +34,17 @@
3434
)
3535
def test_parse_url(url: str, expectation: AbstractContextManager[Any]) -> None:
3636
with expectation:
37-
parse_target(mock.Mock(), "pr_url", url)
37+
TargetParser(allow_git_repo=True)(mock.Mock(), "pr_url", url)
38+
39+
40+
def test_parse_url_does_not_accept_git_repo() -> None:
41+
with pytest.raises(click.exceptions.BadParameter, match="The PR URL must be a valid URL"):
42+
TargetParser(allow_git_repo=False)(mock.Mock(), "pr_url", "./foo/bar/baz")
3843

3944

4045
def test_parse_url_gitlab_valid() -> None:
4146
url = "https://gitlab.com/foo/-/merge_requests/1"
42-
parsed = parse_target(mock.Mock(), "pr_url", url)
47+
parsed = TargetParser(allow_git_repo=True)(mock.Mock(), "pr_url", url)
4348

4449
assert parsed == PRUrl(
4550
full_url=url,
@@ -52,7 +57,7 @@ def test_parse_url_gitlab_valid() -> None:
5257

5358
def test_parse_url_github_valid() -> None:
5459
url = "https://github.com/elementsinteractive/sheriff/pull/61"
55-
parsed = parse_target(mock.Mock(), "pr_url", url)
60+
parsed = TargetParser(allow_git_repo=True)(mock.Mock(), "pr_url", url)
5661

5762
assert parsed == PRUrl(
5863
full_url=url,

0 commit comments

Comments
 (0)