diff --git a/contentcuration/contentcuration/tests/test_gcs_storage.py b/contentcuration/contentcuration/tests/test_gcs_storage.py index a58420873e..7f19cfb175 100755 --- a/contentcuration/contentcuration/tests/test_gcs_storage.py +++ b/contentcuration/contentcuration/tests/test_gcs_storage.py @@ -8,6 +8,7 @@ from google.cloud.storage.blob import Blob from mixer.main import mixer +from contentcuration.utils.files import hex_to_base64 from contentcuration.utils.gcs_storage import CompositeGCS from contentcuration.utils.gcs_storage import GoogleCloudStorage @@ -232,3 +233,21 @@ def test_get_created_time(self): self.storage.get_created_time("blob"), self.blob_cls.return_value.time_created, ) + + def test_get_stored_object_md5(self): + mock_blob = self.blob_cls("blob", "blob") + mock_blob.md5_hash = hex_to_base64("d41d8cd98f00b204e9800998ecf8427e") + self.mock_default_bucket.get_blob.return_value = mock_blob + self.assertEqual( + self.storage.get_stored_object_md5("blob"), + "d41d8cd98f00b204e9800998ecf8427e", + ) + + def test_get_stored_object_md5__returns_none_if_not_found(self): + # Regression: a not-yet-uploaded object is in no backend, so + # _get_readable_backend raises FileNotFoundError. get_stored_object_md5 + # must swallow that and return None (else the resumable upload_url + # endpoint 500s on every new file), not propagate the error. + self.mock_default_bucket.get_blob.return_value = None + self.mock_anon_bucket.get_blob.return_value = None + self.assertIsNone(self.storage.get_stored_object_md5("blob")) diff --git a/contentcuration/contentcuration/utils/gcs_storage.py b/contentcuration/contentcuration/utils/gcs_storage.py index 75987e443c..7ad581f56d 100644 --- a/contentcuration/contentcuration/utils/gcs_storage.py +++ b/contentcuration/contentcuration/utils/gcs_storage.py @@ -302,4 +302,11 @@ def create_resumable_upload_session(self, name, md5_b64, size): ) def get_stored_object_md5(self, name): - return self._get_readable_backend(name).get_stored_object_md5(name) + # A not-yet-uploaded object exists in no backend; treat that as "no + # stored md5" (None) rather than raising, matching + # GoogleCloudStorage.get_stored_object_md5 for a missing blob. + try: + backend = self._get_readable_backend(name) + except FileNotFoundError: + return None + return backend.get_stored_object_md5(name)