From dc9e23cfc90f2d51639c452c64fb800fccdcc740 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 26 May 2026 13:30:36 +0000 Subject: [PATCH] Add unit test for note image cleanup --- src/backend/InvenTree/common/tests.py | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/backend/InvenTree/common/tests.py b/src/backend/InvenTree/common/tests.py index af0420e62d..453282b8e2 100644 --- a/src/backend/InvenTree/common/tests.py +++ b/src/backend/InvenTree/common/tests.py @@ -42,6 +42,7 @@ from .models import ( InvenTreeCustomUserStateModel, InvenTreeSetting, InvenTreeUserSetting, + Note, NotesImage, NotificationEntry, NotificationMessage, @@ -1693,6 +1694,66 @@ class NotesImageTest(InvenTreeAPITestCase): # Check that a new file has been created self.assertEqual(NotesImage.objects.count(), n + 1) + def test_image_cleanup(self): + """Images no longer referenced in note content are deleted when the note is saved. + + Specifically: + - An image removed from the content is deleted (DB record and file on disk) + - An image still referenced in the content is preserved (DB record and file on disk) + """ + part = Part.objects.create( + name='Note Cleanup Test Part', description='Part for image-cleanup test' + ) + part_ct = ContentType.objects.get_for_model(Part) + + note = Note( + model_type=part_ct, + model_id=part.pk, + title='Image Cleanup Test Note', + content='initial', + ) + note.save() + + # Build a minimal valid PNG in memory + img_obj = Image.new('RGB', (10, 10), color='blue') + with io.BytesIO() as buf: + img_obj.save(buf, format='PNG') + png_bytes = buf.getvalue() + + # Attach two images to the note + ni1 = NotesImage(note=note) + ni1.image.save('cleanup_keep.png', ContentFile(png_bytes)) + + ni2 = NotesImage(note=note) + ni2.image.save('cleanup_remove.png', ContentFile(png_bytes)) + + url1, url2 = ni1.image.url, ni2.image.url + name1, name2 = ni1.image.name, ni2.image.name + + # Both records and files exist before any content-driven cleanup + self.assertEqual(note.images.count(), 2) + self.assertTrue(default_storage.exists(name1)) + self.assertTrue(default_storage.exists(name2)) + + # Save with content that references both images — nothing should be removed + note.content = f'' + note.save() + self.assertEqual(note.images.count(), 2) + self.assertTrue(default_storage.exists(name1)) + self.assertTrue(default_storage.exists(name2)) + + # Remove the second image from the content and save + note.content = f'' + note.save() + + # The removed image must be gone from both the DB and the file system + self.assertFalse(NotesImage.objects.filter(pk=ni2.pk).exists()) + self.assertFalse(default_storage.exists(name2)) + + # The retained image must still exist in both the DB and the file system + self.assertTrue(NotesImage.objects.filter(pk=ni1.pk).exists()) + self.assertTrue(default_storage.exists(name1)) + class ProjectCodesTest(InvenTreeAPITestCase): """Units tests for the ProjectCodes model and API endpoints."""