feat: implement gitea_comment and discord_reply tools
This commit is contained in:
parent
b2ad726fc4
commit
e8983d8534
@ -1,5 +1,32 @@
|
||||
"""Discord message tool. Phase 2 implementation."""
|
||||
"""Discord 채널/스레드 메시지 전송 도구."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent.utils.discord_client import get_discord_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def discord_reply(message: str) -> dict:
|
||||
raise NotImplementedError("Phase 2")
|
||||
def discord_reply(message: str) -> dict[str, Any]:
|
||||
if not message.strip():
|
||||
return {"success": False, "error": "빈 메시지는 전송할 수 없습니다."}
|
||||
|
||||
channel_id = os.environ.get("DISCORD_CHANNEL_ID", "")
|
||||
if not channel_id:
|
||||
return {"success": False, "error": "DISCORD_CHANNEL_ID가 설정되지 않았습니다."}
|
||||
|
||||
client = get_discord_client()
|
||||
|
||||
try:
|
||||
result = asyncio.run(
|
||||
client.send_message(channel_id=channel_id, content=message)
|
||||
)
|
||||
logger.info("Sent Discord message to channel %s", channel_id)
|
||||
return {"success": True, "message_id": result.get("id")}
|
||||
except Exception as e:
|
||||
logger.exception("Failed to send Discord message")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@ -1,5 +1,40 @@
|
||||
"""Gitea issue/PR comment tool. Phase 2 implementation."""
|
||||
"""Gitea 이슈/PR 코멘트 작성 도구."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from agent.utils.gitea_client import get_gitea_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def gitea_comment(message: str, issue_number: int) -> dict:
|
||||
raise NotImplementedError("Phase 2")
|
||||
def _get_repo_info() -> tuple[str, str]:
|
||||
owner = os.environ.get("DEFAULT_REPO_OWNER", "quant")
|
||||
repo = os.environ.get("DEFAULT_REPO_NAME", "galaxis-po")
|
||||
return owner, repo
|
||||
|
||||
|
||||
def gitea_comment(message: str, issue_number: int) -> dict[str, Any]:
|
||||
if not issue_number or issue_number <= 0:
|
||||
return {"success": False, "error": "유효한 issue_number가 필요합니다."}
|
||||
|
||||
if not message.strip():
|
||||
return {"success": False, "error": "빈 메시지는 작성할 수 없습니다."}
|
||||
|
||||
owner, repo = _get_repo_info()
|
||||
client = get_gitea_client()
|
||||
|
||||
try:
|
||||
result = asyncio.run(
|
||||
client.create_issue_comment(
|
||||
owner=owner, repo=repo, issue_number=issue_number, body=message
|
||||
)
|
||||
)
|
||||
logger.info("Posted comment on %s/%s#%d", owner, repo, issue_number)
|
||||
return {"success": True, "comment_id": result.get("id")}
|
||||
except Exception as e:
|
||||
logger.exception("Failed to post comment on %s/%s#%d", owner, repo, issue_number)
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@ -1,9 +1,45 @@
|
||||
"""Discord bot integration. Phase 2 implementation."""
|
||||
"""Discord REST API 클라이언트."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DISCORD_API_BASE = "https://discord.com/api/v10"
|
||||
|
||||
|
||||
class DiscordClient:
|
||||
async def send_message(self, channel_id: str, content: str) -> dict:
|
||||
raise NotImplementedError("Phase 2")
|
||||
def __init__(self, token: str):
|
||||
self.token = token
|
||||
self._client = httpx.AsyncClient(
|
||||
base_url=DISCORD_API_BASE,
|
||||
headers={"Authorization": f"Bot {self.token}"},
|
||||
timeout=15.0,
|
||||
)
|
||||
|
||||
async def send_thread_reply(self, channel_id, thread_id, content) -> dict:
|
||||
raise NotImplementedError("Phase 2")
|
||||
async def send_message(self, channel_id: str, content: str) -> dict:
|
||||
resp = await self._client.post(
|
||||
f"/channels/{channel_id}/messages",
|
||||
json={"content": content},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
async def send_thread_reply(self, channel_id: str, thread_id: str, content: str) -> dict:
|
||||
return await self.send_message(channel_id=thread_id, content=content)
|
||||
|
||||
async def close(self):
|
||||
await self._client.aclose()
|
||||
|
||||
|
||||
_client: DiscordClient | None = None
|
||||
|
||||
|
||||
def get_discord_client() -> DiscordClient:
|
||||
global _client
|
||||
if _client is None:
|
||||
_client = DiscordClient(token=os.environ.get("DISCORD_TOKEN", ""))
|
||||
return _client
|
||||
|
||||
48
tests/test_discord_reply.py
Normal file
48
tests/test_discord_reply.py
Normal file
@ -0,0 +1,48 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
||||
def test_discord_reply_success():
|
||||
mock_client = MagicMock()
|
||||
mock_client.send_message = AsyncMock(
|
||||
return_value={"id": "123456", "content": "test message"}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"agent.tools.discord_reply.get_discord_client", return_value=mock_client
|
||||
), patch.dict("os.environ", {"DISCORD_CHANNEL_ID": "999"}):
|
||||
from agent.tools.discord_reply import discord_reply
|
||||
result = discord_reply(message="test message")
|
||||
assert result["success"] is True
|
||||
mock_client.send_message.assert_called_once()
|
||||
|
||||
|
||||
def test_discord_reply_empty_message():
|
||||
from agent.tools.discord_reply import discord_reply
|
||||
result = discord_reply(message="")
|
||||
assert result["success"] is False
|
||||
|
||||
|
||||
def test_discord_reply_no_channel_configured():
|
||||
with patch.dict("os.environ", {"DISCORD_CHANNEL_ID": ""}, clear=False):
|
||||
from agent.tools.discord_reply import discord_reply
|
||||
result = discord_reply(message="test")
|
||||
assert result["success"] is False
|
||||
assert "DISCORD" in result.get("error", "")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_discord_client_send_message():
|
||||
import httpx
|
||||
mock_resp = MagicMock(spec=httpx.Response)
|
||||
mock_resp.status_code = 200
|
||||
mock_resp.json.return_value = {"id": "msg123", "content": "hello"}
|
||||
mock_resp.raise_for_status = MagicMock()
|
||||
|
||||
from agent.utils.discord_client import DiscordClient
|
||||
client = DiscordClient(token="test-token")
|
||||
client._client.post = AsyncMock(return_value=mock_resp)
|
||||
|
||||
result = await client.send_message(channel_id="999", content="hello")
|
||||
assert result["id"] == "msg123"
|
||||
client._client.post.assert_called_once()
|
||||
48
tests/test_gitea_comment.py
Normal file
48
tests/test_gitea_comment.py
Normal file
@ -0,0 +1,48 @@
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch, MagicMock
|
||||
|
||||
|
||||
def test_gitea_comment_success():
|
||||
mock_client = MagicMock()
|
||||
mock_client.create_issue_comment = AsyncMock(
|
||||
return_value={"id": 42, "body": "test comment"}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"agent.tools.gitea_comment.get_gitea_client", return_value=mock_client
|
||||
), patch(
|
||||
"agent.tools.gitea_comment._get_repo_info",
|
||||
return_value=("quant", "galaxis-po"),
|
||||
):
|
||||
from agent.tools.gitea_comment import gitea_comment
|
||||
result = gitea_comment(message="test comment", issue_number=1)
|
||||
assert result["success"] is True
|
||||
assert result["comment_id"] == 42
|
||||
|
||||
|
||||
def test_gitea_comment_missing_issue_number():
|
||||
from agent.tools.gitea_comment import gitea_comment
|
||||
result = gitea_comment(message="test", issue_number=0)
|
||||
assert result["success"] is False
|
||||
assert "issue_number" in result["error"]
|
||||
|
||||
|
||||
def test_gitea_comment_api_error():
|
||||
import httpx
|
||||
mock_client = MagicMock()
|
||||
mock_client.create_issue_comment = AsyncMock(
|
||||
side_effect=httpx.HTTPStatusError(
|
||||
"404", request=MagicMock(), response=MagicMock(status_code=404)
|
||||
)
|
||||
)
|
||||
|
||||
with patch(
|
||||
"agent.tools.gitea_comment.get_gitea_client", return_value=mock_client
|
||||
), patch(
|
||||
"agent.tools.gitea_comment._get_repo_info",
|
||||
return_value=("quant", "galaxis-po"),
|
||||
):
|
||||
from agent.tools.gitea_comment import gitea_comment
|
||||
result = gitea_comment(message="test", issue_number=999)
|
||||
assert result["success"] is False
|
||||
assert "error" in result
|
||||
Loading…
x
Reference in New Issue
Block a user