Repository: sooperset/mcp-atlassian

Issue:
[bug] Some non-custom fields are not caught in JiraIssue
## Describe the bug
Some fields like `duedate` are not caught. They are not `JiraIssue`'s members.

## Analyze
In https://github.com/sooperset/mcp-atlassian/blob/88f074195337efaf1fe061cd768df4b6b180e780/src/mcp_atlassian/models/jira.py#L759-L820

The properties mentioned below are not caught by the catching loop.

```py
known_fields = {
    "summary",          # ✓
    "description",      # ✓ 
    "status",           # ✓ 
    "issuetype",        # ✓ (issue_type)
    "priority",         # ✓ 
    "assignee",         # ✓ 
    "reporter",         # ✓ 
    "components",       # ✓
    "comment",          # ✓ (comments)
    "attachment",       # ✓ (attachments)
    "fixVersions",      # ✓ (fix_versions)
    "labels",           # ✓ 
    "created",          # ✓ 
    "updated",          # ✓ 
    "duedate",          # ✗ 
    "resolutiondate",   # ✗ 
    "resolution",       # ✗ 
    "project",          # ✗ 
    "parent",           # ✗ 
    "subtasks",         # ✗ 
    "timetracking",     # ✓ 
    "security",         # ✗ 
    "worklog",          # ✗
}
```

The affecting commit is https://github.com/sooperset/mcp-atlassian/commit/88f074195337efaf1fe061cd768df4b6b180e780.

## Approach
We can consider just delete them in `known_fields` or add new members.

Discussion:
@cutekibry: By the way, some of the field's names are not consistent with Jira API. I think it might be better if we add alias for `JiraIssue`'s `__getattr__`.@sooperset: Thanks for reporting this! I'm thinking of refactoring the jira pydnatic model to address these issues. I'll look into this!

