✨ improve acapy exception handling #2693
GitHub Actions / JUnit Test Report
failed
Oct 29, 2024 in 0s
835 tests run, 826 passed, 6 skipped, 3 failed.
Annotations
Check failure on line 105 in app/tests/e2e/test_did_exchange.py
github-actions / JUnit Test Report
test_did_exchange.test_create_did_exchange_request[clean-clean-None-None-False]
fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
Raw output
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb240e0>
method = 'delete', url = '/v1/connections/afbdb0eb-ced0-4163-a1ca-99c9c47c23fc'
kwargs = {}, attempt = 0, response = <Response [500 Internal Server Error]>
code = 500
async def _request_with_retries(self, method: str, url: str, **kwargs) -> Response:
for attempt in range(self.retries):
try:
response = await getattr(super(), method)(url, **kwargs)
> return await self._handle_response(response)
shared/util/rich_async_client.py:64:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
shared/util/rich_async_client.py:47: in _handle_response
response.raise_for_status() # Raise exception for 4xx and 5xx status codes
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Response [500 Internal Server Error]>
def raise_for_status(self) -> Response:
"""
Raise the `HTTPStatusError` if one occurred.
"""
request = self._request
if request is None:
raise RuntimeError(
"Cannot call `raise_for_status` as the request "
"instance has not been set on this response."
)
if self.is_success:
return self
if self.has_redirect_location:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"Redirect location: '{0.headers[location]}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
else:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
status_class = self.status_code // 100
error_types = {
1: "Informational response",
3: "Redirect response",
4: "Client error",
5: "Server error",
}
error_type = error_types.get(status_class, "Invalid status code")
message = message.format(self, error_type=error_type)
> raise HTTPStatusError(message, request=request, response=self)
E httpx.HTTPStatusError: Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech.com/tenant/v1/connections/afbdb0eb-ced0-4163-a1ca-99c9c47c23fc'
E For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
/usr/local/lib/python3.12/site-packages/httpx/_models.py:763: HTTPStatusError
The above exception was the direct cause of the following exception:
alice_member_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb240e0>
faber_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb27410>
alice_acapy_client = <aries_cloudcontroller.acapy_client.AcaPyClient object at 0x7f6abdb47b60>
faber_acapy_client = <aries_cloudcontroller.acapy_client.AcaPyClient object at 0x7f6abdb264e0>
use_did = None, use_did_method = None, use_public_did = False
@pytest.mark.anyio
@pytest.mark.parametrize(
"use_did,use_did_method,use_public_did",
[
(None, None, False),
(True, None, False),
(None, "did:peer:2", False),
(None, "did:peer:4", False),
(True, "did:peer:4", False),
(None, None, True),
],
)
async def test_create_did_exchange_request(
alice_member_client: RichAsyncClient,
faber_client: RichAsyncClient,
alice_acapy_client: AcaPyClient,
faber_acapy_client: AcaPyClient,
use_did: Optional[str],
use_did_method: Optional[str],
use_public_did: bool,
):
faber_public_did = await acapy_wallet.get_public_did(controller=faber_acapy_client)
request_data = {"their_public_did": qualified_did_sov(faber_public_did.did)}
if use_did:
new_did = await acapy_wallet.create_did(controller=alice_acapy_client)
request_data["use_did"] = new_did.did
if use_did_method:
request_data["use_did_method"] = use_did_method
if use_public_did:
request_data["use_public_did"] = use_public_did
if use_public_did: # Alice doesn't have a public DID
with pytest.raises(HTTPException) as exc_info:
response = await alice_member_client.post(
f"{CONNECTIONS_BASE_PATH}/did-exchange/create-request",
params=request_data,
)
assert exc_info.value.status_code == 400
assert exc_info.value.detail == """{"detail":"No public DID configured."}"""
elif use_did and use_did_method:
with pytest.raises(HTTPException) as exc_info:
await alice_member_client.post(
f"{CONNECTIONS_BASE_PATH}/did-exchange/create-request",
params=request_data,
)
assert exc_info.value.status_code == 400
assert (
exc_info.value.detail
== """{"detail":"Cannot specify both use_did and use_did_method."}"""
)
else:
response = await alice_member_client.post(
f"{CONNECTIONS_BASE_PATH}/did-exchange/create-request", params=request_data
)
assert response.status_code == 200
connection_record = response.json()
assert_that(connection_record).contains("connection_id", "state")
assert_that(connection_record["state"]).is_equal_to("request-sent")
alice_connection_id = connection_record["connection_id"]
alice_did = connection_record["my_did"]
try:
# Due to auto-accepts, Alice's connection is complete
assert await check_webhook_state(
alice_member_client,
topic="connections",
state="completed",
filter_map={"connection_id": alice_connection_id},
)
# Faber now has a complete connection too
assert await check_webhook_state(
faber_client,
topic="connections",
state="completed",
filter_map={"their_did": alice_did},
)
finally:
await asyncio.sleep(1) # Short sleep assists in avoiding 500 error
# Delete connection records:
> await alice_member_client.delete(
f"{CONNECTIONS_BASE_PATH}/{alice_connection_id}"
)
app/tests/e2e/test_did_exchange.py:105:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
shared/util/rich_async_client.py:84: in delete
return await self._request_with_retries("delete", url, **kwargs)
shared/util/rich_async_client.py:75: in _request_with_retries
await self._handle_error(e, url, method)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb240e0>
e = HTTPStatusError("Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech...ed0-4163-a1ca-99c9c47c23fc'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500")
url = '/v1/connections/afbdb0eb-ced0-4163-a1ca-99c9c47c23fc', method = 'delete'
async def _handle_error(self, e: HTTPStatusError, url: str, method: str) -> None:
code = e.response.status_code
message = e.response.text
log_message = (
f"{self.name} {method} `{url}` failed. "
f"Status code: {code}. Response: `{message}`."
)
logger.error(log_message)
> raise HTTPException(status_code=code, detail=message) from e
E fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
shared/util/rich_async_client.py:58: HTTPException
Check failure on line 105 in app/tests/e2e/test_did_exchange.py
github-actions / JUnit Test Report
test_did_exchange.test_create_did_exchange_request[clean-clean-None-did:peer:4-False]
fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
Raw output
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb2c980>
method = 'delete', url = '/v1/connections/3d57af7d-e662-4c98-8392-37743b5d6b7b'
kwargs = {}, attempt = 0, response = <Response [500 Internal Server Error]>
code = 500
async def _request_with_retries(self, method: str, url: str, **kwargs) -> Response:
for attempt in range(self.retries):
try:
response = await getattr(super(), method)(url, **kwargs)
> return await self._handle_response(response)
shared/util/rich_async_client.py:64:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
shared/util/rich_async_client.py:47: in _handle_response
response.raise_for_status() # Raise exception for 4xx and 5xx status codes
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Response [500 Internal Server Error]>
def raise_for_status(self) -> Response:
"""
Raise the `HTTPStatusError` if one occurred.
"""
request = self._request
if request is None:
raise RuntimeError(
"Cannot call `raise_for_status` as the request "
"instance has not been set on this response."
)
if self.is_success:
return self
if self.has_redirect_location:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"Redirect location: '{0.headers[location]}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
else:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
status_class = self.status_code // 100
error_types = {
1: "Informational response",
3: "Redirect response",
4: "Client error",
5: "Server error",
}
error_type = error_types.get(status_class, "Invalid status code")
message = message.format(self, error_type=error_type)
> raise HTTPStatusError(message, request=request, response=self)
E httpx.HTTPStatusError: Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech.com/tenant/v1/connections/3d57af7d-e662-4c98-8392-37743b5d6b7b'
E For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
/usr/local/lib/python3.12/site-packages/httpx/_models.py:763: HTTPStatusError
The above exception was the direct cause of the following exception:
alice_member_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb2c980>
faber_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb27410>
alice_acapy_client = <aries_cloudcontroller.acapy_client.AcaPyClient object at 0x7f6abdb2c3e0>
faber_acapy_client = <aries_cloudcontroller.acapy_client.AcaPyClient object at 0x7f6abe314560>
use_did = None, use_did_method = 'did:peer:4', use_public_did = False
@pytest.mark.anyio
@pytest.mark.parametrize(
"use_did,use_did_method,use_public_did",
[
(None, None, False),
(True, None, False),
(None, "did:peer:2", False),
(None, "did:peer:4", False),
(True, "did:peer:4", False),
(None, None, True),
],
)
async def test_create_did_exchange_request(
alice_member_client: RichAsyncClient,
faber_client: RichAsyncClient,
alice_acapy_client: AcaPyClient,
faber_acapy_client: AcaPyClient,
use_did: Optional[str],
use_did_method: Optional[str],
use_public_did: bool,
):
faber_public_did = await acapy_wallet.get_public_did(controller=faber_acapy_client)
request_data = {"their_public_did": qualified_did_sov(faber_public_did.did)}
if use_did:
new_did = await acapy_wallet.create_did(controller=alice_acapy_client)
request_data["use_did"] = new_did.did
if use_did_method:
request_data["use_did_method"] = use_did_method
if use_public_did:
request_data["use_public_did"] = use_public_did
if use_public_did: # Alice doesn't have a public DID
with pytest.raises(HTTPException) as exc_info:
response = await alice_member_client.post(
f"{CONNECTIONS_BASE_PATH}/did-exchange/create-request",
params=request_data,
)
assert exc_info.value.status_code == 400
assert exc_info.value.detail == """{"detail":"No public DID configured."}"""
elif use_did and use_did_method:
with pytest.raises(HTTPException) as exc_info:
await alice_member_client.post(
f"{CONNECTIONS_BASE_PATH}/did-exchange/create-request",
params=request_data,
)
assert exc_info.value.status_code == 400
assert (
exc_info.value.detail
== """{"detail":"Cannot specify both use_did and use_did_method."}"""
)
else:
response = await alice_member_client.post(
f"{CONNECTIONS_BASE_PATH}/did-exchange/create-request", params=request_data
)
assert response.status_code == 200
connection_record = response.json()
assert_that(connection_record).contains("connection_id", "state")
assert_that(connection_record["state"]).is_equal_to("request-sent")
alice_connection_id = connection_record["connection_id"]
alice_did = connection_record["my_did"]
try:
# Due to auto-accepts, Alice's connection is complete
assert await check_webhook_state(
alice_member_client,
topic="connections",
state="completed",
filter_map={"connection_id": alice_connection_id},
)
# Faber now has a complete connection too
assert await check_webhook_state(
faber_client,
topic="connections",
state="completed",
filter_map={"their_did": alice_did},
)
finally:
await asyncio.sleep(1) # Short sleep assists in avoiding 500 error
# Delete connection records:
> await alice_member_client.delete(
f"{CONNECTIONS_BASE_PATH}/{alice_connection_id}"
)
app/tests/e2e/test_did_exchange.py:105:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
shared/util/rich_async_client.py:84: in delete
return await self._request_with_retries("delete", url, **kwargs)
shared/util/rich_async_client.py:75: in _request_with_retries
await self._handle_error(e, url, method)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7f6abdb2c980>
e = HTTPStatusError("Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech...662-4c98-8392-37743b5d6b7b'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500")
url = '/v1/connections/3d57af7d-e662-4c98-8392-37743b5d6b7b', method = 'delete'
async def _handle_error(self, e: HTTPStatusError, url: str, method: str) -> None:
code = e.response.status_code
message = e.response.text
log_message = (
f"{self.name} {method} `{url}` failed. "
f"Status code: {code}. Response: `{message}`."
)
logger.error(log_message)
> raise HTTPException(status_code=code, detail=message) from e
E fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
shared/util/rich_async_client.py:58: HTTPException
Check failure on line 237 in app/tests/e2e/verifier/test_verifier_oob.py
github-actions / JUnit Test Report
test_verifier_oob.test_accept_proof_request_verifier_oob_connection[clean-clean-clean-clean-clean-clean]
fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
Raw output
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb49f3b1f40>
method = 'delete', url = '/v1/connections/dadd9ab5-7721-4dc3-aa6c-37ea137213ce'
kwargs = {}, attempt = 0, response = <Response [500 Internal Server Error]>
code = 500
async def _request_with_retries(self, method: str, url: str, **kwargs) -> Response:
for attempt in range(self.retries):
try:
response = await getattr(super(), method)(url, **kwargs)
> return await self._handle_response(response)
shared/util/rich_async_client.py:64:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
shared/util/rich_async_client.py:47: in _handle_response
response.raise_for_status() # Raise exception for 4xx and 5xx status codes
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Response [500 Internal Server Error]>
def raise_for_status(self) -> Response:
"""
Raise the `HTTPStatusError` if one occurred.
"""
request = self._request
if request is None:
raise RuntimeError(
"Cannot call `raise_for_status` as the request "
"instance has not been set on this response."
)
if self.is_success:
return self
if self.has_redirect_location:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"Redirect location: '{0.headers[location]}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
else:
message = (
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
"For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
)
status_class = self.status_code // 100
error_types = {
1: "Informational response",
3: "Redirect response",
4: "Client error",
5: "Server error",
}
error_type = error_types.get(status_class, "Invalid status code")
message = message.format(self, error_type=error_type)
> raise HTTPStatusError(message, request=request, response=self)
E httpx.HTTPStatusError: Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech.com/tenant/v1/connections/dadd9ab5-7721-4dc3-aa6c-37ea137213ce'
E For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500
/usr/local/lib/python3.12/site-packages/httpx/_models.py:763: HTTPStatusError
The above exception was the direct cause of the following exception:
credential_definition_id = '8YdbCMTxURZYSNgZ783nEm:3:CL:4004:tag'
issue_credential_to_alice = {'attributes': {'age': '44', 'name': 'Alice', 'speed': '10'}, 'connection_id': 'b3becb29-d037-4710-ba8b-8499cbf0ff0b', 'created_at': '2024-10-29T13:54:22.107446Z', 'credential_definition_id': '8YdbCMTxURZYSNgZ783nEm:3:CL:4004:tag', ...}
acme_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb49f3f5970>
alice_member_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb49f3b1f40>
@pytest.mark.anyio
@pytest.mark.skipif(
TestMode.regression_run in TestMode.fixture_params,
reason="Verifier trust registry OOB connection already tested in test_verifier",
)
async def test_accept_proof_request_verifier_oob_connection(
credential_definition_id: str,
issue_credential_to_alice: CredentialExchange, # pylint: disable=unused-argument
acme_client: RichAsyncClient,
alice_member_client: RichAsyncClient,
):
# Create connection between holder and verifier
# We need to use the multi-use didcomm invitation from the trust registry
acme_wallet_id = get_wallet_id_from_async_client(acme_client)
verifier_actor = await fetch_actor_by_id(acme_wallet_id)
# Get Alice's wallet_label from RichAsyncClient instance, striping prefix and suffix added by fixtures
# Example of RichAsyncClient name format: "Tenant alice_VCPSU - HTTP"
their_label = alice_member_client.name[7:-7]
assert verifier_actor
assert verifier_actor.didcomm_invitation
invitation_json = base64_to_json(
verifier_actor.didcomm_invitation.split("?oob=")[1]
)
invitation_response = (
await alice_member_client.post(
OOB_BASE_PATH + "/accept-invitation",
json={"invitation": invitation_json},
)
).json()
payload = await check_webhook_state(
client=acme_client,
topic="connections",
state="completed",
filter_map={"their_label": their_label},
)
holder_verifier_connection_id = invitation_response["connection_id"]
verifier_holder_connection_id = payload["connection_id"]
try:
# Present proof from holder to verifier
request_body = {
"connection_id": verifier_holder_connection_id,
"indy_proof_request": {
"name": "Age Check",
"version": "1.0",
"requested_attributes": {
"name": {
"name": "name",
"restrictions": [{"cred_def_id": credential_definition_id}],
}
},
"requested_predicates": {
"age_over_21": {
"name": "age",
"p_type": ">=",
"p_value": 21,
"restrictions": [{"cred_def_id": credential_definition_id}],
}
},
},
}
send_proof_response = await send_proof_request(acme_client, request_body)
payload = await check_webhook_state(
client=alice_member_client,
topic="proofs",
state="request-received",
filter_map={
"connection_id": holder_verifier_connection_id,
},
)
verifier_proof_exchange_id = send_proof_response["proof_id"]
holder_proof_exchange_id = payload["proof_id"]
available_credentials = (
await alice_member_client.get(
f"{VERIFIER_BASE_PATH}/proofs/{holder_proof_exchange_id}/credentials",
)
).json()
cred_id = available_credentials[0]["cred_info"]["referent"]
await alice_member_client.post(
VERIFIER_BASE_PATH + "/accept-request",
json={
"proof_id": holder_proof_exchange_id,
"indy_presentation_spec": {
"requested_attributes": {
"name": {
"cred_id": cred_id,
"revealed": True,
}
},
"requested_predicates": {"age_over_21": {"cred_id": cred_id}},
"self_attested_attributes": {},
},
},
)
event = await check_webhook_state(
client=acme_client,
topic="proofs",
state="done",
filter_map={
"proof_id": verifier_proof_exchange_id,
},
)
assert event["verified"]
finally:
# Clean up temp connection records
> await alice_member_client.delete(
f"{CONNECTIONS_BASE_PATH}/{holder_verifier_connection_id}"
)
app/tests/e2e/verifier/test_verifier_oob.py:237:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
shared/util/rich_async_client.py:84: in delete
return await self._request_with_retries("delete", url, **kwargs)
shared/util/rich_async_client.py:75: in _request_with_retries
await self._handle_error(e, url, method)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb49f3b1f40>
e = HTTPStatusError("Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech...721-4dc3-aa6c-37ea137213ce'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500")
url = '/v1/connections/dadd9ab5-7721-4dc3-aa6c-37ea137213ce', method = 'delete'
async def _handle_error(self, e: HTTPStatusError, url: str, method: str) -> None:
code = e.response.status_code
message = e.response.text
log_message = (
f"{self.name} {method} `{url}` failed. "
f"Status code: {code}. Response: `{message}`."
)
logger.error(log_message)
> raise HTTPException(status_code=code, detail=message) from e
E fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
shared/util/rich_async_client.py:58: HTTPException
Loading