134 lines
4.9 KiB
Python
134 lines
4.9 KiB
Python
import json
|
|
from pathlib import Path
|
|
import pytest
|
|
from unittest.mock import MagicMock, AsyncMock, patch
|
|
|
|
from core.orchestrator import AgentOrchestrator
|
|
from gitea.client import GiteaClient
|
|
from gitea.tools.gitea_tools import GiteaTools
|
|
from gitea.models import IssueModel, PullRequestModel, RepositoryModel
|
|
|
|
pytestmark = pytest.mark.anyio
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_state_file(tmp_path: Path) -> Path:
|
|
"""Fixture to mock state file path."""
|
|
state_file = tmp_path / "agent_state.json"
|
|
return state_file
|
|
|
|
|
|
@patch("core.orchestrator.AgentOrchestrator._get_state_file_path")
|
|
@patch("core.orchestrator.AgentDispatcher")
|
|
@patch("core.orchestrator.WorkspaceManager")
|
|
async def test_poll_and_dispatch_no_notifications(
|
|
mock_workspace_class: MagicMock,
|
|
mock_dispatcher_class: MagicMock,
|
|
mock_get_path: MagicMock,
|
|
temp_state_file: Path
|
|
) -> None:
|
|
mock_get_path.return_value = temp_state_file
|
|
mock_client = MagicMock(spec=GiteaClient)
|
|
mock_tools = MagicMock(spec=GiteaTools)
|
|
|
|
# Return no notifications
|
|
mock_client.list_unread_notifications.return_value = []
|
|
|
|
orchestrator = AgentOrchestrator(mock_client, mock_tools)
|
|
await orchestrator.poll_and_dispatch()
|
|
|
|
mock_client.list_unread_notifications.assert_called_once_with(since=None)
|
|
assert not temp_state_file.exists()
|
|
|
|
|
|
@patch("core.orchestrator.AgentOrchestrator._get_state_file_path")
|
|
@patch("core.orchestrator.AgentDispatcher")
|
|
@patch("core.orchestrator.WorkspaceManager")
|
|
async def test_poll_and_dispatch_with_notifications(
|
|
mock_workspace_class: MagicMock,
|
|
mock_dispatcher_class: MagicMock,
|
|
mock_get_path: MagicMock,
|
|
temp_state_file: Path
|
|
) -> None:
|
|
mock_get_path.return_value = temp_state_file
|
|
mock_client = MagicMock(spec=GiteaClient)
|
|
mock_tools = MagicMock(spec=GiteaTools)
|
|
|
|
# Set up mock Gitea notifications
|
|
notifications = [
|
|
{
|
|
"id": 101,
|
|
"updated_at": "2026-06-30T10:00:00Z",
|
|
"subject": {
|
|
"type": "issue",
|
|
"url": "https://gitea.meeks.freeddns.org/api/v1/repos/meeks/repo1/issues/42"
|
|
},
|
|
"repository": {
|
|
"name": "repo1",
|
|
"full_name": "meeks/repo1",
|
|
"owner": {"login": "meeks"}
|
|
}
|
|
},
|
|
{
|
|
"id": 102,
|
|
"updated_at": "2026-06-30T11:00:00Z",
|
|
"subject": {
|
|
"type": "pull",
|
|
"url": "https://gitea.meeks.freeddns.org/api/v1/repos/meeks/repo1/pulls/10"
|
|
},
|
|
"repository": {
|
|
"name": "repo1",
|
|
"full_name": "meeks/repo1",
|
|
"owner": {"login": "meeks"}
|
|
}
|
|
}
|
|
]
|
|
mock_client.list_unread_notifications.return_value = notifications
|
|
|
|
# Mock issue and PR get methods
|
|
issue_model = IssueModel(number=42, title="Bug issue", repository=RepositoryModel(name="repo1", full_name="meeks/repo1"))
|
|
pr_model = PullRequestModel(number=10, title="Fix PR", repository=RepositoryModel(name="repo1", full_name="meeks/repo1"))
|
|
mock_client.get_issue.return_value = issue_model
|
|
mock_client.get_pull_request.return_value = pr_model
|
|
|
|
# Mock dispatcher and workspace path
|
|
mock_dispatcher_instance = MagicMock()
|
|
mock_dispatcher_instance.dispatch = AsyncMock(return_value=["Issue comment posted", "PR verified"])
|
|
mock_dispatcher_class.return_value = mock_dispatcher_instance
|
|
|
|
mock_workspace_instance = MagicMock()
|
|
mock_workspace_instance.get_repo_path.return_value.exists.return_value = True
|
|
mock_workspace_class.return_value = mock_workspace_instance
|
|
|
|
# Create orchestrator and poll
|
|
orchestrator = AgentOrchestrator(mock_client, mock_tools)
|
|
await orchestrator.poll_and_dispatch()
|
|
|
|
# Assert notifications were checked with None (first execution)
|
|
mock_client.list_unread_notifications.assert_called_once_with(since=None)
|
|
|
|
# Assert issue and PR details were fetched
|
|
mock_client.get_issue.assert_called_once_with("meeks", "repo1", 42)
|
|
mock_client.get_pull_request.assert_called_once_with("meeks", "repo1", 10)
|
|
|
|
# Assert work was processed by dispatcher
|
|
mock_dispatcher_instance.dispatch.assert_called_once()
|
|
work_items = mock_dispatcher_instance.dispatch.call_args[0][1]
|
|
assert len(work_items) == 2
|
|
assert work_items[0].task_number == 42
|
|
assert work_items[0].notification_id == 101
|
|
assert work_items[1].task_number == 10
|
|
assert work_items[1].notification_id == 102
|
|
|
|
# Assert notifications were marked as read
|
|
mock_client.mark_notification_as_read.assert_any_call(101)
|
|
mock_client.mark_notification_as_read.assert_any_call(102)
|
|
assert mock_client.mark_notification_as_read.call_count == 2
|
|
|
|
# Assert checkpoint date was persisted
|
|
assert temp_state_file.exists()
|
|
with open(temp_state_file, "r") as f:
|
|
state = json.load(f)
|
|
# Checkpoint should match latest updated_at
|
|
assert state["last_checked"] == "2026-06-30T11:00:00Z"
|