Skip to content

Commit

Permalink
🐛 Fix registering issuer did (#1219)
Browse files Browse the repository at this point in the history
* 🐛 Fix registering issuer did

Should be called as the issuer, with `create_transaction_for_endorser=True`. Previously was being called as endorser and automatically written to ledger

* 🎨

* ✨ Validate if signature request is applicable for endorsement

* ✅ Test coverage for new method

* ✅ Full test coverage for remaining methods in endorsement_processor, while I'm here

* ✅ Full test coverage for remaining methods in util/endorsement, while I'm here

* ✅ Full test coverage for main, while I'm here

* 🔧 Remove `asyncio_default_fixture_loop_scope` config (again... raises unknown config warning)

* 🎨 Rearrange methods, in particular to await did transaction is endorser before setting as public did

* ✅ Fix assertion

* 🎨 Make delay an argument for the set_endorser_metadata methods

* ✅⚡ Speed up unit tests by overriding sleeps

* 🚧 Test fix: `their_public_did` should be qualified

* 🎨

* 🐛✅ Wait for both transactions to be endorsed before proceeding. 🎨 Rename method to reflect change

* 🎨 Add timeout error description

* 🚧 Debug error logs

* 🎨 Use bound logger

* ✅ Update tests and reduce duration to run faster

* ✨ Replace passing of Endorsement model to just pass transaction_id instead. Return the TransactionRecord is successful, instead of just True

* ✅ Update tests

* ✨ Replace passing of Endorsement model to pass transaction_id instead. Log transaction record upon success

* 🎨

* 🔧 Modify subscription fetch args

* 🎨 Fix heartbeat value too large

* 🔧 Increase default for `WAIT_ISSUER_DID_MAX_ATTEMPTS`

* 🔧 Increase endorser batch fetch amount

* 🎨 Update log

* 🎨

* 🎨 Obfuscate messages_attach from transaction record log

* ✅

* 🔧 increase publish-revocations timeout and make configurable (#987)

* ✅ increase test sleep duration

* 🔧 make publish revocations timeout configurable

* 🐛 Endorser should fetch 1 message at a time

* 🔧 Add `CRED_DEF_ACK_TIMEOUT` env var to configure cred def creation timeout

* 🔧 Increase default max duration for awaiting webhooks

* 🎨 Clean up constants

* 🎨 Increase default timeout

* 🎨 Adjust fetch timeout
  • Loading branch information
ff137 authored Dec 11, 2024
1 parent 458e7a4 commit c649e76
Show file tree
Hide file tree
Showing 24 changed files with 450 additions and 209 deletions.
1 change: 0 additions & 1 deletion app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,3 @@ profile = "black"
[tool.pytest.ini_options]
addopts = "--junitxml=junit.xml -p no:cacheprovider --cov-report=xml --cov-report=term"
junit_family = "xunit2"
asyncio_default_fixture_loop_scope = "function"
3 changes: 2 additions & 1 deletion app/routes/revocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)
from app.services import revocation_registry
from app.util.retry_method import coroutine_with_retry_until_value
from shared import PUBLISH_REVOCATIONS_TIMEOUT
from shared.log_config import get_logger

logger = get_logger(__name__)
Expand Down Expand Up @@ -209,7 +210,7 @@ async def publish_revocations(
field_name="state",
expected_value="transaction_acked",
logger=bound_logger,
max_attempts=30,
max_attempts=PUBLISH_REVOCATIONS_TIMEOUT,
retry_delay=1,
)
except asyncio.TimeoutError as e:
Expand Down
7 changes: 5 additions & 2 deletions app/services/definitions/credential_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from app.util.assert_public_did import assert_public_did
from app.util.definitions import credential_definition_from_acapy
from app.util.transaction_acked import wait_for_transaction_ack
from shared import REGISTRY_SIZE
from shared import CRED_DEF_ACK_TIMEOUT, REGISTRY_SIZE
from shared.log_config import get_logger

logger = get_logger(__name__)
Expand Down Expand Up @@ -58,7 +58,10 @@ async def create_credential_definition(

if result.txn and result.txn.transaction_id:
await wait_for_transaction_ack(
aries_controller=aries_controller, transaction_id=result.txn.transaction_id
aries_controller=aries_controller,
transaction_id=result.txn.transaction_id,
max_attempts=CRED_DEF_ACK_TIMEOUT,
retry_delay=1,
)

if support_revocation:
Expand Down
9 changes: 1 addition & 8 deletions app/services/onboarding/issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from app.services.onboarding.util.register_issuer_did import (
create_connection_with_endorser,
register_issuer_did,
wait_issuer_did_transaction_endorsed,
)
from app.util.did import qualified_did_sov
from shared.log_config import get_logger
Expand Down Expand Up @@ -115,17 +114,11 @@ async def onboard_issuer_no_public_did(
)

issuer_did = await register_issuer_did(
endorser_controller=endorser_controller,
issuer_controller=issuer_controller,
issuer_label=issuer_label,
issuer_endorser_connection_id=issuer_connection_id,
logger=bound_logger,
)

await wait_issuer_did_transaction_endorsed(
issuer_controller=issuer_controller,
issuer_connection_id=issuer_connection_id,
logger=logger,
)
except Exception as e:
bound_logger.exception("Could not create connection with endorser.")
raise CloudApiException(
Expand Down
70 changes: 50 additions & 20 deletions app/services/onboarding/util/register_issuer_did.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
import os
from logging import Logger

from aries_cloudcontroller import (
Expand All @@ -17,9 +16,7 @@
set_endorser_info,
set_endorser_role,
)
from shared import ACAPY_ENDORSER_ALIAS

MAX_ATTEMPTS = int(os.getenv("WAIT_ISSUER_DID_MAX_ATTEMPTS", "15"))
from shared import ACAPY_ENDORSER_ALIAS, ISSUER_DID_ENDORSE_TIMEOUT


async def create_connection_with_endorser(
Expand Down Expand Up @@ -162,32 +159,46 @@ async def configure_endorsement(

async def register_issuer_did(
*,
endorser_controller: AcaPyClient,
issuer_controller: AcaPyClient,
issuer_label: str,
issuer_endorser_connection_id: str,
logger: Logger,
) -> DID:
logger.debug("Accepting TAA on behalf of issuer")
await acapy_ledger.accept_taa_if_required(issuer_controller)

logger.info("Creating DID for issuer")
issuer_did = await acapy_wallet.create_did(issuer_controller)

await acapy_ledger.register_nym_on_ledger(
endorser_controller,
issuer_controller,
did=issuer_did.did,
verkey=issuer_did.verkey,
alias=issuer_label,
create_transaction_for_endorser=True,
)

logger.debug("Waiting for issuer DID transaction to be endorsed")
await wait_transactions_endorsed( # Needs to be endorsed before setting public DID
issuer_controller=issuer_controller,
issuer_connection_id=issuer_endorser_connection_id,
logger=logger,
)

logger.debug("Accepting TAA on behalf of issuer")
await acapy_ledger.accept_taa_if_required(issuer_controller)
# NOTE: Still needs endorsement in 0.7.5 release
# Otherwise did has no associated services.
logger.debug("Setting public DID for issuer")
await acapy_wallet.set_public_did(
issuer_controller,
did=issuer_did.did,
create_transaction_for_endorser=True,
)

logger.debug("Waiting for ATTRIB transaction to be endorsed")
await wait_transactions_endorsed( # Needs to be endorsed before continuing
issuer_controller=issuer_controller,
issuer_connection_id=issuer_endorser_connection_id,
logger=logger,
)

logger.debug("Issuer DID registered.")
return issuer_did

Expand Down Expand Up @@ -239,12 +250,12 @@ async def wait_endorser_connection_completed(
raise asyncio.TimeoutError


async def wait_issuer_did_transaction_endorsed(
async def wait_transactions_endorsed(
*,
issuer_controller: AcaPyClient,
issuer_connection_id: str,
logger: Logger,
max_attempts: int = MAX_ATTEMPTS,
max_attempts: int = ISSUER_DID_ENDORSE_TIMEOUT,
retry_delay: float = 1.0,
) -> None:
attempt = 0
Expand All @@ -255,19 +266,38 @@ async def wait_issuer_did_transaction_endorsed(
await issuer_controller.endorse_transaction.get_records()
)

for transaction in transactions_response.results:
if (
transaction.connection_id == issuer_connection_id
and transaction.state == "transaction_acked"
):
return
transactions = [
transaction
for transaction in transactions_response.results
if transaction.connection_id == issuer_connection_id
]

if not transactions:
logger.error(
"No transactions found for connection {}. Found {} transactions.",
issuer_connection_id,
transactions_response,
)
raise CloudApiException("No transactions found for connection", 404)

all_acked = all(
transaction.state == "transaction_acked" for transaction in transactions
)

if all_acked:
return
else:
logger.debug(
"Waiting for transaction acknowledgements. Current states: %s",
", ".join(f"{t.transaction_id}: {t.state}" for t in transactions),
)

except Exception as e: # pylint: disable=W0718
if attempt + 1 == max_attempts:
logger.error(
"Maximum number of retries exceeded with exception. Failing."
)
raise asyncio.TimeoutError from e # Raise TimeoutError if max attempts exceeded
raise asyncio.TimeoutError("Timeout waiting for endorsement") from e

logger.warning(
(
Expand All @@ -284,4 +314,4 @@ async def wait_issuer_did_transaction_endorsed(
attempt += 1

logger.error("Maximum number of retries exceeded while waiting for transaction ack")
raise asyncio.TimeoutError
raise asyncio.TimeoutError("Timeout waiting for endorsement")
19 changes: 14 additions & 5 deletions app/services/onboarding/util/set_endorser_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@

# todo: Migrate to endorser service
async def set_endorser_role(
*, endorser_controller: AcaPyClient, endorser_connection_id: str, logger: Logger
*,
endorser_controller: AcaPyClient,
endorser_connection_id: str,
logger: Logger,
delay: float = DEFAULT_DELAY,
):
try:
logger.debug("Setting roles for endorser on endorser-issuer connection.")
Expand All @@ -24,7 +28,7 @@ async def set_endorser_role(
transaction_my_job="TRANSACTION_ENDORSER",
)
logger.debug("Successfully set endorser role.")
await asyncio.sleep(DEFAULT_DELAY) # Allow ACA-Py records to update
await asyncio.sleep(delay) # Allow ACA-Py records to update
except CloudApiException as e:
logger.error("Failed to set endorser role: {}.", e)
raise CloudApiException(
Expand All @@ -35,7 +39,11 @@ async def set_endorser_role(


async def set_author_role(
*, issuer_controller: AcaPyClient, issuer_connection_id: str, logger: Logger
*,
issuer_controller: AcaPyClient,
issuer_connection_id: str,
logger: Logger,
delay: float = DEFAULT_DELAY,
):
try:
logger.debug("Setting roles for author on issuer-endorser connection")
Expand All @@ -46,7 +54,7 @@ async def set_author_role(
transaction_my_job="TRANSACTION_AUTHOR",
)
logger.debug("Successfully set author role.")
await asyncio.sleep(DEFAULT_DELAY) # Allow ACA-Py records to update
await asyncio.sleep(delay) # Allow ACA-Py records to update
except CloudApiException as e:
logger.error("Failed to set author role: {}.", e)
raise CloudApiException(
Expand All @@ -62,6 +70,7 @@ async def set_endorser_info(
issuer_connection_id: str,
endorser_did: str,
logger: Logger,
delay: float = DEFAULT_DELAY,
):
try:
logger.debug("Setting endorser info on issuer-endorser connection")
Expand All @@ -72,7 +81,7 @@ async def set_endorser_info(
endorser_did=endorser_did,
)
logger.debug("Successfully set endorser info.")
await asyncio.sleep(DEFAULT_DELAY) # Allow ACA-Py records to update
await asyncio.sleep(delay) # Allow ACA-Py records to update
except CloudApiException as e:
logger.error("Failed to set endorser info: {}.", e)
raise CloudApiException(
Expand Down
5 changes: 3 additions & 2 deletions app/tests/e2e/verifier/test_proof_revoked_credential.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio

Check failure on line 1 in app/tests/e2e/verifier/test_proof_revoked_credential.py

View workflow job for this annotation

GitHub Actions / JUnit Test Report

test_proof_revoked_credential.test_proof_revoked_credential[clean-clean-clean-clean-clean-clean-auto_publish_true]

failed on setup with "fastapi.exceptions.HTTPException: 504: <html> <head><title>504 Gateway Time-out</title></head> <body> <center><h1>504 Gateway Time-out</h1></center> <hr><center>nginx</center> </body> </html>"
Raw output
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb16b1a53d0>
method = 'post', url = '/v1/tenants'
kwargs = {'json': {'extra_settings': None, 'group_id': 'IssuerGroup', 'image_url': None, 'roles': ['issuer'], ...}}
attempt = 0, response = <Response [504 Gateway Time-out]>, code = 504

    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:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
shared/util/rich_async_client.py:50: in _handle_response
    response.raise_for_status()  # Raise exception for 4xx and 5xx status codes
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Response [504 Gateway Time-out]>

    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 '504 Gateway Time-out' for url 'https://multitenant-web.cloudapi.dev.didxtech.com/tenant-admin/v1/tenants'
E       For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504

/usr/local/lib/python3.12/site-packages/httpx/_models.py:829: HTTPStatusError

The above exception was the direct cause of the following exception:

anyio_backend = 'asyncio'
request = <SubRequest 'faber_issuer' for <Function test_send_jsonld_key_ed25519[clean-clean-clean]>>
args = ()
kwargs = {'request': <SubRequest 'faber_issuer' for <Function test_send_jsonld_key_ed25519[clean-clean-clean]>>}
local_func = <function faber_issuer at 0x7fb16e9bd8a0>, backend_name = 'asyncio'
backend_options = {}
runner = <anyio._backends._asyncio.TestRunner object at 0x7fb16a1254f0>

    def wrapper(
        *args: Any, anyio_backend: Any, request: SubRequest, **kwargs: Any
    ) -> Any:
        # Rebind any fixture methods to the request instance
        if (
            request.instance
            and ismethod(func)
            and type(func.__self__) is type(request.instance)
        ):
            local_func = func.__func__.__get__(request.instance)
        else:
            local_func = func
    
        backend_name, backend_options = extract_backend_and_options(anyio_backend)
        if has_backend_arg:
            kwargs["anyio_backend"] = anyio_backend
    
        if has_request_arg:
            kwargs["request"] = request
    
        with get_runner(backend_name, backend_options) as runner:
            if isasyncgenfunction(local_func):
>               yield from runner.run_asyncgen_fixture(local_func, kwargs)

/usr/local/lib/python3.12/site-packages/anyio/pytest_plugin.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2278: in run_asyncgen_fixture
    fixturevalue: T_Retval = self.get_loop().run_until_complete(
/usr/local/lib/python3.12/asyncio/base_events.py:686: in run_until_complete
    return future.result()
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2270: in _call_in_runner_task
    return await future
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2237: in _run_tests_and_fixtures
    retval = await coro
app/tests/fixtures/member_wallets.py:83: in faber_issuer
    issuer_tenant = await create_issuer_tenant(admin_client, "faber")
app/tests/util/tenants.py:26: in create_issuer_tenant
    return await post_tenant_request(admin_client, request)
app/tests/util/tenants.py:16: in post_tenant_request
    response = await admin_client.post(TENANT_BASE_PATH, json=request.model_dump())
shared/util/rich_async_client.py:81: in post
    return await self._request_with_retries("post", url, **kwargs)
shared/util/rich_async_client.py:78: in _request_with_retries
    await self._handle_error(e, url, method)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb16b1a53d0>
e = HTTPStatusError("Server error '504 Gateway Time-out' for url 'https://multitenant-web.cloudapi.dev.didxtech.com/tenant-admin/v1/tenants'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504")
url = '/v1/tenants', 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: 504: <html>
E       <head><title>504 Gateway Time-out</title></head>
E       <body>
E       <center><h1>504 Gateway Time-out</h1></center>
E       <hr><center>nginx</center>
E       </body>
E       </html>

shared/util/rich_async_client.py:61: HTTPException

Check failure on line 1 in app/tests/e2e/verifier/test_proof_revoked_credential.py

View workflow job for this annotation

GitHub Actions / JUnit Test Report

test_proof_revoked_credential.test_proof_revoked_credential[clean-clean-clean-clean-clean-clean-default]

failed on setup with "fastapi.exceptions.HTTPException: 504: <html> <head><title>504 Gateway Time-out</title></head> <body> <center><h1>504 Gateway Time-out</h1></center> <hr><center>nginx</center> </body> </html>"
Raw output
self = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb16b1a53d0>
method = 'post', url = '/v1/tenants'
kwargs = {'json': {'extra_settings': None, 'group_id': 'IssuerGroup', 'image_url': None, 'roles': ['issuer'], ...}}
attempt = 0, response = <Response [504 Gateway Time-out]>, code = 504

    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:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
shared/util/rich_async_client.py:50: in _handle_response
    response.raise_for_status()  # Raise exception for 4xx and 5xx status codes
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Response [504 Gateway Time-out]>

    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 '504 Gateway Time-out' for url 'https://multitenant-web.cloudapi.dev.didxtech.com/tenant-admin/v1/tenants'
E       For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504

/usr/local/lib/python3.12/site-packages/httpx/_models.py:829: HTTPStatusError

The above exception was the direct cause of the following exception:

anyio_backend = 'asyncio'
request = <SubRequest 'faber_issuer' for <Function test_send_jsonld_key_ed25519[clean-clean-clean]>>
args = ()
kwargs = {'request': <SubRequest 'faber_issuer' for <Function test_send_jsonld_key_ed25519[clean-clean-clean]>>}
local_func = <function faber_issuer at 0x7fb16e9bd8a0>, backend_name = 'asyncio'
backend_options = {}
runner = <anyio._backends._asyncio.TestRunner object at 0x7fb16a1254f0>

    def wrapper(
        *args: Any, anyio_backend: Any, request: SubRequest, **kwargs: Any
    ) -> Any:
        # Rebind any fixture methods to the request instance
        if (
            request.instance
            and ismethod(func)
            and type(func.__self__) is type(request.instance)
        ):
            local_func = func.__func__.__get__(request.instance)
        else:
            local_func = func
    
        backend_name, backend_options = extract_backend_and_options(anyio_backend)
        if has_backend_arg:
            kwargs["anyio_backend"] = anyio_backend
    
        if has_request_arg:
            kwargs["request"] = request
    
        with get_runner(backend_name, backend_options) as runner:
            if isasyncgenfunction(local_func):
>               yield from runner.run_asyncgen_fixture(local_func, kwargs)

/usr/local/lib/python3.12/site-packages/anyio/pytest_plugin.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2278: in run_asyncgen_fixture
    fixturevalue: T_Retval = self.get_loop().run_until_complete(
/usr/local/lib/python3.12/asyncio/base_events.py:686: in run_until_complete
    return future.result()
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2270: in _call_in_runner_task
    return await future
/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py:2237: in _run_tests_and_fixtures
    retval = await coro
app/tests/fixtures/member_wallets.py:83: in faber_issuer
    issuer_tenant = await create_issuer_tenant(admin_client, "faber")
app/tests/util/tenants.py:26: in create_issuer_tenant
    return await post_tenant_request(admin_client, request)
app/tests/util/tenants.py:16: in post_tenant_request
    response = await admin_client.post(TENANT_BASE_PATH, json=request.model_dump())
shared/util/rich_async_client.py:81: in post
    return await self._request_with_retries("post", url, **kwargs)
shared/util/rich_async_client.py:78: in _request_with_retries
    await self._handle_error(e, url, method)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <shared.util.rich_async_client.RichAsyncClient object at 0x7fb16b1a53d0>
e = HTTPStatusError("Server error '504 Gateway Time-out' for url 'https://multitenant-web.cloudapi.dev.didxtech.com/tenant-admin/v1/tenants'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504")
url = '/v1/tenants', 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: 504: <html>
E       <head><title>504 Gateway Time-out</title></head>
E       <body>
E       <center><h1>504 Gateway Time-out</h1></center>
E       <hr><center>nginx</center>
E       </body>
E       </html>

shared/util/rich_async_client.py:61: HTTPException
import time
from typing import List

Expand Down Expand Up @@ -34,7 +35,7 @@ async def test_proof_revoked_credential(
alice_member_client: RichAsyncClient,
acme_and_alice_connection: AcmeAliceConnect,
):
time.sleep(10) # moment for revocation registry to update
await asyncio.sleep(14) # moment for revocation registry to update
# todo: remove sleep when issue resolved: https://github.com/openwallet-foundation/acapy/issues/3018

# Do proof request
Expand Down Expand Up @@ -124,7 +125,7 @@ async def test_regression_proof_revoked_credential(
alice_member_client: RichAsyncClient,
acme_and_alice_connection: AcmeAliceConnect,
):
time.sleep(10) # moment for revocation registry to update
await asyncio.sleep(14) # moment for revocation registry to update
# todo: remove sleep when issue resolved: https://github.com/openwallet-foundation/acapy/issues/3018

referent = get_or_issue_regression_cred_revoked.referent
Expand Down
36 changes: 18 additions & 18 deletions app/tests/services/onboarding/test_onboarding.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest.mock import AsyncMock, patch

import pytest
from aries_cloudcontroller import (
DID,
Expand Down Expand Up @@ -88,22 +90,19 @@ async def test_onboard_issuer_no_public_did(

endorser_controller = get_mock_agent_controller()

# Mock the necessary functions and methods
when(acapy_wallet).get_public_did(controller=mock_agent_controller).thenRaise(
CloudApiException(detail="Error")
)
when(acapy_wallet).get_public_did(controller=endorser_controller).thenReturn(
to_async(did_object)
)

when(endorser_controller.out_of_band).create_invitation(...).thenReturn(
to_async(InvitationRecord(invitation=InvitationMessage()))
)

# Mock responses
when(mock_agent_controller.out_of_band).receive_invitation(...).thenReturn(
to_async(ConnRecord(connection_id=issuer_connection_id))
)

when(endorser_controller.connection).get_connections(...).thenReturn(
to_async(
ConnectionList(
Expand All @@ -115,20 +114,17 @@ async def test_onboard_issuer_no_public_did(
)
)
)

when(mock_agent_controller.endorse_transaction).set_endorser_role(...).thenReturn(
to_async()
)

when(endorser_controller.endorse_transaction).set_endorser_role(...).thenReturn(
to_async()
)
when(mock_agent_controller.endorse_transaction).set_endorser_info(...).thenAnswer(
lambda conn_id, endorser_did: to_async()
)

when(mock_agent_controller.endorse_transaction).get_records(...).thenReturn(
to_async(
when(mock_agent_controller.endorse_transaction).get_records(...).thenAnswer(
lambda: to_async( # lambda to avoid "cannot reuse already awaited coroutine"
TransactionList(
results=[
TransactionRecord(
Expand All @@ -138,7 +134,6 @@ async def test_onboard_issuer_no_public_did(
)
)
)

when(acapy_wallet).create_did(mock_agent_controller).thenReturn(
to_async(did_object)
)
Expand All @@ -156,27 +151,32 @@ async def test_onboard_issuer_no_public_did(
)
)

onboard_result = await issuer.onboard_issuer(
issuer_label="issuer_name",
endorser_controller=endorser_controller,
issuer_controller=mock_agent_controller,
issuer_wallet_id="issuer_wallet_id",
)
# Patch asyncio.sleep to return immediately
with patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
onboard_result = await issuer.onboard_issuer(
issuer_label="issuer_name",
endorser_controller=endorser_controller,
issuer_controller=mock_agent_controller,
issuer_wallet_id="issuer_wallet_id",
)

# Assertions
assert_that(onboard_result).has_did("did:sov:WgWxqztrNooG92RXvxSTWv")
verify(acapy_ledger).accept_taa_if_required(mock_agent_controller)
verify(acapy_wallet).create_did(mock_agent_controller)
verify(acapy_ledger).register_nym_on_ledger(
endorser_controller,
mock_agent_controller,
did="WgWxqztrNooG92RXvxSTWv",
verkey="WgWxqztrNooG92RXvxSTWvWgWxqztrNooG92RXvxSTWv",
alias="issuer_name",
create_transaction_for_endorser=True,
)
verify(acapy_ledger).accept_taa_if_required(mock_agent_controller)
verify(acapy_wallet).set_public_did(
mock_agent_controller,
did="WgWxqztrNooG92RXvxSTWv",
create_transaction_for_endorser=True,
)
mock_sleep.assert_awaited() # Ensure that sleep was called and patched


@pytest.mark.anyio
Expand Down
Loading

0 comments on commit c649e76

Please sign in to comment.