diff --git a/news/fix_image_scale_bloat.bugfix b/news/fix_image_scale_bloat.bugfix new file mode 100644 index 000000000..69e741cd9 --- /dev/null +++ b/news/fix_image_scale_bloat.bugfix @@ -0,0 +1 @@ +Optimization: Avoid redundant object updates in PATCH requests when image content is identical to existing content. diff --git a/src/plone/restapi/tests/test_content_patch.py b/src/plone/restapi/tests/test_content_patch.py index 067b33c30..f01b4ff5e 100644 --- a/src/plone/restapi/tests/test_content_patch.py +++ b/src/plone/restapi/tests/test_content_patch.py @@ -1,3 +1,4 @@ +from importlib import import_module from OFS.interfaces import IObjectWillBeAddedEvent from plone.app.testing import login from plone.app.testing import setRoles @@ -19,6 +20,10 @@ import transaction import unittest +HAS_PLONE_62 = getattr( + import_module("Products.CMFPlone.factory"), "PLONE62MARKER", False +) + class TestContentPatch(unittest.TestCase): @@ -197,3 +202,57 @@ def test_patch_document_with_apostrophe_dont_return_500(self): self.assertEqual(204, response.status_code) transaction.begin() self.assertEqual("
example with '
", self.portal.doc1.text.raw) + + @unittest.skipUnless(HAS_PLONE_62, "Requires Plone 6.2+") + def test_patch_image_redundant_no_event(self): + image_data = "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" + response = requests.post( + self.portal.absolute_url(), + headers={"Accept": "application/json"}, + auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), + json={ + "@type": "Image", + "image": { + "data": image_data, + "encoding": "base64", + "content-type": "image/gif", + "filename": "test.gif", + }, + }, + ) + self.assertEqual(201, response.status_code) + image_url = response.json()["@id"] + transaction.commit() + + # Track ObjectModifiedEvent + sm = getGlobalSiteManager() + fired_events = [] + + def record_event(event): + fired_events.append(event) + + sm.registerHandler(record_event, (IObjectModifiedEvent,)) + + # Patch with same data + response = requests.patch( + image_url, + headers={"Accept": "application/json"}, + auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), + json={ + "image": { + "data": image_data, + "encoding": "base64", + "content-type": "image/gif", + "filename": "test.gif", + }, + }, + ) + self.assertEqual(204, response.status_code) + + # In current Plone, this will be 1 because of identity mismatch + # We want it to be 0 + self.assertEqual( + len(fired_events), 0, "ObjectModifiedEvent was fired for redundant PATCH" + ) + + sm.unregisterHandler(record_event, (IObjectModifiedEvent,))