"""Tests for the Jira Comments mixin."""
from unittest.mock import Mock
import pytest
from mcp_atlassian.jira.comments import CommentsMixin
class TestCommentsMixin:
"""Tests for the CommentsMixin class."""
@pytest.fixture
def comments_mixin(self, jira_client):
"""Create a CommentsMixin instance with mocked dependencies."""
mixin = CommentsMixin(config=jira_client.config)
mixin.jira = jira_client.jira
# Set up a mock preprocessor with markdown_to_jira method
mixin.preprocessor = Mock()
mixin.preprocessor.markdown_to_jira = Mock(
return_value="*This* is _Jira_ formatted"
)
# Mock the clean_text method
mixin._clean_text = Mock(side_effect=lambda x: x)
return mixin
def test_get_issue_comments_basic(self, comments_mixin):
"""Test get_issue_comments with basic data."""
# Setup mock response
comments_mixin.jira.issue_get_comments.return_value = {
"comments": [
{
"id": "10001",
"body": "This is a comment",
"created": "2024-01-01T10:00:00.000+0000",
"updated": "2024-01-01T11:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
]
}
# Call the method
result = comments_mixin.get_issue_comments("TEST-123")
# Verify
comments_mixin.jira.issue_get_comments.assert_called_once_with("TEST-123")
assert len(result) == 1
assert result[0]["id"] == "10001"
assert result[0]["body"] == "This is a comment"
assert result[0]["created"] == "2024-01-01 10:00:00+00:00" # Parsed date
assert result[0]["author"] == "John Doe"
def test_get_issue_comments_with_limit(self, comments_mixin):
"""Test get_issue_comments with limit parameter."""
# Setup mock response with multiple comments
comments_mixin.jira.issue_get_comments.return_value = {
"comments": [
{
"id": "10001",
"body": "First comment",
"created": "2024-01-01T10:00:00.000+0000",
"author": {"displayName": "John Doe"},
},
{
"id": "10002",
"body": "Second comment",
"created": "2024-01-02T10:00:00.000+0000",
"author": {"displayName": "Jane Smith"},
},
{
"id": "10003",
"body": "Third comment",
"created": "2024-01-03T10:00:00.000+0000",
"author": {"displayName": "Bob Johnson"},
},
]
}
# Call the method with limit=2
result = comments_mixin.get_issue_comments("TEST-123", limit=2)
# Verify
comments_mixin.jira.issue_get_comments.assert_called_once_with("TEST-123")
assert len(result) == 2 # Only 2 comments should be returned
assert result[0]["id"] == "10001"
assert result[1]["id"] == "10002"
# Third comment shouldn't be included due to limit
def test_get_issue_comments_with_missing_fields(self, comments_mixin):
"""Test get_issue_comments with missing fields in the response."""
# Setup mock response with missing fields
comments_mixin.jira.issue_get_comments.return_value = {
"comments": [
{
"id": "10001",
# Missing body field
"created": "2024-01-01T10:00:00.000+0000",
# Missing author field
},
{
# Missing id field
"body": "Second comment",
# Missing created field
"author": {}, # Empty author object
},
{
"id": "10003",
"body": "Third comment",
"created": "2024-01-03T10:00:00.000+0000",
"author": {"name": "user123"}, # Using name instead of displayName
},
]
}
# Call the method
result = comments_mixin.get_issue_comments("TEST-123")
# Verify
assert len(result) == 3
assert result[0]["id"] == "10001"
assert result[0]["body"] == "" # Should default to empty string
assert result[0]["author"] == "Unknown" # Should default to Unknown
assert (
"id" not in result[1] or not result[1]["id"]
) # Should be missing or empty
assert result[1]["author"] == "Unknown" # Should default to Unknown
assert (
result[2]["author"] == "Unknown"
) # Should use Unknown when only name is available
def test_get_issue_comments_with_empty_response(self, comments_mixin):
"""Test get_issue_comments with an empty response."""
# Setup mock response with no comments
comments_mixin.jira.issue_get_comments.return_value = {"comments": []}
# Call the method
result = comments_mixin.get_issue_comments("TEST-123")
# Verify
assert len(result) == 0 # Should return an empty list
def test_get_issue_comments_with_error(self, comments_mixin):
"""Test get_issue_comments with an error response."""
# Setup mock to raise exception
comments_mixin.jira.issue_get_comments.side_effect = Exception("API Error")
# Verify it raises the wrapped exception
with pytest.raises(Exception, match="Error getting comments"):
comments_mixin.get_issue_comments("TEST-123")
def test_add_comment_basic(self, comments_mixin):
"""Test add_comment with basic data (Cloud → ADF conversion)."""
# Setup mock response
comments_mixin.jira.issue_add_comment.return_value = {
"id": "10001",
"body": "This is a comment",
"created": "2024-01-01T10:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
# Call the method
result = comments_mixin.add_comment("TEST-123", "Test comment")
# On Cloud, _markdown_to_jira returns ADF dict (not wiki markup)
call_args = comments_mixin.jira.issue_add_comment.call_args
adf_arg = call_args[0][1]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
assert adf_arg["type"] == "doc"
# preprocessor.markdown_to_jira should NOT be called on Cloud
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["id"] == "10001"
assert result["body"] == "This is a comment"
assert result["created"] == "2024-01-01 10:00:00+00:00"
assert result["author"] == "John Doe"
def test_add_comment_with_markdown_conversion(self, comments_mixin):
"""Test add_comment with markdown conversion (Cloud → ADF)."""
# Setup mock response
comments_mixin.jira.issue_add_comment.return_value = {
"id": "10001",
"body": "Heading and content",
"created": "2024-01-01T10:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
markdown_comment = "# Heading 1\n\nThis is **bold** text."
# Call the method
result = comments_mixin.add_comment("TEST-123", markdown_comment)
# On Cloud, should produce ADF, not call preprocessor
call_args = comments_mixin.jira.issue_add_comment.call_args
adf_arg = call_args[0][1]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["body"] == "Heading and content"
def test_add_comment_with_empty_comment(self, comments_mixin):
"""Test add_comment with an empty comment (Cloud → minimal ADF)."""
# Setup mock response
comments_mixin.jira.issue_add_comment.return_value = {
"id": "10001",
"body": "",
"created": "2024-01-01T10:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
# Call the method with empty comment
result = comments_mixin.add_comment("TEST-123", "")
# On Cloud, empty string produces a minimal ADF dict
call_args = comments_mixin.jira.issue_add_comment.call_args
adf_arg = call_args[0][1]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["body"] == ""
def test_add_comment_with_restricted_visibility(self, comments_mixin):
"""Test add_comment with visibility set (Cloud → ADF)."""
# Setup mock response
comments_mixin.jira.issue_add_comment.return_value = {
"id": "10001",
"body": "This is a comment",
"created": "2024-01-01T10:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
# Call the method
result = comments_mixin.add_comment(
"TEST-123", "Test comment", {"type": "group", "value": "restricted"}
)
# Verify ADF conversion on Cloud
call_args = comments_mixin.jira.issue_add_comment.call_args
adf_arg = call_args[0][1]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
visibility_arg = call_args[0][2]
assert visibility_arg == {"type": "group", "value": "restricted"}
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["id"] == "10001"
assert result["body"] == "This is a comment"
assert result["created"] == "2024-01-01 10:00:00+00:00"
assert result["author"] == "John Doe"
def test_add_comment_with_error(self, comments_mixin):
"""Test add_comment with an error response."""
# Setup mock to raise exception
comments_mixin.jira.issue_add_comment.side_effect = Exception("API Error")
# Verify it raises the wrapped exception
with pytest.raises(Exception, match="Error adding comment"):
comments_mixin.add_comment("TEST-123", "Test comment")
def test_edit_comment_basic(self, comments_mixin):
"""Test edit_comment with basic data (Cloud → ADF)."""
# Setup mock response
comments_mixin.jira.issue_edit_comment.return_value = {
"id": "10001",
"body": "This is an updated comment",
"updated": "2024-01-01T12:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
# Call the method
result = comments_mixin.edit_comment("TEST-123", "10001", "Updated comment")
# On Cloud, should produce ADF dict
call_args = comments_mixin.jira.issue_edit_comment.call_args
adf_arg = call_args[0][2]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["id"] == "10001"
assert result["body"] == "This is an updated comment"
assert result["updated"] == "2024-01-01 12:00:00+00:00"
assert result["author"] == "John Doe"
def test_edit_comment_with_markdown_conversion(self, comments_mixin):
"""Test edit_comment with markdown conversion (Cloud → ADF)."""
# Setup mock response
comments_mixin.jira.issue_edit_comment.return_value = {
"id": "10001",
"body": "Updated content",
"updated": "2024-01-01T12:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
markdown_comment = "# Updated Heading\n\nThis is **updated** text."
# Call the method
result = comments_mixin.edit_comment("TEST-123", "10001", markdown_comment)
# On Cloud, should produce ADF dict
call_args = comments_mixin.jira.issue_edit_comment.call_args
adf_arg = call_args[0][2]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["body"] == "Updated content"
def test_edit_comment_with_empty_comment(self, comments_mixin):
"""Test edit_comment with an empty comment (Cloud → minimal ADF)."""
# Setup mock response
comments_mixin.jira.issue_edit_comment.return_value = {
"id": "10001",
"body": "",
"updated": "2024-01-01T12:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
# Call the method with empty comment
result = comments_mixin.edit_comment("TEST-123", "10001", "")
# On Cloud, empty string produces a minimal ADF dict
call_args = comments_mixin.jira.issue_edit_comment.call_args
adf_arg = call_args[0][2]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["body"] == ""
def test_edit_comment_with_restricted_visibility(self, comments_mixin):
"""Test edit_comment with visibility set (Cloud → ADF)."""
# Setup mock response
comments_mixin.jira.issue_edit_comment.return_value = {
"id": "10001",
"body": "This is an updated comment",
"updated": "2024-01-01T12:00:00.000+0000",
"author": {"displayName": "John Doe"},
}
# Call the method
result = comments_mixin.edit_comment(
"TEST-123",
"10001",
"Updated comment",
{"type": "group", "value": "restricted"},
)
# Verify ADF conversion on Cloud
call_args = comments_mixin.jira.issue_edit_comment.call_args
adf_arg = call_args[0][2]
assert isinstance(adf_arg, dict)
assert adf_arg["version"] == 1
visibility_arg = call_args[0][3]
assert visibility_arg == {"type": "group", "value": "restricted"}
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
assert result["id"] == "10001"
assert result["body"] == "This is an updated comment"
assert result["updated"] == "2024-01-01 12:00:00+00:00"
assert result["author"] == "John Doe"
def test_edit_comment_with_error(self, comments_mixin):
"""Test edit_comment with an error response."""
# Setup mock to raise exception
comments_mixin.jira.issue_edit_comment.side_effect = Exception("API Error")
# Verify it raises the wrapped exception
with pytest.raises(Exception, match="Error editing comment"):
comments_mixin.edit_comment("TEST-123", "10001", "Updated comment")
def test_markdown_to_jira_cloud(self, comments_mixin):
"""Test _markdown_to_jira returns ADF dict on Cloud."""
result = comments_mixin._markdown_to_jira("Markdown text")
# Cloud config → ADF dict
assert isinstance(result, dict)
assert result["version"] == 1
assert result["type"] == "doc"
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
def test_markdown_to_jira_cloud_empty(self, comments_mixin):
"""Test _markdown_to_jira with empty text on Cloud returns ADF."""
result = comments_mixin._markdown_to_jira("")
assert isinstance(result, dict)
assert result["version"] == 1
comments_mixin.preprocessor.markdown_to_jira.assert_not_called()
# --- Server/DC path tests ---
@pytest.fixture
def server_comments_mixin(self, jira_config_factory):
"""Create a CommentsMixin configured for Server/DC."""
config = jira_config_factory(url="https://jira.example.com")
mixin = CommentsMixin(config=config)
mixin.jira = Mock()
mixin.preprocessor = Mock()
mixin.preprocessor.markdown_to_jira = Mock(return_value="h1. Hello")
mixin._clean_text = Mock(side_effect=lambda x: x)
return mixin
def test_markdown_to_jira_server_returns_string(self, server_comments_mixin):
"""Server/DC path returns wiki markup string."""
result = server_comments_mixin._markdown_to_jira("# Hello")
assert isinstance(result, str)
assert result == "h1. Hello"
server_comments_mixin.preprocessor.markdown_to_jira.assert_called_once()
def test_add_comment_server_sends_string(self, server_comments_mixin):
"""Server/DC add_comment sends wiki markup string to API."""
server_comments_mixin.jira.issue_add_comment.return_value = {
"id": "10001",
"body": "h1. Hello",
"created": "2024-01-01T10:00:00.000+0000",
"author": {"displayName": "Test User"},
}
result = server_comments_mixin.add_comment("TEST-123", "# Hello")
call_args = server_comments_mixin.jira.issue_add_comment.call_args
comment_arg = call_args[0][1]
assert isinstance(comment_arg, str)
assert result["body"] == "h1. Hello"
def test_edit_comment_server_sends_string(self, server_comments_mixin):
"""Server/DC edit_comment sends wiki markup string to API."""
server_comments_mixin.jira.issue_edit_comment.return_value = {
"id": "10001",
"body": "h1. Updated",
"updated": "2024-01-01T11:00:00.000+0000",
"author": {"displayName": "Test User"},
}
server_comments_mixin.preprocessor.markdown_to_jira.return_value = "h1. Updated"
result = server_comments_mixin.edit_comment("TEST-123", "10001", "# Updated")
call_args = server_comments_mixin.jira.issue_edit_comment.call_args
comment_arg = call_args[0][2]
assert isinstance(comment_arg, str)
assert result["body"] == "h1. Updated"