Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✅ test coverage for issuer routes #843

Merged
merged 3 commits into from
Jun 21, 2024
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
36 changes: 9 additions & 27 deletions app/routes/issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,7 @@ async def send_credential(
f"Failed to send credential: {e.detail}", e.status_code
) from e

if result:
bound_logger.info("Successfully sent credential.")
else:
bound_logger.warning("No result from sending credential.")
bound_logger.info("Successfully sent credential.")
return result


Expand Down Expand Up @@ -212,10 +209,7 @@ async def create_offer(
credential=credential,
)

if result:
bound_logger.info("Successfully created credential offer.")
else:
bound_logger.warning("No result from creating credential offer.")
bound_logger.info("Successfully created credential offer.")
return result


Expand Down Expand Up @@ -281,10 +275,7 @@ async def request_credential(
controller=aries_controller, credential_exchange_id=credential_exchange_id
)

if result:
bound_logger.info("Successfully sent credential request.")
else:
bound_logger.warning("No result from sending credential request.")
bound_logger.info("Successfully sent credential request.")
return result


Expand Down Expand Up @@ -332,10 +323,7 @@ async def store_credential(
controller=aries_controller, credential_exchange_id=credential_exchange_id
)

if result:
bound_logger.info("Successfully stored credential.")
else:
bound_logger.warning("No result from storing credential.")
bound_logger.info("Successfully stored credential.")
return result


Expand Down Expand Up @@ -459,10 +447,7 @@ async def get_credential(
controller=aries_controller, credential_exchange_id=credential_exchange_id
)

if result:
bound_logger.info("Successfully fetched credential.")
else:
bound_logger.info("No credential returned.")
bound_logger.info("Successfully fetched credential.")
return result


