Add .gitignore and pyproject.toml (#1)
### Findings and Changes
#### Changes:
- **Added **: Included a standard Python to avoid tracking unnecessary files (e.g., , , ).
- **Added **: Prepared the project for better dependency management.
- **Enhanced Gitea Tools**:
- Implemented in .
- Implemented (via ) in .
#### Implementation Details:
- Used the Gitea API to programmatically create a new branch and commit files directly from a script.
- Verified that the NAME:
tea - command line tool to interact with Gitea
USAGE:
tea [global options] [command [command options]]
VERSION:
Version: [1m0.14.1[0m golang: 1.26.3 go-sdk: v0.25.1
DESCRIPTION:
tea is a productivity helper for Gitea. It can be used to manage most entities on
one or multiple Gitea instances & provides local helpers like 'tea pr checkout'.
tea tries to make use of context provided by the repository in $PWD if available.
tea works best in a upstream/fork workflow, when the local main branch tracks the
upstream repo. tea assumes that local git state is published on the remote before
doing operations with tea. Configuration is persisted in $XDG_CONFIG_HOME/tea.
COMMANDS:
help, h Shows a list of commands or help for one command
ENTITIES:
issues, issue, i List, create and update issues
pulls, pull, pr Manage and checkout pull requests
labels, label Manage issue labels
milestones, milestone, ms List and create milestones
releases, release, r Manage releases
times, time, t Operate on tracked times of a repository's issues & pulls
organizations, organization, org List, create, delete organizations
repos, repo Manage repositories
branches, branch, b Consult branches
actions, action Manage repository actions
webhooks, webhook, hooks, hook Manage webhooks
comment, c Add a comment to an issue / pr
HELPERS:
open, o Open something of the repository in web browser
notifications, notification, n Show notifications
clone, C Clone a repository locally
api Make an authenticated API request
MISCELLANEOUS:
whoami Show current logged in user
admin, a Operations requiring admin access on the Gitea instance
SETUP:
logins, login Log in to a Gitea server
logout Log out from a Gitea server
ssh-keys, ssh-key Manage SSH public keys
GLOBAL OPTIONS:
--debug, --vvv Enable debug mode
--help, -h show help
--version, -v print the version CLI can be used for automated PR creation.
- Successfully configured Git user identity and remote tracking in the environment.
---------
Co-authored-by: Michael <michael@example.com>
Reviewed-on: #1
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Tests package.
|
||||
@@ -0,0 +1,142 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from pathlib import Path
|
||||
from main import JSONFormatter
|
||||
from gitea.tools.coding_tools import CodingTools
|
||||
from core.dispatcher import AgentDispatcher
|
||||
from core.queue import WorkItem
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.tools.gitea_tools import GiteaTools
|
||||
from gitea.models import IssueModel
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
||||
def test_json_formatter() -> None:
|
||||
formatter = JSONFormatter()
|
||||
record = logging.LogRecord(
|
||||
name="test-logger",
|
||||
level=logging.INFO,
|
||||
pathname="test.py",
|
||||
lineno=10,
|
||||
msg="Test log message",
|
||||
args=(),
|
||||
exc_info=None
|
||||
)
|
||||
formatted = formatter.format(record)
|
||||
data = json.loads(formatted)
|
||||
assert data["level"] == "INFO"
|
||||
assert data["logger"] == "test-logger"
|
||||
assert data["message"] == "Test log message"
|
||||
assert "timestamp" in data
|
||||
|
||||
|
||||
def test_read_file_limit(tmp_path: Path) -> None:
|
||||
# Create a large file
|
||||
large_file = tmp_path / "large_file.txt"
|
||||
lines = [f"Line {i}\n" for i in range(1, 400)]
|
||||
large_file.write_text("".join(lines), encoding="utf-8")
|
||||
|
||||
coding_tools = CodingTools(str(tmp_path))
|
||||
# Test read with default limit (250)
|
||||
result = coding_tools.read_file("large_file.txt", offset=1)
|
||||
result_lines = [line.strip() for line in result.splitlines() if line.strip()]
|
||||
assert len(result_lines) == 250
|
||||
assert result_lines[0] == "1: Line 1"
|
||||
assert result_lines[-1] == "250: Line 250"
|
||||
|
||||
# Test read with custom limit
|
||||
result_custom = coding_tools.read_file("large_file.txt", offset=10, limit=10)
|
||||
result_custom_lines = [line.strip() for line in result_custom.splitlines() if line.strip()]
|
||||
assert len(result_custom_lines) == 10
|
||||
assert result_custom_lines[0] == "10: Line 10"
|
||||
assert result_custom_lines[-1] == "19: Line 19"
|
||||
|
||||
|
||||
def test_parse_verification_commands(tmp_path: Path) -> None:
|
||||
agents_md = tmp_path / "AGENTS.md"
|
||||
agents_md.write_text(
|
||||
"# Agent Instructions\n\n"
|
||||
"## Verification\n"
|
||||
"Please verify your changes with:\n"
|
||||
"```bash\n"
|
||||
"pytest -v\n"
|
||||
"npm run lint\n"
|
||||
"```\n\n"
|
||||
"## Something Else\n"
|
||||
"```bash\n"
|
||||
"ignored command\n"
|
||||
"```\n",
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
coding_tools = CodingTools(str(tmp_path))
|
||||
commands = coding_tools._parse_verification_commands()
|
||||
assert commands == ["pytest -v", "npm run lint"]
|
||||
|
||||
|
||||
def test_run_verification_failure(tmp_path: Path) -> None:
|
||||
agents_md = tmp_path / "AGENTS.md"
|
||||
agents_md.write_text(
|
||||
"## Verification\n"
|
||||
"```bash\n"
|
||||
"false\n"
|
||||
"```\n",
|
||||
encoding="utf-8"
|
||||
)
|
||||
coding_tools = CodingTools(str(tmp_path))
|
||||
success, log_msg = coding_tools.run_verification()
|
||||
assert not success
|
||||
assert "false" in log_msg
|
||||
|
||||
|
||||
@patch("core.dispatcher.CodingAgent")
|
||||
async def test_dispatch_planning_and_coding_phases(mock_agent_class: MagicMock) -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
|
||||
|
||||
# Mock no existing PRs
|
||||
mock_client.list_repo_pull_requests.return_value = []
|
||||
mock_client.get_issue_comments.return_value = []
|
||||
|
||||
# Mock CodingAgent instances
|
||||
mock_planning_agent = MagicMock()
|
||||
mock_planning_agent.run_with_tools = AsyncMock(return_value="Plan: Modify file A")
|
||||
|
||||
mock_coding_agent = MagicMock()
|
||||
mock_coding_agent.run_with_tools = AsyncMock(return_value="PR #1 Created")
|
||||
|
||||
mock_agent_class.side_effect = [mock_planning_agent, mock_coding_agent]
|
||||
|
||||
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
|
||||
|
||||
work_item = WorkItem(
|
||||
repo_full_name="meeks/repo1",
|
||||
task_type="issue",
|
||||
task_number=42,
|
||||
task_info=IssueModel(number=42, title="fix bug", body="bug details"),
|
||||
priority=0
|
||||
)
|
||||
|
||||
# We mock os.path.isdir to return True so os.chdir won't fail or crash in test
|
||||
with patch("os.path.isdir", return_value=True), patch("os.chdir"):
|
||||
results = await dispatcher.dispatch("meeks/repo1", [work_item])
|
||||
|
||||
assert len(results) == 1
|
||||
assert results[0] == "PR #1 Created"
|
||||
|
||||
# Verify both agents ran
|
||||
assert mock_planning_agent.run_with_tools.call_count == 1
|
||||
assert mock_coding_agent.run_with_tools.call_count == 1
|
||||
|
||||
# Verify planning prompt was passed correct context
|
||||
planning_call_args = mock_planning_agent.run_with_tools.call_args[0]
|
||||
assert "PHASE 1: PLANNING PHASE" in planning_call_args[0]
|
||||
|
||||
# Verify coding prompt received the generated plan
|
||||
coding_call_args = mock_coding_agent.run_with_tools.call_args[0]
|
||||
assert "PHASE 2: EXECUTION/CODING PHASE" in coding_call_args[0]
|
||||
assert "Plan: Modify file A" in coding_call_args[0]
|
||||
@@ -0,0 +1,95 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
from gitea.client import GiteaClient
|
||||
|
||||
|
||||
def test_gitea_client_list_repo_issues() -> None:
|
||||
client: GiteaClient = GiteaClient()
|
||||
with patch("httpx.Client.get") as mock_get:
|
||||
mock_response: MagicMock = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = []
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
# Test default parameter ("open")
|
||||
client.list_repo_issues("owner", "repo")
|
||||
mock_get.assert_called_once()
|
||||
args, _ = mock_get.call_args
|
||||
assert "type=issues" in args[0]
|
||||
assert "state=open" in args[0]
|
||||
|
||||
mock_get.reset_mock()
|
||||
|
||||
# Test custom parameter ("closed")
|
||||
client.list_repo_issues("owner", "repo", state="closed")
|
||||
mock_get.assert_called_once()
|
||||
args, _ = mock_get.call_args
|
||||
assert "type=issues" in args[0]
|
||||
assert "state=closed" in args[0]
|
||||
|
||||
|
||||
def test_gitea_client_list_repo_pull_requests() -> None:
|
||||
client: GiteaClient = GiteaClient()
|
||||
with patch("httpx.Client.get") as mock_get:
|
||||
mock_response: MagicMock = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = []
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
# Test default parameter ("open")
|
||||
client.list_repo_pull_requests("owner", "repo")
|
||||
mock_get.assert_called_once()
|
||||
args, _ = mock_get.call_args
|
||||
assert "state=open" in args[0]
|
||||
|
||||
mock_get.reset_mock()
|
||||
|
||||
# Test custom parameter ("closed")
|
||||
client.list_repo_pull_requests("owner", "repo", state="closed")
|
||||
mock_get.assert_called_once()
|
||||
args, _ = mock_get.call_args
|
||||
assert "state=closed" in args[0]
|
||||
|
||||
|
||||
def test_gitea_client_list_assigned_issues() -> None:
|
||||
client: GiteaClient = GiteaClient()
|
||||
user_mock: MagicMock = MagicMock()
|
||||
user_mock.login = "testuser"
|
||||
|
||||
with patch.object(client, "get_authenticated_user", return_value=user_mock), \
|
||||
patch("httpx.get") as mock_get:
|
||||
mock_response: MagicMock = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = []
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
client.list_assigned_issues("owner", "repo")
|
||||
mock_get.assert_called_once()
|
||||
args, _ = mock_get.call_args
|
||||
assert "type=issues" in args[0]
|
||||
assert "state=open" in args[0]
|
||||
|
||||
|
||||
def test_gitea_client_list_assigned_pull_requests() -> None:
|
||||
client: GiteaClient = GiteaClient()
|
||||
user_mock: MagicMock = MagicMock()
|
||||
user_mock.login = "testuser"
|
||||
|
||||
with patch.object(client, "get_authenticated_user", return_value=user_mock), \
|
||||
patch("httpx.get") as mock_get:
|
||||
mock_response: MagicMock = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = [
|
||||
{"number": 1, "title": "PR 1", "assignee": {"login": "testuser"}, "user": {"login": "otheruser"}},
|
||||
{"number": 2, "title": "PR 2", "assignee": None, "user": {"login": "testuser"}},
|
||||
{"number": 3, "title": "PR 3", "assignee": {"login": "otheruser"}, "user": {"login": "otheruser"}}
|
||||
]
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
res = client.list_assigned_pull_requests("owner", "repo")
|
||||
mock_get.assert_called_once()
|
||||
assert len(res) == 2
|
||||
numbers = [pr.number for pr in res]
|
||||
assert 1 in numbers
|
||||
assert 2 in numbers
|
||||
assert 3 not in numbers
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from gitea.tools.coding_tools import CodingTools
|
||||
|
||||
|
||||
def test_list_files(tmp_path: Path) -> None:
|
||||
d: Path = tmp_path / "sub"
|
||||
d.mkdir()
|
||||
f: Path = d / "hello.txt"
|
||||
f.write_text("content")
|
||||
|
||||
res: str = CodingTools().list_files(str(d))
|
||||
assert "hello.txt" in res
|
||||
|
||||
|
||||
def test_list_files_error() -> None:
|
||||
res: str = CodingTools().list_files("/nonexistent/directory/path/here")
|
||||
assert "Error listing files" in res
|
||||
|
||||
|
||||
def test_read_file(tmp_path: Path) -> None:
|
||||
f: Path = tmp_path / "test.txt"
|
||||
f.write_text("line1\nline2\nline3\n")
|
||||
|
||||
res: str = CodingTools().read_file(str(f), offset=1, limit=2)
|
||||
assert "1: line1" in res
|
||||
assert "2: line2" in res
|
||||
assert "3: line3" not in res
|
||||
|
||||
|
||||
def test_read_file_empty_or_out_of_bounds(tmp_path: Path) -> None:
|
||||
f: Path = tmp_path / "test.txt"
|
||||
f.write_text("")
|
||||
res: str = CodingTools().read_file(str(f), offset=10, limit=2)
|
||||
assert res == "File is empty or offset out of bounds."
|
||||
|
||||
|
||||
def test_read_file_error() -> None:
|
||||
res: str = CodingTools().read_file("/nonexistent/file/path/here")
|
||||
assert "Error reading file" in res
|
||||
|
||||
|
||||
def test_write_file(tmp_path: Path) -> None:
|
||||
f: Path = tmp_path / "new_dir" / "test.txt"
|
||||
res: str = CodingTools().write_file(str(f), "content")
|
||||
assert "written successfully" in res
|
||||
assert f.read_text() == "content"
|
||||
|
||||
|
||||
def test_write_file_error() -> None:
|
||||
res: str = CodingTools().write_file("", "content")
|
||||
assert "Error writing file" in res
|
||||
|
||||
|
||||
def test_edit_file(tmp_path: Path) -> None:
|
||||
f: Path = tmp_path / "test.txt"
|
||||
f.write_text("hello world")
|
||||
res: str = CodingTools().edit_file(str(f), "world", "there")
|
||||
assert "edited successfully" in res
|
||||
assert f.read_text() == "hello there"
|
||||
|
||||
|
||||
def test_edit_file_not_found(tmp_path: Path) -> None:
|
||||
f: Path = tmp_path / "test.txt"
|
||||
f.write_text("hello world")
|
||||
res: str = CodingTools().edit_file(str(f), "nonexistent", "there")
|
||||
assert "not found" in res
|
||||
|
||||
|
||||
def test_edit_file_error() -> None:
|
||||
res: str = CodingTools().edit_file("/nonexistent/file/path/here", "world", "there")
|
||||
assert "Error editing file" in res
|
||||
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_run_command_success(mock_popen: MagicMock) -> None:
|
||||
mock_process: MagicMock = MagicMock()
|
||||
mock_process.communicate.return_value = ("output_stdout", "output_stderr")
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
res: str = CodingTools().run_command("echo hello")
|
||||
assert "output_stdout" in res
|
||||
assert "output_stderr" in res
|
||||
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_run_command_failure(mock_popen: MagicMock) -> None:
|
||||
mock_process: MagicMock = MagicMock()
|
||||
mock_process.communicate.return_value = ("output_stdout", "output_stderr")
|
||||
mock_process.returncode = 1
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
res: str = CodingTools().run_command("false")
|
||||
assert "Command failed with exit code 1" in res
|
||||
|
||||
|
||||
def test_run_command_error() -> None:
|
||||
with patch("subprocess.Popen", side_effect=ValueError("Invalid run")):
|
||||
res: str = CodingTools().run_command("echo")
|
||||
assert "Error running command" in res
|
||||
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_run_command_timeout(mock_popen: MagicMock) -> None:
|
||||
mock_process: MagicMock = MagicMock()
|
||||
mock_process.communicate.side_effect = [
|
||||
subprocess.TimeoutExpired(cmd="test", timeout=1),
|
||||
("stdout_after_kill", "stderr_after_kill")
|
||||
]
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
res: str = CodingTools().run_command("hang_cmd", timeout=1)
|
||||
assert "Command timed out after 1 seconds" in res
|
||||
assert "stdout_after_kill" in res
|
||||
mock_process.kill.assert_called_once()
|
||||
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_grep_search_success(mock_popen: MagicMock) -> None:
|
||||
mock_process: MagicMock = MagicMock()
|
||||
mock_process.communicate.return_value = ("match_line", "")
|
||||
mock_process.returncode = 0
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
res: str = CodingTools().grep_search("pattern", "/path")
|
||||
assert res == "match_line"
|
||||
|
||||
|
||||
@patch("subprocess.Popen")
|
||||
def test_grep_search_no_matches(mock_popen: MagicMock) -> None:
|
||||
mock_process: MagicMock = MagicMock()
|
||||
mock_process.communicate.return_value = ("", "")
|
||||
mock_process.returncode = 1
|
||||
mock_popen.return_value = mock_process
|
||||
|
||||
res: str = CodingTools().grep_search("pattern", "/path")
|
||||
assert "No matches found" in res
|
||||
|
||||
|
||||
def test_grep_search_error() -> None:
|
||||
with patch("subprocess.Popen", side_effect=ValueError("Invalid run")):
|
||||
res: str = CodingTools().grep_search("pattern")
|
||||
assert "Error during grep search" in res
|
||||
@@ -0,0 +1,250 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from core.dispatcher import AgentDispatcher
|
||||
from core.queue import WorkItem
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.tools.gitea_tools import GiteaTools
|
||||
from gitea.models import PullRequestModel, IssueModel, CommentModel
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
||||
async def test_dispatch_skips_issue_with_existing_pr() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
|
||||
|
||||
# Mock list_repo_pull_requests to return a PR that closes issue #42
|
||||
pr = PullRequestModel(
|
||||
number=101,
|
||||
title="fix: resolve bug",
|
||||
body="closes #42"
|
||||
)
|
||||
mock_client.list_repo_pull_requests.return_value = [pr]
|
||||
|
||||
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
|
||||
|
||||
work_item = WorkItem(
|
||||
repo_full_name="meeks/repo1",
|
||||
task_type="issue",
|
||||
task_number=42,
|
||||
task_info=IssueModel(number=42),
|
||||
priority=0
|
||||
)
|
||||
|
||||
results = await dispatcher.dispatch("meeks/repo1", [work_item])
|
||||
|
||||
assert len(results) == 1
|
||||
assert "SKIP: A pull request (PR #101) addressing issue #42 already exists" in results[0]
|
||||
mock_client.list_repo_pull_requests.assert_called_once_with("meeks", "repo1")
|
||||
|
||||
|
||||
@patch("core.dispatcher.CodingAgent")
|
||||
async def test_dispatch_processes_issue_without_pr(mock_agent_class: MagicMock) -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
|
||||
|
||||
# Mock list_repo_pull_requests to return PRs that don't address issue #42
|
||||
pr = PullRequestModel(
|
||||
number=101,
|
||||
title="feat: add something",
|
||||
body="closes #99"
|
||||
)
|
||||
mock_client.list_repo_pull_requests.return_value = [pr]
|
||||
mock_client.get_issue_comments.return_value = []
|
||||
|
||||
# Mock CodingAgent run_with_tools
|
||||
mock_agent_instance = MagicMock()
|
||||
mock_agent_instance.run_with_tools = AsyncMock(return_value="Issue resolved.")
|
||||
mock_agent_class.return_value = mock_agent_instance
|
||||
|
||||
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
|
||||
|
||||
work_item = WorkItem(
|
||||
repo_full_name="meeks/repo1",
|
||||
task_type="issue",
|
||||
task_number=42,
|
||||
task_info=IssueModel(number=42, title="test", body="test desc"),
|
||||
priority=0
|
||||
)
|
||||
|
||||
results = await dispatcher.dispatch("meeks/repo1", [work_item])
|
||||
|
||||
assert len(results) == 1
|
||||
assert results[0] == "Issue resolved."
|
||||
|
||||
|
||||
@patch("core.dispatcher.CodingAgent")
|
||||
async def test_build_pr_mission_injects_issue_context(mock_agent_class: MagicMock) -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
|
||||
|
||||
# Mock get_pull_request, get_pull_request_diff, etc.
|
||||
pr = PullRequestModel(
|
||||
number=101,
|
||||
title="fix: resolve bug",
|
||||
body="closes #42",
|
||||
head={"ref": "branch1"},
|
||||
base={"ref": "master"}
|
||||
)
|
||||
mock_client.get_pull_request.return_value = pr
|
||||
mock_client.get_pull_request_diff.return_value = "diff context"
|
||||
mock_client.get_pull_request_files.return_value = []
|
||||
mock_client.get_pull_request_comments.return_value = []
|
||||
|
||||
# Mock the connected issue and its comments
|
||||
issue = IssueModel(number=42, title="bug description")
|
||||
mock_client.get_issue.return_value = issue
|
||||
|
||||
comment = CommentModel(id=1, body="First comment")
|
||||
mock_client.get_issue_comments.return_value = [comment]
|
||||
|
||||
# Mock CodingAgent
|
||||
mock_agent_instance = MagicMock()
|
||||
mock_agent_instance.run_with_tools = AsyncMock(return_value="PR Reviewed.")
|
||||
mock_agent_class.return_value = mock_agent_instance
|
||||
|
||||
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
|
||||
|
||||
work_item = WorkItem(
|
||||
repo_full_name="meeks/repo1",
|
||||
task_type="pr",
|
||||
task_number=101,
|
||||
task_info=PullRequestModel(number=101, title="fix: resolve bug", body="closes #42"),
|
||||
priority=0
|
||||
)
|
||||
|
||||
# We will patch the dispatcher._build_pr_mission output validation
|
||||
mission = dispatcher._build_pr_mission(work_item)
|
||||
|
||||
assert "CONNECTED ISSUE CONTEXT" in mission
|
||||
assert "Connected Issue #42" in mission
|
||||
assert "bug description" in mission
|
||||
assert "First comment" in mission
|
||||
|
||||
mock_client.get_issue.assert_called_once_with("meeks", "repo1", 42)
|
||||
mock_client.get_issue_comments.assert_called_once_with("meeks", "repo1", 42)
|
||||
|
||||
|
||||
async def test_find_pr_for_issue_by_branch() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
|
||||
|
||||
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
|
||||
|
||||
# 1. Matches fix/issue-42-some-desc
|
||||
pr1 = PullRequestModel(number=102, head={"ref": "fix/issue-42-some-desc"})
|
||||
mock_client.list_repo_pull_requests.return_value = [pr1]
|
||||
assert dispatcher._find_pr_for_issue("meeks/repo1", 42) is not None
|
||||
|
||||
# 2. Matches fix/42
|
||||
pr2 = PullRequestModel(number=102, head={"ref": "fix/42"})
|
||||
mock_client.list_repo_pull_requests.return_value = [pr2]
|
||||
assert dispatcher._find_pr_for_issue("meeks/repo1", 42) is not None
|
||||
|
||||
# 3. Matches fix-42_desc
|
||||
pr3 = PullRequestModel(number=102, head={"ref": "fix-42_desc"})
|
||||
mock_client.list_repo_pull_requests.return_value = [pr3]
|
||||
assert dispatcher._find_pr_for_issue("meeks/repo1", 42) is not None
|
||||
|
||||
# 4. Does NOT match fix/142
|
||||
pr4 = PullRequestModel(number=102, head={"ref": "fix/142"})
|
||||
mock_client.list_repo_pull_requests.return_value = [pr4]
|
||||
assert dispatcher._find_pr_for_issue("meeks/repo1", 42) is None
|
||||
|
||||
# 5. Does NOT match fix/421
|
||||
pr5 = PullRequestModel(number=102, head={"ref": "fix/421"})
|
||||
mock_client.list_repo_pull_requests.return_value = [pr5]
|
||||
assert dispatcher._find_pr_for_issue("meeks/repo1", 42) is None
|
||||
|
||||
|
||||
|
||||
async def test_find_pr_for_issue_by_raw_mention() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
|
||||
|
||||
# PR body mentions #42
|
||||
pr = PullRequestModel(
|
||||
number=103,
|
||||
title="some fix",
|
||||
body="This is for #42 to fix the bug",
|
||||
head={"ref": "some-branch"}
|
||||
)
|
||||
mock_client.list_repo_pull_requests.return_value = [pr]
|
||||
|
||||
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
|
||||
res = dispatcher._find_pr_for_issue("meeks/repo1", 42)
|
||||
assert res is not None
|
||||
assert res.number == 103
|
||||
|
||||
|
||||
async def test_dispatch_skips_already_reviewed_pr() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_tools: MagicMock = MagicMock(spec=GiteaTools)
|
||||
|
||||
pr = PullRequestModel(
|
||||
number=104,
|
||||
title="already reviewed PR",
|
||||
body="closes #42"
|
||||
)
|
||||
mock_client.get_pull_request.return_value = pr
|
||||
mock_client.get_pull_request_diff.return_value = "diff"
|
||||
mock_client.get_pull_request_comments.return_value = [
|
||||
CommentModel(id=1, body="Reviewed by AI Agent: Looks good.")
|
||||
]
|
||||
|
||||
dispatcher = AgentDispatcher(client=mock_client, tools=mock_tools)
|
||||
|
||||
work_item = WorkItem(
|
||||
repo_full_name="meeks/repo1",
|
||||
task_type="pr",
|
||||
task_number=104,
|
||||
task_info=PullRequestModel(number=104, title="already reviewed PR", body="closes #42"),
|
||||
priority=0
|
||||
)
|
||||
|
||||
results = await dispatcher.dispatch("meeks/repo1", [work_item])
|
||||
assert len(results) == 1
|
||||
assert "SKIP: PR #104 has already been addressed by AI" in results[0]
|
||||
|
||||
|
||||
# ── _is_awaiting_reply tests ─────────────────────────────────────────────────
|
||||
|
||||
def _make_comment(login: str, body: str) -> CommentModel:
|
||||
from gitea.models import UserModel
|
||||
user = UserModel(login=login)
|
||||
return CommentModel(id=1, body=body, user=user)
|
||||
|
||||
|
||||
def test_is_awaiting_reply_no_comments() -> None:
|
||||
dispatcher = AgentDispatcher(client=MagicMock(), tools=MagicMock())
|
||||
assert dispatcher._is_awaiting_reply([]) is False
|
||||
|
||||
|
||||
def test_is_awaiting_reply_no_question() -> None:
|
||||
dispatcher = AgentDispatcher(client=MagicMock(), tools=MagicMock())
|
||||
comments = [_make_comment("meeks-ai", "I will fix this now.")]
|
||||
assert dispatcher._is_awaiting_reply(comments) is False
|
||||
|
||||
|
||||
def test_is_awaiting_reply_agent_question_no_human_reply() -> None:
|
||||
dispatcher = AgentDispatcher(client=MagicMock(), tools=MagicMock())
|
||||
body = "Should I use approach A or B?\n<!-- agent:awaiting-reply -->"
|
||||
comments = [_make_comment("meeks-ai", body)]
|
||||
assert dispatcher._is_awaiting_reply(comments) is True
|
||||
|
||||
|
||||
def test_is_awaiting_reply_agent_question_human_replied() -> None:
|
||||
dispatcher = AgentDispatcher(client=MagicMock(), tools=MagicMock())
|
||||
body = "Should I use approach A or B?\n<!-- agent:awaiting-reply -->"
|
||||
comments = [
|
||||
_make_comment("meeks-ai", body),
|
||||
_make_comment("michael", "Use approach A please."),
|
||||
]
|
||||
assert dispatcher._is_awaiting_reply(comments) is False
|
||||
|
||||
|
||||
def test_is_awaiting_reply_no_marker_not_detected() -> None:
|
||||
"""Agent asked a question but forgot the marker — should NOT be skipped."""
|
||||
dispatcher = AgentDispatcher(client=MagicMock(), tools=MagicMock())
|
||||
comments = [_make_comment("meeks-ai", "Should I use approach A or B?")]
|
||||
assert dispatcher._is_awaiting_reply(comments) is False
|
||||
@@ -0,0 +1,97 @@
|
||||
from unittest.mock import MagicMock
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.tools.file_tools import FileTools
|
||||
|
||||
|
||||
def test_get_file_content_string_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_file_content.return_value = "file content here"
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.get_file_content("owner", "repo", "path/to/file")
|
||||
assert res == "file content here"
|
||||
mock_client.get_file_content.assert_called_once_with("owner", "repo", "path/to/file")
|
||||
|
||||
|
||||
def test_get_file_content_list_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_file_content.return_value = ["line1", "line2"]
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.get_file_content("owner", "repo", "path/to/file")
|
||||
assert res == "line1\nline2"
|
||||
|
||||
|
||||
def test_get_file_content_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_file_content.side_effect = Exception("API Error")
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.get_file_content("owner", "repo", "path/to/file")
|
||||
assert "Error getting file content: API Error" in res
|
||||
|
||||
|
||||
def test_get_file_content_with_ref_string_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_file_content.return_value = "file content here"
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.get_file_content_with_ref("owner", "repo", "path/to/file", "main")
|
||||
assert res == "file content here"
|
||||
mock_client.get_file_content.assert_called_once_with("owner", "repo", "path/to/file", "main")
|
||||
|
||||
|
||||
def test_get_file_content_with_ref_list_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_file_content.return_value = ["line1", "line2"]
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.get_file_content_with_ref("owner", "repo", "path/to/file", "main")
|
||||
assert res == "line1\nline2"
|
||||
|
||||
|
||||
def test_get_file_content_with_ref_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_file_content.side_effect = Exception("API Error")
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.get_file_content_with_ref("owner", "repo", "path/to/file", "main")
|
||||
assert "Error getting file content: API Error" in res
|
||||
|
||||
|
||||
def test_commit_file_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.update_file.return_value = {}
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.commit_file("owner", "repo", "path/to/file", "msg", "content", "branch")
|
||||
assert "committed successfully" in res
|
||||
mock_client.update_file.assert_called_once_with("owner", "repo", "path/to/file", "msg", "content", "branch")
|
||||
|
||||
|
||||
def test_commit_file_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.update_file.side_effect = Exception("API Error")
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.commit_file("owner", "repo", "path/to/file", "msg", "content", "branch")
|
||||
assert "Error committing file: API Error" in res
|
||||
|
||||
|
||||
def test_update_file_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.update_file.return_value = {}
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.update_file("owner", "repo", "path/to/file", "msg", "content", "branch")
|
||||
assert "updated in" in res
|
||||
mock_client.update_file.assert_called_once_with("owner", "repo", "path/to/file", "msg", "content", "branch")
|
||||
|
||||
|
||||
def test_update_file_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.update_file.side_effect = Exception("API Error")
|
||||
|
||||
file_tools: FileTools = FileTools(mock_client)
|
||||
res: str = file_tools.update_file("owner", "repo", "path/to/file", "msg", "content", "branch")
|
||||
assert "Error updating file: API Error" in res
|
||||
@@ -0,0 +1,22 @@
|
||||
from unittest.mock import MagicMock
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.tools.git_tools import GitTools
|
||||
|
||||
|
||||
def test_create_branch_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.create_ref.return_value = {}
|
||||
|
||||
git_tools: GitTools = GitTools(mock_client)
|
||||
res: str = git_tools.create_branch("owner", "repo", "ref", "sha")
|
||||
assert res == "Branch 'ref' created successfully in owner/repo."
|
||||
mock_client.create_ref.assert_called_once_with("owner", "repo", "ref", "sha")
|
||||
|
||||
|
||||
def test_create_branch_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.create_ref.side_effect = Exception("API Error")
|
||||
|
||||
git_tools: GitTools = GitTools(mock_client)
|
||||
res: str = git_tools.create_branch("owner", "repo", "ref", "sha")
|
||||
assert res == "Error creating branch: API Error"
|
||||
@@ -0,0 +1,113 @@
|
||||
from unittest.mock import MagicMock
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.tools.gitea_tools import GiteaTools
|
||||
|
||||
|
||||
def test_gitea_tools_delegation() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
gitea_tools: GiteaTools = GiteaTools(mock_client)
|
||||
|
||||
# 1. get_issue
|
||||
gitea_tools.get_issue("owner", "repo", 1)
|
||||
mock_client.get_issue.assert_called_once_with("owner", "repo", 1)
|
||||
|
||||
# 2. get_pull_request
|
||||
gitea_tools.get_pull_request("owner", "repo", 2)
|
||||
mock_client.get_pull_request.assert_called_once_with("owner", "repo", 2)
|
||||
|
||||
# 3. close_issue
|
||||
gitea_tools.close_issue("owner", "repo", 3)
|
||||
mock_client.close_issue.assert_called_once_with("owner", "repo", 3)
|
||||
|
||||
# 4. close_pull_request
|
||||
gitea_tools.close_pull_request("owner", "repo", 4)
|
||||
mock_client.close_pull_request.assert_called_once_with("owner", "repo", 4)
|
||||
|
||||
# 5. get_issue_comments
|
||||
gitea_tools.get_issue_comments("owner", "repo", 5)
|
||||
mock_client.get_issue_comments.assert_called_once_with("owner", "repo", 5)
|
||||
|
||||
# 6. get_pull_request_comments
|
||||
gitea_tools.get_pull_request_comments("owner", "repo", 6)
|
||||
mock_client.get_pull_request_comments.assert_called_once_with("owner", "repo", 6)
|
||||
|
||||
# 7. list_assigned_issues
|
||||
mock_client.list_all_user_repos.return_value = []
|
||||
gitea_tools.list_assigned_issues()
|
||||
mock_client.list_all_user_repos.assert_called()
|
||||
|
||||
# 8. list_assigned_pull_requests
|
||||
gitea_tools.list_assigned_pull_requests()
|
||||
mock_client.list_all_user_repos.assert_called()
|
||||
|
||||
# 9. list_issues
|
||||
gitea_tools.list_issues("owner", "repo")
|
||||
mock_client.list_repo_issues.assert_called_once_with("owner", "repo", "open")
|
||||
|
||||
# 10. list_pull_requests
|
||||
gitea_tools.list_pull_requests("owner", "repo")
|
||||
mock_client.list_repo_pull_requests.assert_called_once_with("owner", "repo", "open")
|
||||
|
||||
# 11. get_file_content
|
||||
gitea_tools.get_file_content("owner", "repo", "path")
|
||||
mock_client.get_file_content.assert_called_with("owner", "repo", "path")
|
||||
|
||||
# 12. create_pull_request
|
||||
gitea_tools.create_pull_request("owner", "repo", "head", "base", "title", "desc")
|
||||
mock_client.create_pr_via_tea.assert_called_once_with("owner", "repo", "title", "desc", "head", "base")
|
||||
|
||||
# 13. create_issue
|
||||
gitea_tools.create_issue("owner", "repo", "title", "body")
|
||||
mock_client.create_issue.assert_called_once_with("owner", "repo", "title", "body", None, None)
|
||||
|
||||
# 14. create_branch
|
||||
gitea_tools.create_branch("owner", "repo", "branch", "sha")
|
||||
mock_client.create_ref.assert_called_once_with("owner", "repo", "branch", "sha")
|
||||
|
||||
# 15. commit_file
|
||||
gitea_tools.commit_file("owner", "repo", "path", "msg", "content", "branch")
|
||||
mock_client.update_file.assert_called_with("owner", "repo", "path", "msg", "content", "branch")
|
||||
|
||||
# 16. add_label_to_issue
|
||||
gitea_tools.add_label_to_issue("owner", "repo", 1, "bug")
|
||||
mock_client.add_label.assert_called_with("owner", "repo", 1, "bug")
|
||||
|
||||
# 17. add_label_to_pr
|
||||
gitea_tools.add_label_to_pr("owner", "repo", 1, "bug")
|
||||
mock_client.add_label_pr.assert_called_once_with("owner", "repo", 1, "bug")
|
||||
|
||||
# 18. add_comment_to_issue
|
||||
gitea_tools.add_comment_to_issue("owner", "repo", 1, "body")
|
||||
mock_client.add_comment.assert_called_with("owner", "repo", 1, "body")
|
||||
|
||||
# 19. get_pull_request_diff
|
||||
gitea_tools.get_pull_request_diff("owner", "repo", 1)
|
||||
mock_client.get_pull_request_diff.assert_called_once_with("owner", "repo", 1)
|
||||
|
||||
# 20. get_pull_request_patch
|
||||
gitea_tools.get_pull_request_patch("owner", "repo", 1)
|
||||
mock_client.get_pull_request_patch.assert_called_once_with("owner", "repo", 1)
|
||||
|
||||
# 21. approve_pull_request
|
||||
gitea_tools.approve_pull_request("owner", "repo", 1, "good")
|
||||
mock_client.approve_pr.assert_called_once_with("owner", "repo", 1, "good")
|
||||
|
||||
# 22. request_changes
|
||||
gitea_tools.request_changes("owner", "repo", 1, "bad")
|
||||
mock_client.request_changes_pr.assert_called_once_with("owner", "repo", 1, "bad")
|
||||
|
||||
# 23. add_comment
|
||||
gitea_tools.add_comment("owner", "repo", 1, "body")
|
||||
mock_client.add_comment.assert_called_with("owner", "repo", 1, "body")
|
||||
|
||||
# 24. add_label
|
||||
gitea_tools.add_label("owner", "repo", 1, "bug")
|
||||
mock_client.add_label.assert_called_with("owner", "repo", 1, "bug")
|
||||
|
||||
# 25. get_file_content_with_ref
|
||||
gitea_tools.get_file_content_with_ref("owner", "repo", "path", "ref")
|
||||
mock_client.get_file_content.assert_called_with("owner", "repo", "path", "ref")
|
||||
|
||||
# 26. update_file
|
||||
gitea_tools.update_file("owner", "repo", "path", "msg", "content", "branch")
|
||||
mock_client.update_file.assert_called_with("owner", "repo", "path", "msg", "content", "branch")
|
||||
@@ -0,0 +1,213 @@
|
||||
import json
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.models import IssueModel, CommentModel, LabelModel, RepositoryModel
|
||||
from gitea.tools.issue_tools import IssueTools
|
||||
|
||||
|
||||
def test_get_issue_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
issue: IssueModel = IssueModel(number=1, title="Test Issue", state="open")
|
||||
mock_client.get_issue.return_value = issue
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.get_issue("owner", "repo", 1)
|
||||
|
||||
data: dict[str, Any] = json.loads(res)
|
||||
assert data["number"] == 1
|
||||
assert data["title"] == "Test Issue"
|
||||
mock_client.get_issue.assert_called_once_with("owner", "repo", 1)
|
||||
|
||||
|
||||
def test_get_issue_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_issue.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.get_issue("owner", "repo", 1)
|
||||
assert "Error getting issue: API Error" in res
|
||||
|
||||
|
||||
def test_close_issue_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.close_issue.return_value = IssueModel(number=1, state="closed")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.close_issue("owner", "repo", 1)
|
||||
assert res == "Issue #1 closed successfully."
|
||||
mock_client.close_issue.assert_called_once_with("owner", "repo", 1)
|
||||
|
||||
|
||||
def test_close_issue_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.close_issue.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.close_issue("owner", "repo", 1)
|
||||
assert "Error closing issue: API Error" in res
|
||||
|
||||
|
||||
def test_get_issue_comments_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
comment: CommentModel = CommentModel(id=123, body="Comment body")
|
||||
mock_client.get_issue_comments.return_value = [comment]
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.get_issue_comments("owner", "repo", 1)
|
||||
data: list[dict[str, Any]] = json.loads(res)
|
||||
assert len(data) == 1
|
||||
assert data[0]["body"] == "Comment body"
|
||||
|
||||
|
||||
def test_get_issue_comments_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_issue_comments.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.get_issue_comments("owner", "repo", 1)
|
||||
assert "Error getting issue comments: API Error" in res
|
||||
|
||||
|
||||
def test_list_assigned_issues_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
repo: RepositoryModel = RepositoryModel(name="repo1", owner="owner1")
|
||||
issue: IssueModel = IssueModel(number=1, title="Test Issue")
|
||||
mock_client.list_all_user_repos.return_value = [repo]
|
||||
mock_client.list_assigned_issues.return_value = [issue]
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: list[dict[str, Any]] = issue_tools.list_assigned_issues()
|
||||
assert len(res) == 1
|
||||
assert res[0]["number"] == 1
|
||||
mock_client.list_all_user_repos.assert_called_once()
|
||||
mock_client.list_assigned_issues.assert_called_once_with("owner1", "repo1")
|
||||
|
||||
|
||||
def test_list_assigned_issues_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.list_all_user_repos.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: list[dict[str, Any]] = issue_tools.list_assigned_issues()
|
||||
assert res == []
|
||||
|
||||
|
||||
def test_list_issues_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
issue: IssueModel = IssueModel(number=1, title="Test Issue")
|
||||
mock_client.list_repo_issues.return_value = [issue]
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.list_issues("owner", "repo")
|
||||
assert "#1: Test Issue" in res
|
||||
|
||||
|
||||
def test_list_issues_empty() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.list_repo_issues.return_value = []
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.list_issues("owner", "repo")
|
||||
assert res == "No issues in owner/repo."
|
||||
|
||||
|
||||
def test_list_issues_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.list_repo_issues.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.list_issues("owner", "repo")
|
||||
assert "Error listing issues: API Error" in res
|
||||
|
||||
|
||||
def test_create_issue_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
issue: IssueModel = IssueModel(number=2)
|
||||
mock_client.create_issue.return_value = issue
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.create_issue("owner", "repo", "Title", "Body", ["label1"], ["assignee1"])
|
||||
assert res == "Issue #2 created successfully in owner/repo."
|
||||
mock_client.create_issue.assert_called_once_with("owner", "repo", "Title", "Body", ["label1"], ["assignee1"])
|
||||
|
||||
|
||||
def test_create_issue_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.create_issue.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.create_issue("owner", "repo", "Title", "Body")
|
||||
assert "Error creating issue: API Error" in res
|
||||
|
||||
|
||||
def test_add_label_to_issue_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_label.return_value = LabelModel(name="bug")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_label_to_issue("owner", "repo", 1, "bug")
|
||||
assert res == "Label 'bug' added to issue #1."
|
||||
|
||||
|
||||
def test_add_label_to_issue_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_label.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_label_to_issue("owner", "repo", 1, "bug")
|
||||
assert "Error adding label to issue #1: API Error" in res
|
||||
|
||||
|
||||
def test_add_comment_to_issue_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_comment.return_value = CommentModel(id=1)
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_comment_to_issue("owner", "repo", 1, "body")
|
||||
assert res == "Comment added to issue #1."
|
||||
|
||||
|
||||
def test_add_comment_to_issue_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_comment.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_comment_to_issue("owner", "repo", 1, "body")
|
||||
assert "Error adding comment to issue #1: API Error" in res
|
||||
|
||||
|
||||
def test_add_comment_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_comment.return_value = CommentModel(id=1)
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_comment("owner", "repo", 1, "body")
|
||||
assert res == "Comment added to #1."
|
||||
|
||||
|
||||
def test_add_comment_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_comment.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_comment("owner", "repo", 1, "body")
|
||||
assert "Error adding comment: API Error" in res
|
||||
|
||||
|
||||
def test_add_label_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_label.return_value = LabelModel(name="bug")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_label("owner", "repo", 1, "bug")
|
||||
assert res == "Label 'bug' added to #1."
|
||||
|
||||
|
||||
def test_add_label_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_label.side_effect = Exception("API Error")
|
||||
|
||||
issue_tools: IssueTools = IssueTools(mock_client)
|
||||
res: str = issue_tools.add_label("owner", "repo", 1, "bug")
|
||||
assert "Error adding label: API Error" in res
|
||||
@@ -0,0 +1,232 @@
|
||||
import json
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
from gitea.client import GiteaClient
|
||||
from gitea.models import PullRequestModel, CommentModel, RepositoryModel
|
||||
from gitea.tools.pr_tools import PRTools
|
||||
|
||||
|
||||
def test_get_pull_request_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
pr: PullRequestModel = PullRequestModel(number=1, title="Test PR", state="open")
|
||||
mock_client.get_pull_request.return_value = pr
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request("owner", "repo", 1)
|
||||
|
||||
data: dict[str, Any] = json.loads(res)
|
||||
assert data["number"] == 1
|
||||
assert data["title"] == "Test PR"
|
||||
mock_client.get_pull_request.assert_called_once_with("owner", "repo", 1)
|
||||
|
||||
|
||||
def test_get_pull_request_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_pull_request.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request("owner", "repo", 1)
|
||||
assert "Error getting pull request: API Error" in res
|
||||
|
||||
|
||||
def test_close_pull_request_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.close_pull_request.return_value = PullRequestModel(number=1, state="closed")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.close_pull_request("owner", "repo", 1)
|
||||
assert res == "Pull request #1 closed successfully."
|
||||
mock_client.close_pull_request.assert_called_once_with("owner", "repo", 1)
|
||||
|
||||
|
||||
def test_close_pull_request_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.close_pull_request.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.close_pull_request("owner", "repo", 1)
|
||||
assert "Error closing pull request: API Error" in res
|
||||
|
||||
|
||||
def test_get_pull_request_comments_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
comment: CommentModel = CommentModel(id=123, body="Comment body")
|
||||
mock_client.get_pull_request_comments.return_value = [comment]
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request_comments("owner", "repo", 1)
|
||||
data: list[dict[str, Any]] = json.loads(res)
|
||||
assert len(data) == 1
|
||||
assert data[0]["body"] == "Comment body"
|
||||
|
||||
|
||||
def test_get_pull_request_comments_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_pull_request_comments.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request_comments("owner", "repo", 1)
|
||||
assert "Error getting PR comments: API Error" in res
|
||||
|
||||
|
||||
def test_list_assigned_pull_requests_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
repo: RepositoryModel = RepositoryModel(name="repo1", owner="owner1")
|
||||
pr: PullRequestModel = PullRequestModel(number=1, title="Test PR")
|
||||
mock_client.list_all_user_repos.return_value = [repo]
|
||||
mock_client.list_assigned_pull_requests.return_value = [pr]
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: list[dict[str, Any]] = pr_tools.list_assigned_pull_requests()
|
||||
assert len(res) == 1
|
||||
assert res[0]["number"] == 1
|
||||
mock_client.list_all_user_repos.assert_called_once()
|
||||
mock_client.list_assigned_pull_requests.assert_called_once_with("owner1", "repo1")
|
||||
|
||||
|
||||
def test_list_assigned_pull_requests_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.list_all_user_repos.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: list[dict[str, Any]] = pr_tools.list_assigned_pull_requests()
|
||||
assert res == []
|
||||
|
||||
|
||||
def test_list_pull_requests_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
pr: PullRequestModel = PullRequestModel(number=1, title="Test PR")
|
||||
mock_client.list_repo_pull_requests.return_value = [pr]
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.list_pull_requests("owner", "repo")
|
||||
assert "#1: Test PR" in res
|
||||
|
||||
|
||||
def test_list_pull_requests_empty() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.list_repo_pull_requests.return_value = []
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.list_pull_requests("owner", "repo")
|
||||
assert res == "No PRs in owner/repo."
|
||||
|
||||
|
||||
def test_list_pull_requests_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.list_repo_pull_requests.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.list_pull_requests("owner", "repo")
|
||||
assert "Error listing PRs: API Error" in res
|
||||
|
||||
|
||||
def test_create_pull_request_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
pr: PullRequestModel = PullRequestModel(number=2, title="Title")
|
||||
mock_client.create_pr_via_tea.return_value = pr
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.create_pull_request("owner", "repo", "head", "base", "Title", "Desc")
|
||||
data: dict[str, Any] = json.loads(res)
|
||||
assert data["number"] == 2
|
||||
mock_client.create_pr_via_tea.assert_called_once_with("owner", "repo", "Title", "Desc", "head", "base")
|
||||
|
||||
|
||||
def test_create_pull_request_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.create_pr_via_tea.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.create_pull_request("owner", "repo", "head", "base", "Title")
|
||||
assert "Error creating PR: API Error" in res
|
||||
|
||||
|
||||
def test_add_label_to_pr_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_label_pr.return_value = {}
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.add_label_to_pr("owner", "repo", 1, "bug")
|
||||
assert res == "Label 'bug' added to PR #1."
|
||||
|
||||
|
||||
def test_add_label_to_pr_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.add_label_pr.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.add_label_to_pr("owner", "repo", 1, "bug")
|
||||
assert "Error adding label to PR #1: API Error" in res
|
||||
|
||||
|
||||
def test_get_pull_request_diff_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_pull_request_diff.return_value = "diff content"
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request_diff("owner", "repo", 1)
|
||||
assert res == "diff content"
|
||||
|
||||
|
||||
def test_get_pull_request_diff_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_pull_request_diff.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request_diff("owner", "repo", 1)
|
||||
assert "Error getting PR diff: API Error" in res
|
||||
|
||||
|
||||
def test_get_pull_request_patch_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_pull_request_patch.return_value = "patch content"
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request_patch("owner", "repo", 1)
|
||||
assert res == "patch content"
|
||||
|
||||
|
||||
def test_get_pull_request_patch_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.get_pull_request_patch.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.get_pull_request_patch("owner", "repo", 1)
|
||||
assert "Error getting PR patch: API Error" in res
|
||||
|
||||
|
||||
def test_approve_pull_request_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.approve_pr.return_value = {}
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.approve_pull_request("owner", "repo", 1, "good")
|
||||
assert res == "Approved PR #1."
|
||||
|
||||
|
||||
def test_approve_pull_request_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.approve_pr.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.approve_pull_request("owner", "repo", 1, "good")
|
||||
assert "Error approving PR: API Error" in res
|
||||
|
||||
|
||||
def test_request_changes_success() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.request_changes_pr.return_value = {}
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.request_changes("owner", "repo", 1, "bad")
|
||||
assert res == "Requested changes on PR #1."
|
||||
|
||||
|
||||
def test_request_changes_failure() -> None:
|
||||
mock_client: MagicMock = MagicMock(spec=GiteaClient)
|
||||
mock_client.request_changes_pr.side_effect = Exception("API Error")
|
||||
|
||||
pr_tools: PRTools = PRTools(mock_client)
|
||||
res: str = pr_tools.request_changes("owner", "repo", 1, "bad")
|
||||
assert "Error requesting changes: API Error" in res
|
||||
Reference in New Issue
Block a user