diff --git a/tests/test_dispatch_actions.py b/tests/test_dispatch_actions.py new file mode 100644 index 0000000..5277d1f --- /dev/null +++ b/tests/test_dispatch_actions.py @@ -0,0 +1,146 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from datetime import datetime +from unittest.mock import MagicMock, patch + +from actions.dispatch_actions import ( + RUN_CI_KEYWORD, + get_pr_branch, + main, + trigger_and_get_workflow_info, + update_comment, +) + + +def test_get_pr_branch(): + """Test getting PR branch name.""" + mock_event = MagicMock() + mock_event.event_data = {"issue": {"number": 123}} + mock_event.get_repo_data.return_value = {"head": {"ref": "feature-branch"}} + + branch = get_pr_branch(mock_event) + + assert branch == "feature-branch" + mock_event.get_repo_data.assert_called_once_with("pulls/123") + + +def test_trigger_and_get_workflow_info(): + """Test triggering workflows and getting info.""" + mock_event = MagicMock() + mock_event.repository = "test/repo" + + # Mock the workflow and runs responses separately + workflow_response = MagicMock() + workflow_response.status_code = 200 + workflow_response.json.return_value = {"name": "CI Workflow"} + + runs_response = MagicMock() + runs_response.status_code = 200 + runs_response.json.return_value = { + "workflow_runs": [{"html_url": "https://github.com/test/repo/actions/runs/123", "run_number": 42}] + } + + # Set up get method to return different responses for different URLs + def get_side_effect(url): + if "workflows/ci.yml" in url and "runs" not in url: + return workflow_response + elif "workflows/ci.yml/runs" in url: + return runs_response + # Return default response for unexpected URLs + default = MagicMock() + default.status_code = 404 + return default + + mock_event.get.side_effect = get_side_effect + + # Use patch to skip time.sleep and limit to one workflow + with patch("time.sleep"): + with patch("actions.dispatch_actions.WORKFLOW_FILES", ["ci.yml"]): + results = trigger_and_get_workflow_info(mock_event, "feature-branch") + + # Check results + assert len(results) == 1 + assert results[0]["name"] == "CI Workflow" + assert results[0]["run_number"] == 42 + + +def test_update_comment_function(): + """Test updating comment with workflow info.""" + mock_event = MagicMock() + mock_event.repository = "test/repo" + mock_event.event_data = {"comment": {"id": 456}} + + comment_body = f"Run tests please {RUN_CI_KEYWORD}" + triggered_actions = [ + { + "name": "CI Workflow", + "file": "ci.yml", + "url": "https://github.com/test/repo/actions/workflows/ci.yml", + "run_number": 42, + } + ] + + # Mock datetime to have a consistent timestamp + with patch("actions.dispatch_actions.datetime") as mock_datetime: + mock_datetime.now.return_value = datetime(2023, 1, 1, 12, 0, 0) + # Call without capturing return value + update_comment(mock_event, comment_body, triggered_actions, "feature-branch") + + # Check that patch was called with expected content + mock_event.patch.assert_called_once() + + # Verify key content in the comment body + args, kwargs = mock_event.patch.call_args + assert "https://api.github.com/repos/test/repo/issues/comments/456" in args[0] + assert "Actions Trigger" in kwargs["json"]["body"] + assert "CI Workflow" in kwargs["json"]["body"] + assert "2023-01-01 12:00:00 UTC" in kwargs["json"]["body"] + + +def test_main_triggers_workflows(): + """Test main function when comment contains trigger keyword.""" + with patch("actions.dispatch_actions.Action") as MockAction: + # Configure mock + mock_event = MockAction.return_value + mock_event.event_name = "issue_comment" + mock_event.repository = "test/repo" + mock_event.event_data = { + "action": "created", + "issue": {"pull_request": {}}, + "comment": {"body": f"Please run CI {RUN_CI_KEYWORD}", "user": {"login": "testuser"}, "id": 789}, + } + mock_event.is_org_member.return_value = True + + # Create minimal patches for the functions called by main + with patch("actions.dispatch_actions.get_pr_branch") as mock_get_branch: + with patch("actions.dispatch_actions.trigger_and_get_workflow_info") as mock_trigger: + with patch("actions.dispatch_actions.update_comment"): + # Set return values + mock_get_branch.return_value = "feature-branch" + mock_trigger.return_value = [{"name": "CI", "file": "ci.yml", "url": "url", "run_number": 1}] + + # Call the function + main() + + # Verify main component calls were made + mock_event.is_org_member.assert_called_once_with("testuser") + mock_get_branch.assert_called_once() + mock_trigger.assert_called_once() + + +def test_main_skips_non_pr_comments(): + """Test main function skips non-PR comments.""" + with patch("actions.dispatch_actions.Action") as MockAction: + # Configure mock + mock_event = MockAction.return_value + mock_event.event_name = "issue_comment" + mock_event.event_data = { + "action": "created", + "issue": {}, # No pull_request key + "comment": {"body": f"Please run CI {RUN_CI_KEYWORD}", "user": {"login": "testuser"}}, + } + + main() + + # Verify toggle_eyes_reaction was not called + mock_event.toggle_eyes_reaction.assert_not_called() diff --git a/tests/test_first_interaction.py b/tests/test_first_interaction.py new file mode 100644 index 0000000..db8592f --- /dev/null +++ b/tests/test_first_interaction.py @@ -0,0 +1,116 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from unittest.mock import MagicMock, patch + +from actions.first_interaction import ( + create_alert_label, + get_event_content, + get_first_interaction_response, + get_relevant_labels, +) + + +def test_get_event_content_issue(): + """Test extracting content from issue event.""" + # Create mock event + mock_event = MagicMock() + mock_event.event_name = "issues" + mock_event.event_data = { + "action": "opened", + "issue": { + "number": 123, + "node_id": "node123", + "title": "Test Issue", + "body": "Issue description", + "user": {"login": "testuser"}, + }, + } + + number, node_id, title, body, username, issue_type, action = get_event_content(mock_event) + + assert number == 123 + assert node_id == "node123" + assert title == "Test Issue" + assert body == "Issue description" + assert username == "testuser" + assert issue_type == "issue" + assert action == "opened" + + +def test_get_event_content_pr(): + """Test extracting content from PR event.""" + # Create mock event + mock_event = MagicMock() + mock_event.event_name = "pull_request" + mock_event.event_data = {"action": "opened", "pull_request": {"number": 456}} + + # Mock PR data returned from API + mock_event.get_repo_data.return_value = { + "number": 456, + "node_id": "node456", + "title": "Test PR", + "body": "PR description", + "user": {"login": "testuser"}, + } + + number, node_id, title, body, username, issue_type, action = get_event_content(mock_event) + + assert number == 456 + assert node_id == "node456" + assert title == "Test PR" + assert body == "PR description" + assert username == "testuser" + assert issue_type == "pull request" + assert action == "opened" + + +@patch("actions.first_interaction.get_completion") +def test_get_relevant_labels(mock_get_completion): + """Test getting relevant labels for an issue.""" + mock_get_completion.return_value = "bug, enhancement" + + available_labels = { + "bug": "A bug in the software", + "enhancement": "New feature or request", + "documentation": "Improvements to documentation", + } + + labels = get_relevant_labels( + issue_type="issue", + title="Bug: App crashes on startup", + body="The application crashes when started", + available_labels=available_labels, + current_labels=[], + ) + + assert labels == ["bug", "enhancement"] + mock_get_completion.assert_called_once() + + +@patch("actions.first_interaction.get_completion") +def test_get_first_interaction_response(mock_get_completion): + """Test generating first interaction response.""" + mock_get_completion.return_value = "Thank you for your issue" + + mock_event = MagicMock() + mock_event.repository = "test/repo" + + response = get_first_interaction_response( + event=mock_event, issue_type="issue", title="Test Issue", body="Issue description", username="testuser" + ) + + assert response == "Thank you for your issue" + mock_get_completion.assert_called_once() + + +def test_create_alert_label(): + """Test creating Alert label.""" + mock_event = MagicMock() + + create_alert_label(mock_event) + + mock_event.post.assert_called_once() + args, kwargs = mock_event.post.call_args + assert "labels" in args[0] + assert kwargs["json"]["name"] == "Alert" + assert kwargs["json"]["color"] == "FF0000" diff --git a/tests/test_summarize_pr.py b/tests/test_summarize_pr.py new file mode 100644 index 0000000..b81f1c5 --- /dev/null +++ b/tests/test_summarize_pr.py @@ -0,0 +1,73 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from unittest.mock import MagicMock, patch + +from actions.summarize_pr import ( + generate_issue_comment, + generate_merge_message, + generate_pr_summary, + update_pr_description, +) + + +@patch("actions.summarize_pr.get_completion") +def test_generate_pr_summary(mock_get_completion): + """Test generating PR summary with expected formatting.""" + mock_get_completion.return_value = "Test PR summary content" + summary = generate_pr_summary("test/repo", "diff content") + + assert summary.startswith("## 🛠️ PR Summary") + assert "Test PR summary content" in summary + mock_get_completion.assert_called_once() + + +@patch("actions.summarize_pr.get_completion") +def test_generate_merge_message(mock_get_completion): + """Test generating merge thank you messages.""" + mock_get_completion.return_value = "Thank you for your contribution!" + + message = generate_merge_message( + pr_summary="Feature implementation", pr_credit="@testuser", pr_url="https://github.com/test/repo/pull/1" + ) + + assert message == "Thank you for your contribution!" + mock_get_completion.assert_called_once() + + +@patch("actions.summarize_pr.get_completion") +def test_generate_issue_comment(mock_get_completion): + """Test generating issue comments about PR fixes.""" + mock_get_completion.return_value = "This issue is fixed in PR #123" + + comment = generate_issue_comment( + pr_url="https://api.github.com/repos/owner/repo/pulls/123", + pr_summary="Fixed bug", + pr_credit="@testuser", + pr_title="Bug fix PR", + ) + + assert comment == "This issue is fixed in PR #123" + mock_get_completion.assert_called_once() + + +def test_update_pr_description(): + """Test updating PR description with summary.""" + # Mock Action class + mock_event = MagicMock() + mock_event.repository = "test/repo" + mock_event.pr = {"number": 123} + + # Mock response for get request + mock_response = MagicMock() + mock_response.json.return_value = {"body": "Original description"} + mock_event.get.return_value = mock_response + + # Test updating description + update_pr_description(mock_event, "## 🛠️ PR Summary\nNew summary") + + # Verify patch was called with correct parameters + mock_event.patch.assert_called_once() + args, kwargs = mock_event.patch.call_args + assert args[0] == "https://api.github.com/repos/test/repo/pulls/123" + assert "Original description" in kwargs["json"]["body"] + assert "## 🛠️ PR Summary\nNew summary" in kwargs["json"]["body"] diff --git a/tests/test_summarize_release.py b/tests/test_summarize_release.py new file mode 100644 index 0000000..273aa86 --- /dev/null +++ b/tests/test_summarize_release.py @@ -0,0 +1,127 @@ +# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license + +from unittest.mock import MagicMock, patch + +from actions.summarize_release import ( + create_github_release, + generate_release_summary, + get_prs_between_tags, + get_release_diff, +) + + +def test_get_release_diff(): + """Test retrieving diff between tags.""" + mock_event = MagicMock() + mock_event.repository = "test/repo" + mock_event.headers_diff = {"Accept": "application/vnd.github.v3.diff"} + + # Mock response + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.text = "diff content" + mock_event.get.return_value = mock_response + + diff = get_release_diff(mock_event, "v1.0.0", "v1.1.0") + + assert diff == "diff content" + mock_event.get.assert_called_once_with( + "https://api.github.com/repos/test/repo/compare/v1.0.0...v1.1.0", headers=mock_event.headers_diff + ) + + +def test_get_prs_between_tags(): + """Test retrieving PRs between tags.""" + mock_event = MagicMock() + mock_event.repository = "test/repo" + + # Mock compare response + mock_compare_response = MagicMock() + mock_compare_response.json.return_value = { + "commits": [{"commit": {"message": "Fix bug #123"}}, {"commit": {"message": "Add feature #456"}}] + } + + # Mock PR responses + mock_pr1_response = MagicMock() + mock_pr1_response.status_code = 200 + mock_pr1_response.json.return_value = { + "number": 123, + "title": "Fix bug", + "body": "Fixes a bug", + "user": {"login": "user1"}, + "html_url": "https://github.com/test/repo/pull/123", + "merged_at": "2023-01-01T12:00:00Z", + } + + mock_pr2_response = MagicMock() + mock_pr2_response.status_code = 200 + mock_pr2_response.json.return_value = { + "number": 456, + "title": "Add feature", + "body": "Adds a new feature", + "user": {"login": "user2"}, + "html_url": "https://github.com/test/repo/pull/456", + "merged_at": "2023-01-02T12:00:00Z", + } + + # Set up get method to return different responses + mock_event.get.side_effect = [mock_compare_response, mock_pr1_response, mock_pr2_response] + + # Use patch to skip the sleep + with patch("time.sleep"): + prs = get_prs_between_tags(mock_event, "v1.0.0", "v1.1.0") + + assert len(prs) == 2 + assert prs[0]["number"] == 123 + assert prs[1]["number"] == 456 + + +@patch("actions.summarize_release.get_completion") +def test_generate_release_summary(mock_get_completion): + """Test generating release summary.""" + mock_get_completion.return_value = "Release summary content" + + mock_event = MagicMock() + mock_event.repository = "test/repo" + + # Create test data with matching author for new contributor + test_prs = [ + { + "number": 123, + "title": "Fix bug", + "body": "Fixes a bug", + "author": "newuser", # Match the contributor name + "html_url": "https://github.com/test/repo/pull/123", + "merged_at": "2023-01-01T12:00:00Z", + } + ] + + # Mock new contributors function + with patch("actions.summarize_release.get_new_contributors", return_value=["newuser"]): + summary = generate_release_summary( + event=mock_event, diff="diff content", prs=test_prs, latest_tag="v1.1.0", previous_tag="v1.0.0" + ) + + assert "Release summary content" in summary + assert "What's Changed" in summary + assert "https://github.com/test/repo/compare/v1.0.0...v1.1.0" in summary + mock_get_completion.assert_called_once() + + +def test_create_github_release(): + """Test creating GitHub release.""" + mock_event = MagicMock() + mock_event.repository = "test/repo" + + create_github_release(event=mock_event, tag_name="v1.1.0", name="Version 1.1.0", body="Release notes") + + mock_event.post.assert_called_once_with( + "https://api.github.com/repos/test/repo/releases", + json={ + "tag_name": "v1.1.0", + "name": "Version 1.1.0", + "body": "Release notes", + "draft": False, + "prerelease": False, + }, + )