Skip to content

✨ improve acapy exception handling #2693

✨ improve acapy exception handling

✨ 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

See this annotation in the file changed.

@github-actions github-actions / JUnit Test Report

test_did_exchange.test_create_did_exchange_request[clean-clean-None-did:peer:2-False]

fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
Raw output
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7fc271b05e20>
method = 'delete', url = '/v1/connections/66e1a605-d7b0-4b00-8fb1-8282d273eb6e'
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/66e1a605-d7b0-4b00-8fb1-8282d273eb6e'
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 0x7fc271b05e20>
faber_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7fc271f95610>
alice_acapy_client = <aries_cloudcontroller.acapy_client.AcaPyClient object at 0x7fc271f95400>
faber_acapy_client = <aries_cloudcontroller.acapy_client.AcaPyClient object at 0x7fc271b067e0>
use_did = None, use_did_method = 'did:peer:2', 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 0x7fc271b05e20>
e = HTTPStatusError("Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech...7b0-4b00-8fb1-8282d273eb6e'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500")
url = '/v1/connections/66e1a605-d7b0-4b00-8fb1-8282d273eb6e', 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 85 in app/tests/e2e/test_did_rotate.py

See this annotation in the file changed.

@github-actions github-actions / JUnit Test Report

test_did_rotate.test_hangup_did_rotation[clean-clean]

fastapi.exceptions.HTTPException: 500: {"detail":"Internal Server Error"}
Raw output
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7f96963587d0>
method = 'post', url = '/v1/connections/did-rotate/hangup'
kwargs = {'params': {'connection_id': 'b17ea6e6-125b-4592-bf3e-8a95ba05733d'}}
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/did-rotate/hangup?connection_id=b17ea6e6-125b-4592-bf3e-8a95ba05733d'
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 0x7f96963587d0>
faber_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7f96963bce60>
faber_acapy_client = <aries_cloudcontroller.acapy_client.AcaPyClient object at 0x7f9696359940>

    @pytest.mark.anyio
    async def test_hangup_did_rotation(
        alice_member_client: RichAsyncClient,
        faber_client: RichAsyncClient,
        faber_acapy_client: AcaPyClient,
    ):
        # First, create did-exchange connections between Alice and Faber:
        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)}
        response = await alice_member_client.post(
            f"{CONNECTIONS_BASE_PATH}/did-exchange/create-request", params=request_data
        )
        connection_record = response.json()
    
        alice_connection_id = connection_record["connection_id"]
        alice_did = connection_record["my_did"]
        assert await check_webhook_state(
            alice_member_client,
            topic="connections",
            state="completed",
            filter_map={"connection_id": alice_connection_id},
        )
    
        faber_event = await check_webhook_state(
            faber_client,
            topic="connections",
            state="completed",
            filter_map={"their_did": alice_did},
        )
        faber_connection_id = faber_event["connection_id"]
    
        # Hangup the DID rotation
>       hangup_response = await alice_member_client.post(
            f"{CONNECTIONS_BASE_PATH}/did-rotate/hangup",
            params={"connection_id": alice_connection_id},
        )

app/tests/e2e/test_did_rotate.py:85: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
shared/util/rich_async_client.py:78: in post
    return await self._request_with_retries("post", 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 0x7f96963587d0>
e = HTTPStatusError("Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech...25b-4592-bf3e-8a95ba05733d'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500")
url = '/v1/connections/did-rotate/hangup', method = 'post'

    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

See this annotation in the file changed.

@github-actions 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 0x7f7f579e12b0>
method = 'delete', url = '/v1/connections/365328f1-7886-4604-9b83-4833a9ddfb14'
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/365328f1-7886-4604-9b83-4833a9ddfb14'
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 = 'NbVEy35yU3RofJN9LWiFyA:3:CL:4224:tag'
issue_credential_to_alice = {'attributes': {'age': '44', 'name': 'Alice', 'speed': '10'}, 'connection_id': '4d2ca715-2d65-4c6b-80ff-8792754ab029', 'created_at': '2024-10-29T14:43:08.743803Z', 'credential_definition_id': 'NbVEy35yU3RofJN9LWiFyA:3:CL:4224:tag', ...}
acme_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7f7f5790fb00>
alice_member_client = <shared.util.rich_async_client.RichAsyncClient object at 0x7f7f579e12b0>

    @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 0x7f7f579e12b0>
e = HTTPStatusError("Server error '500 Internal Server Error' for url 'https://governance-tenant-web.cloudapi.dev.didxtech...886-4604-9b83-4833a9ddfb14'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500")
url = '/v1/connections/365328f1-7886-4604-9b83-4833a9ddfb14', 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