diff --git a/app/models/verifier.py b/app/models/verifier.py index 678f284d49..f1665a0aff 100644 --- a/app/models/verifier.py +++ b/app/models/verifier.py @@ -2,6 +2,7 @@ from typing import Optional, Union from aries_cloudcontroller import ( + AnoncredsPresentationRequest, DIFPresSpec, DIFProofRequest, IndyNonRevocationInterval, @@ -18,6 +19,7 @@ class ProofRequestType(str, Enum): INDY: str = "indy" JWT: str = "jwt" LD_PROOF: str = "ld_proof" + ANONCREDS: str = "anoncreds" class IndyProofRequest(AcaPyIndyProofRequest): @@ -29,10 +31,11 @@ class ProofRequestBase(BaseModel): type: ProofRequestType = ProofRequestType.INDY indy_proof_request: Optional[IndyProofRequest] = None dif_proof_request: Optional[DIFProofRequest] = None + anoncreds_proof_request: Optional[AnoncredsPresentationRequest] = None @model_validator(mode="before") @classmethod - def check_indy_proof_request(cls, values: Union[dict, "ProofRequestBase"]): + def check_proof_request(cls, values: Union[dict, "ProofRequestBase"]): # pydantic v2 removed safe way to get key, because `values` can be a dict or this type if not isinstance(values, dict): values = values.__dict__ @@ -40,6 +43,12 @@ def check_indy_proof_request(cls, values: Union[dict, "ProofRequestBase"]): proof_type = values.get("type") indy_proof = values.get("indy_proof_request") dif_proof = values.get("dif_proof_request") + anoncreds_proof = values.get("anoncreds_proof_request") + + if proof_type == ProofRequestType.ANONCREDS and anoncreds_proof is None: + raise CloudApiValueError( + "anoncreds_proof_request must be populated if `anoncreds` type is selected" + ) if proof_type == ProofRequestType.INDY and indy_proof is None: raise CloudApiValueError( @@ -51,14 +60,25 @@ def check_indy_proof_request(cls, values: Union[dict, "ProofRequestBase"]): "dif_proof_request must be populated if `ld_proof` type is selected" ) - if proof_type == ProofRequestType.INDY and dif_proof is not None: + if proof_type == ProofRequestType.INDY and ( + dif_proof is not None or anoncreds_proof is not None + ): + raise CloudApiValueError( + "Only indy_proof_request must not be populated if `indy` type is selected" + ) + + if proof_type == ProofRequestType.LD_PROOF and ( + indy_proof is not None or anoncreds_proof is not None + ): raise CloudApiValueError( - "dif_proof_request must not be populated if `indy` type is selected" + "Only dif_proof_request must not be populated if `ld_proof` type is selected" ) - if proof_type == ProofRequestType.LD_PROOF and indy_proof is not None: + if proof_type == ProofRequestType.ANONCREDS and ( + indy_proof is not None or dif_proof is not None + ): raise CloudApiValueError( - "indy_proof_request must not be populated if `ld_proof` type is selected" + "Only anoncreds_proof_request must not be populated if `anoncreds` type is selected" ) return values @@ -86,6 +106,8 @@ class AcceptProofRequest(ProofId, SaveExchangeRecordField): type: ProofRequestType = ProofRequestType.INDY indy_presentation_spec: Optional[IndyPresSpec] = None dif_presentation_spec: Optional[DIFPresSpec] = None + # Controller uses IndyPresSpec for anoncreds + anoncreds_presentation_spec: Optional[IndyPresSpec] = None @field_validator("indy_presentation_spec", mode="before") @classmethod @@ -105,6 +127,15 @@ def check_dif_presentation_spec(cls, value, values: ValidationInfo): ) return value + @field_validator("anoncreds_presentation_spec", mode="before") + @classmethod + def check_anoncreds_proof_request(cls, value, values: ValidationInfo): + if values.data.get("type") == ProofRequestType.ANONCREDS and value is None: + raise CloudApiValueError( + "anoncreds_proof_request must be populated if `anoncreds` type is selected" + ) + return value + class RejectProofRequest(ProofId): problem_report: str = Field( diff --git a/app/services/verifier/acapy_verifier_v2.py b/app/services/verifier/acapy_verifier_v2.py index ca4c193dad..c2dc456f87 100644 --- a/app/services/verifier/acapy_verifier_v2.py +++ b/app/services/verifier/acapy_verifier_v2.py @@ -46,6 +46,10 @@ async def create_proof_request( presentation_request = V20PresRequestByFormat( dif=create_proof_request.dif_proof_request ) + elif create_proof_request.type == ProofRequestType.ANONCREDS: + presentation_request = V20PresRequestByFormat( + anoncreds=create_proof_request.anoncreds_proof_request + ) else: raise CloudApiException( f"Unsupported credential type: {create_proof_request.type.value}", @@ -89,6 +93,10 @@ async def send_proof_request( presentation_request = V20PresRequestByFormat( dif=send_proof_request.dif_proof_request ) + elif send_proof_request.type == ProofRequestType.ANONCREDS: + presentation_request = V20PresRequestByFormat( + anoncreds=send_proof_request.anoncreds_proof_request + ) else: raise CloudApiException( f"Unsupported credential type: {send_proof_request.type.value}", @@ -134,6 +142,11 @@ async def accept_proof_request( presentation_spec = V20PresSpecByFormatRequest( auto_remove=auto_remove, dif=accept_proof_request.dif_presentation_spec ) + elif accept_proof_request.type == ProofRequestType.ANONCREDS: + presentation_spec = V20PresSpecByFormatRequest( + auto_remove=auto_remove, + anoncreds=accept_proof_request.anoncreds_presentation_spec, + ) else: raise CloudApiException( f"Unsupported credential type: {accept_proof_request.type.value}", diff --git a/app/tests/models/test_verifier.py b/app/tests/models/test_verifier.py index 6f5b43b223..12108c2732 100644 --- a/app/tests/models/test_verifier.py +++ b/app/tests/models/test_verifier.py @@ -3,6 +3,7 @@ from app.models.verifier import ( AcceptProofRequest, + AnoncredsPresentationRequest, DIFPresSpec, IndyPresSpec, IndyProofRequest, @@ -19,7 +20,11 @@ def test_proof_request_base_model(): assert exc.value.detail == ( "indy_proof_request must be populated if `indy` type is selected" ) - + with pytest.raises(CloudApiValueError) as exc: + ProofRequestBase(type=ProofRequestType.ANONCREDS, anoncreds_proof_request=None) + assert exc.value.detail == ( + "anoncreds_proof_request must be populated if `anoncreds` type is selected" + ) with pytest.raises(CloudApiValueError) as exc: ProofRequestBase( type=ProofRequestType.LD_PROOF, @@ -31,7 +36,7 @@ def test_proof_request_base_model(): ), ) assert exc.value.detail == ( - "indy_proof_request must not be populated if `ld_proof` type is selected" + "Only dif_proof_request must not be populated if `ld_proof` type is selected" ) with pytest.raises(CloudApiValueError) as exc: @@ -45,7 +50,21 @@ def test_proof_request_base_model(): ), ) assert exc.value.detail == ( - "dif_proof_request must not be populated if `indy` type is selected" + "Only indy_proof_request must not be populated if `indy` type is selected" + ) + + with pytest.raises(CloudApiValueError) as exc: + ProofRequestBase( + type=ProofRequestType.ANONCREDS, + anoncreds_proof_request=AnoncredsPresentationRequest( + requested_attributes={}, requested_predicates={} + ), + dif_proof_request=DIFProofRequest( + presentation_definition=PresentationDefinition() + ), + ) + assert exc.value.detail == ( + "Only anoncreds_proof_request must not be populated if `anoncreds` type is selected" ) with pytest.raises(CloudApiValueError) as exc: @@ -54,7 +73,7 @@ def test_proof_request_base_model(): "dif_proof_request must be populated if `ld_proof` type is selected" ) - ProofRequestBase.check_indy_proof_request( + ProofRequestBase.check_proof_request( values=ProofRequestBase( indy_proof_request=IndyProofRequest( requested_attributes={}, requested_predicates={} @@ -79,6 +98,15 @@ def test_accept_proof_request_model(): dif_presentation_spec=DIFPresSpec(), ) + AcceptProofRequest( + proof_id="abc", + anoncreds_presentation_spec=IndyPresSpec( + requested_attributes={}, + requested_predicates={}, + self_attested_attributes={}, + ), + ) + with pytest.raises(CloudApiValueError) as exc: AcceptProofRequest(type=ProofRequestType.INDY, indy_presentation_spec=None) assert exc.value.detail == ( @@ -89,6 +117,13 @@ def test_accept_proof_request_model(): assert exc.value.detail == ( "dif_presentation_spec must be populated if `ld_proof` type is selected" ) + with pytest.raises(CloudApiValueError) as exc: + AcceptProofRequest( + type=ProofRequestType.ANONCREDS, anoncreds_presentation_spec=None + ) + assert exc.value.detail == ( + "anoncreds_proof_request must be populated if `anoncreds` type is selected" + ) def test_reject_proof_request_model(): diff --git a/app/tests/services/verifier/test_acapy_verifier_v2.py b/app/tests/services/verifier/test_acapy_verifier_v2.py index 70212f12e9..d08b644a36 100644 --- a/app/tests/services/verifier/test_acapy_verifier_v2.py +++ b/app/tests/services/verifier/test_acapy_verifier_v2.py @@ -22,6 +22,7 @@ from app.services.verifier.acapy_verifier_v2 import VerifierV2 from app.tests.services.verifier.utils import ( dif_proof_request, + sample_anoncreds_proof_request, sample_indy_proof_request, v20_presentation_exchange_records, ) @@ -37,7 +38,12 @@ @pytest.mark.anyio @pytest.mark.parametrize( "proof_type", - [ProofRequestType.INDY, ProofRequestType.LD_PROOF, ProofRequestType.JWT], + [ + ProofRequestType.INDY, + ProofRequestType.LD_PROOF, + ProofRequestType.JWT, + ProofRequestType.ANONCREDS, + ], ) async def test_create_proof_request(mock_agent_controller: AcaPyClient, proof_type): if proof_type != ProofRequestType.JWT: @@ -52,6 +58,11 @@ async def test_create_proof_request(mock_agent_controller: AcaPyClient, proof_ty dif_proof_request=( dif_proof_request if proof_type.value == "ld_proof" else None ), + anoncreds_proof_request=( + sample_anoncreds_proof_request() + if proof_type.value == "anoncreds" + else None + ), type=proof_type, ) @@ -102,7 +113,12 @@ async def test_create_proof_request_exception( @pytest.mark.anyio @pytest.mark.parametrize( "proof_type", - [ProofRequestType.INDY, ProofRequestType.LD_PROOF, ProofRequestType.JWT], + [ + ProofRequestType.INDY, + ProofRequestType.LD_PROOF, + ProofRequestType.JWT, + ProofRequestType.ANONCREDS, + ], ) async def test_send_proof_request(mock_agent_controller: AcaPyClient, proof_type): if proof_type != ProofRequestType.JWT: @@ -118,6 +134,11 @@ async def test_send_proof_request(mock_agent_controller: AcaPyClient, proof_type dif_proof_request=( dif_proof_request if proof_type.value == "ld_proof" else None ), + anoncreds_proof_request=( + sample_anoncreds_proof_request() + if proof_type.value == "anoncreds" + else None + ), connection_id="abcde", ) @@ -177,7 +198,12 @@ async def test_send_proof_request_exception( @pytest.mark.anyio @pytest.mark.parametrize( "proof_type", - [ProofRequestType.INDY, ProofRequestType.LD_PROOF, ProofRequestType.JWT], + [ + ProofRequestType.INDY, + ProofRequestType.LD_PROOF, + ProofRequestType.JWT, + ProofRequestType.ANONCREDS, + ], ) async def test_accept_proof_request(mock_agent_controller: AcaPyClient, proof_type): if proof_type != ProofRequestType.JWT: @@ -199,6 +225,15 @@ async def test_accept_proof_request(mock_agent_controller: AcaPyClient, proof_ty dif_presentation_spec=( DIFPresSpec() if proof_type.value == "ld_proof" else None ), + anoncreds_presentation_spec=( + IndyPresSpec( + requested_attributes={}, + requested_predicates={}, + self_attested_attributes={}, + ) + if proof_type.value == "anoncreds" + else None + ), proof_id="v2-123", ) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index abbfdee7c0..96a1ae5c87 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -90,6 +90,78 @@ async def test_send_proof_request_v2( ) +@pytest.mark.anyio +async def test_send_proof_request_v2_exception( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + # V2 + when(VerifierV2).send_proof_request(...).thenRaise(CloudApiException("ERROR")) + mocker.patch.object( + test_module, + "assert_valid_verifier", + return_value=None, + ) + + send_proof_request = test_module.SendProofRequest( + connection_id="abcde", + indy_proof_request=sample_indy_proof_request(), + ) + + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + with pytest.raises(CloudApiException, match="500: ERROR") as exc: + await test_module.send_proof_request( + body=send_proof_request, + auth=mock_tenant_auth, + ) + assert exc.value.status_code == 500 + + +@pytest.mark.anyio +async def test_send_proof_request_v2_no_response( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + # V2 + when(VerifierV2).send_proof_request(...).thenReturn(to_async(None)) + + mocker.patch.object( + test_module, + "assert_valid_verifier", + return_value=None, + ) + + send_proof_request = test_module.SendProofRequest( + connection_id="abcde", + indy_proof_request=sample_indy_proof_request(), + ) + + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + result = await test_module.send_proof_request( + body=send_proof_request, + auth=mock_tenant_auth, + ) + + assert result is None + verify(VerifierV2).send_proof_request( + controller=mock_agent_controller, send_proof_request=send_proof_request + ) + + @pytest.mark.anyio async def test_create_proof_request(mock_tenant_auth: AcaPyAuth): when(VerifierV2).create_proof_request(...).thenReturn( @@ -105,6 +177,33 @@ async def test_create_proof_request(mock_tenant_auth: AcaPyAuth): assert result is presentation_exchange_record_2 +@pytest.mark.anyio +async def test_create_proof_request_exception(mock_tenant_auth: AcaPyAuth): + when(VerifierV2).create_proof_request(...).thenRaise(CloudApiException("ERROR")) + with pytest.raises(CloudApiException, match="500: ERROR") as exc: + await test_module.create_proof_request( + body=test_module.CreateProofRequest( + indy_proof_request=sample_indy_proof_request(), + connection_id="abcde", + ), + auth=mock_tenant_auth, + ) + assert exc.value.status_code == 500 + + +@pytest.mark.anyio +async def test_create_proof_request_no_result(mock_tenant_auth: AcaPyAuth): + when(VerifierV2).create_proof_request(...).thenReturn(to_async(None)) + result = await test_module.create_proof_request( + body=test_module.CreateProofRequest( + indy_proof_request=sample_indy_proof_request(), + connection_id="abcde", + ), + auth=mock_tenant_auth, + ) + assert result is None + + @pytest.mark.anyio async def test_accept_proof_request_v2( mock_agent_controller: AcaPyClient, @@ -143,6 +242,117 @@ async def test_accept_proof_request_v2( verify(VerifierV2).accept_proof_request(...) +@pytest.mark.anyio +async def test_accept_proof_request_v2_exception( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + # V2 + when(VerifierV2).accept_proof_request(...).thenRaise(CloudApiException("ERROR")) + when(VerifierV2).get_proof_record(...).thenReturn( + to_async(presentation_exchange_record_2) + ) + + presentation = test_module.AcceptProofRequest( + proof_id="v2-1234", indy_presentation_spec=indy_pres_spec + ) + + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + mocker.patch.object( + test_module, "assert_valid_prover", new_callable=AsyncMock, return_value=None + ) + + with pytest.raises(CloudApiException, match="500: ERROR") as exc: + await test_module.accept_proof_request( + body=presentation, + auth=mock_tenant_auth, + ) + assert exc.value.status_code == 500 + + +@pytest.mark.anyio +async def test_accept_proof_request_v2_no_result( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + # V2 + when(VerifierV2).accept_proof_request(...).thenReturn(to_async(None)) + when(VerifierV2).get_proof_record(...).thenReturn( + to_async(presentation_exchange_record_2) + ) + + presentation = test_module.AcceptProofRequest( + proof_id="v2-1234", indy_presentation_spec=indy_pres_spec + ) + + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + mocker.patch.object( + test_module, "assert_valid_prover", new_callable=AsyncMock, return_value=None + ) + + result = await test_module.accept_proof_request( + body=presentation, + auth=mock_tenant_auth, + ) + + assert result is None + verify(VerifierV2).accept_proof_request(...) + + +@pytest.mark.anyio +async def test_accept_proof_request_v2_no_connection( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + presentation_exchange_record_no_conn = presentation_exchange_record_2.model_copy() + presentation_exchange_record_no_conn.connection_id = None + # V2 + when(VerifierV2).accept_proof_request(...).thenReturn( + to_async(presentation_exchange_record_no_conn) + ) + when(VerifierV2).get_proof_record(...).thenReturn( + to_async(presentation_exchange_record_no_conn) + ) + + presentation = test_module.AcceptProofRequest( + proof_id="v2-1234", indy_presentation_spec=indy_pres_spec + ) + + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + mocker.patch.object( + test_module, "assert_valid_prover", new_callable=AsyncMock, return_value=None + ) + + result = await test_module.accept_proof_request( + body=presentation, + auth=mock_tenant_auth, + ) + + assert result is presentation_exchange_record_no_conn + verify(VerifierV2).accept_proof_request(...) + + @pytest.mark.anyio async def test_reject_proof_request( mock_agent_controller: AcaPyClient, @@ -184,6 +394,44 @@ async def test_reject_proof_request( ) +@pytest.mark.anyio +async def test_reject_proof_request_bad_state( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + proof_request_v2 = test_module.RejectProofRequest( + proof_id="v2-1234", problem_report="rejected" + ) + + presentation_exchange_record_2.state = "done" + when(VerifierV2).get_proof_record( + controller=mock_agent_controller, proof_id=proof_request_v2.proof_id + ).thenReturn(to_async(presentation_exchange_record_2)) + + with pytest.raises( + CloudApiException, + match="400: Proof record must be in state `request-received` to reject; record has state: `done`.", + ): + await test_module.reject_proof_request( + body=test_module.RejectProofRequest( + proof_id="v2-1234", problem_report="rejected" + ), + auth=mock_tenant_auth, + ) + + verify(VerifierV2).get_proof_record( + controller=mock_agent_controller, proof_id=proof_request_v2.proof_id + ) + + @pytest.mark.anyio async def test_delete_proof( mock_agent_controller: AcaPyClient, @@ -209,6 +457,29 @@ async def test_delete_proof( ) +@pytest.mark.anyio +async def test_delete_proof_exception( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + when(VerifierV2).delete_proof( + controller=mock_agent_controller, proof_id="v2-1234" + ).thenRaise(CloudApiException("ERROR")) + + with pytest.raises(CloudApiException, match="500: ERROR") as exc: + await test_module.delete_proof(proof_id="v2-1234", auth=mock_tenant_auth) + + assert exc.value.status_code == 500 + + @pytest.mark.anyio async def test_get_proof_record( mock_agent_controller: AcaPyClient, @@ -238,6 +509,62 @@ async def test_get_proof_record( ) +@pytest.mark.anyio +async def test_get_proof_record_exception( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + # V2 + when(VerifierV2).get_proof_record( + controller=mock_agent_controller, proof_id="v2-abcd" + ).thenRaise(CloudApiException("ERROR")) + + with pytest.raises(CloudApiException, match="500: ERROR") as exc: + await test_module.get_proof_record( + proof_id="v2-abcd", + auth=mock_tenant_auth, + ) + + assert exc.value.status_code == 500 + + +@pytest.mark.anyio +async def test_get_proof_record_no_result( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + # V2 + when(VerifierV2).get_proof_record( + controller=mock_agent_controller, proof_id="v2-abcd" + ).thenReturn(to_async(None)) + + result = await test_module.get_proof_record( + proof_id="v2-abcd", + auth=mock_tenant_auth, + ) + + assert result is None + verify(VerifierV2).get_proof_record( + controller=mock_agent_controller, proof_id="v2-abcd" + ) + + @pytest.mark.anyio async def test_get_proof_records( mock_agent_controller: AcaPyClient, @@ -289,6 +616,92 @@ async def test_get_proof_records( ) +@pytest.mark.anyio +async def test_get_proof_records_exception( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + with when(VerifierV2).get_proof_records( + controller=mock_agent_controller, + limit=100, + offset=0, + order_by="id", + descending=True, + connection_id=None, + role=None, + state=None, + thread_id=None, + ).thenRaise(CloudApiException("ERROR")): + with pytest.raises(CloudApiException, match="500: ERROR"): + await test_module.get_proof_records( + auth=mock_tenant_auth, + limit=100, + offset=0, + order_by="id", + descending=True, + connection_id=None, + role=None, + state=None, + thread_id=None, + ) + + +@pytest.mark.anyio +async def test_get_proof_records_no_result( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + with when(VerifierV2).get_proof_records( + controller=mock_agent_controller, + limit=100, + offset=0, + order_by="id", + descending=True, + connection_id=None, + role=None, + state=None, + thread_id=None, + ).thenReturn(to_async(None)): + result = await test_module.get_proof_records( + auth=mock_tenant_auth, + limit=100, + offset=0, + order_by="id", + descending=True, + connection_id=None, + role=None, + state=None, + thread_id=None, + ) + + assert result is None + verify(VerifierV2).get_proof_records( + controller=mock_agent_controller, + limit=100, + offset=0, + order_by="id", + descending=True, + connection_id=None, + role=None, + state=None, + thread_id=None, + ) + + @pytest.mark.anyio async def test_get_credentials_by_proof_id( mock_agent_controller: AcaPyClient, @@ -348,6 +761,38 @@ async def test_get_credentials_by_proof_id( ) +@pytest.mark.anyio +async def test_get_credentials_by_proof_id_exception( + mock_agent_controller: AcaPyClient, + mock_context_managed_controller: MockContextManagedController, + mock_tenant_auth: AcaPyAuth, + mocker: MockerFixture, +): + mocker.patch.object( + test_module, + "client_from_auth", + return_value=mock_context_managed_controller(mock_agent_controller), + ) + + # V2 + when(VerifierV2).get_credentials_by_proof_id( + controller=mock_agent_controller, + proof_id="v2-abcd", + referent=None, + limit=100, + offset=0, + ).thenRaise(CloudApiException("ERROR")) + + with pytest.raises(CloudApiException, match="500: ERROR"): + await test_module.get_credentials_by_proof_id( + proof_id="v2-abcd", + auth=mock_tenant_auth, + referent=None, + limit=100, + offset=0, + ) + + @pytest.mark.anyio async def test_get_credentials_by_proof_id_bad_limit(): client = TestClient(app) diff --git a/app/tests/services/verifier/utils.py b/app/tests/services/verifier/utils.py index c30baee89a..ce109d084e 100644 --- a/app/tests/services/verifier/utils.py +++ b/app/tests/services/verifier/utils.py @@ -1,4 +1,7 @@ from aries_cloudcontroller import ( + AnoncredsPresentationReqAttrSpec, + AnoncredsPresentationRequest, + AnoncredsPresentationRequestNonRevoked, AttachDecorator, AttachDecoratorData, DIFProofRequest, @@ -50,6 +53,22 @@ def sample_indy_proof_request(restrictions=None) -> IndyProofRequest: ) +def sample_anoncreds_proof_request(restrictions=None) -> AnoncredsPresentationRequest: + return AnoncredsPresentationRequest( + name="string", + non_revoked=AnoncredsPresentationRequestNonRevoked(), + nonce="12345", + requested_attributes={ + "0_speed_uuid": AnoncredsPresentationReqAttrSpec( + name="speed", + restrictions=restrictions, + ) + }, + requested_predicates={}, + version="1.0", + ) + + dif_proof_request = DIFProofRequest( options=None, presentation_definition=PresentationDefinition() ) diff --git a/shared/models/presentation_exchange.py b/shared/models/presentation_exchange.py index 331789ca3e..24478029ee 100644 --- a/shared/models/presentation_exchange.py +++ b/shared/models/presentation_exchange.py @@ -46,19 +46,21 @@ class PresentationExchange(BaseModel): def presentation_record_to_model(record: V20PresExRecord) -> PresentationExchange: if isinstance(record, V20PresExRecord): try: - presentation = ( - IndyProof(**record.by_format.pres["indy"]) - if record.by_format.pres - else None - ) + presentation = None + if record.by_format.pres: + key = list(record.by_format.pres.keys())[0] + presentation = record.by_format.pres[key] + except AttributeError: logger.info("Presentation record has no indy presentation") presentation = None try: - presentation_request = IndyProofRequest( - **record.by_format.pres_request["indy"] - ) + presentation_request = None + if record.by_format.pres_request: + key = list(record.by_format.pres_request.keys())[0] + presentation_request = record.by_format.pres_request[key] + except AttributeError: logger.info("Presentation record has no indy presentation request") presentation_request = None