diff --git a/acapy_agent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py b/acapy_agent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py index 9b065ab2d6..7765173daf 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py @@ -68,17 +68,32 @@ async def handle(self, context: RequestContext, responder: BaseResponder): # Automatically move to next state if flag is set if context.settings.get("debug.auto_store_credential"): + cred_store_succeeded = False try: cred_ex_record = await cred_manager.store_credential(cred_ex_record) + cred_store_succeeded = True + except StorageError as err: + # Leave record in credential-received to allow manual retry + self._logger.exception( + "Auto-store failed; leaving credential in received state" + ) + if cred_ex_record: + cred_ex_record.error_msg = err.roll_up + async with context.profile.session() as session: + await cred_ex_record.save( + session, + reason=err.roll_up, + ) except ( BaseModelError, AnonCredsHolderError, IndyHolderError, - StorageError, V20CredManagerError, ) as err: # treat failure to store as mangled on receipt hence protocol error - self._logger.exception("Error storing issued credential") + self._logger.exception( + "Auto-store failed; abandoning credential exchange" + ) if cred_ex_record: async with context.profile.session() as session: await cred_ex_record.save_error_state( @@ -92,11 +107,11 @@ async def handle(self, context: RequestContext, responder: BaseResponder): ) ) - cred_ack_message = await cred_manager.send_cred_ack(cred_ex_record) - - trace_event( - context.settings, - cred_ack_message, - outcome="V20CredIssueHandler.handle.STORE", - perf_counter=r_time, - ) + if cred_store_succeeded: + cred_ack_message = await cred_manager.send_cred_ack(cred_ex_record) + trace_event( + context.settings, + cred_ack_message, + outcome="V20CredIssueHandler.handle.STORE", + perf_counter=r_time, + ) diff --git a/acapy_agent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py b/acapy_agent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py index fa491d5f4a..16213ef655 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py @@ -75,10 +75,12 @@ async def test_called_auto_store_x_indy(self): with mock.patch.object( test_module, "V20CredManager", autospec=True ) as mock_cred_mgr: + mock_cred_ex = mock.MagicMock( + save_error_state=mock.CoroutineMock(), + save=mock.CoroutineMock(), + ) mock_cred_mgr.return_value = mock.MagicMock( - receive_credential=mock.CoroutineMock( - return_value=mock.MagicMock(save_error_state=mock.CoroutineMock()) - ), + receive_credential=mock.CoroutineMock(return_value=mock_cred_ex), store_credential=mock.CoroutineMock( side_effect=[ test_module.IndyHolderError, @@ -92,18 +94,25 @@ async def test_called_auto_store_x_indy(self): self.request_context.connection_ready = True handler_inst = test_module.V20CredIssueHandler() responder = MockResponder() + self.request_context.settings["debug.auto_store_credential"] = True await handler_inst.handle(self.request_context, responder) # holder error await handler_inst.handle(self.request_context, responder) # storage error + assert mock_cred_mgr.return_value.send_cred_ack.call_count == 0 + assert mock_cred_ex.save_error_state.call_count == 1 + assert mock_cred_ex.save.call_count == 1 + async def test_called_auto_store_x_anoncreds(self): with mock.patch.object( test_module, "V20CredManager", autospec=True ) as mock_cred_mgr: + mock_cred_ex = mock.MagicMock( + save_error_state=mock.CoroutineMock(), + save=mock.CoroutineMock(), + ) mock_cred_mgr.return_value = mock.MagicMock( - receive_credential=mock.CoroutineMock( - return_value=mock.MagicMock(save_error_state=mock.CoroutineMock()) - ), + receive_credential=mock.CoroutineMock(return_value=mock_cred_ex), store_credential=mock.CoroutineMock( side_effect=[ test_module.AnonCredsHolderError, @@ -117,10 +126,15 @@ async def test_called_auto_store_x_anoncreds(self): self.request_context.connection_ready = True handler_inst = test_module.V20CredIssueHandler() responder = MockResponder() + self.request_context.settings["debug.auto_store_credential"] = True await handler_inst.handle(self.request_context, responder) # holder error await handler_inst.handle(self.request_context, responder) # storage error + assert mock_cred_mgr.return_value.send_cred_ack.call_count == 0 + assert mock_cred_ex.save_error_state.call_count == 1 + assert mock_cred_ex.save.call_count == 1 + async def test_called_not_ready(self): with mock.patch.object( test_module, "V20CredManager", autospec=True