diff --git a/atlassian/confluence/cloud/__init__.py b/atlassian/confluence/cloud/__init__.py index 306991eb6..51bce6e86 100644 --- a/atlassian/confluence/cloud/__init__.py +++ b/atlassian/confluence/cloud/__init__.py @@ -244,23 +244,22 @@ def blog_post_exists(self, space_key, title, **kwargs): result = self.get_blog_post_by_title(space_key, title, **kwargs) return len(result.get("results", [])) > 0 - def page_exists(self, space_key, title, type=None, **kwargs): - """Check whether a page exists using the Cloud V2 page endpoint. + def _get_v2_content_by_title(self, space_key, title, type=None, **kwargs): + """Return the V2 pages (or blog posts) matching a space key and title. - The retired V1 content lookup accepted a space key directly. V2 page - queries require a space ID, so resolve the key first and request only - one matching page. Pass type="blogpost" to check the V2 blog post - endpoint instead. + V2 page queries require a space ID, so resolve the key first and + request only one match. Pass type="blogpost" to query the V2 blog post + endpoint instead. Returns an empty list when nothing matches. """ try: spaces = self.get("api/v2/spaces", params={"keys": [space_key], "limit": 1}) except HTTPError as error: if error.response is not None and error.response.status_code == 404: - return False + return [] raise space_results = spaces.get("results", []) if not space_results: - return False + return [] try: result = self.get( @@ -276,9 +275,30 @@ def page_exists(self, space_key, title, type=None, **kwargs): ) except HTTPError as error: if error.response is not None and error.response.status_code == 404: - return False + return [] raise - return bool(result.get("results", [])) + return result.get("results", []) + + def page_exists(self, space_key, title, type=None, **kwargs): + """Check whether a page exists using the Cloud V2 page endpoint. + + The retired V1 content lookup accepted a space key directly. V2 page + queries require a space ID, so resolve the key first and request only + one matching page. Pass type="blogpost" to check the V2 blog post + endpoint instead. + """ + return bool(self._get_v2_content_by_title(space_key, title, type=type, **kwargs)) + + def get_page_id(self, space, title, type="page"): + """Get a page ID by space key and title using the Cloud V2 API. + + Pass type="blogpost" to look up a blog post instead of a page. + Returns the ID of the first match, or None if there is none. + """ + results = self._get_v2_content_by_title(space, title, type=type) + if results: + return str(results[0]["id"]) + return None def get_page_child_by_type(self, page_id, type="page", start=None, limit=None, expand=None): """ diff --git a/tests/confluence/test_confluence_cloud.py b/tests/confluence/test_confluence_cloud.py index adcf08e42..4e4877eb5 100644 --- a/tests/confluence/test_confluence_cloud.py +++ b/tests/confluence/test_confluence_cloud.py @@ -247,6 +247,52 @@ def test_page_exists_returns_false_for_unknown_space(self, mock_get, confluence_ assert confluence_cloud.page_exists("MISSING", "Test Page") is False mock_get.assert_called_once_with("api/v2/spaces", params={"keys": ["MISSING"], "limit": 1}) + @patch.object(ConfluenceCloud, "get") + def test_get_page_id_uses_v2_space_and_page_endpoints(self, mock_get, confluence_cloud): + mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": [{"id": "123"}]}] + + assert confluence_cloud.get_page_id("TEST", "Test Page") == "123" + assert mock_get.call_args_list[0].args == ("api/v2/spaces",) + assert mock_get.call_args_list[0].kwargs == {"params": {"keys": ["TEST"], "limit": 1}} + assert mock_get.call_args_list[1].args == ("api/v2/pages",) + assert mock_get.call_args_list[1].kwargs == { + "params": { + "space-id": "42", + "title": "Test Page", + "status": "current", + "body-format": "none", + "limit": 1, + } + } + + @patch.object(ConfluenceCloud, "get") + def test_get_page_id_uses_v2_blogpost_endpoint_for_blogpost_type(self, mock_get, confluence_cloud): + mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": [{"id": "456"}]}] + + assert confluence_cloud.get_page_id("TEST", "Release notes", type="blogpost") == "456" + assert mock_get.call_args_list[1].args == ("api/v2/blogposts",) + assert mock_get.call_args_list[1].kwargs["params"]["title"] == "Release notes" + + @patch.object(ConfluenceCloud, "get") + def test_get_page_id_returns_none_when_not_found(self, mock_get, confluence_cloud): + mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": []}] + + assert confluence_cloud.get_page_id("TEST", "Missing") is None + + @patch.object(ConfluenceCloud, "get") + def test_get_page_id_returns_none_for_unknown_space(self, mock_get, confluence_cloud): + mock_get.return_value = {"results": []} + + assert confluence_cloud.get_page_id("MISSING", "Test Page") is None + mock_get.assert_called_once_with("api/v2/spaces", params={"keys": ["MISSING"], "limit": 1}) + + @patch.object(ConfluenceCloud, "get") + def test_get_page_id_through_confluence_factory(self, mock_get): + mock_get.side_effect = [{"results": [{"id": "42", "key": "TEST"}]}, {"results": [{"id": "123"}]}] + confluence = Confluence(url="https://test.atlassian.net", username="user", password="api-token") + + assert confluence.get_page_id("TEST", "Test Page") == "123" + @patch.object(ConfluenceCloud, "get") def test_get_all_blog_posts_from_space(self, mock_get, confluence_cloud): """Test get_all_blog_posts_from_space method."""