Expand Down Expand Up @@ -582,7 +567,7 @@ async def get_credential_revocation_record(
Raises:
---
CloudApiException: 400
If credential_exchange_id is not provided, BOTH credential_revocation_id and revocation_registry_id must be.
If credential_exchange_id is not provided, both credential_revocation_id and revocation_registry_id must be.
"""
bound_logger = logger.bind(
body={
Expand All @@ -597,8 +582,8 @@ async def get_credential_revocation_record(
credential_revocation_id is None or revocation_registry_id is None
):
raise CloudApiException(
"If credential_exchange_id is not provided BOTH the credential_revocation_id and \
revocation_registry_id MUST be provided.",
"If credential_exchange_id is not provided then both "
"credential_revocation_id and revocation_registry_id must be provided.",
400,
)

Expand All @@ -611,10 +596,7 @@ async def get_credential_revocation_record(
revocation_registry_id=revocation_registry_id,
)

if revocation_record:
bound_logger.info("Successfully fetched credential revocation record.")
else:
bound_logger.info("No credential revocation record returned.")
bound_logger.info("Successfully fetched credential revocation record.")
return revocation_record


Expand Down
75 changes: 75 additions & 0 deletions app/tests/routes/issuer/test_clear_pending_revocations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from unittest.mock import AsyncMock, patch
Dismissed Show dismissed Hide dismissed

import pytest
from aries_cloudcontroller.exceptions import (
ApiException,
BadRequestException,
NotFoundException,
)
from fastapi import HTTPException

from app.models.issuer import ClearPendingRevocationsRequest
from app.routes.issuer import clear_pending_revocations


@pytest.mark.anyio
async def test_clear_pending_revocations_success():
mock_aries_controller = AsyncMock()
mock_clear_pending_revocations = AsyncMock()

with patch("app.routes.issuer.client_from_auth") as mock_client_from_auth, patch(
"app.services.revocation_registry.clear_pending_revocations",
mock_clear_pending_revocations,
):
mock_client_from_auth.return_value.__aenter__.return_value = (
mock_aries_controller
)

clear_request = ClearPendingRevocationsRequest(
revocation_registry_credential_map={}
)

await clear_pending_revocations(
clear_pending_request=clear_request, auth="mocked_auth"
)

mock_clear_pending_revocations.assert_awaited_once_with(
controller=mock_aries_controller, revocation_registry_credential_map={}
)


@pytest.mark.anyio
@pytest.mark.parametrize(
"exception_class, expected_status_code, expected_detail",
[
(BadRequestException, 400, "Bad request"),
(NotFoundException, 404, "Not found"),
(ApiException, 500, "Internal Server Error"),
],
)
async def test_clear_pending_revocations_fail_acapy_error(
exception_class, expected_status_code, expected_detail
):
mock_aries_controller = AsyncMock()
mock_aries_controller.revocation.clear_pending_revocations = AsyncMock(
side_effect=exception_class(status=expected_status_code, reason=expected_detail)
)

with patch(
"app.routes.issuer.client_from_auth"
) as mock_client_from_auth, pytest.raises(
HTTPException, match=expected_detail
) as exc:
mock_client_from_auth.return_value.__aenter__.return_value = (
mock_aries_controller
)

clear_request = ClearPendingRevocationsRequest(
revocation_registry_credential_map={}
)

await clear_pending_revocations(
clear_pending_request=clear_request, auth="mocked_auth"
)

assert exc.value.status_code == expected_status_code
152 changes: 152 additions & 0 deletions app/tests/routes/issuer/test_create_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
from unittest.mock import AsyncMock, Mock, patch

import pytest
from aries_cloudcontroller import Credential, LDProofVCDetail, LDProofVCOptions
from aries_cloudcontroller.exceptions import (
ApiException,
BadRequestException,
NotFoundException,
)
from fastapi import HTTPException

from app.exceptions.cloudapi_exception import CloudApiException
from app.models.issuer import CreateOffer, CredentialType, IndyCredential
from app.routes.issuer import create_offer

indy_cred = IndyCredential(
credential_definition_id="WgWxqztrNooG92RXvxSTWv:3:CL:20:tag", attributes={}
)
ld_cred = LDProofVCDetail(
credential=Credential(
context=[
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1",
],
type=["VerifiableCredential", "UniversityDegreeCredential"],
credentialSubject={
"degree": {
"type": "BachelorDegree",
"name": "Bachelor of Science and Arts",
},
"college": "Faber College",
},
issuanceDate="2021-04-12",
issuer="",
),
options=LDProofVCOptions(proofType="Ed25519Signature2018"),
)


@pytest.mark.anyio
@pytest.mark.parametrize(
"credential",
[
CreateOffer(
protocol_version="v2",
type=CredentialType.INDY,
indy_credential_detail=indy_cred,
),
CreateOffer(
protocol_version="v2",
type=CredentialType.LD_PROOF,
ld_credential_detail=ld_cred,
),
],
)
async def test_create_offer_success(credential):
mock_aries_controller = AsyncMock()
issuer = Mock()
issuer.create_offer = AsyncMock()

with patch("app.routes.issuer.client_from_auth") as mock_client_from_auth, patch(
"app.routes.issuer.issuer_from_protocol_version",
return_value=issuer,
), patch("app.routes.issuer.assert_public_did", return_value="public_did"), patch(
"app.routes.issuer.schema_id_from_credential_definition_id",
return_value="schema_id",
), patch(
"app.routes.issuer.assert_valid_issuer"
):
mock_client_from_auth.return_value.__aenter__.return_value = (
mock_aries_controller
)

await create_offer(credential=credential, auth="mocked_auth")

issuer.create_offer.assert_awaited_once_with(
controller=mock_aries_controller, credential=credential
)


@pytest.mark.anyio
@pytest.mark.parametrize(
"exception_class, expected_status_code, expected_detail",
[
(BadRequestException, 400, "Bad request"),
(NotFoundException, 404, "Not found"),
(ApiException, 500, "Internal Server Error"),
],
)
async def test_create_offer_fail_acapy_error(
exception_class, expected_status_code, expected_detail
):
mock_aries_controller = AsyncMock()
mock_aries_controller.issue_credential_v2_0.create_offer = AsyncMock(
side_effect=exception_class(status=expected_status_code, reason=expected_detail)
)

with patch(
"app.routes.issuer.client_from_auth"
) as mock_client_from_auth, pytest.raises(
HTTPException, match=expected_detail
) as exc, patch(
"app.routes.issuer.assert_public_did", return_value="public_did"
), patch(
"app.routes.issuer.schema_id_from_credential_definition_id",
return_value="schema_id",
), patch(
"app.routes.issuer.assert_valid_issuer"
):
mock_client_from_auth.return_value.__aenter__.return_value = (
mock_aries_controller
)

await create_offer(
credential=CreateOffer(
protocol_version="v2",
type=CredentialType.LD_PROOF,
ld_credential_detail=ld_cred,
),
auth="mocked_auth",
)

assert exc.value.status_code == expected_status_code


@pytest.mark.anyio
async def test_create_offer_fail_bad_public_did():
credential = CreateOffer(
protocol_version="v2",
type=CredentialType.INDY,
indy_credential_detail=indy_cred,
)

mock_aries_controller = AsyncMock()
mock_aries_controller.issue_credential_v2_0.issue_credential_automated = AsyncMock()

with patch("app.routes.issuer.client_from_auth") as mock_client_from_auth, patch(
"app.routes.issuer.assert_public_did",
AsyncMock(side_effect=CloudApiException(status_code=404, detail="Not found")),
), pytest.raises(
HTTPException,
match="Wallet making this request has no public DID. Only issuers with a public DID can make this request.",
) as exc:
mock_client_from_auth.return_value.__aenter__.return_value = (
mock_aries_controller
)

await create_offer(credential=credential, auth="mocked_auth")

mock_aries_controller.issue_credential_v2_0.issue_credential_automated.assert_awaited_once()

assert exc.value.status_code == 403
62 changes: 62 additions & 0 deletions app/tests/routes/issuer/test_get_credential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from unittest.mock import AsyncMock, Mock, patch

import pytest

from app.exceptions.cloudapi_exception import CloudApiException
from app.routes.issuer import get_credential


@pytest.mark.anyio
async def test_get_credential_success():
mock_aries_controller = AsyncMock()
issuer = Mock()
issuer.get_record = AsyncMock()

with patch("app.routes.issuer.client_from_auth") as mock_client_from_auth, patch(
"app.routes.issuer.issuer_from_id", return_value=issuer
):
mock_client_from_auth.return_value.__aenter__.return_value = (
mock_aries_controller
)

await get_credential(credential_exchange_id="test_id", auth="mocked_auth")

issuer.get_record.assert_awaited_once_with(
controller=mock_aries_controller, credential_exchange_id="test_id"
)


@pytest.mark.anyio
@pytest.mark.parametrize(
"exception_class, expected_status_code, expected_detail",
[
(CloudApiException, 400, "Bad request"),
(CloudApiException, 404, "Not found"),
(CloudApiException, 500, "Internal Server Error"),
],
)
async def test_get_credential_fail_acapy_error(
exception_class, expected_status_code, expected_detail
):
mock_aries_controller = AsyncMock()
issuer = Mock()
issuer.get_record = AsyncMock(
side_effect=exception_class(
status_code=expected_status_code, detail=expected_detail
)
)

with patch(
"app.routes.issuer.client_from_auth"
) as mock_client_from_auth, pytest.raises(
CloudApiException, match=expected_detail
) as exc, patch(
"app.routes.issuer.issuer_from_id", return_value=issuer
):
mock_client_from_auth.return_value.__aenter__.return_value = (
mock_aries_controller
)

await get_credential(credential_exchange_id="test_id", auth="mocked_auth")

assert exc.value.status_code == expected_status_code
Loading
Loading