Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/review_classification/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def _fetch_repo(
reset_db: bool,
verbose: bool,
incremental: bool = False,
skip_empty_incremental: bool = False,
) -> None:
"""Fetch and store PRs for a single repository."""
repo = GitHubRepo.from_string(repo_name)
Expand All @@ -75,6 +76,13 @@ def _fetch_repo(
f"Incremental fetch: Found latest PR date {start_date} in database."
)
else:
if skip_empty_incremental:
typer.echo(
f"Skipping {full_name}: no existing PR data found for "
"incremental fetch.",
err=True,
)
return
typer.echo(
f"Error: Cannot use --incremental for {full_name} "
"on an initial fetch or empty database.",
Expand Down Expand Up @@ -307,6 +315,7 @@ def add_repo(rc: RepoConfig) -> None:
# Create a RepoConfig inheriting from org_cfg, then global
rc = RepoConfig(
name=r,
from_organization=True,
collate_start=(
org_cfg.collate_start
if org_cfg.collate_start is not None
Expand Down Expand Up @@ -353,6 +362,7 @@ def add_repo(rc: RepoConfig) -> None:
for r in org_repos:
rc = RepoConfig(
name=r,
from_organization=True,
collate_start=default_collate_start,
collate_end=default_collate_end,
threshold=default_threshold,
Expand Down Expand Up @@ -456,6 +466,7 @@ def fetch(
reset_db,
verbose,
incremental=incremental,
skip_empty_incremental=target.from_organization,
)

except (FileNotFoundError, ValueError) as e:
Expand Down
2 changes: 2 additions & 0 deletions src/review_classification/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class RepoConfig:
min_samples: int | None = None
start: str | None = None
end: str | None = None
from_organization: bool = False


@dataclass
Expand Down Expand Up @@ -63,6 +64,7 @@ def resolve(self, repo: RepoConfig) -> RepoConfig:
"""
return RepoConfig(
name=repo.name,
from_organization=repo.from_organization,
collate_start=(
repo.collate_start
if repo.collate_start is not None
Expand Down
102 changes: 102 additions & 0 deletions tests/cli/test_fetch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Tests for fetch command behavior."""

from datetime import UTC, datetime
from importlib import import_module
from pathlib import Path
from typing import Any

import pytest
import typer

cli_app = import_module("review_classification.cli.app")


def _org_repos(_org_name: str) -> list[str]:
return ["my-org/existing", "my-org/new"]


def _save_pr(_pr: Any) -> None:
return None


def _no_latest_pr_date(_repo_name: str) -> None:
return None


def test_incremental_org_fetch_skips_repo_without_existing_data(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
"""Org incremental fetches skip newly discovered repos without DB history."""
fetched_repos: list[str] = []

def latest_pr_date(repo_name: str) -> datetime | None:
if repo_name == "my-org/existing":
return datetime(2024, 1, 15, tzinfo=UTC)
return None

def fetch_prs(
repo_name: str,
start_date: str | None = None,
end_date: str | None = None,
token: str | None = None,
) -> list[Any]:
del start_date, end_date, token
fetched_repos.append(repo_name)
return []

monkeypatch.setenv("GITHUB_TOKEN", "test-token")
monkeypatch.setattr(
"review_classification.queries.github_client.get_org_repos", _org_repos
)
monkeypatch.setattr(
"review_classification.sqlite.database.get_latest_pr_date", latest_pr_date
)
monkeypatch.setattr(
"review_classification.queries.github_client.fetch_prs", fetch_prs
)
monkeypatch.setattr("review_classification.sqlite.database.init_db", lambda: None)
monkeypatch.setattr("review_classification.sqlite.database.save_pr", _save_pr)

cli_app.fetch(
repo=None,
org=["my-org"],
config=None,
collate_start=None,
collate_end=None,
reset_db=False,
verbose=False,
incremental=True,
)

captured = capsys.readouterr()
assert fetched_repos == ["my-org/existing"]
assert (
"Skipping my-org/new: no existing PR data found for incremental fetch."
in captured.err
)


def test_incremental_repo_fetch_still_errors_without_existing_data(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""Explicit repo incremental fetches still require existing DB history."""
monkeypatch.chdir(tmp_path)
monkeypatch.setattr(
"review_classification.sqlite.database.get_latest_pr_date", _no_latest_pr_date
)

with pytest.raises(typer.Exit) as exc_info:
cli_app.fetch(
repo=["my-org/new"],
org=None,
config=None,
collate_start=None,
collate_end=None,
reset_db=False,
verbose=False,
incremental=True,
)

assert exc_info.value.exit_code == 1
Loading