Skip to content

Commit b232c65

Browse files
author
JKAW
committed
fix: address CI failures
- Fix ruff-format: multi-line list comprehension in search.py - Replace Path.resolve() with direct Path.is_relative_to() to avoid breaking os.path mocks in tests (resolve() internally calls os.path.isabs on Python 3.12) - Add os.getcwd mocks to download_issue_attachments tests that use /tmp/attachments paths
1 parent 9855b7c commit b232c65

3 files changed

Lines changed: 27 additions & 18 deletions

File tree

src/mcp_atlassian/jira/attachments.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,11 @@ def download_attachment(self, url: str, target_path: str) -> bool:
3838
target_path = os.path.abspath(target_path)
3939

4040
# Guard against path traversal
41-
base_dir = Path(os.getcwd()).resolve()
42-
resolved = Path(target_path).resolve()
43-
if not resolved.is_relative_to(base_dir):
41+
base_dir = os.getcwd()
42+
if not Path(target_path).is_relative_to(base_dir):
4443
raise ValueError(
45-
f"Path traversal detected: {resolved} is outside {base_dir}"
44+
f"Path traversal detected: {target_path} is outside {base_dir}"
4645
)
47-
target_path = str(resolved)
4846

4947
logger.info(f"Downloading attachment from {url} to {target_path}")
5048

@@ -217,13 +215,11 @@ def download_issue_attachments(
217215
target_dir = os.path.abspath(target_dir)
218216

219217
# Guard against path traversal
220-
base_dir = Path(os.getcwd()).resolve()
221-
resolved_dir = Path(target_dir).resolve()
222-
if not resolved_dir.is_relative_to(base_dir):
218+
base_dir = os.getcwd()
219+
if not Path(target_dir).is_relative_to(base_dir):
223220
raise ValueError(
224-
f"Path traversal detected: {resolved_dir} is outside {base_dir}"
221+
f"Path traversal detected: {target_dir} is outside {base_dir}"
225222
)
226-
target_dir = str(resolved_dir)
227223

228224
logger.info(
229225
f"Downloading attachments for {issue_key} to directory: {target_dir}"

src/mcp_atlassian/jira/search.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ def search_issues(
6060
# Build the project filter query part
6161
# Sanitize project names to prevent JQL injection
6262
# Escape backslashes before double-quotes to prevent bypass
63-
projects = [p.replace('\\', '\\\\').replace('"', '\\"') for p in projects]
63+
projects = [
64+
p.replace("\\", "\\\\").replace('"', '\\"') for p in projects
65+
]
6466

6567
if len(projects) == 1:
6668
project_query = f'project = "{projects[0]}"'

tests/unit/jira/test_attachments.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ def test_download_issue_attachments_success(
240240
"mcp_atlassian.models.jira.JiraAttachment.from_api_response",
241241
side_effect=[mock_attachment1, mock_attachment2],
242242
),
243+
patch("os.getcwd", return_value="/tmp"),
243244
):
244245
result = attachments_mixin.download_issue_attachments(
245246
"TEST-123", "/tmp/attachments"
@@ -290,6 +291,7 @@ def test_download_issue_attachments_relative_path(
290291
),
291292
patch("os.path.isabs") as mock_isabs,
292293
patch("os.path.abspath") as mock_abspath,
294+
patch("os.getcwd", return_value="/absolute/path"),
293295
):
294296
mock_isabs.return_value = False
295297
mock_abspath.return_value = "/absolute/path/attachments"
@@ -311,7 +313,10 @@ def test_download_issue_attachments_no_attachments(
311313
mock_issue = {"fields": {"attachment": []}}
312314
attachments_mixin.jira.issue.return_value = mock_issue
313315

314-
with patch("pathlib.Path.mkdir") as mock_mkdir:
316+
with (
317+
patch("pathlib.Path.mkdir") as mock_mkdir,
318+
patch("os.getcwd", return_value="/tmp"),
319+
):
315320
result = attachments_mixin.download_issue_attachments(
316321
"TEST-123", "/tmp/attachments"
317322
)
@@ -329,9 +334,12 @@ def test_download_issue_attachments_issue_not_found(
329334
"""Test download when issue cannot be retrieved."""
330335
attachments_mixin.jira.issue.return_value = None
331336

332-
with pytest.raises(
333-
TypeError,
334-
match="Unexpected return value type from `jira.issue`: <class 'NoneType'>",
337+
with (
338+
patch("os.getcwd", return_value="/tmp"),
339+
pytest.raises(
340+
TypeError,
341+
match="Unexpected return value type from `jira.issue`: <class 'NoneType'>",
342+
),
335343
):
336344
attachments_mixin.download_issue_attachments("TEST-123", "/tmp/attachments")
337345

@@ -343,9 +351,10 @@ def test_download_issue_attachments_no_fields(
343351
mock_issue = {} # Missing 'fields' key
344352
attachments_mixin.jira.issue.return_value = mock_issue
345353

346-
result = attachments_mixin.download_issue_attachments(
347-
"TEST-123", "/tmp/attachments"
348-
)
354+
with patch("os.getcwd", return_value="/tmp"):
355+
result = attachments_mixin.download_issue_attachments(
356+
"TEST-123", "/tmp/attachments"
357+
)
349358

350359
# Assertions
351360
assert result["success"] is False
@@ -395,6 +404,7 @@ def test_download_issue_attachments_some_failures(
395404
"mcp_atlassian.models.jira.JiraAttachment.from_api_response",
396405
side_effect=[mock_attachment1, mock_attachment2],
397406
),
407+
patch("os.getcwd", return_value="/tmp"),
398408
):
399409
result = attachments_mixin.download_issue_attachments(
400410
"TEST-123", "/tmp/attachments"
@@ -439,6 +449,7 @@ def test_download_issue_attachments_missing_url(
439449
"mcp_atlassian.models.jira.JiraAttachment.from_api_response",
440450
return_value=mock_attachment,
441451
),
452+
patch("os.getcwd", return_value="/tmp"),
442453
):
443454
result = attachments_mixin.download_issue_attachments(
444455
"TEST-123", "/tmp/attachments"

0 commit comments

Comments
 (0)