Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
Comment on lines +78 to +81
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock object mock_cred_ex needs to include the error_msg attribute because the handler code sets cred_ex_record.error_msg = err.roll_up when a StorageError occurs. Without this attribute in the mock, the test may not properly validate that the error message is being set correctly on the credential exchange record.

Copilot uses AI. Check for mistakes.
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,
Comment on lines 84 to 86
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StorageError() instantiation should provide a message or have a mocked roll_up property. In the handler code at line 81, err.roll_up is accessed when a StorageError occurs. Without a proper message or mocked roll_up attribute, this will cause the roll_up property to return "StorageError." which may not provide meaningful testing of the error message handling. Consider passing a message like StorageError("Database connection failed") to make the test more realistic.

Copilot uses AI. Check for mistakes.
Expand All @@ -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(),
)
Comment on lines +110 to +113
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock object mock_cred_ex needs to include the error_msg attribute because the handler code sets cred_ex_record.error_msg = err.roll_up when a StorageError occurs. Without this attribute in the mock, the test may not properly validate that the error message is being set correctly on the credential exchange record.

Copilot uses AI. Check for mistakes.
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,
Comment on lines 116 to 118
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StorageError() instantiation should provide a message or have a mocked roll_up property. In the handler code at line 81, err.roll_up is accessed when a StorageError occurs. Without a proper message or mocked roll_up attribute, this will cause the roll_up property to return "StorageError." which may not provide meaningful testing of the error message handling. Consider passing a message like StorageError("Database connection failed") to make the test more realistic.

Copilot uses AI. Check for mistakes.
Expand All @@ -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
Expand Down
Loading