diff --git a/enterprise/integrations/github/github_manager.py b/enterprise/integrations/github/github_manager.py index de4f45d755..88d31b8850 100644 --- a/enterprise/integrations/github/github_manager.py +++ b/enterprise/integrations/github/github_manager.py @@ -200,22 +200,26 @@ class GithubManager(Manager): self._add_reaction(github_view, 'eyes', installation_token) await self.start_job(github_view) - async def send_message(self, message: Message, github_view: ResolverViewInterface): - installation_token = await self.token_manager.load_org_token( + async def send_message(self, message: str, github_view: ResolverViewInterface): + """Send a message to GitHub. + + Args: + message: The message content to send (plain text string) + github_view: The GitHub view object containing issue/PR/comment info + """ + installation_token = self.token_manager.load_org_token( github_view.installation_id ) if not installation_token: logger.warning('Missing installation token') return - outgoing_message = message.message - if isinstance(github_view, GithubInlinePRComment): with Github(auth=Auth.Token(installation_token)) as github_client: repo = github_client.get_repo(github_view.full_repo_name) pr = repo.get_pull(github_view.issue_number) pr.create_review_comment_reply( - comment_id=github_view.comment_id, body=outgoing_message + comment_id=github_view.comment_id, body=message ) elif ( @@ -226,7 +230,7 @@ class GithubManager(Manager): with Github(auth=Auth.Token(installation_token)) as github_client: repo = github_client.get_repo(github_view.full_repo_name) issue = repo.get_issue(number=github_view.issue_number) - issue.create_comment(outgoing_message) + issue.create_comment(message) else: logger.warning('Unsupported location') @@ -245,7 +249,7 @@ class GithubManager(Manager): ) try: - msg_info = None + msg_info: str = '' try: user_info = github_view.user_info @@ -361,15 +365,13 @@ class GithubManager(Manager): msg_info = get_session_expired_message(user_info.username) - msg = self.create_outgoing_message(msg_info) - await self.send_message(msg, github_view) + await self.send_message(msg_info, github_view) except Exception: logger.exception('[Github]: Error starting job') - msg = self.create_outgoing_message( - msg='Uh oh! There was an unexpected error starting the job :(' + await self.send_message( + 'Uh oh! There was an unexpected error starting the job :(', github_view ) - await self.send_message(msg, github_view) try: await self.data_collector.save_data(github_view) diff --git a/enterprise/integrations/gitlab/gitlab_manager.py b/enterprise/integrations/gitlab/gitlab_manager.py index ccc6da3621..9574e7ff44 100644 --- a/enterprise/integrations/gitlab/gitlab_manager.py +++ b/enterprise/integrations/gitlab/gitlab_manager.py @@ -121,12 +121,11 @@ class GitlabManager(Manager): # Check if the user has write access to the repository return has_write_access - async def send_message(self, message: Message, gitlab_view: ResolverViewInterface): - """ - Send a message to GitLab based on the view type. + async def send_message(self, message: str, gitlab_view: ResolverViewInterface): + """Send a message to GitLab based on the view type. Args: - message: The message to send + message: The message content to send (plain text string) gitlab_view: The GitLab view object containing issue/PR/comment info """ keycloak_user_id = gitlab_view.user_info.keycloak_user_id @@ -138,8 +137,6 @@ class GitlabManager(Manager): external_auth_id=keycloak_user_id ) - outgoing_message = message.message - if isinstance(gitlab_view, GitlabInlineMRComment) or isinstance( gitlab_view, GitlabMRComment ): @@ -147,7 +144,7 @@ class GitlabManager(Manager): gitlab_view.project_id, gitlab_view.issue_number, gitlab_view.discussion_id, - message.message, + message, ) elif isinstance(gitlab_view, GitlabIssueComment): @@ -155,14 +152,14 @@ class GitlabManager(Manager): gitlab_view.project_id, gitlab_view.issue_number, gitlab_view.discussion_id, - outgoing_message, + message, ) elif isinstance(gitlab_view, GitlabIssue): await gitlab_service.reply_to_issue( gitlab_view.project_id, gitlab_view.issue_number, None, # no discussion id, issue is tagged - outgoing_message, + message, ) else: logger.warning( @@ -262,12 +259,10 @@ class GitlabManager(Manager): msg_info = get_session_expired_message(user_info.username) # Send the acknowledgment message - msg = self.create_outgoing_message(msg_info) - await self.send_message(msg, gitlab_view) + await self.send_message(msg_info, gitlab_view) except Exception as e: logger.exception(f'[GitLab] Error starting job: {str(e)}') - msg = self.create_outgoing_message( - msg='Uh oh! There was an unexpected error starting the job :(' + await self.send_message( + 'Uh oh! There was an unexpected error starting the job :(', gitlab_view ) - await self.send_message(msg, gitlab_view) diff --git a/enterprise/integrations/jira/jira_manager.py b/enterprise/integrations/jira/jira_manager.py index dba45e8e80..05e255651c 100644 --- a/enterprise/integrations/jira/jira_manager.py +++ b/enterprise/integrations/jira/jira_manager.py @@ -341,17 +341,25 @@ class JiraManager(Manager): async def send_message( self, - message: Message, + message: str, issue_key: str, jira_cloud_id: str, svc_acc_email: str, svc_acc_api_key: str, ): - """Send a comment to a Jira issue.""" + """Send a comment to a Jira issue. + + Args: + message: The message content to send (plain text string) + issue_key: The Jira issue key (e.g., 'PROJ-123') + jira_cloud_id: The Jira Cloud ID + svc_acc_email: Service account email for authentication + svc_acc_api_key: Service account API key for authentication + """ url = ( f'{JIRA_CLOUD_API_URL}/{jira_cloud_id}/rest/api/2/issue/{issue_key}/comment' ) - data = {'body': message.message} + data = {'body': message} async with httpx.AsyncClient(verify=httpx_verify_option()) as client: response = await client.post( url, auth=(svc_acc_email, svc_acc_api_key), json=data @@ -366,7 +374,7 @@ class JiraManager(Manager): view.jira_workspace.svc_acc_api_key ) await self.send_message( - self.create_outgoing_message(msg=msg), + msg, issue_key=view.payload.issue_key, jira_cloud_id=view.jira_workspace.jira_cloud_id, svc_acc_email=view.jira_workspace.svc_acc_email, @@ -388,7 +396,7 @@ class JiraManager(Manager): try: api_key = self.token_manager.decrypt_text(workspace.svc_acc_api_key) await self.send_message( - self.create_outgoing_message(msg=error_msg), + error_msg, issue_key=payload.issue_key, jira_cloud_id=workspace.jira_cloud_id, svc_acc_email=workspace.svc_acc_email, diff --git a/enterprise/integrations/jira_dc/jira_dc_manager.py b/enterprise/integrations/jira_dc/jira_dc_manager.py index a658a791b3..acbd1dfead 100644 --- a/enterprise/integrations/jira_dc/jira_dc_manager.py +++ b/enterprise/integrations/jira_dc/jira_dc_manager.py @@ -418,7 +418,7 @@ class JiraDcManager(Manager): jira_dc_view.jira_dc_workspace.svc_acc_api_key ) await self.send_message( - self.create_outgoing_message(msg=msg_info), + msg_info, issue_key=jira_dc_view.job_context.issue_key, base_api_url=jira_dc_view.job_context.base_api_url, svc_acc_api_key=api_key, @@ -456,12 +456,19 @@ class JiraDcManager(Manager): return title, description async def send_message( - self, message: Message, issue_key: str, base_api_url: str, svc_acc_api_key: str + self, message: str, issue_key: str, base_api_url: str, svc_acc_api_key: str ): - """Send message/comment to Jira DC issue.""" + """Send message/comment to Jira DC issue. + + Args: + message: The message content to send (plain text string) + issue_key: The Jira issue key (e.g., 'PROJ-123') + base_api_url: The base API URL for the Jira DC instance + svc_acc_api_key: Service account API key for authentication + """ url = f'{base_api_url}/rest/api/2/issue/{issue_key}/comment' headers = {'Authorization': f'Bearer {svc_acc_api_key}'} - data = {'body': message.message} + data = {'body': message} async with httpx.AsyncClient(verify=httpx_verify_option()) as client: response = await client.post(url, headers=headers, json=data) response.raise_for_status() @@ -481,7 +488,7 @@ class JiraDcManager(Manager): try: api_key = self.token_manager.decrypt_text(workspace.svc_acc_api_key) await self.send_message( - self.create_outgoing_message(msg=error_msg), + error_msg, issue_key=job_context.issue_key, base_api_url=job_context.base_api_url, svc_acc_api_key=api_key, @@ -502,7 +509,7 @@ class JiraDcManager(Manager): ) await self.send_message( - self.create_outgoing_message(msg=comment_msg), + comment_msg, issue_key=jira_dc_view.job_context.issue_key, base_api_url=jira_dc_view.job_context.base_api_url, svc_acc_api_key=api_key, diff --git a/enterprise/integrations/linear/linear_manager.py b/enterprise/integrations/linear/linear_manager.py index 9ac7d8ed49..1e78c2bc97 100644 --- a/enterprise/integrations/linear/linear_manager.py +++ b/enterprise/integrations/linear/linear_manager.py @@ -408,7 +408,7 @@ class LinearManager(Manager): linear_view.linear_workspace.svc_acc_api_key ) await self.send_message( - self.create_outgoing_message(msg=msg_info), + msg_info, linear_view.job_context.issue_id, api_key, ) @@ -473,8 +473,14 @@ class LinearManager(Manager): return title, description - async def send_message(self, message: Message, issue_id: str, api_key: str): - """Send message/comment to Linear issue.""" + async def send_message(self, message: str, issue_id: str, api_key: str): + """Send message/comment to Linear issue. + + Args: + message: The message content to send (plain text string) + issue_id: The Linear issue ID to comment on + api_key: The Linear API key for authentication + """ query = """ mutation CommentCreate($input: CommentCreateInput!) { commentCreate(input: $input) { @@ -485,7 +491,7 @@ class LinearManager(Manager): } } """ - variables = {'input': {'issueId': issue_id, 'body': message.message}} + variables = {'input': {'issueId': issue_id, 'body': message}} return await self._query_api(query, variables, api_key) async def _send_error_comment( @@ -498,9 +504,7 @@ class LinearManager(Manager): try: api_key = self.token_manager.decrypt_text(workspace.svc_acc_api_key) - await self.send_message( - self.create_outgoing_message(msg=error_msg), issue_id, api_key - ) + await self.send_message(error_msg, issue_id, api_key) except Exception as e: logger.error(f'[Linear] Failed to send error comment: {str(e)}') @@ -517,7 +521,7 @@ class LinearManager(Manager): ) await self.send_message( - self.create_outgoing_message(msg=comment_msg), + comment_msg, linear_view.job_context.issue_id, api_key, ) diff --git a/enterprise/integrations/manager.py b/enterprise/integrations/manager.py index 88f59fff7e..f27804b1e8 100644 --- a/enterprise/integrations/manager.py +++ b/enterprise/integrations/manager.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from typing import Any from integrations.models import Message, SourceType @@ -12,14 +13,15 @@ class Manager(ABC): raise NotImplementedError @abstractmethod - def send_message(self, message: Message): - "Send message to integration from Openhands server" + def send_message(self, message: str, *args: Any, **kwargs: Any): + """Send message to integration from OpenHands server. + + Args: + message: The message content to send (plain text string). + """ raise NotImplementedError @abstractmethod def start_job(self): "Kick off a job with openhands agent" raise NotImplementedError - - def create_outgoing_message(self, msg: str | dict, ephemeral: bool = False): - return Message(source=SourceType.OPENHANDS, message=msg, ephemeral=ephemeral) diff --git a/enterprise/integrations/models.py b/enterprise/integrations/models.py index b963c9b18f..7df94ef3fd 100644 --- a/enterprise/integrations/models.py +++ b/enterprise/integrations/models.py @@ -1,4 +1,5 @@ from enum import Enum +from typing import Any from pydantic import BaseModel @@ -16,8 +17,16 @@ class SourceType(str, Enum): class Message(BaseModel): + """Message model for incoming webhook payloads from integrations. + + Note: This model is intended for INCOMING messages only. + For outgoing messages (e.g., sending comments to GitHub/GitLab), + pass strings directly to the send_message methods instead of + wrapping them in a Message object. + """ + source: SourceType - message: str | dict + message: dict[str, Any] ephemeral: bool = False diff --git a/enterprise/integrations/slack/slack_manager.py b/enterprise/integrations/slack/slack_manager.py index a72a251b54..9325637178 100644 --- a/enterprise/integrations/slack/slack_manager.py +++ b/enterprise/integrations/slack/slack_manager.py @@ -1,4 +1,5 @@ import re +from typing import Any import jwt from integrations.manager import Manager @@ -202,9 +203,7 @@ class SlackManager(Manager): msg = self.login_link.format(link) logger.info('slack_not_yet_authenticated') - await self.send_message( - self.create_outgoing_message(msg, ephemeral=True), slack_view - ) + await self.send_message(msg, slack_view, ephemeral=True) return if not await self.is_job_requested(message, slack_view): @@ -212,27 +211,40 @@ class SlackManager(Manager): await self.start_job(slack_view) - async def send_message(self, message: Message, slack_view: SlackViewInterface): + async def send_message( + self, + message: str | dict[str, Any], + slack_view: SlackViewInterface, + ephemeral: bool = False, + ): + """Send a message to Slack. + + Args: + message: The message content. Can be a string (for simple text) or + a dict with 'text' and 'blocks' keys (for structured messages). + slack_view: The Slack view object containing channel/thread info. + ephemeral: If True, send as an ephemeral message visible only to the user. + """ client = AsyncWebClient(token=slack_view.bot_access_token) - if message.ephemeral and isinstance(message.message, str): + if ephemeral and isinstance(message, str): await client.chat_postEphemeral( channel=slack_view.channel_id, - markdown_text=message.message, + markdown_text=message, user=slack_view.slack_user_id, thread_ts=slack_view.thread_ts, ) - elif message.ephemeral and isinstance(message.message, dict): + elif ephemeral and isinstance(message, dict): await client.chat_postEphemeral( channel=slack_view.channel_id, user=slack_view.slack_user_id, thread_ts=slack_view.thread_ts, - text=message.message['text'], - blocks=message.message['blocks'], + text=message['text'], + blocks=message['blocks'], ) else: await client.chat_postMessage( channel=slack_view.channel_id, - markdown_text=message.message, + markdown_text=message, thread_ts=slack_view.message_ts, ) @@ -279,10 +291,7 @@ class SlackManager(Manager): repos, slack_view.message_ts, slack_view.thread_ts ), } - await self.send_message( - self.create_outgoing_message(repo_selection_msg, ephemeral=True), - slack_view, - ) + await self.send_message(repo_selection_msg, slack_view, ephemeral=True) return False @@ -368,9 +377,10 @@ class SlackManager(Manager): except StartingConvoException as e: msg_info = str(e) - await self.send_message(self.create_outgoing_message(msg_info), slack_view) + await self.send_message(msg_info, slack_view) except Exception: logger.exception('[Slack]: Error starting job') - msg = 'Uh oh! There was an unexpected error starting the job :(' - await self.send_message(self.create_outgoing_message(msg), slack_view) + await self.send_message( + 'Uh oh! There was an unexpected error starting the job :(', slack_view + ) diff --git a/enterprise/server/conversation_callback_processor/github_callback_processor.py b/enterprise/server/conversation_callback_processor/github_callback_processor.py index f87118f1ea..41e42b530a 100644 --- a/enterprise/server/conversation_callback_processor/github_callback_processor.py +++ b/enterprise/server/conversation_callback_processor/github_callback_processor.py @@ -3,7 +3,6 @@ from datetime import datetime from integrations.github.github_manager import GithubManager from integrations.github.github_view import GithubViewType -from integrations.models import Message, SourceType from integrations.utils import ( extract_summary_from_conversation_manager, get_summary_instruction, @@ -35,16 +34,12 @@ class GithubCallbackProcessor(ConversationCallbackProcessor): send_summary_instruction: bool = True async def _send_message_to_github(self, message: str) -> None: - """ - Send a message to GitHub. + """Send a message to GitHub. Args: message: The message content to send to GitHub """ try: - # Create a message object for GitHub - message_obj = Message(source=SourceType.OPENHANDS, message=message) - # Get the token manager token_manager = TokenManager() @@ -53,8 +48,8 @@ class GithubCallbackProcessor(ConversationCallbackProcessor): github_manager = GithubManager(token_manager, GitHubDataCollector()) - # Send the message - await github_manager.send_message(message_obj, self.github_view) + # Send the message directly as a string + await github_manager.send_message(message, self.github_view) logger.info( f'[GitHub] Sent summary message to {self.github_view.full_repo_name}#{self.github_view.issue_number}' diff --git a/enterprise/server/conversation_callback_processor/gitlab_callback_processor.py b/enterprise/server/conversation_callback_processor/gitlab_callback_processor.py index d71c818fe1..acefaeccff 100644 --- a/enterprise/server/conversation_callback_processor/gitlab_callback_processor.py +++ b/enterprise/server/conversation_callback_processor/gitlab_callback_processor.py @@ -3,7 +3,6 @@ from datetime import datetime from integrations.gitlab.gitlab_manager import GitlabManager from integrations.gitlab.gitlab_view import GitlabViewType -from integrations.models import Message, SourceType from integrations.utils import ( extract_summary_from_conversation_manager, get_summary_instruction, @@ -28,8 +27,7 @@ gitlab_manager = GitlabManager(token_manager) class GitlabCallbackProcessor(ConversationCallbackProcessor): - """ - Processor for sending conversation summaries to GitLab. + """Processor for sending conversation summaries to GitLab. This processor is used to send summaries of conversations to GitLab when agent state changes occur. @@ -39,22 +37,18 @@ class GitlabCallbackProcessor(ConversationCallbackProcessor): send_summary_instruction: bool = True async def _send_message_to_gitlab(self, message: str) -> None: - """ - Send a message to GitLab. + """Send a message to GitLab. Args: message: The message content to send to GitLab """ try: - # Create a message object for GitHub - message_obj = Message(source=SourceType.OPENHANDS, message=message) - # Get the token manager token_manager = TokenManager() gitlab_manager = GitlabManager(token_manager) - # Send the message - await gitlab_manager.send_message(message_obj, self.gitlab_view) + # Send the message directly as a string + await gitlab_manager.send_message(message, self.gitlab_view) logger.info( f'[GitLab] Sent summary message to {self.gitlab_view.full_repo_name}#{self.gitlab_view.issue_number}' diff --git a/enterprise/server/conversation_callback_processor/jira_callback_processor.py b/enterprise/server/conversation_callback_processor/jira_callback_processor.py index 2e056b4ef6..987e04970a 100644 --- a/enterprise/server/conversation_callback_processor/jira_callback_processor.py +++ b/enterprise/server/conversation_callback_processor/jira_callback_processor.py @@ -37,8 +37,7 @@ class JiraCallbackProcessor(ConversationCallbackProcessor): workspace_name: str async def _send_comment_to_jira(self, message: str) -> None: - """ - Send a comment to Jira issue. + """Send a comment to Jira issue. Args: message: The message content to send to Jira @@ -59,8 +58,9 @@ class JiraCallbackProcessor(ConversationCallbackProcessor): # Decrypt API key api_key = jira_manager.token_manager.decrypt_text(workspace.svc_acc_api_key) + # Send comment directly as a string await jira_manager.send_message( - jira_manager.create_outgoing_message(msg=message), + message, issue_key=self.issue_key, jira_cloud_id=workspace.jira_cloud_id, svc_acc_email=workspace.svc_acc_email, diff --git a/enterprise/server/conversation_callback_processor/jira_dc_callback_processor.py b/enterprise/server/conversation_callback_processor/jira_dc_callback_processor.py index b5a7ba27fd..15c485a52a 100644 --- a/enterprise/server/conversation_callback_processor/jira_dc_callback_processor.py +++ b/enterprise/server/conversation_callback_processor/jira_dc_callback_processor.py @@ -37,8 +37,7 @@ class JiraDcCallbackProcessor(ConversationCallbackProcessor): base_api_url: str async def _send_comment_to_jira_dc(self, message: str) -> None: - """ - Send a comment to Jira DC issue. + """Send a comment to Jira DC issue. Args: message: The message content to send to Jira DC @@ -61,8 +60,9 @@ class JiraDcCallbackProcessor(ConversationCallbackProcessor): workspace.svc_acc_api_key ) + # Send comment directly as a string await jira_dc_manager.send_message( - jira_dc_manager.create_outgoing_message(msg=message), + message, issue_key=self.issue_key, base_api_url=self.base_api_url, svc_acc_api_key=api_key, diff --git a/enterprise/server/conversation_callback_processor/linear_callback_processor.py b/enterprise/server/conversation_callback_processor/linear_callback_processor.py index 09caf14fbd..c617ce32f4 100644 --- a/enterprise/server/conversation_callback_processor/linear_callback_processor.py +++ b/enterprise/server/conversation_callback_processor/linear_callback_processor.py @@ -36,8 +36,7 @@ class LinearCallbackProcessor(ConversationCallbackProcessor): workspace_name: str async def _send_comment_to_linear(self, message: str) -> None: - """ - Send a comment to Linear issue. + """Send a comment to Linear issue. Args: message: The message content to send to Linear @@ -60,9 +59,9 @@ class LinearCallbackProcessor(ConversationCallbackProcessor): workspace.svc_acc_api_key ) - # Send comment + # Send comment directly as a string await linear_manager.send_message( - linear_manager.create_outgoing_message(msg=message), + message, self.issue_id, api_key, ) diff --git a/enterprise/server/conversation_callback_processor/slack_callback_processor.py b/enterprise/server/conversation_callback_processor/slack_callback_processor.py index 1f922fd7bd..d29a39e9a6 100644 --- a/enterprise/server/conversation_callback_processor/slack_callback_processor.py +++ b/enterprise/server/conversation_callback_processor/slack_callback_processor.py @@ -26,8 +26,7 @@ slack_manager = SlackManager(token_manager) class SlackCallbackProcessor(ConversationCallbackProcessor): - """ - Processor for sending conversation summaries to Slack. + """Processor for sending conversation summaries to Slack. This processor is used to send summaries of conversations to Slack channels when agent state changes occur. @@ -41,14 +40,13 @@ class SlackCallbackProcessor(ConversationCallbackProcessor): last_user_msg_id: int | None = None async def _send_message_to_slack(self, message: str) -> None: - """ - Send a message to Slack using the conversation_manager's send_to_event_stream method. + """Send a message to Slack. Args: message: The message content to send to Slack """ try: - # Create a message object for Slack + # Create a message object for Slack view creation (incoming message format) message_obj = Message( source=SourceType.SLACK, message={ @@ -67,9 +65,8 @@ class SlackCallbackProcessor(ConversationCallbackProcessor): slack_view = SlackFactory.create_slack_view_from_payload( message_obj, slack_user, saas_user_auth ) - await slack_manager.send_message( - slack_manager.create_outgoing_message(message), slack_view - ) + # Send the message directly as a string + await slack_manager.send_message(message, slack_view) logger.info( f'[Slack] Sent summary message to channel {self.channel_id} ' diff --git a/enterprise/tests/unit/integrations/jira/test_jira_manager.py b/enterprise/tests/unit/integrations/jira/test_jira_manager.py index ec128a0481..8a4d5015b6 100644 --- a/enterprise/tests/unit/integrations/jira/test_jira_manager.py +++ b/enterprise/tests/unit/integrations/jira/test_jira_manager.py @@ -7,7 +7,6 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest from integrations.jira.jira_manager import JiraManager from integrations.jira.jira_payload import JiraEventType, JiraWebhookPayload -from integrations.models import Message, SourceType from openhands.server.types import ( LLMAuthenticationError, @@ -274,9 +273,8 @@ class TestSendMessage: return_value=mock_response ) - message = Message(source=SourceType.JIRA, message='Test message') result = await jira_manager.send_message( - message, + 'Test message', 'PROJ-123', 'cloud-123', 'service@test.com', diff --git a/enterprise/tests/unit/integrations/jira_dc/test_jira_dc_manager.py b/enterprise/tests/unit/integrations/jira_dc/test_jira_dc_manager.py index 9efbc55c04..e4c5025165 100644 --- a/enterprise/tests/unit/integrations/jira_dc/test_jira_dc_manager.py +++ b/enterprise/tests/unit/integrations/jira_dc/test_jira_dc_manager.py @@ -738,7 +738,7 @@ class TestStartJob: # Should send error message about re-login jira_dc_manager.send_message.assert_called_once() call_args = jira_dc_manager.send_message.call_args[0] - assert 'Please re-login' in call_args[0].message + assert 'Please re-login' in call_args[0] @pytest.mark.asyncio async def test_start_job_llm_authentication_error( @@ -763,7 +763,7 @@ class TestStartJob: # Should send error message about LLM API key jira_dc_manager.send_message.assert_called_once() call_args = jira_dc_manager.send_message.call_args[0] - assert 'valid LLM API key' in call_args[0].message + assert 'valid LLM API key' in call_args[0] @pytest.mark.asyncio async def test_start_job_session_expired_error( @@ -788,8 +788,8 @@ class TestStartJob: # Should send error message about session expired jira_dc_manager.send_message.assert_called_once() call_args = jira_dc_manager.send_message.call_args[0] - assert 'session has expired' in call_args[0].message - assert 'login again' in call_args[0].message + assert 'session has expired' in call_args[0] + assert 'login again' in call_args[0] @pytest.mark.asyncio async def test_start_job_unexpected_error( @@ -814,7 +814,7 @@ class TestStartJob: # Should send generic error message jira_dc_manager.send_message.assert_called_once() call_args = jira_dc_manager.send_message.call_args[0] - assert 'unexpected error' in call_args[0].message + assert 'unexpected error' in call_args[0] @pytest.mark.asyncio async def test_start_job_send_message_fails( @@ -943,9 +943,8 @@ class TestSendMessage: return_value=mock_response ) - message = Message(source=SourceType.JIRA_DC, message='Test message') result = await jira_dc_manager.send_message( - message, 'PROJ-123', 'https://jira.company.com', 'bearer_token' + 'Test message', 'PROJ-123', 'https://jira.company.com', 'bearer_token' ) assert result == {'id': 'comment_id'} @@ -1014,7 +1013,7 @@ class TestSendRepoSelectionComment: jira_dc_manager.send_message.assert_called_once() call_args = jira_dc_manager.send_message.call_args[0] - assert 'which repository to work with' in call_args[0].message + assert 'which repository to work with' in call_args[0] @pytest.mark.asyncio async def test_send_repo_selection_comment_send_fails( diff --git a/enterprise/tests/unit/integrations/linear/test_linear_manager.py b/enterprise/tests/unit/integrations/linear/test_linear_manager.py index ac48fe8014..c10fd7c507 100644 --- a/enterprise/tests/unit/integrations/linear/test_linear_manager.py +++ b/enterprise/tests/unit/integrations/linear/test_linear_manager.py @@ -802,7 +802,7 @@ class TestStartJob: # Should send error message about re-login linear_manager.send_message.assert_called_once() call_args = linear_manager.send_message.call_args[0] - assert 'Please re-login' in call_args[0].message + assert 'Please re-login' in call_args[0] @pytest.mark.asyncio async def test_start_job_llm_authentication_error( @@ -828,7 +828,7 @@ class TestStartJob: # Should send error message about LLM API key linear_manager.send_message.assert_called_once() call_args = linear_manager.send_message.call_args[0] - assert 'valid LLM API key' in call_args[0].message + assert 'valid LLM API key' in call_args[0] @pytest.mark.asyncio async def test_start_job_session_expired_error( @@ -854,8 +854,8 @@ class TestStartJob: # Should send error message about session expired linear_manager.send_message.assert_called_once() call_args = linear_manager.send_message.call_args[0] - assert 'session has expired' in call_args[0].message - assert 'login again' in call_args[0].message + assert 'session has expired' in call_args[0] + assert 'login again' in call_args[0] @pytest.mark.asyncio async def test_start_job_unexpected_error( @@ -881,7 +881,7 @@ class TestStartJob: # Should send generic error message linear_manager.send_message.assert_called_once() call_args = linear_manager.send_message.call_args[0] - assert 'unexpected error' in call_args[0].message + assert 'unexpected error' in call_args[0] @pytest.mark.asyncio async def test_start_job_send_message_fails( @@ -1049,8 +1049,9 @@ class TestSendMessage: linear_manager._query_api = AsyncMock(return_value=mock_response) - message = Message(source=SourceType.LINEAR, message='Test message') - result = await linear_manager.send_message(message, 'issue_id', 'api_key') + result = await linear_manager.send_message( + 'Test message', 'issue_id', 'api_key' + ) assert result == mock_response linear_manager._query_api.assert_called_once() @@ -1114,7 +1115,7 @@ class TestSendRepoSelectionComment: linear_manager.send_message.assert_called_once() call_args = linear_manager.send_message.call_args[0] - assert 'which repository to work with' in call_args[0].message + assert 'which repository to work with' in call_args[0] @pytest.mark.asyncio async def test_send_repo_selection_comment_send_fails( diff --git a/enterprise/tests/unit/server/conversation_callback_processor/test_jira_callback_processor.py b/enterprise/tests/unit/server/conversation_callback_processor/test_jira_callback_processor.py index e1515e2285..ea635334e1 100644 --- a/enterprise/tests/unit/server/conversation_callback_processor/test_jira_callback_processor.py +++ b/enterprise/tests/unit/server/conversation_callback_processor/test_jira_callback_processor.py @@ -34,7 +34,6 @@ async def test_send_comment_to_jira_success(mock_jira_manager, processor): ) mock_jira_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_jira_manager.send_message = AsyncMock() - mock_jira_manager.create_outgoing_message.return_value = MagicMock() # Action await processor._send_comment_to_jira('This is a summary.') @@ -130,7 +129,6 @@ async def test_call_sends_summary_to_jira( return_value=mock_workspace ) mock_jira_manager.send_message = AsyncMock() - mock_jira_manager.create_outgoing_message.return_value = MagicMock() with patch( 'server.conversation_callback_processor.jira_callback_processor.asyncio.create_task' @@ -200,7 +198,6 @@ async def test_send_comment_to_jira_api_error(mock_jira_manager, processor): ) mock_jira_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_jira_manager.send_message = AsyncMock(side_effect=Exception('API Error')) - mock_jira_manager.create_outgoing_message.return_value = MagicMock() # Action - should not raise exception, but handle it gracefully await processor._send_comment_to_jira('This is a summary.') @@ -328,18 +325,15 @@ async def test_send_comment_to_jira_message_construction(mock_jira_manager, proc ) mock_jira_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_jira_manager.send_message = AsyncMock() - mock_outgoing_message = MagicMock() - mock_jira_manager.create_outgoing_message.return_value = mock_outgoing_message test_message = 'This is a test summary message.' # Action await processor._send_comment_to_jira(test_message) - # Assert - mock_jira_manager.create_outgoing_message.assert_called_once_with(msg=test_message) + # Assert - send_message now receives the string directly mock_jira_manager.send_message.assert_called_once_with( - mock_outgoing_message, + test_message, issue_key='TEST-123', jira_cloud_id='cloud123', svc_acc_email='service@test.com', @@ -386,7 +380,6 @@ async def test_call_creates_background_task_for_sending( return_value=mock_workspace ) mock_jira_manager.send_message = AsyncMock() - mock_jira_manager.create_outgoing_message.return_value = MagicMock() with patch( 'server.conversation_callback_processor.jira_callback_processor.asyncio.create_task' diff --git a/enterprise/tests/unit/server/conversation_callback_processor/test_jira_dc_callback_processor.py b/enterprise/tests/unit/server/conversation_callback_processor/test_jira_dc_callback_processor.py index ac18a519ef..6e5e490c68 100644 --- a/enterprise/tests/unit/server/conversation_callback_processor/test_jira_dc_callback_processor.py +++ b/enterprise/tests/unit/server/conversation_callback_processor/test_jira_dc_callback_processor.py @@ -32,7 +32,6 @@ async def test_send_comment_to_jira_dc_success(mock_jira_dc_manager, processor): ) mock_jira_dc_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_jira_dc_manager.send_message = AsyncMock() - mock_jira_dc_manager.create_outgoing_message.return_value = MagicMock() # Action await processor._send_comment_to_jira_dc('This is a summary.') @@ -125,7 +124,6 @@ async def test_call_sends_summary_to_jira_dc( return_value=mock_workspace ) mock_jira_dc_manager.send_message = AsyncMock() - mock_jira_dc_manager.create_outgoing_message.return_value = MagicMock() with patch( 'server.conversation_callback_processor.jira_dc_callback_processor.asyncio.create_task' @@ -200,7 +198,6 @@ async def test_send_comment_to_jira_dc_api_error(mock_jira_dc_manager, processor ) mock_jira_dc_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_jira_dc_manager.send_message = AsyncMock(side_effect=Exception('API Error')) - mock_jira_dc_manager.create_outgoing_message.return_value = MagicMock() # Action - should not raise exception, but handle it gracefully await processor._send_comment_to_jira_dc('This is a summary.') @@ -328,20 +325,15 @@ async def test_send_comment_to_jira_dc_message_construction( ) mock_jira_dc_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_jira_dc_manager.send_message = AsyncMock() - mock_outgoing_message = MagicMock() - mock_jira_dc_manager.create_outgoing_message.return_value = mock_outgoing_message test_message = 'This is a test summary message.' # Action await processor._send_comment_to_jira_dc(test_message) - # Assert - mock_jira_dc_manager.create_outgoing_message.assert_called_once_with( - msg=test_message - ) + # Assert - send_message now receives the string directly mock_jira_dc_manager.send_message.assert_called_once_with( - mock_outgoing_message, + test_message, issue_key='TEST-123', base_api_url='https://test-jira-dc.company.com', svc_acc_api_key='decrypted_key', @@ -384,7 +376,6 @@ async def test_call_creates_background_task_for_sending( return_value=mock_workspace ) mock_jira_dc_manager.send_message = AsyncMock() - mock_jira_dc_manager.create_outgoing_message.return_value = MagicMock() with patch( 'server.conversation_callback_processor.jira_dc_callback_processor.asyncio.create_task' diff --git a/enterprise/tests/unit/server/conversation_callback_processor/test_linear_callback_processor.py b/enterprise/tests/unit/server/conversation_callback_processor/test_linear_callback_processor.py index be5f90bc7f..513b15aa07 100644 --- a/enterprise/tests/unit/server/conversation_callback_processor/test_linear_callback_processor.py +++ b/enterprise/tests/unit/server/conversation_callback_processor/test_linear_callback_processor.py @@ -32,7 +32,6 @@ async def test_send_comment_to_linear_success(mock_linear_manager, processor): ) mock_linear_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_linear_manager.send_message = AsyncMock() - mock_linear_manager.create_outgoing_message.return_value = MagicMock() # Action await processor._send_comment_to_linear('This is a summary.') @@ -125,7 +124,6 @@ async def test_call_sends_summary_to_linear( return_value=mock_workspace ) mock_linear_manager.send_message = AsyncMock() - mock_linear_manager.create_outgoing_message.return_value = MagicMock() with patch( 'server.conversation_callback_processor.linear_callback_processor.asyncio.create_task' @@ -200,7 +198,6 @@ async def test_send_comment_to_linear_api_error(mock_linear_manager, processor): ) mock_linear_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_linear_manager.send_message = AsyncMock(side_effect=Exception('API Error')) - mock_linear_manager.create_outgoing_message.return_value = MagicMock() # Action - should not raise exception, but handle it gracefully await processor._send_comment_to_linear('This is a summary.') @@ -328,20 +325,15 @@ async def test_send_comment_to_linear_message_construction( ) mock_linear_manager.token_manager.decrypt_text.return_value = 'decrypted_key' mock_linear_manager.send_message = AsyncMock() - mock_outgoing_message = MagicMock() - mock_linear_manager.create_outgoing_message.return_value = mock_outgoing_message test_message = 'This is a test summary message.' # Action await processor._send_comment_to_linear(test_message) - # Assert - mock_linear_manager.create_outgoing_message.assert_called_once_with( - msg=test_message - ) + # Assert - send_message now receives the string directly mock_linear_manager.send_message.assert_called_once_with( - mock_outgoing_message, + test_message, 'TEST-123', # issue_id 'decrypted_key', # api_key ) @@ -383,7 +375,6 @@ async def test_call_creates_background_task_for_sending( return_value=mock_workspace ) mock_linear_manager.send_message = AsyncMock() - mock_linear_manager.create_outgoing_message.return_value = MagicMock() with patch( 'server.conversation_callback_processor.linear_callback_processor.asyncio.create_task' diff --git a/enterprise/tests/unit/test_slack_callback_processor.py b/enterprise/tests/unit/test_slack_callback_processor.py index 7e0e4b0636..79475d4f2a 100644 --- a/enterprise/tests/unit/test_slack_callback_processor.py +++ b/enterprise/tests/unit/test_slack_callback_processor.py @@ -234,7 +234,6 @@ class TestSlackCallbackProcessor: mock_slack_user = MagicMock() mock_saas_user_auth = MagicMock() mock_slack_view = MagicMock() - mock_outgoing_message = MagicMock() # Mock the authenticate_user method on slack_manager mock_slack_manager.authenticate_user = AsyncMock( @@ -248,9 +247,6 @@ class TestSlackCallbackProcessor: mock_slack_factory.create_slack_view_from_payload.return_value = ( mock_slack_view ) - mock_slack_manager.create_outgoing_message.return_value = ( - mock_outgoing_message - ) mock_slack_manager.send_message = AsyncMock() # Call the method @@ -283,12 +279,9 @@ class TestSlackCallbackProcessor: ) assert message_call.message['team_id'] == slack_callback_processor.team_id - # Verify the slack manager methods were called correctly - mock_slack_manager.create_outgoing_message.assert_called_once_with( - 'Test message' - ) + # Verify send_message was called with the string directly mock_slack_manager.send_message.assert_called_once_with( - mock_outgoing_message, mock_slack_view + 'Test message', mock_slack_view ) @patch('server.conversation_callback_processor.slack_callback_processor.logger')