test_sprints.py•11 kB
"""Tests for the Jira SprintMixin"""
from unittest.mock import MagicMock
import pytest
import requests
from mcp_atlassian.jira import JiraConfig
from mcp_atlassian.jira.sprints import SprintsMixin
from mcp_atlassian.models.jira import JiraSprint
@pytest.fixture
def mock_config():
"""Fixture to create a mock JiraConfig instance."""
config = MagicMock(spec=JiraConfig)
config.url = "https://test.atlassian.net"
config.username = "test@example.com"
config.api_token = "test-token"
config.auth_type = "pat"
return config
@pytest.fixture
def sprints_mixin(mock_config):
"""Fixture to create a SprintsMixin instance for testing."""
mixin = SprintsMixin(config=mock_config)
mixin.jira = MagicMock()
return mixin
@pytest.fixture
def mock_sprints():
"""Fixture to return mock boards data."""
return {
"maxResults": 50,
"startAt": 0,
"isLast": True,
"values": [
{
"id": 10000,
"self": "https://test.atlassian.net/rest/agile/1.0/sprint/10000",
"state": "closed",
"name": "Sprint 0",
"startDate": "2099-05-06T02:00:00.000Z",
"endDate": "2100-05-17T10:00:00.000Z",
"completeDate": "2024-05-20T05:17:24.302Z",
"activatedDate": "2024-05-07T01:22:45.128Z",
"originBoardId": 1000,
"goal": "",
"synced": False,
"autoStartStop": False,
},
{
"id": 10001,
"self": "https://test.atlassian.net/rest/agile/1.0/sprint/10001",
"state": "active",
"name": "Sprint 1",
"startDate": "2099-03-24T06:13:00.000Z",
"endDate": "2100-04-07T06:13:00.000Z",
"activatedDate": "2025-03-24T06:13:20.729Z",
"originBoardId": 1000,
"goal": "",
"synced": False,
"autoStartStop": False,
},
{
"id": 10002,
"self": "https://test.atlassian.net/rest/agile/1.0/sprint/10002",
"state": "future",
"name": "Sprint 2",
"originBoardId": 1000,
"synced": False,
"autoStartStop": False,
},
],
}
def test_get_all_sprints_from_board(sprints_mixin, mock_sprints):
"""Test get_all_sprints_from_board method."""
sprints_mixin.jira.get_all_sprints_from_board.return_value = mock_sprints
result = sprints_mixin.get_all_sprints_from_board("1000")
assert result == mock_sprints["values"]
def test_get_all_sprints_from_board_exception(sprints_mixin):
"""Test get_all_sprints_from_board method with exception."""
sprints_mixin.jira.get_all_sprints_from_board.side_effect = Exception("API Error")
result = sprints_mixin.get_all_sprints_from_board("1000")
assert result == []
sprints_mixin.jira.get_all_sprints_from_board.assert_called_once()
def test_get_all_sprints_from_board_http_error(sprints_mixin):
"""Test get_all_sprints_from_board method with HTTPError."""
sprints_mixin.jira.get_all_sprints_from_board.side_effect = requests.HTTPError(
response=MagicMock(content="API Error content")
)
result = sprints_mixin.get_all_sprints_from_board("1000")
assert result == []
sprints_mixin.jira.get_all_sprints_from_board.assert_called_once()
def test_get_all_sprints_from_board_non_dict_response(sprints_mixin):
"""Test get_all_sprints_from_board method with non-list response."""
sprints_mixin.jira.get_all_sprints_from_board.return_value = "not a dict"
result = sprints_mixin.get_all_sprints_from_board("1000")
assert result == []
sprints_mixin.jira.get_all_sprints_from_board.assert_called_once()
def test_get_all_sprints_from_board_model(sprints_mixin, mock_sprints):
sprints_mixin.jira.get_all_sprints_from_board.return_value = mock_sprints
result = sprints_mixin.get_all_sprints_from_board_model(board_id="1000", state=None)
assert result == [
JiraSprint.from_api_response(value) for value in mock_sprints["values"]
]
def test_create_sprint(sprints_mixin, mock_sprints):
"""Test create_sprint method."""
sprints_mixin.jira.create_sprint.return_value = mock_sprints["values"][1]
result = sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="2099-05-01T00:00:00.000Z",
end_date="2100-05-01T00:00:00.000Z",
goal="Your goal",
)
assert result == JiraSprint.from_api_response(mock_sprints["values"][1])
def test_create_sprint_http_exception(sprints_mixin, mock_sprints):
"""Test create_sprint method."""
sprints_mixin.jira.create_sprint.side_effect = requests.HTTPError(
response=MagicMock(content="API Error content")
)
with pytest.raises(requests.HTTPError):
sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="2099-05-01T00:00:00.000Z",
end_date="2100-05-15T00:00:00.000Z",
goal="Your goal",
)
def test_create_sprint_exception(sprints_mixin, mock_sprints):
"""Test create_sprint method throws general Exception."""
sprints_mixin.jira.create_sprint.side_effect = Exception
with pytest.raises(Exception):
sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="2099-05-01T00:00:00.000Z",
end_date="2100-05-15T00:00:00.000Z",
goal="Your goal",
)
def test_create_sprint_test_missing_startdate(sprints_mixin, mock_sprints):
"""Test create_sprint method."""
sprints_mixin.jira.create_sprint.return_value = mock_sprints["values"][1]
with pytest.raises(ValueError) as excinfo:
sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="",
end_date="2100-05-15T00:00:00.000Z",
goal="Your goal",
)
assert str(excinfo.value) == "Start date is required."
def test_create_sprint_test_invalid_startdate(sprints_mixin, mock_sprints):
"""Test create_sprint method."""
sprints_mixin.jira.create_sprint.return_value = mock_sprints["values"][1]
with pytest.raises(ValueError):
sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="IAMNOTADATE!",
end_date="2100-05-15T00:00:00.000Z",
goal="Your goal",
)
def test_create_sprint_test_no_enddate(sprints_mixin, mock_sprints):
"""Test create_sprint method."""
sprints_mixin.jira.create_sprint.return_value = mock_sprints["values"][1]
result = sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="2099-05-15T00:00:00.000Z",
end_date=None,
goal="Your goal",
)
assert result == JiraSprint.from_api_response(mock_sprints["values"][1])
def test_create_sprint_test_invalid_enddate(sprints_mixin, mock_sprints):
"""Test create_sprint method."""
sprints_mixin.jira.create_sprint.return_value = mock_sprints["values"][1]
with pytest.raises(ValueError):
sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="2099-05-15T00:00:00.000Z",
end_date="IAMNOTADATE!",
goal="Your goal",
)
def test_create_sprint_test_startdate_after_enddate(sprints_mixin, mock_sprints):
"""Test create_sprint method."""
sprints_mixin.jira.create_sprint.return_value = mock_sprints["values"][1]
with pytest.raises(ValueError, match="Start date must be before end date."):
sprints_mixin.create_sprint(
sprint_name="Sprint 1",
board_id="10001",
start_date="2100-05-15T00:00:00.000Z",
end_date="2099-05-15T00:00:00.000Z",
goal="Your goal",
)
def test_update_sprint_success(sprints_mixin, mock_sprints):
"""Test update_sprint method with valid data."""
mock_updated_sprint = mock_sprints["values"][0]
sprints_mixin.jira.update_partially_sprint.return_value = mock_updated_sprint
result = sprints_mixin.update_sprint(
sprint_id="10000",
sprint_name="Updated Sprint Name",
state="active",
start_date="2024-05-01T00:00:00.000Z",
end_date="2024-05-15T00:00:00.000Z",
goal="Updated goal",
)
assert result == JiraSprint.from_api_response(mock_updated_sprint)
sprints_mixin.jira.update_partially_sprint.assert_called_once_with(
sprint_id="10000",
data={
"name": "Updated Sprint Name",
"state": "active",
"startDate": "2024-05-01T00:00:00.000Z",
"endDate": "2024-05-15T00:00:00.000Z",
"goal": "Updated goal",
},
)
def test_update_sprint_invalid_state(sprints_mixin):
"""Test update_sprint method with invalid state."""
result = sprints_mixin.update_sprint(
sprint_id="10000",
sprint_name="Updated Sprint Name",
state="invalid_state",
start_date=None,
end_date=None,
goal=None,
)
assert result is None
sprints_mixin.jira.update_partially_sprint.assert_not_called()
def test_update_sprint_missing_sprint_id(sprints_mixin):
"""Test update_sprint method with missing sprint_id."""
result = sprints_mixin.update_sprint(
sprint_id=None,
sprint_name="Updated Sprint Name",
state="active",
start_date=None,
end_date=None,
goal=None,
)
assert result is None
sprints_mixin.jira.update_partially_sprint.assert_not_called()
def test_update_sprint_http_error(sprints_mixin):
"""Test update_sprint method with HTTPError."""
sprints_mixin.jira.update_partially_sprint.side_effect = requests.HTTPError(
response=MagicMock(content="API Error content")
)
result = sprints_mixin.update_sprint(
sprint_id="10000",
sprint_name="Updated Sprint Name",
state="active",
start_date=None,
end_date=None,
goal=None,
)
assert result is None
sprints_mixin.jira.update_partially_sprint.assert_called_once()
def test_update_sprint_exception(sprints_mixin):
"""Test update_sprint method with a generic exception."""
sprints_mixin.jira.update_partially_sprint.side_effect = Exception(
"Unexpected Error"
)
result = sprints_mixin.update_sprint(
sprint_id="10000",
sprint_name="Updated Sprint Name",
state="active",
start_date=None,
end_date=None,
goal=None,
)
assert result is None
sprints_mixin.jira.update_partially_sprint.assert_called_once()