We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/sooperset/mcp-atlassian'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""Tests for Jira development information operations."""
from unittest.mock import MagicMock, Mock
import pytest
from requests.exceptions import HTTPError
from mcp_atlassian.jira.development import DevelopmentMixin
class TestDevelopmentMixin:
@pytest.fixture
def development_mixin(self, mock_config, mock_atlassian_jira):
mixin = DevelopmentMixin(config=mock_config)
mixin.jira = mock_atlassian_jira
return mixin
@pytest.fixture
def mock_dev_status_response(self):
"""Mock response from dev-status API with PRs."""
return {
"detail": [
{
"_instance": {
"name": "Bitbucket Server",
"baseUrl": "https://stash.example.com",
},
"pullRequests": [
{
"id": "123",
"name": "[TEST-123] Fix bug in login",
"status": "MERGED",
"url": "https://stash.example.com/projects/PROJ/repos/app/pull-requests/123",
"source": {
"branch": "bugfix/TEST-123-login-fix",
"repository": {
"name": "app",
"url": "https://stash.example.com/projects/PROJ/repos/app",
},
},
"destination": {"branch": "develop"},
"author": {"name": "John Doe"},
"reviewers": [{"name": "Jane Smith"}],
"lastUpdate": "2024-01-15T10:30:00.000Z",
}
],
"branches": [
{
"name": "bugfix/TEST-123-login-fix",
"url": "https://stash.example.com/projects/PROJ/repos/app/browse?at=bugfix/TEST-123-login-fix",
"createPullRequestUrl": "https://stash.example.com/projects/PROJ/repos/app/pull-requests?create&sourceBranch=bugfix/TEST-123-login-fix",
}
],
"repositories": [],
}
]
}
@pytest.fixture
def mock_dev_status_with_commits(self):
"""Mock response with commits in repositories."""
return {
"detail": [
{
"_instance": {
"name": "Bitbucket Server",
"baseUrl": "https://stash.example.com",
},
"pullRequests": [],
"branches": [],
"repositories": [
{
"name": "app",
"url": "https://stash.example.com/projects/PROJ/repos/app",
"avatar": "https://stash.example.com/avatar.png",
"commits": [
{
"id": "abc123def456",
"displayId": "abc123d",
"message": "TEST-123: Fix login bug",
"author": {"name": "John Doe"},
"authorTimestamp": "2024-01-15T09:00:00.000Z",
"url": "https://stash.example.com/projects/PROJ/repos/app/commits/abc123def456",
}
],
}
],
}
]
}
def test_get_issue_development_info_success(
self, development_mixin, mock_dev_status_response
):
"""Test successful retrieval of development info."""
# Mock get_issue to return issue with ID
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
# Mock the session.get call
mock_response = MagicMock()
mock_response.json.return_value = mock_dev_status_response
mock_response.raise_for_status = MagicMock()
development_mixin.jira._session.get.return_value = mock_response
result = development_mixin.get_issue_development_info(
"TEST-123", application_type="stash", data_type="pullrequest"
)
assert result["issue_key"] == "TEST-123"
assert len(result["pullRequests"]) == 1
assert result["pullRequests"][0]["name"] == "[TEST-123] Fix bug in login"
assert result["pullRequests"][0]["status"] == "MERGED"
assert result["pullRequests"][0]["author"] == "John Doe"
assert len(result["branches"]) == 1
assert result["branches"][0]["name"] == "bugfix/TEST-123-login-fix"
def test_get_issue_development_info_with_commits(
self, development_mixin, mock_dev_status_with_commits
):
"""Test retrieval of development info with commits."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
mock_response = MagicMock()
mock_response.json.return_value = mock_dev_status_with_commits
mock_response.raise_for_status = MagicMock()
development_mixin.jira._session.get.return_value = mock_response
result = development_mixin.get_issue_development_info(
"TEST-123", application_type="stash", data_type="repository"
)
assert result["issue_key"] == "TEST-123"
assert len(result["commits"]) == 1
assert result["commits"][0]["message"] == "TEST-123: Fix login bug"
assert result["commits"][0]["author"] == "John Doe"
assert len(result["repositories"]) == 1
assert result["repositories"][0]["name"] == "app"
def test_get_issue_development_info_empty_response(self, development_mixin):
"""Test handling of empty development info response."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
mock_response = MagicMock()
mock_response.json.return_value = {"detail": []}
mock_response.raise_for_status = MagicMock()
development_mixin.jira._session.get.return_value = mock_response
result = development_mixin.get_issue_development_info(
"TEST-123", application_type="stash", data_type="pullrequest"
)
assert result["issue_key"] == "TEST-123"
assert result["pullRequests"] == []
assert result["branches"] == []
assert result["commits"] == []
def test_get_issue_development_info_issue_not_found(self, development_mixin):
"""Test error when issue is not found."""
development_mixin.jira.get_issue.return_value = {"key": "TEST-123"}
with pytest.raises(Exception, match="Could not get issue ID"):
development_mixin.get_issue_development_info(
"TEST-123", application_type="stash", data_type="pullrequest"
)
def test_get_issue_development_info_api_error(self, development_mixin):
"""Test handling of API errors."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
mock_response = MagicMock()
mock_response.raise_for_status.side_effect = HTTPError(
response=Mock(status_code=500)
)
development_mixin.jira._session.get.return_value = mock_response
with pytest.raises(Exception, match="Error retrieving development info"):
development_mixin.get_issue_development_info(
"TEST-123", application_type="stash", data_type="pullrequest"
)
def test_get_issue_development_info_auto_discovery(self, development_mixin):
"""Test auto-discovery of application types when not specified."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
# Return empty for all combinations
mock_response = MagicMock()
mock_response.json.return_value = {"detail": []}
mock_response.raise_for_status = MagicMock()
development_mixin.jira._session.get.return_value = mock_response
result = development_mixin.get_issue_development_info("TEST-123")
# Should have tried multiple app types
assert development_mixin.jira._session.get.call_count > 1
assert result["issue_key"] == "TEST-123"
def test_get_issues_development_info_success(
self, development_mixin, mock_dev_status_response
):
"""Test batch retrieval of development info for multiple issues."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
mock_response = MagicMock()
mock_response.json.return_value = mock_dev_status_response
mock_response.raise_for_status = MagicMock()
development_mixin.jira._session.get.return_value = mock_response
results = development_mixin.get_issues_development_info(
["TEST-123", "TEST-456"], application_type="stash", data_type="pullrequest"
)
assert len(results) == 2
# Each result has its own issue_key from the input list
assert results[0]["issue_key"] == "TEST-123"
assert results[1]["issue_key"] == "TEST-456"
# Both should have PRs from the mock response
assert len(results[0]["pullRequests"]) == 1
assert len(results[1]["pullRequests"]) == 1
def test_get_issues_development_info_partial_failure(self, development_mixin):
"""Test batch retrieval with some failures."""
# First call succeeds, second fails
development_mixin.jira.get_issue.side_effect = [
{"id": "12345", "key": "TEST-123"},
Exception("Issue not found"),
]
mock_response = MagicMock()
mock_response.json.return_value = {"detail": []}
mock_response.raise_for_status = MagicMock()
development_mixin.jira._session.get.return_value = mock_response
results = development_mixin.get_issues_development_info(
["TEST-123", "TEST-456"], application_type="stash", data_type="pullrequest"
)
assert len(results) == 2
assert results[0]["issue_key"] == "TEST-123"
assert "error" in results[1]
assert results[1]["pullRequests"] == []
def test_get_issue_development_info_plugin_not_found(self, development_mixin):
"""Test 404 response returns descriptive error message without raising."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
mock_response = MagicMock()
mock_response.status_code = 404
development_mixin.jira._session.get.return_value = mock_response
result = development_mixin.get_issue_development_info(
"TEST-123", application_type="stash", data_type="pullrequest"
)
assert "error" in result
assert "dev-status plugin" in result["error"]
assert result["pullRequests"] == []
assert result["branches"] == []
assert result["commits"] == []
def test_get_issue_development_info_access_denied(self, development_mixin):
"""Test 403 response returns descriptive error message without raising."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
mock_response = MagicMock()
mock_response.status_code = 403
development_mixin.jira._session.get.return_value = mock_response
result = development_mixin.get_issue_development_info(
"TEST-123", application_type="stash", data_type="pullrequest"
)
assert "error" in result
assert "Access denied" in result["error"]
assert result["pullRequests"] == []
assert result["branches"] == []
assert result["commits"] == []
def test_get_issue_development_info_auto_discovery_404(self, development_mixin):
"""Test auto-discovery stops early on 404 and propagates error."""
development_mixin.jira.get_issue.return_value = {
"id": "12345",
"key": "TEST-123",
}
mock_response = MagicMock()
mock_response.status_code = 404
development_mixin.jira._session.get.return_value = mock_response
result = development_mixin.get_issue_development_info("TEST-123")
assert "error" in result
assert "dev-status plugin" in result["error"]
# Should stop after first 404, not exhaust all 12 combinations
assert development_mixin.jira._session.get.call_count == 1
def test_parse_development_info_with_reviewers(self, development_mixin):
"""Test parsing of PR reviewers."""
response = {
"detail": [
{
"_instance": {"name": "Bitbucket", "baseUrl": "https://bb.com"},
"pullRequests": [
{
"id": "1",
"name": "Test PR",
"status": "OPEN",
"url": "https://bb.com/pr/1",
"source": {"branch": "feature", "repository": {}},
"destination": {"branch": "main"},
"author": {"name": "Author"},
"reviewers": [
{"name": "Reviewer1"},
{"name": "Reviewer2"},
],
"lastUpdate": "2024-01-01",
}
],
"branches": [],
"repositories": [],
}
]
}
result = development_mixin._parse_development_info(response, "TEST-1")
assert len(result["pullRequests"]) == 1
assert result["pullRequests"][0]["reviewers"] == ["Reviewer1", "Reviewer2"]
def test_parse_development_info_missing_fields(self, development_mixin):
"""Test parsing handles missing optional fields gracefully."""
response = {
"detail": [
{
"_instance": {},
"pullRequests": [
{
"id": "1",
"name": "Minimal PR",
"status": "OPEN",
"url": "https://example.com/pr/1",
}
],
"branches": [],
"repositories": [],
}
]
}
result = development_mixin._parse_development_info(response, "TEST-1")
assert len(result["pullRequests"]) == 1
pr = result["pullRequests"][0]
assert pr["name"] == "Minimal PR"
assert pr["source"] == ""
assert pr["destination"] == ""
assert pr["author"] == ""
assert pr["reviewers"] == []