|
9 | 9 | from mcp.types import EmbeddedResource, TextContent |
10 | 10 |
|
11 | 11 | from mcp_atlassian.confluence.attachments import AttachmentsMixin |
| 12 | +from mcp_atlassian.confluence.config import ConfluenceConfig |
12 | 13 |
|
13 | 14 | # Test scenarios for AttachmentsMixin |
14 | 15 | # |
@@ -1487,3 +1488,88 @@ def test_download_content_attachments_absolute_escape( |
1487 | 1488 | """download_content_attachments rejects directory escape.""" |
1488 | 1489 | with pytest.raises(ValueError, match="Path traversal detected"): |
1489 | 1490 | confluence_mixin.download_content_attachments("12345", "/etc") |
| 1491 | + |
| 1492 | + |
| 1493 | +class TestResolveAttachmentDownloadUrl: |
| 1494 | + """Tests for AttachmentsMixin._resolve_attachment_download_url. |
| 1495 | +
|
| 1496 | + Covers the CONFLUENCE_ATTACHMENT_DOWNLOAD_USE_V1 Cloud workaround: when |
| 1497 | + enabled, the (removed) legacy /download/attachments/... link is rewritten to |
| 1498 | + the v1 REST endpoint; otherwise the original link is preserved. |
| 1499 | + """ |
| 1500 | + |
| 1501 | + def _make_mixin( |
| 1502 | + self, *, use_v1: bool | None, url: str = "https://example.atlassian.net/wiki" |
| 1503 | + ) -> AttachmentsMixin: |
| 1504 | + with patch( |
| 1505 | + "mcp_atlassian.confluence.attachments.ConfluenceClient.__init__", |
| 1506 | + return_value=None, |
| 1507 | + ): |
| 1508 | + mixin = AttachmentsMixin() |
| 1509 | + config = ConfluenceConfig(url=url, auth_type="basic") |
| 1510 | + config.attachment_download_use_v1 = use_v1 |
| 1511 | + mixin.config = config |
| 1512 | + return mixin |
| 1513 | + |
| 1514 | + def test_v1_enabled_builds_rest_endpoint(self) -> None: |
| 1515 | + mixin = self._make_mixin(use_v1=True) |
| 1516 | + url = mixin._resolve_attachment_download_url( |
| 1517 | + "/download/attachments/123/foo.png?version=1&api=v2", |
| 1518 | + attachment_id="att999", |
| 1519 | + ) |
| 1520 | + assert url == ( |
| 1521 | + "https://example.atlassian.net/wiki" |
| 1522 | + "/rest/api/content/123/child/attachment/att999/download?version=1" |
| 1523 | + ) |
| 1524 | + |
| 1525 | + def test_v1_preserves_only_version_param(self) -> None: |
| 1526 | + # The v1 download endpoint documents only ``version``; keep that (so a |
| 1527 | + # version-specific link doesn't fall back to latest) and drop the other |
| 1528 | + # legacy query params (cacheVersion / api / ...). |
| 1529 | + mixin = self._make_mixin(use_v1=True) |
| 1530 | + url = mixin._resolve_attachment_download_url( |
| 1531 | + "/download/attachments/123/foo.png?version=3&cacheVersion=1&api=v2", |
| 1532 | + attachment_id="att999", |
| 1533 | + ) |
| 1534 | + assert url.endswith("/att999/download?version=3") |
| 1535 | + assert "cacheVersion" not in url |
| 1536 | + assert "api=v2" not in url |
| 1537 | + |
| 1538 | + def test_v1_enabled_uses_explicit_content_id(self) -> None: |
| 1539 | + mixin = self._make_mixin(use_v1=True) |
| 1540 | + url = mixin._resolve_attachment_download_url( |
| 1541 | + "/download/attachments/123/foo.png", |
| 1542 | + attachment_id="att999", |
| 1543 | + content_id="555", |
| 1544 | + ) |
| 1545 | + assert "/rest/api/content/555/child/attachment/att999/download" in url |
| 1546 | + |
| 1547 | + def test_disabled_returns_legacy_link(self) -> None: |
| 1548 | + mixin = self._make_mixin(use_v1=False) |
| 1549 | + url = mixin._resolve_attachment_download_url( |
| 1550 | + "/download/attachments/123/foo.png", attachment_id="att999" |
| 1551 | + ) |
| 1552 | + assert "/download/attachments/123/" in url |
| 1553 | + assert "/child/attachment/" not in url |
| 1554 | + |
| 1555 | + def test_missing_attachment_id_falls_back_to_legacy(self) -> None: |
| 1556 | + mixin = self._make_mixin(use_v1=True) |
| 1557 | + url = mixin._resolve_attachment_download_url( |
| 1558 | + "/download/attachments/123/foo.png", attachment_id=None |
| 1559 | + ) |
| 1560 | + assert "/download/attachments/123/" in url |
| 1561 | + assert "/child/attachment/" not in url |
| 1562 | + |
| 1563 | + def test_auto_cloud_uses_v1(self) -> None: |
| 1564 | + mixin = self._make_mixin(use_v1=None) |
| 1565 | + url = mixin._resolve_attachment_download_url( |
| 1566 | + "/download/attachments/123/foo.png", attachment_id="att999" |
| 1567 | + ) |
| 1568 | + assert "/rest/api/content/123/child/attachment/att999/download" in url |
| 1569 | + |
| 1570 | + def test_auto_server_dc_returns_legacy(self) -> None: |
| 1571 | + mixin = self._make_mixin(use_v1=None, url="https://confluence.example.com") |
| 1572 | + url = mixin._resolve_attachment_download_url( |
| 1573 | + "/download/attachments/123/foo.png", attachment_id="att999" |
| 1574 | + ) |
| 1575 | + assert "/child/attachment/" not in url |
0 commit comments