Solution patch:
diff --git a/src/mcp_atlassian/models/__init__.py b/src/mcp_atlassian/models/__init__.py
index c970360..540e933 100644
--- a/src/mcp_atlassian/models/__init__.py
+++ b/src/mcp_atlassian/models/__init__.py
@@ -44,6 +44,7 @@ from .jira import (
     JiraIssueType,
     JiraPriority,
     JiraProject,
+    JiraResolution,
     JiraSearchResult,
     JiraSprint,
     JiraStatus,
@@ -84,6 +85,7 @@ __all__ = [
     "JiraComment",
     "JiraIssue",
     "JiraProject",
+    "JiraResolution",
     "JiraTransition",
     "JiraWorklog",
     "JiraSearchResult",
diff --git a/src/mcp_atlassian/models/jira/__init__.py b/src/mcp_atlassian/models/jira/__init__.py
index 0d36d77..9570c74 100644
--- a/src/mcp_atlassian/models/jira/__init__.py
+++ b/src/mcp_atlassian/models/jira/__init__.py
@@ -11,6 +11,7 @@ from .common import (
     JiraAttachment,
     JiraIssueType,
     JiraPriority,
+    JiraResolution,
     JiraStatus,
     JiraStatusCategory,
     JiraTimetracking,
@@ -30,6 +31,7 @@ __all__ = [
     "JiraIssueType",
     "JiraPriority",
     "JiraAttachment",
+    "JiraResolution",
     "JiraTimetracking",
     # Entity-specific models
     "JiraComment",
diff --git a/src/mcp_atlassian/models/jira/common.py b/src/mcp_atlassian/models/jira/common.py
index 0406dd9..058b88c 100644
--- a/src/mcp_atlassian/models/jira/common.py
+++ b/src/mcp_atlassian/models/jira/common.py
@@ -413,3 +413,30 @@ class JiraTimetracking(ApiModel):
             result["time_spent"] = self.time_spent
 
         return result
+
+
+class JiraResolution(ApiModel):
+    """Model representing a Jira issue resolution."""
+
+    id: str = JIRA_DEFAULT_ID
+    name: str = UNKNOWN
+    description: str | None = None
+
+    @classmethod
+    def from_api_response(cls, data: dict[str, Any], **kwargs: Any) -> "JiraResolution":
+        """Create a JiraResolution from a Jira API response."""
+        if not isinstance(data, dict):
+            return cls()
+        resolution_id = data.get("id", JIRA_DEFAULT_ID)
+        return cls(
+            id=str(resolution_id),
+            name=data.get("name", UNKNOWN),
+            description=data.get("description"),
+        )
+
+    def to_simplified_dict(self) -> dict[str, Any]:
+        """Convert to simplified dictionary for API response."""
+        result = {"name": self.name}
+        if self.id != JIRA_DEFAULT_ID:
+            result["id"] = self.id
+        return result
diff --git a/src/mcp_atlassian/models/jira/issue.py b/src/mcp_atlassian/models/jira/issue.py
index 3507d7f..f6cf3d2 100644
--- a/src/mcp_atlassian/models/jira/issue.py
+++ b/src/mcp_atlassian/models/jira/issue.py
@@ -21,6 +21,7 @@ from .common import (
     JiraAttachment,
     JiraIssueType,
     JiraPriority,
+    JiraResolution,
     JiraStatus,
     JiraTimetracking,
     JiraUser,
@@ -76,6 +77,13 @@ class JiraIssue(ApiModel, TimestampMixin):
     custom_fields: dict[str, Any] = Field(default_factory=dict)
     requested_fields: Literal["*all"] | list[str] | None = None
     project: JiraProject | None = None
+    resolution: JiraResolution | None = None
+    duedate: str | None = None
+    resolutiondate: str | None = None
+    parent: dict | None = None
+    subtasks: list[dict] = Field(default_factory=list)
+    security: dict | None = None
+    worklog: dict | None = None
 
     def __getattribute__(self, name: str) -> Any:
         """
@@ -296,9 +304,39 @@ class JiraIssue(ApiModel, TimestampMixin):
         # Extract project data
         project = None
         project_data = fields.get("project")
-        if project_data:
+        if isinstance(project_data, dict):
             project = JiraProject.from_api_response(project_data)
 
+        resolution = None
+        resolution_data = fields.get("resolution")
+        if isinstance(resolution_data, dict):
+            resolution = JiraResolution.from_api_response(resolution_data)
+
+        duedate = (
+            fields.get("duedate") if isinstance(fields.get("duedate"), str) else None
+        )
+        resolutiondate = (
+            fields.get("resolutiondate")
+            if isinstance(fields.get("resolutiondate"), str)
+            else None
+        )
+        parent = (
+            fields.get("parent") if isinstance(fields.get("parent"), dict) else None
+        )
+        # Ensure subtasks is a list of dicts
+        subtasks_raw = fields.get("subtasks", [])
+        subtasks = (
+            [st for st in subtasks_raw if isinstance(st, dict)]
+            if isinstance(subtasks_raw, list)
+            else []
+        )
+        security = (
+            fields.get("security") if isinstance(fields.get("security"), dict) else None
+        )
+        worklog = (
+            fields.get("worklog") if isinstance(fields.get("worklog"), dict) else None
+        )
+
         # Lists of strings
         labels = []
         if labels_data := fields.get("labels"):
@@ -410,6 +448,13 @@ class JiraIssue(ApiModel, TimestampMixin):
             assignee=assignee,
             reporter=reporter,
             project=project,
+            resolution=resolution,
+            duedate=duedate,
+            resolutiondate=resolutiondate,
+            parent=parent,
+            subtasks=subtasks,
+            security=security,
+            worklog=worklog,
             labels=labels,
             components=components,
             comments=comments,
@@ -430,156 +475,126 @@ class JiraIssue(ApiModel, TimestampMixin):
             "key": self.key,
         }
 
-        # Add summary by default unless explicitly filtering by requested_fields
-        add_summary = True
-        if (
-            isinstance(self.requested_fields, list)
-            and "summary" not in self.requested_fields
-        ):
-            add_summary = False
+        # Helper method to check if a field should be included
+        def should_include_field(field_name: str) -> bool:
+            return (
+                self.requested_fields == "*all"
+                or not isinstance(self.requested_fields, list)
+                or field_name in self.requested_fields
+            )
 
-        if add_summary:
+        # Add summary if requested
+        if should_include_field("summary"):
             result["summary"] = self.summary
 
-        # Add URL if available
-        if self.url:
+        # Add URL if available and requested
+        if self.url and should_include_field("url"):
             result["url"] = self.url
 
-        # Add description if available, unless explicitly filtered
-        add_description = True
-        if (
-            isinstance(self.requested_fields, list)
-            and "description" not in self.requested_fields
-        ):
-            add_description = False
-
-        if add_description and self.description:
+        # Add description if available and requested
+        if self.description and should_include_field("description"):
             result["description"] = self.description
 
-        # Add status
-        if self.status:
+        # Add status if available and requested
+        if self.status and should_include_field("status"):
             result["status"] = self.status.to_simplified_dict()
-        else:
-            result["status"] = {"name": "Unknown"}
 
-        # Add issue type
-        if self.issue_type:
+        # Add issue type if available and requested
+        if self.issue_type and should_include_field("issue_type"):
             result["issue_type"] = self.issue_type.to_simplified_dict()
-        else:
-            result["issue_type"] = {"name": "Unknown"}
-
-        # Add priority if not explicitly filtered
-        if self.priority and (
-            not isinstance(self.requested_fields, list)
-            or "priority" in self.requested_fields
-        ):
+
+        # Add priority if available and requested
+        if self.priority and should_include_field("priority"):
             result["priority"] = self.priority.to_simplified_dict()
 
-        # Add project info if not explicitly filtered
-        if self.project and (
-            not isinstance(self.requested_fields, list)
-            or "project" in self.requested_fields
-        ):
+        # Add project info if available and requested
+        if self.project and should_include_field("project"):
             result["project"] = self.project.to_simplified_dict()
 
-        # Add assignee and reporter if not explicitly filtered
-        add_assignee = (
-            not isinstance(self.requested_fields, list)
-            or "assignee" in self.requested_fields
-        )
-        if add_assignee:
+        # Add resolution if available and requested
+        if self.resolution and should_include_field("resolution"):
+            result["resolution"] = self.resolution.to_simplified_dict()
+
+        # Add dates if available and requested
+        if self.duedate and should_include_field("duedate"):
+            result["duedate"] = self.duedate
+
+        if self.resolutiondate and should_include_field("resolutiondate"):
+            result["resolutiondate"] = self.resolutiondate
+
+        # Add parent and subtasks if available and requested
+        if self.parent and should_include_field("parent"):
+            result["parent"] = self.parent
+
+        if self.subtasks and should_include_field("subtasks"):
+            result["subtasks"] = self.subtasks
+
+        # Add security and worklog if available and requested
+        if self.security and should_include_field("security"):
+            result["security"] = self.security
+
+        if self.worklog and should_include_field("worklog"):
+            result["worklog"] = self.worklog
+
+        # Add assignee if requested
+        if should_include_field("assignee"):
             if self.assignee:
                 result["assignee"] = self.assignee.to_simplified_dict()
             else:
                 result["assignee"] = {"display_name": "Unassigned"}
 
-        if self.reporter and (
-            not isinstance(self.requested_fields, list)
-            or "reporter" in self.requested_fields
-        ):
+        # Add reporter if available and requested
+        if self.reporter and should_include_field("reporter"):
             result["reporter"] = self.reporter.to_simplified_dict()
 
-        # Add lists if not explicitly filtered
-        if self.labels and (
-            not isinstance(self.requested_fields, list)
-            or "labels" in self.requested_fields
-        ):
+        # Add lists if available and requested
+        if self.labels and should_include_field("labels"):
             result["labels"] = self.labels
 
-        if self.components and (
-            not isinstance(self.requested_fields, list)
-            or "components" in self.requested_fields
-        ):
+        if self.components and should_include_field("components"):
             result["components"] = self.components
 
-        if self.fix_versions and (
-            not isinstance(self.requested_fields, list)
-            or "fix_versions" in self.requested_fields
-        ):
+        if self.fix_versions and should_include_field("fix_versions"):
             result["fix_versions"] = self.fix_versions
 
-        # Add epic fields if not explicitly filtered
-        if self.epic_key and (
-            not isinstance(self.requested_fields, list)
-            or "epic_key" in self.requested_fields
-        ):
+        # Add epic fields if available and requested
+        if self.epic_key and should_include_field("epic_key"):
             result["epic_key"] = self.epic_key
 
-        if self.epic_name and (
-            not isinstance(self.requested_fields, list)
-            or "epic_name" in self.requested_fields
-        ):
+        if self.epic_name and should_include_field("epic_name"):
             result["epic_name"] = self.epic_name
 
-        # Add time tracking only if explicitly requested
-        add_timetracking = self.requested_fields == "*all" or (
-            isinstance(self.requested_fields, list)
-            and "timetracking" in self.requested_fields
-        )
-        if add_timetracking and self.timetracking:
+        # Add time tracking if available and requested
+        if self.timetracking and should_include_field("timetracking"):
             result["timetracking"] = self.timetracking.to_simplified_dict()
 
-        # Add created and updated timestamps if not explicitly filtered
-        if self.created and (
-            not isinstance(self.requested_fields, list)
-            or "created" in self.requested_fields
-        ):
+        # Add created and updated timestamps if available and requested
+        if self.created and should_include_field("created"):
             result["created"] = self.created
 
-        if self.updated and (
-            not isinstance(self.requested_fields, list)
-            or "updated" in self.requested_fields
-        ):
+        if self.updated and should_include_field("updated"):
             result["updated"] = self.updated
 
-        # Add comments if requested
-        if self.requested_fields == "*all" or (
-            isinstance(self.requested_fields, list)
-            and "comments" in self.requested_fields
-        ):
+        # Add comments if available and requested
+        if self.comments and should_include_field("comments"):
             result["comments"] = [
                 comment.to_simplified_dict() for comment in self.comments
             ]
 
-        # Add attachments if requested
-        if self.requested_fields == "*all" or (
-            isinstance(self.requested_fields, list)
-            and "attachments" in self.requested_fields
-        ):
+        # Add attachments if available and requested
+        if self.attachments and should_include_field("attachment"):
             result["attachments"] = [
                 attachment.to_simplified_dict() for attachment in self.attachments
             ]
 
         # Process custom fields
         if self.custom_fields:
-            # Add custom fields if requested with "*all"
+            # Add all custom fields if "*all" is requested
             if self.requested_fields == "*all":
                 # Loop through custom fields and add all of them
                 for field_id, field_value in self.custom_fields.items():
                     # Process the value to make it more user-friendly
                     processed_value = self._process_custom_field_value(field_value)
-
-                    # Add with original ID only
                     result[field_id] = processed_value
 
             # Add specific requested custom fields
@@ -605,7 +620,7 @@ class JiraIssue(ApiModel, TimestampMixin):
                             )
                             result[full_id] = value
 
-        return result
+        return {k: v for k, v in result.items() if v is not None}
 
     def _process_custom_field_value(self, field_value: Any) -> Any:
         """


Test patch:
diff --git a/tests/fixtures/jira_mocks.py b/tests/fixtures/jira_mocks.py
index b5341bb..1d3e1bc 100644
--- a/tests/fixtures/jira_mocks.py
+++ b/tests/fixtures/jira_mocks.py
@@ -8,19 +8,12 @@ MOCK_JIRA_ISSUE_RESPONSE = {
         "description": "This is a test issue description",
         "created": "2024-01-01T10:00:00.000+0000",
         "updated": "2024-01-02T15:30:00.000+0000",
-        "duedate": "2024-12-31",
-        "priority": {
-            "self": "https://example.atlassian.net/rest/api/2/priority/3",
-            "iconUrl": "https://example.atlassian.net/images/icons/priorities/medium.svg",
-            "name": "Medium",
-            "id": "3",
-        },
         "status": {
-            "self": "https://example.atlassian.net/rest/api/2/status/10000",
-            "description": "",
-            "iconUrl": "https://example.atlassian.net/",
+            "self": "https://example.atlassian.net/rest/api/2/status/3",
+            "description": "This issue is currently being worked on.",
+            "iconUrl": "https://example.atlassian.net/images/icons/statuses/inprogress.png",
             "name": "In Progress",
-            "id": "10000",
+            "id": "3",
             "statusCategory": {
                 "self": "https://example.atlassian.net/rest/api/2/statuscategory/4",
                 "id": 4,
@@ -30,64 +23,53 @@ MOCK_JIRA_ISSUE_RESPONSE = {
             },
         },
         "issuetype": {
-            "self": "https://example.atlassian.net/rest/api/2/issuetype/10000",
-            "id": "10000",
+            "self": "https://example.atlassian.net/rest/api/2/issuetype/10001",
+            "id": "10001",
             "description": "A task that needs to be done.",
-            "iconUrl": "https://example.atlassian.net/images/icons/issuetypes/task.svg",
+            "iconUrl": "https://example.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10318&avatarType=issuetype",
             "name": "Task",
             "subtask": False,
-            "avatarId": 10318,
-            "hierarchyLevel": 0,
         },
-        "project": {
-            "self": "https://example.atlassian.net/rest/api/2/project/10000",
-            "id": "10000",
-            "key": "PROJ",
-            "name": "Test Project",
-            "projectTypeKey": "software",
-            "simplified": True,
-            "avatarUrls": {
-                "48x48": "https://example.atlassian.net/secure/projectavatar?size=large&pid=10000",
-                "24x24": "https://example.atlassian.net/secure/projectavatar?size=small&pid=10000",
-                "16x16": "https://example.atlassian.net/secure/projectavatar?size=xsmall&pid=10000",
-                "32x32": "https://example.atlassian.net/secure/projectavatar?size=medium&pid=10000",
-            },
+        "priority": {
+            "self": "https://example.atlassian.net/rest/api/2/priority/3",
+            "iconUrl": "https://example.atlassian.net/images/icons/priorities/medium.svg",
+            "name": "Medium",
+            "id": "3",
         },
         "assignee": {
-            "self": "https://example.atlassian.net/rest/api/2/user?accountId=123456789",
-            "accountId": "123456789",
-            "emailAddress": "user@example.com",
+            "self": "https://example.atlassian.net/rest/api/2/user?accountId=123",
+            "accountId": "123",
+            "emailAddress": "test@example.com",
             "avatarUrls": {
                 "48x48": "https://secure.gravatar.com/avatar/123?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
-                "24x24": "https://secure.gravatar.com/avatar/123?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
-                "16x16": "https://secure.gravatar.com/avatar/123?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
-                "32x32": "https://secure.gravatar.com/avatar/123?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
             },
             "displayName": "Test User",
             "active": True,
             "timeZone": "UTC",
-            "accountType": "atlassian",
         },
         "reporter": {
-            "self": "https://example.atlassian.net/rest/api/2/user?accountId=987654321",
-            "accountId": "987654321",
-            "avatarUrls": {
-                "48x48": "https://secure.gravatar.com/avatar/456?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
-                "24x24": "https://secure.gravatar.com/avatar/456?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
-                "16x16": "https://secure.gravatar.com/avatar/456?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
-                "32x32": "https://secure.gravatar.com/avatar/456?d=https%3A%2F%2Favatar.example.com%2Fdefault.png",
-            },
+            "self": "https://example.atlassian.net/rest/api/2/user?accountId=456",
+            "accountId": "456",
             "displayName": "Reporter User",
             "active": True,
-            "timeZone": "UTC",
-            "accountType": "atlassian",
         },
+        "labels": ["test-label"],
+        "components": [{"name": "Backend"}],
+        "fixVersions": [{"name": "v1.0"}],
+        "attachment": [
+            {
+                "id": "10000",
+                "filename": "test_attachment.txt",
+                "size": 1024,
+                "mimeType": "text/plain",
+                "content": "https://example.atlassian.net/secure/attachment/10000/test_attachment.txt",
+            }
+        ],
         "comment": {
             "comments": [
                 {
-                    "self": "https://example.atlassian.net/rest/api/2/issue/12345/comment/10000",
-                    "id": "10000",
-                    "author": {"displayName": "Comment User", "active": True},
+                    "id": "10001",
+                    "author": {"displayName": "Commenter User"},
                     "body": "This is a test comment",
                     "created": "2024-01-01T12:00:00.000+0000",
                     "updated": "2024-01-01T12:00:00.000+0000",
@@ -97,11 +79,54 @@ MOCK_JIRA_ISSUE_RESPONSE = {
             "total": 1,
             "startAt": 0,
         },
-        "labels": ["test-label"],
-        "timetracking": {},
-        "security": None,
-        "attachment": [],
+        "timetracking": {
+            "originalEstimate": "1d",
+            "remainingEstimate": "4h",
+            "timeSpent": "4h",
+            "originalEstimateSeconds": 28800,
+            "remainingEstimateSeconds": 14400,
+            "timeSpentSeconds": 14400,
+        },
+        "project": {
+            "id": "10000",
+            "key": "PROJ",
+            "name": "Test Project",
+            "self": "https://example.atlassian.net/rest/api/2/project/10000",
+            "avatarUrls": {
+                "48x48": "https://example.atlassian.net/secure/projectavatar?size=large&pid=10000"
+            },
+        },
+        "resolution": {
+            "self": "https://example.atlassian.net/rest/api/2/resolution/10000",
+            "id": "10000",
+            "description": "Work has been completed on this issue.",
+            "name": "Fixed",
+        },
+        "duedate": "2024-12-31",
+        "resolutiondate": "2024-01-15T11:00:00.000+0000",
+        "parent": {
+            "id": "12344",
+            "key": "PROJ-122",
+            "fields": {"summary": "Parent Issue Summary"},
+        },
+        "subtasks": [
+            {
+                "id": "12346",
+                "key": "PROJ-124",
+                "fields": {"summary": "Subtask 1 Summary"},
+            }
+        ],
+        "security": {"name": "Internal", "id": "10001"},
         "worklog": {"startAt": 0, "maxResults": 20, "total": 0, "worklogs": []},
+        # Custom fields for testing
+        "customfield_10011": "Epic Name Example",  # Epic Name
+        "customfield_10014": "EPIC-KEY-1",  # Epic Link
+        "customfield_10001": "Custom Text Field Value",
+        "customfield_10002": {"value": "Custom Select Value"},
+        "customfield_10003": [
+            {"value": "Custom MultiSelect 1"},
+            {"value": "Custom MultiSelect 2"},
+        ],
     },
 }
 
@@ -469,6 +494,30 @@ MOCK_JIRA_ISSUE_RESPONSE_SIMPLIFIED = {
             "total": 1,
         },
         "labels": ["test-label"],
+        "fixVersions": [{"name": "v1.0"}],
+        "attachment": [
+            {
+                "id": "10000",
+                "filename": "test_attachment.txt",
+                "author": {"displayName": "Test User"},
+                "created": "2024-01-01T10:00:00.000+0000",
+                "size": 1024,
+                "mimeType": "text/plain",
+                "content": "https://example.atlassian.net/secure/attachment/10000/test_attachment.txt",
+            }
+        ],
+        "timetracking": {
+            "originalEstimate": "1d",
+            "remainingEstimate": "4h",
+            "timeSpent": "4h",
+        },
+        "custom_fields": {
+            "customfield_10011": "My Awesome Epic Name",
+            "customfield_10014": "EPIC-123",
+            "customfield_10001": "Simple string value",
+            "customfield_10002": {"value": "Option value"},
+            "customfield_10003": [{"value": "Item 1"}, {"value": "Item 2"}],
+        },
     },
 }
 
diff --git a/tests/unit/models/test_jira_models.py b/tests/unit/models/test_jira_models.py
index 30b611a..ccda3f1 100644
--- a/tests/unit/models/test_jira_models.py
+++ b/tests/unit/models/test_jira_models.py
@@ -22,6 +22,7 @@ from src.mcp_atlassian.models.jira import (
     JiraIssueType,
     JiraPriority,
     JiraProject,
+    JiraResolution,
     JiraSearchResult,
     JiraStatus,
     JiraStatusCategory,
@@ -33,9 +34,53 @@ from src.mcp_atlassian.models.jira import (
 
 # Optional: Import real API client for optional real-data testing
 try:
-    from src.mcp_atlassian.jira.client import JiraClient  # noqa: F401
+    from atlassian import Jira
+
+    from src.mcp_atlassian.jira import JiraConfig, JiraFetcher
+    from src.mcp_atlassian.jira.issues import IssuesMixin
+    from src.mcp_atlassian.jira.projects import ProjectsMixin
+    from src.mcp_atlassian.jira.transitions import TransitionsMixin
+    from src.mcp_atlassian.jira.worklog import WorklogMixin
+
+    real_api_available = True
 except ImportError:
-    pass
+    real_api_available = False
+
+    # Create a module-level namespace for dummy classes
+    class _DummyClasses:
+        """Namespace for dummy classes when real imports fail."""
+
+        class JiraFetcher:
+            pass
+
+        class JiraConfig:
+            @staticmethod
+            def from_env():
+                return None
+
+        class IssuesMixin:
+            pass
+
+        class ProjectsMixin:
+            pass
+
+        class TransitionsMixin:
+            pass
+
+        class WorklogMixin:
+            pass
+
+        class Jira:
+            pass
+
+    # Assign dummy classes to module namespace
+    JiraFetcher = _DummyClasses.JiraFetcher
+    JiraConfig = _DummyClasses.JiraConfig
+    IssuesMixin = _DummyClasses.IssuesMixin
+    ProjectsMixin = _DummyClasses.ProjectsMixin
+    TransitionsMixin = _DummyClasses.TransitionsMixin
+    WorklogMixin = _DummyClasses.WorklogMixin
+    Jira = _DummyClasses.Jira
 
 
 class TestJiraUser:
@@ -418,6 +463,88 @@ class TestJiraIssue:
         assert len(issue.comments) == 1
         assert issue.comments[0].body == "This is a test comment"
 
+        assert isinstance(issue.fix_versions, list)
+        assert "v1.0" in issue.fix_versions
+
+        assert isinstance(issue.attachments, list)
+        assert len(issue.attachments) == 1
+        assert issue.attachments[0].filename == "test_attachment.txt"
+
+        assert isinstance(issue.timetracking, JiraTimetracking)
+        assert issue.timetracking.original_estimate == "1d"
+
+        assert issue.project is not None
+        assert issue.project.key == "PROJ"
+        assert issue.project.name == "Test Project"
+        assert issue.resolution is not None
+        assert issue.resolution.name == "Fixed"
+        assert issue.duedate == "2024-12-31"
+        assert issue.resolutiondate == "2024-01-15T11:00:00.000+0000"
+        assert issue.parent is not None
+        assert issue.parent["key"] == "PROJ-122"
+        assert issue.subtasks is not None
+        assert len(issue.subtasks) == 1
+        assert issue.subtasks[0]["key"] == "PROJ-124"
+        assert issue.security is not None
+        assert issue.security["name"] == "Internal"
+        assert issue.worklog is not None
+        assert issue.worklog["total"] == 0
+        assert issue.worklog["maxResults"] == 20
+
+    def test_from_api_response_with_new_fields(self):
+        """Test creating a JiraIssue focusing on parsing the new fields."""
+        # Construct local mock data including the new fields
+        local_issue_data = {
+            "id": "9999",
+            "key": "NEW-1",
+            "fields": {
+                "summary": "Issue testing new fields",
+                "project": {
+                    "id": "10001",
+                    "key": "NEWPROJ",
+                    "name": "New Project",
+                    "avatarUrls": {"48x48": "url"},
+                },
+                "resolution": {"id": "10002", "name": "Fixed"},
+                "duedate": "2025-01-31",
+                "resolutiondate": "2024-08-01T12:00:00.000+0000",
+                "parent": {
+                    "id": "9998",
+                    "key": "NEW-0",
+                    "fields": {"summary": "Parent Task"},
+                },
+                "subtasks": [
+                    {"id": "10000", "key": "NEW-2", "fields": {"summary": "Subtask 1"}},
+                    {"id": "10001", "key": "NEW-3", "fields": {"summary": "Subtask 2"}},
+                ],
+                "security": {"id": "10003", "name": "Dev Only"},
+                "worklog": {"total": 2, "maxResults": 20, "worklogs": []},
+            },
+        }
+        issue = JiraIssue.from_api_response(local_issue_data)
+
+        assert issue.id == "9999"
+        assert issue.key == "NEW-1"
+        assert issue.summary == "Issue testing new fields"
+
+        # Assertions for new fields using LOCAL data
+        assert isinstance(issue.project, JiraProject)
+        assert issue.project.key == "NEWPROJ"
+        assert issue.project.name == "New Project"
+        assert isinstance(issue.resolution, JiraResolution)
+        assert issue.resolution.name == "Fixed"
+        assert issue.duedate == "2025-01-31"
+        assert issue.resolutiondate == "2024-08-01T12:00:00.000+0000"
+        assert isinstance(issue.parent, dict)
+        assert issue.parent["key"] == "NEW-0"
+        assert isinstance(issue.subtasks, list)
+        assert len(issue.subtasks) == 2
+        assert issue.subtasks[0]["key"] == "NEW-2"
+        assert isinstance(issue.security, dict)
+        assert issue.security["name"] == "Dev Only"
+        assert isinstance(issue.worklog, dict)
+        assert issue.worklog["total"] == 2
+
     def test_from_api_response_with_empty_data(self):
         """Test creating a JiraIssue from empty data."""
         issue = JiraIssue.from_api_response({})
@@ -434,6 +561,135 @@ class TestJiraIssue:
         assert issue.reporter is None
         assert len(issue.labels) == 0
         assert len(issue.comments) == 0
+        assert issue.project is None
+        assert issue.resolution is None
+        assert issue.duedate is None
+        assert issue.resolutiondate is None
+        assert issue.parent is None
+        assert issue.subtasks == []
+        assert issue.security is None
+        assert issue.worklog is None
+
+    def test_to_simplified_dict(self, jira_issue_data):
+        """Test converting a JiraIssue to a simplified dictionary."""
+        # --- Test default (essential fields) ---
+        issue = JiraIssue.from_api_response(jira_issue_data)
+        simplified = issue.to_simplified_dict()
+
+        # Essential fields from original test
+        assert isinstance(simplified, dict)
+        assert "key" in simplified
+        assert simplified["key"] == "PROJ-123"
+        assert "summary" in simplified
+        assert simplified["summary"] == "Test Issue Summary"
+
+        assert "created" in simplified
+        assert isinstance(simplified["created"], str)
+        assert "updated" in simplified
+        assert isinstance(simplified["updated"], str)
+
+        if isinstance(simplified["status"], str):
+            assert simplified["status"] == "In Progress"
+        elif isinstance(simplified["status"], dict):
+            assert simplified["status"]["name"] == "In Progress"
+
+        if isinstance(simplified["issue_type"], str):
+            assert simplified["issue_type"] == "Task"
+        elif isinstance(simplified["issue_type"], dict):
+            assert simplified["issue_type"]["name"] == "Task"
+
+        if isinstance(simplified["priority"], str):
+            assert simplified["priority"] == "Medium"
+        elif isinstance(simplified["priority"], dict):
+            assert simplified["priority"]["name"] == "Medium"
+
+        assert "assignee" in simplified
+        assert "reporter" in simplified
+
+        # Test with "*all"
+        issue_all = JiraIssue.from_api_response(
+            jira_issue_data, requested_fields="*all"
+        )
+        simplified_all = issue_all.to_simplified_dict()
+
+        # Check keys for all standard fields (new and old) are present
+        all_standard_keys = {
+            "id",
+            "key",
+            "summary",
+            "description",
+            "created",
+            "updated",
+            "status",
+            "issue_type",
+            "priority",
+            "assignee",
+            "reporter",
+            "labels",
+            "components",
+            "timetracking",
+            "comments",
+            "attachments",
+            "url",
+            "epic_key",
+            "epic_name",
+            "fix_versions",
+            "project",
+            "resolution",
+            "duedate",
+            "resolutiondate",
+            "parent",
+            "subtasks",
+            "security",
+            "worklog",
+            # Custom fields present in the mock data should be at the root level when requesting *all
+            "customfield_10011",
+            "customfield_10014",
+            "customfield_10001",
+            "customfield_10002",
+            "customfield_10003",
+        }
+        assert all_standard_keys.issubset(simplified_all.keys())
+
+        # Check values for new fields based on mock data
+        assert simplified_all["project"]["key"] == "PROJ"
+        assert simplified_all["resolution"]["name"] == "Fixed"
+        assert simplified_all["duedate"] == "2024-12-31"
+        assert simplified_all["resolutiondate"] == "2024-01-15T11:00:00.000+0000"
+        assert simplified_all["parent"]["key"] == "PROJ-122"
+        assert len(simplified_all["subtasks"]) == 1
+        assert simplified_all["security"]["name"] == "Internal"
+        assert isinstance(simplified_all["worklog"], dict)
+
+        requested = [
+            "key",
+            "summary",
+            "project",
+            "resolution",
+            "subtasks",
+            "customfield_10011",
+        ]
+        issue_specific = JiraIssue.from_api_response(
+            jira_issue_data, requested_fields=requested
+        )
+        simplified_specific = issue_specific.to_simplified_dict()
+
+        # Check the requested keys are present
+        assert set(simplified_specific.keys()) == {
+            "id",
+            "key",
+            "summary",
+            "project",
+            "resolution",
+            "subtasks",
+            "customfield_10011",
+        }
+
+        # Check values based on mock data
+        assert simplified_specific["project"]["key"] == "PROJ"
+        assert simplified_specific["resolution"]["name"] == "Fixed"
+        assert len(simplified_specific["subtasks"]) == 1
+        assert simplified_specific["customfield_10011"] == "Epic Name Example"
 
     def test_find_custom_field_in_api_response(self):
         """Test the _find_custom_field_in_api_response method with different field patterns."""
@@ -580,73 +836,18 @@ class TestJiraIssue:
         assert issue.epic_key == "EPIC-789"
         assert issue.epic_name == "Special Epic Name"
 
-    def test_to_simplified_dict(self, jira_issue_data):
-        """Test converting a JiraIssue to a simplified dictionary."""
+    def test_jira_issue_with_custom_fields(self, jira_issue_data):
+        """Test JiraIssue handling of custom fields."""
         issue = JiraIssue.from_api_response(jira_issue_data)
         simplified = issue.to_simplified_dict()
-
-        assert isinstance(simplified, dict)
-        assert "key" in simplified
         assert simplified["key"] == "PROJ-123"
-        assert "summary" in simplified
         assert simplified["summary"] == "Test Issue Summary"
-
-        assert "created" in simplified
-        assert isinstance(simplified["created"], str)
-        assert "updated" in simplified
-        assert isinstance(simplified["updated"], str)
-
-        assert "status" in simplified
-        if isinstance(simplified["status"], str):
-            assert simplified["status"] == "In Progress"
-        elif isinstance(simplified["status"], dict):
-            assert simplified["status"]["name"] == "In Progress"
-
-        assert "issue_type" in simplified
-        if isinstance(simplified["issue_type"], str):
-            assert simplified["issue_type"] == "Task"
-        elif isinstance(simplified["issue_type"], dict):
-            assert simplified["issue_type"]["name"] == "Task"
-
-        assert "priority" in simplified
-        if isinstance(simplified["priority"], str):
-            assert simplified["priority"] == "Medium"
-        elif isinstance(simplified["priority"], dict):
-            assert simplified["priority"]["name"] == "Medium"
-
-        assert "assignee" in simplified
-        assert "reporter" in simplified
-
-        # Test with "*all"
-        issue = JiraIssue.from_api_response(jira_issue_data, requested_fields="*all")
-        simplified = issue.to_simplified_dict()
-        assert "labels" in simplified
-        assert "comments" in simplified
-        assert len(simplified["comments"]) > 0
-
-    def test_jira_issue_with_custom_fields(self):
-        """Test JiraIssue handling of custom fields."""
-        issue_data = {
-            "id": "10001",
-            "key": "TEST-123",
-            "fields": {
-                "summary": "Test issue",
-                "customfield_10001": "Simple string value",
-                "customfield_10002": {"value": "Option value"},
-                "customfield_10003": [{"value": "Item 1"}, {"value": "Item 2"}],
-            },
-        }
-
-        issue = JiraIssue.from_api_response(issue_data)
-        simplified = issue.to_simplified_dict()
-        assert simplified["key"] == "TEST-123"
-        assert simplified["summary"] == "Test issue"
         assert "customfield_10001" not in simplified
         assert "customfield_10002" not in simplified
         assert "customfield_10003" not in simplified
 
         issue = JiraIssue.from_api_response(
-            issue_data, requested_fields="summary,customfield_10001"
+            jira_issue_data, requested_fields="summary,customfield_10001"
         )
         simplified = issue.to_simplified_dict()
         assert "key" in simplified
@@ -655,7 +856,7 @@ class TestJiraIssue:
         assert "customfield_10002" not in simplified
 
         issue = JiraIssue.from_api_response(
-            issue_data, requested_fields=["key", "customfield_10002"]
+            jira_issue_data, requested_fields=["key", "customfield_10002"]
         )
         simplified = issue.to_simplified_dict()
         assert "key" in simplified
@@ -663,7 +864,7 @@ class TestJiraIssue:
         assert "summary" not in simplified
         assert "customfield_10001" not in simplified
 
-        issue = JiraIssue.from_api_response(issue_data, requested_fields="*all")
+        issue = JiraIssue.from_api_response(jira_issue_data, requested_fields="*all")
         simplified = issue.to_simplified_dict()
         assert "key" in simplified
         assert "summary" in simplified
@@ -671,82 +872,82 @@ class TestJiraIssue:
         assert "customfield_10002" in simplified
         assert "customfield_10003" in simplified
 
-    def test_jira_issue_with_default_fields(self):
-        """Test that JiraIssue returns only essential fields by default."""
-        issue_data = {
-            "id": "10001",
-            "key": "TEST-123",
-            "fields": {
-                "summary": "Test issue",
-                "description": "Description",
-                "status": {"name": "Open"},
-                "assignee": {"displayName": "Test User"},
-                "reporter": {"displayName": "Reporter User"},
-                "priority": {"name": "Medium"},
-                "created": "2023-01-01T00:00:00.000+0000",
-                "updated": "2023-01-02T00:00:00.000+0000",
-                "issuetype": {"name": "Task"},
-                "customfield_10001": "Custom value",
-                "customfield_10002": {"value": "Option value"},
-            },
-        }
+        issue_specific = JiraIssue.from_api_response(
+            jira_issue_data, requested_fields="key,customfield_10014"
+        )
+        simplified_specific = issue_specific.to_simplified_dict()
+        assert "customfield_10014" in simplified_specific
+        assert simplified_specific.get("customfield_10014") == "EPIC-KEY-1"
 
-        issue = JiraIssue.from_api_response(issue_data)
+    def test_jira_issue_with_default_fields(self, jira_issue_data):
+        """Test that JiraIssue returns only essential fields by default."""
+        issue = JiraIssue.from_api_response(jira_issue_data)
         simplified = issue.to_simplified_dict()
-        assert "key" in simplified
-        assert "summary" in simplified
-        assert "description" in simplified
-        assert "status" in simplified
+        # Check essential fields ARE present
+        essential_keys = {
+            "id",
+            "key",
+            "summary",
+            "url",
+            "description",
+            "status",
+            "issue_type",
+            "priority",
+            "project",
+            "resolution",
+            "duedate",
+            "resolutiondate",
+            "parent",
+            "subtasks",
+            "security",
+            "worklog",
+            "assignee",
+            "reporter",
+            "labels",
+            "components",
+            "fix_versions",
+            "epic_key",
+            "epic_name",
+            "timetracking",
+            "created",
+            "updated",
+            "comments",
+            "attachments",
+        }
+        # We check if the key is present; value might be None if not in source data
+        for key in essential_keys:
+            assert key in simplified, (
+                f"Essential key '{key}' missing from default simplified dict"
+            )
         assert "customfield_10001" not in simplified
         assert "customfield_10002" not in simplified
 
-        issue = JiraIssue.from_api_response(issue_data, requested_fields="*all")
+        issue = JiraIssue.from_api_response(jira_issue_data, requested_fields="*all")
         simplified = issue.to_simplified_dict()
         assert "customfield_10001" in simplified
         assert "customfield_10002" in simplified
 
-    def test_timetracking_field_processing(self):
+    def test_timetracking_field_processing(self, jira_issue_data):
         """Test that timetracking data is properly processed."""
-        issue_data = {
-            "id": "10000",
-            "key": "TEST-123",
-            "fields": {
-                "summary": "Test Issue",
-                "description": "Test Description",
-                "timetracking": {
-                    "originalEstimate": "2h",
-                    "remainingEstimate": "1h 30m",
-                    "timeSpent": "30m",
-                    "originalEstimateSeconds": 7200,
-                    "remainingEstimateSeconds": 5400,
-                    "timeSpentSeconds": 1800,
-                },
-            },
-        }
-
-        issue = JiraIssue.from_api_response(issue_data)
+        issue = JiraIssue.from_api_response(jira_issue_data)
         assert issue.timetracking is not None
-        assert issue.timetracking.original_estimate == "2h"
-        assert issue.timetracking.remaining_estimate == "1h 30m"
-        assert issue.timetracking.time_spent == "30m"
-        assert issue.timetracking.original_estimate_seconds == 7200
-        assert issue.timetracking.remaining_estimate_seconds == 5400
-        assert issue.timetracking.time_spent_seconds == 1800
+        assert issue.timetracking.original_estimate == "1d"
+        assert issue.timetracking.remaining_estimate == "4h"
+        assert issue.timetracking.time_spent == "4h"
+        assert issue.timetracking.original_estimate_seconds == 28800
+        assert issue.timetracking.remaining_estimate_seconds == 14400
+        assert issue.timetracking.time_spent_seconds == 14400
 
         issue.requested_fields = "*all"
         simplified = issue.to_simplified_dict()
         assert "timetracking" in simplified
-        assert simplified["timetracking"]["original_estimate"] == "2h"
-        assert simplified["timetracking"]["remaining_estimate"] == "1h 30m"
+        assert simplified["timetracking"]["original_estimate"] == "1d"
+        assert simplified["timetracking"]["remaining_estimate"] == "4h"
 
         issue.requested_fields = ["summary", "timetracking"]
         simplified = issue.to_simplified_dict()
         assert "timetracking" in simplified
-        assert simplified["timetracking"]["original_estimate"] == "2h"
-
-        issue.requested_fields = None
-        simplified = issue.to_simplified_dict()
-        assert "timetracking" not in simplified
+        assert simplified["timetracking"]["original_estimate"] == "1d"
 
 
 class TestJiraSearchResult:
@@ -1003,43 +1204,126 @@ class TestJiraWorklog:
 class TestRealJiraData:
     """Tests using real Jira data (optional)."""
 
+    # Helper to get client/config
+    def _get_client(self) -> IssuesMixin | None:
+        if not real_api_available:
+            return None
+        try:
+            config = JiraConfig.from_env()
+            return IssuesMixin(config=config)
+        except ValueError:
+            pytest.skip("Real Jira environment not configured")
+            return None
+
+    def _get_project_client(self) -> ProjectsMixin | None:
+        if not real_api_available:
+            return None
+        try:
+            config = JiraConfig.from_env()
+            return ProjectsMixin(config=config)
+        except ValueError:
+            pytest.skip("Real Jira environment not configured")
+            return None
+
+    def _get_transition_client(self) -> TransitionsMixin | None:
+        if not real_api_available:
+            return None
+        try:
+            config = JiraConfig.from_env()
+            return TransitionsMixin(config=config)
+        except ValueError:
+            pytest.skip("Real Jira environment not configured")
+            return None
+
+    def _get_worklog_client(self) -> WorklogMixin | None:
+        if not real_api_available:
+            return None
+        try:
+            config = JiraConfig.from_env()
+            return WorklogMixin(config=config)
+        except ValueError:
+            pytest.skip("Real Jira environment not configured")
+            return None
+
+    def _get_base_jira_client(self) -> Jira | None:
+        if not real_api_available:
+            return None
+        try:
+            config = JiraConfig.from_env()
+            if config.auth_type == "basic":
+                return Jira(
+                    url=config.url,
+                    username=config.username,
+                    password=config.api_token,
+                    cloud=config.is_cloud,
+                )
+            else:  # token
+                return Jira(
+                    url=config.url, token=config.personal_token, cloud=config.is_cloud
+                )
+        except ValueError:
+            pytest.skip("Real Jira environment not configured")
+            return None
+
     def test_real_jira_issue(self, use_real_jira_data, default_jira_issue_key):
         """Test that the JiraIssue model works with real Jira API data."""
         if not use_real_jira_data:
             pytest.skip("Skipping real Jira data test")
+        issues_client = self._get_client()
+        if not issues_client or not default_jira_issue_key:
+            pytest.skip("Real Jira client/issue key not available")
 
-        from src.mcp_atlassian.jira.config import JiraConfig
-        from src.mcp_atlassian.jira.issues import IssuesMixin
-
-        config = JiraConfig.from_env()
-        issues_client = IssuesMixin(config=config)
-        issue = issues_client.get_issue(default_jira_issue_key)
-
-        assert isinstance(issue, JiraIssue)
-        assert issue.key == default_jira_issue_key
-        assert issue.id is not None
-        assert issue.summary is not None
-
-        simplified = issue.to_simplified_dict()
-        assert simplified["key"] == default_jira_issue_key
+        try:
+            issue = issues_client.get_issue(default_jira_issue_key)
+            assert isinstance(issue, JiraIssue)
+            assert issue.key == default_jira_issue_key
+            assert issue.id is not None
+            assert issue.summary is not None
+
+            assert hasattr(issue, "project")
+            assert issue.project is None or isinstance(issue.project, JiraProject)
+            assert hasattr(issue, "resolution")
+            assert issue.resolution is None or isinstance(
+                issue.resolution, JiraResolution
+            )
+            assert hasattr(issue, "duedate")
+            assert issue.duedate is None or isinstance(issue.duedate, str)
+            assert hasattr(issue, "resolutiondate")
+            assert issue.resolutiondate is None or isinstance(issue.resolutiondate, str)
+            assert hasattr(issue, "parent")
+            assert issue.parent is None or isinstance(issue.parent, dict)
+            assert hasattr(issue, "subtasks")
+            assert isinstance(issue.subtasks, list)
+            if issue.subtasks:
+                assert isinstance(issue.subtasks[0], dict)
+            assert hasattr(issue, "security")
+            assert issue.security is None or isinstance(issue.security, dict)
+            assert hasattr(issue, "worklog")
+            assert issue.worklog is None or isinstance(issue.worklog, dict)
+
+            simplified = issue.to_simplified_dict()
+            assert simplified["key"] == default_jira_issue_key
+        except Exception as e:
+            pytest.fail(f"Error testing real Jira issue: {e}")
 
     def test_real_jira_project(self, use_real_jira_data):
         """Test that the JiraProject model works with real Jira API data."""
         if not use_real_jira_data:
             pytest.skip("Skipping real Jira data test")
+        projects_client = self._get_project_client()
+        if not projects_client:
+            pytest.skip("Real Jira client not available")
 
         # Check for JIRA_TEST_ISSUE_KEY explicitly
         if not os.environ.get("JIRA_TEST_ISSUE_KEY"):
             pytest.skip("JIRA_TEST_ISSUE_KEY environment variable not set")
 
-        from src.mcp_atlassian.jira.config import JiraConfig
-        from src.mcp_atlassian.jira.projects import ProjectsMixin
-
-        config = JiraConfig.from_env()
-        projects_client = ProjectsMixin(config=config)
         default_issue_key = os.environ.get("JIRA_TEST_ISSUE_KEY")
         project_key = default_issue_key.split("-")[0]
 
+        if not project_key:
+            pytest.skip("Could not extract project key from JIRA_TEST_ISSUE_KEY")
+
         try:
             project = projects_client.get_project_model(project_key)
 
@@ -1055,103 +1339,103 @@ class TestRealJiraData:
             assert simplified["key"] == project_key
         except (AttributeError, TypeError, ValueError) as e:
             pytest.skip(f"Error parsing project data: {e}")
+        except Exception as e:
+            pytest.fail(f"Error testing real Jira project: {e}")
 
     def test_real_jira_transitions(self, use_real_jira_data, default_jira_issue_key):
         """Test that the JiraTransition model works with real Jira API data."""
         if not use_real_jira_data:
             pytest.skip("Skipping real Jira data test")
+        transitions_client = self._get_transition_client()
+        if not transitions_client or not default_jira_issue_key:
+            pytest.skip("Real Jira client/issue key not available")
 
-        from atlassian import Jira
-
-        from src.mcp_atlassian.jira.config import JiraConfig
-        from src.mcp_atlassian.models.jira import JiraTransition
+        # Use the underlying Atlassian API client directly for raw data
+        jira = self._get_base_jira_client()
+        if not jira:
+            pytest.skip("Base Jira client failed")
 
+        transitions_data = None  # Initialize
         try:
-            config = JiraConfig.from_env()
-            jira = Jira(
-                url=config.url,
-                username=config.username,
-                password=config.api_token,
-                cloud=config.is_cloud,
-            )
-
             transitions_data = jira.get_issue_transitions(default_jira_issue_key)
 
-            if not transitions_data or len(transitions_data) == 0:
-                pytest.skip("No transitions available for test issue")
-
-            assert len(transitions_data) > 0
-            for transition_item in transitions_data:
-                if "id" in transition_item and not isinstance(
-                    transition_item["id"], str
-                ):
-                    transition_item["id"] = str(transition_item["id"])
-
-                if isinstance(transition_item.get("to"), str):
-                    to_status_name = transition_item.get("to")
-                    transition_item["to"] = {
-                        "id": str(transition_item.get("id", "")),
-                        "name": to_status_name,
-                    }
+            actual_transitions_list = []
+            if isinstance(transitions_data, list):
+                actual_transitions_list = transitions_data
+            else:
+                # Handle unexpected format with test failure
+                pytest.fail(
+                    f"Unexpected transitions data format received from API: "
+                    f"{type(transitions_data)}. Data: {transitions_data}"
+                )
+
+            # Verify transitions list is actually a list
+            assert isinstance(actual_transitions_list, list)
 
-                transition = JiraTransition.from_api_response(transition_item)
-                assert isinstance(transition, JiraTransition)
-                assert transition.id is not None
-                assert transition.name is not None
+            if not actual_transitions_list:
+                pytest.skip(f"No transitions found for issue {default_jira_issue_key}")
 
-                if transition.to_status:
-                    assert transition.to_status.name is not None
+            transition_item = actual_transitions_list[0]
+            assert isinstance(transition_item, dict)
 
-                simplified = transition.to_simplified_dict()
-                assert "id" in simplified
-                assert "name" in simplified
+            # Check for essential keys in the raw data
+            assert "id" in transition_item
+            assert "name" in transition_item
+            assert "to" in transition_item
+
+            # Only check 'to' field name if it's a dictionary
+            if isinstance(transition_item["to"], dict):
+                assert "name" in transition_item["to"]
+
+            # Convert to model
+            transition = JiraTransition.from_api_response(transition_item)
+            assert isinstance(transition, JiraTransition)
+            assert transition.id == str(transition_item["id"])  # Ensure ID is string
+            assert transition.name == transition_item["name"]
+
+            simplified = transition.to_simplified_dict()
+            assert simplified["id"] == str(transition_item["id"])
+            assert simplified["name"] == transition_item["name"]
 
         except Exception as e:
-            pytest.skip(f"Error getting transitions: {str(e)}")
+            # Include data type details in error message
+            error_details = f"Received data type: {type(transitions_data)}"
+            if transitions_data is not None:
+                error_details += (
+                    f", Data: {str(transitions_data)[:200]}..."  # Show partial data
+                )
+
+            pytest.fail(
+                f"Error testing real Jira transitions for issue {default_jira_issue_key}: {e}. {error_details}"
+            )
 
     def test_real_jira_worklog(self, use_real_jira_data, default_jira_issue_key):
         """Test that the JiraWorklog model works with real Jira API data."""
         if not use_real_jira_data:
             pytest.skip("Skipping real Jira data test")
-
-        from atlassian import Jira
-
-        from src.mcp_atlassian.jira.config import JiraConfig
-        from src.mcp_atlassian.models.jira import JiraWorklog
+        worklog_client = self._get_worklog_client()
+        if not worklog_client or not default_jira_issue_key:
+            pytest.skip("Real Jira client/issue key not available")
 
         try:
-            config = JiraConfig.from_env()
-            jira = Jira(
-                url=config.url,
-                username=config.username,
-                password=config.api_token,
-                cloud=config.is_cloud,
-            )
-
-            issue = jira.issue(default_jira_issue_key)
-            if not issue:
-                pytest.skip(f"Could not access issue {default_jira_issue_key}")
-
-            # Using test data as API access might be limited or issue may have no worklogs
-            test_worklog_data = {
-                "id": "12345",
-                "timeSpent": "1h",
-                "timeSpentSeconds": 3600,
-                "author": {"displayName": "Test User"},
-                "created": "2023-01-01T12:00:00.000+0000",
-            }
+            # Get worklogs using the model method
+            worklogs = worklog_client.get_worklog_models(default_jira_issue_key)
+            assert isinstance(worklogs, list)
 
-            worklog = JiraWorklog.from_api_response(test_worklog_data)
+            if not worklogs:
+                pytest.skip(f"Issue {default_jira_issue_key} has no worklogs to test.")
 
+            # Test the first worklog
+            worklog = worklogs[0]
             assert isinstance(worklog, JiraWorklog)
-            assert worklog.id == "12345"
-            assert worklog.time_spent == "1h"
-            assert worklog.time_spent_seconds == 3600
+            assert worklog.id is not None
+            assert worklog.time_spent_seconds >= 0
+            if worklog.author:
+                assert isinstance(worklog.author, JiraUser)
 
             simplified = worklog.to_simplified_dict()
             assert "id" in simplified
             assert "time_spent" in simplified
-            assert simplified["time_spent"] == "1h"
 
         except Exception as e:
-            pytest.skip(f"Error in worklog test: {str(e)}")
+            pytest.fail(f"Error testing real Jira worklog: {e}")