diff --git a/tests/test_bynderassetcopymixin.py b/tests/test_bynderassetcopymixin.py index f620d56..9f02e5f 100644 --- a/tests/test_bynderassetcopymixin.py +++ b/tests/test_bynderassetcopymixin.py @@ -2,8 +2,13 @@ import responses +from django.db import IntegrityError +from django.http import HttpRequest from django.test import TestCase -from wagtail.images import get_image_model +from django.utils.functional import cached_property +from django.views.generic.base import View +from testapp.factories import CustomImageFactory +from testapp.models import CustomImage from wagtail_factories import ImageFactory from wagtail_bynder.views.mixins import BynderAssetCopyMixin @@ -17,8 +22,13 @@ class BynderAssetCopyMixinTests(TestCase): def setUp(self): super().setUp() - self.view = BynderAssetCopyMixin() - self.view.model = get_image_model() + request = HttpRequest() + request.method = "" + request.path = "/" + + self.view = self.view_class() + self.view.setup(request) + # Mock out Bynder API calls responses.add( responses.GET, @@ -27,6 +37,20 @@ def setUp(self): status=200, ) + @cached_property + def view_class(self) -> type[BynderAssetCopyMixin]: + """ + Use the mixin to create a functioning view class + that can be used in tests for this class. + """ + + class TestViewClass(BynderAssetCopyMixin, View): + """A basic view class utilising BynderAssetCopyMixin""" + + model = CustomImage + + return TestViewClass + @responses.activate def test_create_object(self): # After fetching the data from bynder, the method should create a @@ -94,3 +118,74 @@ def test_update_object_when_object_is_outdated(self): is_up_to_date_mock.assert_called_once_with(TEST_ASSET_DATA) update_from_asset_data_mock.assert_called_once_with(TEST_ASSET_DATA) save_mock.assert_called_once() + + @responses.activate + def test_create_object_clash_handling_after_update(self): + # Create an image with a matching bynder_id + existing = CustomImageFactory.create(bynder_id=TEST_ASSET_ID) + + # Trigger the test target directly + with mock.patch.object( + self.view.model, "update_from_asset_data" + ) as update_from_asset_data_mock: + result = self.view.create_object(TEST_ASSET_ID) + + # Check behavior and result + update_from_asset_data_mock.assert_called_once_with(TEST_ASSET_DATA) + self.assertEqual(result, existing) + + @responses.activate + def test_create_object_clash_handling_on_save(self): + def create_dupe_and_throw_integrity_error(): + obj = CustomImage(bynder_id=TEST_ASSET_ID, title="An image") + CustomImage.objects.bulk_create([obj]) + raise IntegrityError("bynder_id must be unique") + + with ( + # Patch update_from_asset_data() to prevent download attempts + # and other unnecessary work + mock.patch.object( + self.view.model, + "update_from_asset_data", + ) as update_from_asset_data_mock, + # Patch save() to trigger our custom code + mock.patch.object( + self.view.model, + "save", + side_effect=create_dupe_and_throw_integrity_error, + ) as save_mock, + ): + # The IntegrityError should be captured, and the object + # that was saved previously should be found and returned + result = self.view.create_object(TEST_ASSET_ID) + + update_from_asset_data_mock.assert_called_once_with(TEST_ASSET_DATA) + save_mock.assert_called_once() + self.assertEqual(result.bynder_id, TEST_ASSET_ID) + + @responses.activate + def test_create_object_clash_handling_on_save_when_bynder_id_match_not_found(self): + def throw_integrity_error(): + raise IntegrityError("Some other field must be unique") + + with ( + # Patch update_from_asset_data() to prevent download attempts + # and other unnecessary work + mock.patch.object( + self.view.model, + "update_from_asset_data", + ) as update_from_asset_data_mock, + # Patch save() to trigger our custom code + mock.patch.object( + self.view.model, + "save", + side_effect=throw_integrity_error, + ) as save_mock, + self.assertRaises(IntegrityError), + ): + # With no 'bynder_id' match to be found, the error is allowed + # to bubble up + self.view.create_object(TEST_ASSET_ID) + + update_from_asset_data_mock.assert_called_once_with(TEST_ASSET_DATA) + save_mock.assert_called_once()