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
19 changes: 19 additions & 0 deletions src/core/pre_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
def block_user_title_edit(event_data, skip_label, github_client, issue):
if not isinstance(event_data, dict):
return False
if event_data["action"] != "edited":
return False
if event_data["sender"]["type"] != "User":
return False

previous_title = event_data["changes"]["title"]["from"]
if not previous_title:
return False
if skip_label not in [label["name"] for label in event_data["issue"]["labels"]]:
return False

github_client.add_issue_comment(
issue, "This issue has already been processed. Please do not change the title."
)
issue.edit(title=previous_title)
return True
2 changes: 2 additions & 0 deletions src/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(self):
self.event_path = os.environ.get("GITHUB_EVENT_PATH")
self.is_issue_event = self.event_name == "issues"
self.issue_number = None
self.event_data = None

# If it's an issue event, get the issue number
if self.is_issue_event and self.event_path and os.path.exists(self.event_path):
Expand All @@ -43,6 +44,7 @@ def __init__(self):

with open(self.event_path) as f:
event_data = json.load(f)
self.event_data = event_data
if "issue" in event_data and "number" in event_data["issue"]:
self.issue_number = event_data["issue"]["number"]
except Exception as e:
Expand Down
5 changes: 5 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from core.github_client import GitHubClient
from core.issue_service import IssueProcessor
from core.llm import create_ai_client
from core.pre_checks import block_user_title_edit
from core.settings import Config
from core.verbose import set_verbose

Expand All @@ -13,6 +14,10 @@ def open_issue_event(config, repo_obj, ai_client, github_client):
print(f"Processing single issue #{config.issue_number} from event trigger")
try:
issue = repo_obj.get_issue(config.issue_number)

if block_user_title_edit(config.event_data, config.skip_label, github_client, issue):
return []

issue_processor = IssueProcessor(
ai_client, github_client, config.prompt, config.skip_label, config.required_labels
)
Expand Down
36 changes: 36 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def mock_config():
config.issue_number = None
config.required_labels = []
config.apply_to_closed = False
config.event_data = None
return config


Expand Down Expand Up @@ -72,6 +73,41 @@ def test_open_issue_event(mock_config, mock_ai_client, mock_github_client, mock_
assert results[0]["issue_number"] == 1


def test_open_issue_event_with_editing(
mock_config, mock_ai_client, mock_github_client, mock_repo, mock_issue
):
mock_config.issue_number = 1
mock_repo.get_issue.return_value = mock_issue

# Setup event_data to simulate a title edit
mock_config.event_data = {
"action": "edited",
"sender": {"type": "User"},
"changes": {"title": {"from": "Previous title"}},
"issue": {"labels": [{"name": "titled"}]},
}

# Add the 'titled' label to the mock issue to match the event_data
mock_issue.labels = [{"name": "titled"}]

results = open_issue_event(mock_config, mock_repo, mock_ai_client, mock_github_client)

# Should have called get_issue
mock_repo.get_issue.assert_called_once_with(1)

# Since the issue has the 'titled' label and was edited, block_user_title_edit should have
# returned True and no results should be returned
assert len(results) == 0

# Verify that the issue was edited back to its previous title
mock_issue.edit.assert_called_once_with(title="Previous title")

# Verify that a comment was added explaining the title change was not allowed
mock_github_client.add_issue_comment.assert_called_once_with(
mock_issue, "This issue has already been processed. Please do not change the title."
)


def test_open_issue_event_error(mock_config, mock_ai_client, mock_github_client, mock_repo):
mock_config.issue_number = 1
mock_repo.get_issue.side_effect = Exception("Issue not found")
Expand Down
92 changes: 92 additions & 0 deletions tests/test_pre_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from unittest.mock import Mock, patch

import pytest

from src.core.pre_checks import block_user_title_edit


@pytest.fixture
def mock_event_data():
"""Create mock event data for edited issue title."""
return {
"action": "edited",
"sender": {"type": "User"},
"changes": {"title": {"from": "Original Title"}},
"issue": {"labels": [{"name": "titled"}]},
}


@pytest.fixture
def mock_github_client():
"""Create a mock GitHub client."""
client = Mock()
client.add_issue_comment = Mock()
return client


@pytest.fixture
def mock_issue():
"""Create a mock GitHub issue."""
issue = Mock()
issue.edit = Mock()
return issue


def test_block_user_title_edit_success(mock_event_data, mock_github_client, mock_issue):
"""Test successful blocking of a user title edit."""
result = block_user_title_edit(mock_event_data, "titled", mock_github_client, mock_issue)

# Assert the function returns True
assert result is True

# Assert comment was added with correct message
mock_github_client.add_issue_comment.assert_called_once_with(
mock_issue, "This issue has already been processed. Please do not change the title."
)

# Assert title was reverted back to the original
mock_issue.edit.assert_called_once_with(title="Original Title")


def test_block_user_title_edit_not_edited_action(mock_event_data, mock_github_client, mock_issue):
"""Test when action is not 'edited'."""
mock_event_data["action"] = "created"

result = block_user_title_edit(mock_event_data, "titled", mock_github_client, mock_issue)

assert result is False
mock_github_client.add_issue_comment.assert_not_called()
mock_issue.edit.assert_not_called()


def test_block_user_title_edit_not_user(mock_event_data, mock_github_client, mock_issue):
"""Test when sender is not a User."""
mock_event_data["sender"]["type"] = "Bot"

result = block_user_title_edit(mock_event_data, "titled", mock_github_client, mock_issue)

assert result is False
mock_github_client.add_issue_comment.assert_not_called()
mock_issue.edit.assert_not_called()


def test_block_user_title_edit_no_previous_title(mock_event_data, mock_github_client, mock_issue):
"""Test when there is no previous title."""
mock_event_data["changes"]["title"]["from"] = ""

result = block_user_title_edit(mock_event_data, "titled", mock_github_client, mock_issue)

assert result is False
mock_github_client.add_issue_comment.assert_not_called()
mock_issue.edit.assert_not_called()


def test_block_user_title_edit_no_skip_label(mock_event_data, mock_github_client, mock_issue):
"""Test when the issue doesn't have the skip label."""
mock_event_data["issue"]["labels"] = [{"name": "bug"}]

result = block_user_title_edit(mock_event_data, "titled", mock_github_client, mock_issue)

assert result is False
mock_github_client.add_issue_comment.assert_not_called()
mock_issue.edit.assert_not_called()
Loading