diff --git a/admin_tests/preprints/test_views.py b/admin_tests/preprints/test_views.py index 357ff643a06..5731c5c9aac 100644 --- a/admin_tests/preprints/test_views.py +++ b/admin_tests/preprints/test_views.py @@ -23,7 +23,6 @@ from osf.models.spam import SpamStatus from osf.utils.workflows import DefaultStates, RequestTypes from osf.utils.permissions import ADMIN -from framework.auth import Auth from admin_tests.utilities import setup_view, setup_log_view, handle_post_view_request diff --git a/osf/models/admin_log_entry.py b/osf/models/admin_log_entry.py index 169bbe3faef..99f4de940f5 100644 --- a/osf/models/admin_log_entry.py +++ b/osf/models/admin_log_entry.py @@ -34,6 +34,9 @@ PREPRINT_REMOVED = 70 PREPRINT_RESTORED = 71 +DOI_CREATION_FAILED = 80 +DOI_UPDATE_FAILED = 81 + def update_admin_log(user_id, object_id, object_repr, message, action_flag=UNKNOWN): AdminLogEntry.objects.log_action( user_id=user_id, diff --git a/osf/models/node.py b/osf/models/node.py index 7aee1cb0880..733e18c60eb 100644 --- a/osf/models/node.py +++ b/osf/models/node.py @@ -1214,6 +1214,30 @@ def set_privacy(self, permissions, auth=None, log=True, save=True, meeting_creat # Embargoed registrations can be made public early self.request_embargo_termination(auth.user) return False + + if not self.get_identifier_value('doi'): + try: + doi = self.request_identifier('doi')['doi'] + self.set_identifier_value('doi', doi) + except Exception as e: + from osf.models.admin_log_entry import update_admin_log, DOI_CREATION_FAILED + logger.exception( + f'Failed to create DOI for registration {self._id} during set_privacy. ' + f'Registration cannot be made public without a DOI.' + ) + if auth and auth.user: + update_admin_log( + user_id=auth.user.id, + object_id=self._id, + object_repr=f'Registration {self.title}', + message=f'DOI creation failed during make public: {str(e)}. DataCite may be unavailable.', + action_flag=DOI_CREATION_FAILED + ) + raise NodeStateError( + 'Unable to make registration public: DOI creation failed. ' + 'This may be due to a temporary DataCite service outage. ' + 'Please try again later or contact support if the issue persists.' + ) self.is_public = True elif permissions == 'private' and self.is_public: if self.is_registration and not self.is_pending_embargo and not force: @@ -1233,12 +1257,24 @@ def set_privacy(self, permissions, auth=None, log=True, save=True, meeting_creat if message: status.push_status_message(message, kind='info', trust=False) - # Update existing identifiers + # Update existing identifiers metadata if self.get_identifier_value('doi'): - update_doi_metadata_on_change(self._id) - elif self.is_registration: - doi = self.request_identifier('doi')['doi'] - self.set_identifier_value('doi', doi) + try: + update_doi_metadata_on_change(self._id) + except Exception as e: + from osf.models.admin_log_entry import update_admin_log, DOI_UPDATE_FAILED + logger.exception( + f'Failed to update DOI metadata for {self._id} during set_privacy. ' + ) + # Log DOI metadata update failures for tracking + if auth and auth.user and self.is_registration: + update_admin_log( + user_id=auth.user.id, + object_id=self._id, + object_repr=f'Registration {self.title}', + message=f'DOI metadata update failed: {str(e)}. DataCite may be unavailable.', + action_flag=DOI_UPDATE_FAILED + ) if log: action = NodeLog.MADE_PUBLIC if permissions == 'public' else NodeLog.MADE_PRIVATE diff --git a/osf_tests/test_registrations.py b/osf_tests/test_registrations.py index a93ffba6264..cb23fdc74ac 100644 --- a/osf_tests/test_registrations.py +++ b/osf_tests/test_registrations.py @@ -6,6 +6,7 @@ from django.utils import timezone from framework.auth.core import Auth from framework.exceptions import PermissionsError +from osf.exceptions import NodeStateError from osf.models import Node, Registration, Sanction, RegistrationSchema, NodeLog, GuidMetadataRecord from addons.wiki.models import WikiPage from osf.utils.permissions import ADMIN @@ -373,6 +374,46 @@ def test_legacy_private_registrations_can_be_made_public(self, registration, aut registration.set_privacy(Node.PUBLIC, auth=auth) assert registration.is_public + def test_registration_cannot_become_public_when_doi_creation_fails(self, registration, auth): + registration.is_public = False + existing_doi = registration.get_identifier('doi') + if existing_doi: + existing_doi.delete() + registration.save() + + assert registration.get_identifier_value('doi') is None + + with mock.patch.object(registration, 'get_doi_client') as mock_get_client: + mock_client = mock.Mock() + mock_client.create_identifier.side_effect = Exception('DataCite API unavailable') + mock_get_client.return_value = mock_client + + with pytest.raises(NodeStateError) as exc_info: + registration.set_privacy(Node.PUBLIC, auth=auth, log=False) + + assert 'Unable to make registration public: DOI creation failed' in str(exc_info.value) + assert registration.is_public is False + + mock_client.create_identifier.assert_called_once() + + @mock.patch('osf.models.node.update_doi_metadata_on_change') + def test_registration_becomes_public_even_when_doi_metadata_update_fails(self, mock_update_doi, registration, auth): + + registration.is_public = False + registration.set_identifier_value('doi', '10.1234/test.doi') + registration.save() + + assert registration.get_identifier_value('doi') == '10.1234/test.doi' + + mock_update_doi.side_effect = Exception('DataCite metadata update failed') + + result = registration.set_privacy(Node.PUBLIC, auth=auth, log=False) + + assert registration.is_public is True + assert result is True + + mock_update_doi.assert_called_once_with(registration._id) + class TestRegisterNodeContributors: