Skip to main content
Glama

MCP Atlassian

by ArconixForge
test_fields.py21 kB
"""Tests for the Jira Fields mixin.""" from typing import Any from unittest.mock import MagicMock import pytest from mcp_atlassian.jira import JiraFetcher from mcp_atlassian.jira.fields import FieldsMixin class TestFieldsMixin: """Tests for the FieldsMixin class.""" @pytest.fixture def fields_mixin(self, jira_fetcher: JiraFetcher) -> FieldsMixin: """Create a FieldsMixin instance with mocked dependencies.""" mixin = jira_fetcher mixin._field_ids_cache = None return mixin @pytest.fixture def mock_fields(self): """Return mock field data.""" return [ {"id": "summary", "name": "Summary", "schema": {"type": "string"}}, {"id": "description", "name": "Description", "schema": {"type": "string"}}, {"id": "status", "name": "Status", "schema": {"type": "status"}}, {"id": "assignee", "name": "Assignee", "schema": {"type": "user"}}, { "id": "customfield_10010", "name": "Epic Link", "schema": { "type": "string", "custom": "com.pyxis.greenhopper.jira:gh-epic-link", }, }, { "id": "customfield_10011", "name": "Epic Name", "schema": { "type": "string", "custom": "com.pyxis.greenhopper.jira:gh-epic-label", }, }, { "id": "customfield_10012", "name": "Story Points", "schema": {"type": "number"}, }, ] def test_get_field_ids_cache(self, fields_mixin: FieldsMixin, mock_fields): """Test get_fields uses cache when available.""" # Set up the cache fields_mixin._field_ids_cache = mock_fields # Call the method result = fields_mixin.get_fields() # Verify cache was used assert result == mock_fields fields_mixin.jira.get_all_fields.assert_not_called() def test_get_fields_refresh(self, fields_mixin: FieldsMixin, mock_fields): """Test get_fields refreshes data when requested.""" # Set up the cache fields_mixin._field_ids_cache = [{"id": "old_data", "name": "old data"}] # Mock the API response fields_mixin.jira.get_all_fields.return_value = mock_fields # Call the method with refresh=True result = fields_mixin.get_fields(refresh=True) # Verify API was called fields_mixin.jira.get_all_fields.assert_called_once() assert result == mock_fields # Verify cache was updated assert fields_mixin._field_ids_cache == mock_fields def test_get_fields_from_api( self, fields_mixin: FieldsMixin, mock_fields: list[dict[str, Any]] ): """Test get_fields fetches from API when no cache exists.""" # Mock the API response fields_mixin.jira.get_all_fields.return_value = mock_fields # Call the method result = fields_mixin.get_fields() # Verify API was called fields_mixin.jira.get_all_fields.assert_called_once() assert result == mock_fields # Verify cache was created assert fields_mixin._field_ids_cache == mock_fields def test_get_fields_error(self, fields_mixin: FieldsMixin): """Test get_fields handles errors gracefully.""" # Mock API error fields_mixin.jira.get_all_fields.side_effect = Exception("API error") # Call the method result = fields_mixin.get_fields() # Verify empty list is returned on error assert result == [] def test_get_field_id_by_exact_match(self, fields_mixin: FieldsMixin, mock_fields): """Test get_field_id finds field by exact name match.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Call the method result = fields_mixin.get_field_id("Summary") # Verify the result assert result == "summary" def test_get_field_id_case_insensitive( self, fields_mixin: FieldsMixin, mock_fields ): """Test get_field_id is case-insensitive.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Call the method with different case result = fields_mixin.get_field_id("summary") # Verify the result assert result == "summary" def test_get_field_id_exact_match_case_insensitive( self, fields_mixin: FieldsMixin, mock_fields ): """Test get_field_id finds field by exact match (case-insensitive) using the map.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Ensure the map is generated based on the mock fields for this test fields_mixin._generate_field_map(force_regenerate=True) # Call the method with exact name (case-insensitive) result = fields_mixin.get_field_id("epic link") # Verify the result (should find Epic Link as first match) assert result == "customfield_10010" def test_get_field_id_not_found(self, fields_mixin: FieldsMixin, mock_fields): """Test get_field_id returns None when field not found.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Call the method with non-existent field result = fields_mixin.get_field_id("NonExistent") # Verify the result assert result is None def test_get_field_id_error(self, fields_mixin: FieldsMixin): """Test get_field_id handles errors gracefully.""" # Make get_fields raise an exception fields_mixin.get_fields = MagicMock( side_effect=Exception("Error getting fields") ) # Call the method result = fields_mixin.get_field_id("Summary") # Verify None is returned on error assert result is None def test_get_field_by_id(self, fields_mixin: FieldsMixin, mock_fields): """Test get_field_by_id retrieves field definition correctly.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Call the method result = fields_mixin.get_field_by_id("customfield_10012") # Verify the result assert result == mock_fields[6] # The Story Points field assert result["name"] == "Story Points" def test_get_field_by_id_not_found(self, fields_mixin: FieldsMixin, mock_fields): """Test get_field_by_id returns None when field not found.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Call the method with non-existent ID result = fields_mixin.get_field_by_id("customfield_99999") # Verify the result assert result is None def test_get_custom_fields(self, fields_mixin: FieldsMixin, mock_fields): """Test get_custom_fields returns only custom fields.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Call the method result = fields_mixin.get_custom_fields() # Verify the result assert len(result) == 3 assert all(field["id"].startswith("customfield_") for field in result) assert result[0]["name"] == "Epic Link" assert result[1]["name"] == "Epic Name" assert result[2]["name"] == "Story Points" def test_get_required_fields(self, fields_mixin: FieldsMixin): """Test get_required_fields retrieves required fields correctly.""" # Mock the response for get_project_issue_types mock_issue_types = [ {"id": "10001", "name": "Bug"}, {"id": "10002", "name": "Task"}, ] fields_mixin.get_project_issue_types = MagicMock(return_value=mock_issue_types) # Mock the response for issue_createmeta_fieldtypes based on API docs mock_field_meta = { "fields": [ { "required": True, "schema": {"type": "string", "system": "summary"}, "name": "Summary", "fieldId": "summary", "autoCompleteUrl": "", "hasDefaultValue": False, "operations": ["set"], "allowedValues": [], }, { "required": False, "schema": {"type": "string", "system": "description"}, "name": "Description", "fieldId": "description", }, { "required": True, "schema": {"type": "string", "custom": "some.custom.type"}, "name": "Epic Link", "fieldId": "customfield_10010", }, ] } fields_mixin.jira.issue_createmeta_fieldtypes.return_value = mock_field_meta # Call the method result = fields_mixin.get_required_fields("Bug", "TEST") # Verify the result assert len(result) == 2 assert "summary" in result assert result["summary"]["required"] is True assert "customfield_10010" in result assert result["customfield_10010"]["required"] is True assert "description" not in result # Verify the correct API was called fields_mixin.get_project_issue_types.assert_called_once_with("TEST") fields_mixin.jira.issue_createmeta_fieldtypes.assert_called_once_with( project="TEST", issue_type_id="10001" ) def test_get_required_fields_not_found(self, fields_mixin: FieldsMixin): """Test get_required_fields handles project/issue type not found.""" # Scenario 1: Issue type not found in project mock_issue_types = [{"id": "10002", "name": "Task"}] # "Bug" is missing fields_mixin.get_project_issue_types = MagicMock(return_value=mock_issue_types) fields_mixin.jira.issue_createmeta_fieldtypes = MagicMock() # Call the method result = fields_mixin.get_required_fields("Bug", "TEST") # Verify issue type lookup was attempted, but field meta was not called fields_mixin.get_project_issue_types.assert_called_once_with("TEST") fields_mixin.jira.issue_createmeta_fieldtypes.assert_not_called() # Verify the result assert result == {} def test_get_required_fields_error(self, fields_mixin: FieldsMixin): """Test get_required_fields handles errors gracefully.""" # Mock the response for get_project_issue_types mock_issue_types = [ {"id": "10001", "name": "Bug"}, ] fields_mixin.get_project_issue_types = MagicMock(return_value=mock_issue_types) # Mock issue_createmeta_fieldtypes to raise an error fields_mixin.jira.issue_createmeta_fieldtypes.side_effect = Exception( "API error" ) # Call the method result = fields_mixin.get_required_fields("Bug", "TEST") # Verify the result assert result == {} # Verify the correct API was called (which then raised the error) fields_mixin.jira.issue_createmeta_fieldtypes.assert_called_once_with( project="TEST", issue_type_id="10001" ) def test_get_jira_field_ids_cached(self, fields_mixin: FieldsMixin): """Test get_field_ids_to_epic returns cached field IDs.""" # Set up the cache fields_mixin._field_ids_cache = [ {"id": "summary", "name": "Summary"}, {"id": "description", "name": "Description"}, ] # Call the method result = fields_mixin.get_field_ids_to_epic() # Verify the result assert result == { "Summary": "summary", "Description": "description", } def test_get_jira_field_ids_from_fields( self, fields_mixin: FieldsMixin, mock_fields: list[dict] ): """Test get_field_ids_to_epic extracts field IDs from field definitions.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Ensure field map is generated fields_mixin._generate_field_map(force_regenerate=True) # Call the method result = fields_mixin.get_field_ids_to_epic() # Verify that epic-specific fields are properly identified assert "epic_link" in result assert "Epic Link" in result assert result["epic_link"] == "customfield_10010" assert "epic_name" in result assert "Epic Name" in result assert result["epic_name"] == "customfield_10011" def test_get_jira_field_ids_error(self, fields_mixin: FieldsMixin): """Test get_field_ids_to_epic handles errors gracefully.""" # Ensure no cache exists fields_mixin._field_ids_cache = None # Make get_fields raise an exception fields_mixin.get_fields = MagicMock( side_effect=Exception("Error getting fields") ) # Call the method result = fields_mixin.get_field_ids_to_epic() # Verify the result assert result == {} def test_is_custom_field(self, fields_mixin: FieldsMixin): """Test is_custom_field correctly identifies custom fields.""" # Test with custom field assert fields_mixin.is_custom_field("customfield_10010") is True # Test with standard field assert fields_mixin.is_custom_field("summary") is False def test_format_field_value_user_field( self, fields_mixin: FieldsMixin, mock_fields ): """Test format_field_value formats user fields correctly.""" # Set up the mocks fields_mixin.get_field_by_id = MagicMock( return_value=mock_fields[3] ) # The Assignee field fields_mixin._get_account_id = MagicMock(return_value="account123") # Call the method with a user field and string value result = fields_mixin.format_field_value("assignee", "johndoe") # Verify the result assert result == {"accountId": "account123"} fields_mixin._get_account_id.assert_called_once_with("johndoe") # FIXME: The test covers impossible case. # # This test is failing because it assumes that the `_get_account_id` # method is unavailable. As default, `format_field_value` will return # `{"name": value}` for server/DC. # # However, in any case `JiraFetcher` always inherits from `UsersMixin` # and will therefore have the `_get_account_id` method available. # # That is to say, the `format_field_value` method will never return in # format `{"name": value}`. # # Further fixes are needed in the `FieldsMixin` class to support the case # for server/DC. # # See also: # https://github.com/sooperset/mcp-atlassian/blob/651c271e8aa76b469e9c67535669d93267ad5da6/src/mcp_atlassian/jira/fields.py#L279-L297 # def test_format_field_value_user_field_no_account_id( # self, fields_mixin: FieldsMixin, mock_fields # ): # """Test format_field_value handles user fields without _get_account_id.""" # # Set up the mocks # fields_mixin.get_field_by_id = MagicMock( # return_value=mock_fields[3] # ) # The Assignee field # # Call the method with a user field and string value # result = fields_mixin.format_field_value("assignee", "johndoe") # # Verify the result - should use name for server/DC # assert result == {"name": "johndoe"} def test_format_field_value_array_field(self, fields_mixin: FieldsMixin): """Test format_field_value formats array fields correctly.""" # Set up the mocks mock_array_field = { "id": "labels", "name": "Labels", "schema": {"type": "array"}, } fields_mixin.get_field_by_id = MagicMock(return_value=mock_array_field) # Test with single value (should convert to list) result = fields_mixin.format_field_value("labels", "bug") assert result == ["bug"] # Test with list value (should keep as list) result = fields_mixin.format_field_value("labels", ["bug", "feature"]) assert result == ["bug", "feature"] def test_format_field_value_option_field(self, fields_mixin: FieldsMixin): """Test format_field_value formats option fields correctly.""" # Set up the mocks mock_option_field = { "id": "priority", "name": "Priority", "schema": {"type": "option"}, } fields_mixin.get_field_by_id = MagicMock(return_value=mock_option_field) # Test with string value result = fields_mixin.format_field_value("priority", "High") assert result == {"value": "High"} # Test with already formatted value already_formatted = {"value": "Medium"} result = fields_mixin.format_field_value("priority", already_formatted) assert result == already_formatted def test_format_field_value_unknown_field(self, fields_mixin: FieldsMixin): """Test format_field_value returns value as-is for unknown fields.""" # Set up the mocks fields_mixin.get_field_by_id = MagicMock(return_value=None) # Call the method with unknown field test_value = "test value" result = fields_mixin.format_field_value("unknown", test_value) # Verify the value is returned as-is assert result == test_value def test_search_fields_empty_keyword(self, fields_mixin: FieldsMixin, mock_fields): """Test search_fields returns first N fields when keyword is empty.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Call with empty keyword and limit=3 result = fields_mixin.search_fields("", limit=3) # Verify first 3 fields are returned assert len(result) == 3 assert result == mock_fields[:3] def test_search_fields_exact_match(self, fields_mixin: FieldsMixin, mock_fields): """Test search_fields finds exact matches with high relevance.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Search for "Story Points" result = fields_mixin.search_fields("Story Points") # Verify Story Points field is first result assert len(result) > 0 assert result[0]["name"] == "Story Points" assert result[0]["id"] == "customfield_10012" def test_search_fields_partial_match(self, fields_mixin: FieldsMixin, mock_fields): """Test search_fields finds partial matches.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Search for "Epic" result = fields_mixin.search_fields("Epic") # Verify Epic-related fields are in results epic_fields = [field["name"] for field in result[:2]] # Top 2 results assert "Epic Link" in epic_fields assert "Epic Name" in epic_fields def test_search_fields_case_insensitive( self, fields_mixin: FieldsMixin, mock_fields ): """Test search_fields is case insensitive.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Search with different cases result_lower = fields_mixin.search_fields("story points") result_upper = fields_mixin.search_fields("STORY POINTS") result_mixed = fields_mixin.search_fields("Story Points") # Verify all searches find the same field assert len(result_lower) > 0 assert len(result_upper) > 0 assert len(result_mixed) > 0 assert result_lower[0]["id"] == result_upper[0]["id"] == result_mixed[0]["id"] assert result_lower[0]["name"] == "Story Points" def test_search_fields_with_limit(self, fields_mixin: FieldsMixin, mock_fields): """Test search_fields respects the limit parameter.""" # Set up the fields fields_mixin.get_fields = MagicMock(return_value=mock_fields) # Search with limit=2 result = fields_mixin.search_fields("field", limit=2) # Verify only 2 results are returned assert len(result) == 2 def test_search_fields_error(self, fields_mixin: FieldsMixin): """Test search_fields handles errors gracefully.""" # Make get_fields raise an exception fields_mixin.get_fields = MagicMock( side_effect=Exception("Error getting fields") ) # Call the method result = fields_mixin.search_fields("test") # Verify empty list is returned on error assert result == []

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ArconixForge/mcp-atlassian'

If you have feedback or need assistance with the MCP directory API, please join our Discord server