diff --git a/CHANGES.rst b/CHANGES.rst index 1513cd6cf2..a4e60a71a8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,6 +19,7 @@ Breaking changes: - Change the @linkintegrity endpoint to add `items_total`, the number of contained items which would be deleted. @davisagli, @danalvrz, @pgrunewald (#1636) - The default branch was renamed from `master` to `main`. @tisto, @davisagli (#1695) - Drop support for Python 3.7. Set python_requires to >= 3.8 @tisto (#1709) +- Expose the sources + their target instead of just some sources in "breaches" attribute in @linkintegrity endpoint response. [jaroel] New features: @@ -461,7 +462,7 @@ Bug fixes: - Added url field to Actions (#817) -- Update statictime tests following changes to p.a.disucssion (see +- Update statictime tests following changes to p.a.disucssion (see https://github.com/plone/plone.app.discussion/pull/204) - [instification] (#1520) - Update @portrait endpoint to use sanitized user id [instification] (#1524) diff --git a/src/plone/restapi/services/linkintegrity/get.py b/src/plone/restapi/services/linkintegrity/get.py index 6c6aa7ea05..396749d05f 100644 --- a/src/plone/restapi/services/linkintegrity/get.py +++ b/src/plone/restapi/services/linkintegrity/get.py @@ -34,16 +34,21 @@ def reply(self): item = uuidToObject(uid) item_path = "/".join(item.getPhysicalPath()) links_info = item.restrictedTraverse("@@delete_confirmation_info") - breaches = links_info.get_breaches() data = getMultiAdapter((item, self.request), ISerializeToJsonSummary)() - data["breaches"] = [] - for breach in breaches: - for source in breach.get("sources", []): - # remove unwanted data - source["@id"] = source["url"] - del source["url"] - del source["accessible"] - data["breaches"].append(source) + data["breaches"] = [ + { + "target": result["target"], + "sources": [ + { + "@id": source["url"], + "uid": source["uid"], + "title": source["title"], + } + for source in result["sources"] + ], + } + for result in links_info.get_breaches() + ] # subtract one because we don't want to count item_path itself data["items_total"] = len(catalog(path=item_path)) - 1 result.append(data) diff --git a/src/plone/restapi/tests/test_services_linkintegrity.py b/src/plone/restapi/tests/test_services_linkintegrity.py index 297843f709..ccbe0046a6 100644 --- a/src/plone/restapi/tests/test_services_linkintegrity.py +++ b/src/plone/restapi/tests/test_services_linkintegrity.py @@ -20,7 +20,6 @@ class TestLinkIntegrity(unittest.TestCase): - layer = PLONE_RESTAPI_BLOCKS_FUNCTIONAL_TESTING def setUp(self): @@ -210,3 +209,130 @@ def test_return_items_total_in_subfolders(self): self.assertEqual(result[0]["@id"], level1.absolute_url()) self.assertEqual(result[0]["breaches"], []) self.assertEqual(result[0]["items_total"], 1) + + def test_tree_breaches_no_duplicates(self): + # /target_parent/target_child + target_parent = createContentInContainer( + self.portal, "Folder", id="target-parent" + ) + target_child = createContentInContainer( + target_parent, "Document", id="target-child" + ) + target_parent_uid = IUUID(target_parent) + target_child_uid = IUUID(target_child) + + source_a = createContentInContainer( + self.portal, + "Document", + id="source_a", + blocks={ + "block-uuid1": { + "@type": "text", + "text": { + "blocks": [{"text": "some link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_parent_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_parent_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + }, + "block-uuid2": { + "@type": "text", + "text": { + "blocks": [{"text": "some other link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_child_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_child_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + }, + }, + ) + + source_b = createContentInContainer( + self.portal, + "Document", + id="source_b", + blocks={ + "block-uuid3": { + "@type": "text", + "text": { + "blocks": [{"text": "some link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_parent_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_parent_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + } + }, + ) + + source_c = createContentInContainer( + self.portal, + "Document", + id="source_c", + blocks={ + "block-uuid4": { + "@type": "text", + "text": { + "blocks": [{"text": "some other link"}], + "entityMap": { + "0": { + "data": { + "href": f"../resolveuid/{target_child_uid}", + "rel": "nofollow", + "url": f"../resolveuid/{target_child_uid}", + }, + "mutability": "MUTABLE", + "type": "LINK", + } + }, + }, + }, + }, + ) + + transaction.commit() + + response = self.api_session.get( + "/@linkintegrity", params={"uids": [target_parent_uid]} + ) + + result = response.json() + self.assertEqual(response.status_code, 200) + self.assertEqual(len(result), 1) + self.assertEqual(result[0]["@id"], target_parent.absolute_url()) + + breaches = result[0]["breaches"] + self.assertEqual(breaches[0]["target"]["uid"], target_parent_uid) + self.assertEqual( + [source["uid"] for source in breaches[0]["sources"]], + [IUUID(source_a), IUUID(source_b)], + ) + self.assertEqual(breaches[1]["target"]["uid"], target_child_uid) + self.assertEqual( + [source["uid"] for source in breaches[1]["sources"]], + [IUUID(source_a), IUUID(source_c)], + ) + self.assertEqual(len(breaches), 3)