arcade-mcp/toolkits/confluence/tests/test_utils.py
Eric Gustin 6bc6f0c574
Add Confluence toolkit (#406)
12 tools for the new Confluence toolkit.

| Name | Description |

|----------------------------------|------------------------------------------------------------------------------------|
| Confluence.CreatePage | Create a new page at the root of the given
space. |
| Confluence.UpdatePageContent | Update a page's content. |
| Confluence.RenamePage | Rename a page by changing its title. |
| Confluence.GetPage | Retrieve a SINGLE page's content by its ID or
title. |
| Confluence.GetPagesById | Get the content of MULTIPLE pages by their
ID in a single efficient request. |
| Confluence.ListPages | Get the content of multiple pages by their ID.
|
| Confluence.ListAttachments | List attachments in a workspace. |
| Confluence.GetAttachmentsForPage | Get attachments for a page by its
ID or title. |
| Confluence.SearchContent | Search for content in Confluence. |
| Confluence.GetSpace | Get the details of a space by its ID or key. |
| Confluence.ListSpaces | List all spaces sorted by name in ascending
order. |
| Confluence.GetSpaceHierarchy | Retrieve the full hierarchical
structure of a Confluence space as a tree structure.|

### Confluence Clients
Confluence has deprecated most of their V1 endpoints, so most of the
tools use V2. However, we still need a V1 client to support search. The
V1 search API has not been deprecated yet, because there is no V2
equivalent. But we need to be aware in the future for when this
deprecation happens.


### Future work
* Content of pages are returned in the Confluence `storage` format. This
is the format that Confluence uses to store pages internally. We should
understand the storage format more deeply, and write utility function to
transform this format into plain text.
* Better protections against extremely large pages. I've tested up to
6,000 word pages.
* Tools for blog posts
* Tool for getting the children of a page. (`get_space_hierarchy` will
suffice for now)
* Allow for numerical titles
2025-05-22 13:18:19 -07:00

195 lines
8.1 KiB
Python

import pytest
from arcade.sdk.errors import RetryableToolError, ToolExecutionError
from arcade_confluence.utils import build_child_url, build_hierarchy, validate_ids
@pytest.mark.parametrize(
"ids, max_length, expected_error",
[
(None, 250, None),
(["123", "456"], 250, None),
(["123", "foo"], 250, ToolExecutionError),
(["123", "456"], 1, RetryableToolError),
],
)
def test_validate_ids(ids: list[str], max_length: int, expected_error: Exception | None) -> None:
if expected_error:
with pytest.raises(expected_error):
validate_ids(ids, max_length)
else:
validate_ids(ids, max_length)
@pytest.mark.parametrize(
"base_url, child, expected",
[
( # Published page
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT",
{"type": "page", "title": "Test Title-1", "id": "123", "status": "current"},
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT/pages/123/Test+Title-1",
),
( # Draft page
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT",
{"type": "page", "title": "Test Title-1", "id": "123", "status": "draft"},
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT/pages/edit-v2/123",
),
( # Whiteboard
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT",
{"type": "whiteboard", "title": "Test Title-1", "id": "123", "status": "current"},
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT/whiteboard/123",
),
( # Database
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT",
{"type": "database", "title": "Test Title-1", "id": "123", "status": "current"},
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT/database/123",
),
( # Embed
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT",
{"type": "embed", "title": "Test Title-1", "id": "123", "status": "current"},
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT/embed/123",
),
( # Folder
"https://tes.atlassian.net/wiki/spaces/SOFTWAREDEVELOPMENT",
{"type": "folder", "title": "Test Title-1", "id": "123", "status": "current"},
None, # Folders don't have URLs
),
],
)
def test_build_child_url(base_url: str, child: dict, expected: str) -> None:
assert build_child_url(base_url, child) == expected
@pytest.mark.parametrize(
"input_transformed_children, input_parent_id, input_parent_node, expected_parent_node",
[
( # Parent node is a leaf
[],
"2195457",
{
"title": "A One Sentence Story About Trees",
"id": "2195457",
"type": "page",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2195457/A+One+Sentence+Story+About+Trees",
"children": [],
},
{
"title": "A One Sentence Story About Trees",
"id": "2195457",
"type": "page",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2195457/A+One+Sentence+Story+About+Trees",
"children": [],
},
),
(
[
{
"title": "The Importance of Trees",
"id": "2555906",
"type": "page",
"parent_id": "2162740",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2555906/The+Importance+of+Trees",
"children": [],
"status": "current",
},
{
"title": "Erics page",
"id": "2686977",
"type": "page",
"parent_id": "2555906",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2686977/Erics+page",
"children": [],
"status": "current",
},
{
"title": "Erics page",
"id": "2719745",
"type": "page",
"parent_id": "2555906",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/edit-v2/2719745",
"children": [],
"status": "draft",
},
{
"title": "Execute tools",
"id": "2621441",
"type": "page",
"parent_id": "2162740",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2621441/Execute+tools",
"children": [],
"status": "current",
},
],
"2162740",
{
"title": "Trees",
"id": "2162740",
"type": "page",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2162740/Trees",
"children": [],
},
{
"title": "Trees",
"id": "2162740",
"type": "page",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2162740/Trees",
"children": [
{
"title": "The Importance of Trees",
"id": "2555906",
"type": "page",
"parent_id": "2162740",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2555906/The+Importance+of+Trees",
"children": [
{
"title": "Erics page",
"id": "2686977",
"type": "page",
"parent_id": "2555906",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2686977/Erics+page",
"children": [],
"status": "current",
},
{
"title": "Erics page",
"id": "2719745",
"type": "page",
"parent_id": "2555906",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/edit-v2/2719745",
"children": [],
"status": "draft",
},
],
"status": "current",
},
{
"title": "Execute tools",
"id": "2621441",
"type": "page",
"parent_id": "2162740",
"parent_type": "TODO",
"url": "https://ericconfluence.atlassian.net/wiki/spaces/SOFTWAREDE/pages/2621441/Execute+tools",
"children": [],
"status": "current",
},
],
},
),
],
)
def test_build_hierarchy(
input_transformed_children: list[dict],
input_parent_id: int,
input_parent_node: dict,
expected_parent_node: dict,
) -> None:
# build_hierarchy modifies the input_parent_node in-place
build_hierarchy(input_transformed_children, input_parent_id, input_parent_node)
assert input_parent_node == expected_parent_node