From 999e4c14397dec1a72ddddfc5447600d20028ab2 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 4 Mar 2025 14:07:53 +0200 Subject: [PATCH 01/22] import new models --- app/models/verifier.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/verifier.py b/app/models/verifier.py index 678f284d49..75c691dd68 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, From fe47da704d30db7f7a31e8f5fb3c675be8062ea3 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 4 Mar 2025 14:08:07 +0200 Subject: [PATCH 02/22] add anoncreds proof type --- app/models/verifier.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/verifier.py b/app/models/verifier.py index 75c691dd68..4ca92bad2f 100644 --- a/app/models/verifier.py +++ b/app/models/verifier.py @@ -19,6 +19,7 @@ class ProofRequestType(str, Enum): INDY: str = "indy" JWT: str = "jwt" LD_PROOF: str = "ld_proof" + ANONCREDS: str = "anoncreds" class IndyProofRequest(AcaPyIndyProofRequest): From 91511faa6daef13e2c3bc6ebdd9fad646ac2a163 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 4 Mar 2025 14:08:42 +0200 Subject: [PATCH 03/22] add anoncreds to base proof request --- app/models/verifier.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/app/models/verifier.py b/app/models/verifier.py index 4ca92bad2f..7cd81e61b3 100644 --- a/app/models/verifier.py +++ b/app/models/verifier.py @@ -31,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__ @@ -42,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( @@ -53,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 From 749eb59bbcc051a689199362b4d89bc74bbd5a3b Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 4 Mar 2025 14:09:08 +0200 Subject: [PATCH 04/22] add anoncreds to accept prof --- app/models/verifier.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/models/verifier.py b/app/models/verifier.py index 7cd81e61b3..f1665a0aff 100644 --- a/app/models/verifier.py +++ b/app/models/verifier.py @@ -106,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 @@ -125,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( From 6b909bde5651d6a72c3d4fd16cee188b788e4be1 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 4 Mar 2025 14:09:53 +0200 Subject: [PATCH 05/22] add anoncreds support --- app/services/verifier/acapy_verifier_v2.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/services/verifier/acapy_verifier_v2.py b/app/services/verifier/acapy_verifier_v2.py index ca4c193dad..0abe6caa75 100644 --- a/app/services/verifier/acapy_verifier_v2.py +++ b/app/services/verifier/acapy_verifier_v2.py @@ -89,6 +89,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 +138,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}", From a518c288290c0d15dc243784193c070db6e0255e Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 4 Mar 2025 14:45:46 +0200 Subject: [PATCH 06/22] add support for anoncreds exchange records --- shared/models/presentation_exchange.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) 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 From af3dbd1f83be520a7347163121698aa6d03a1063 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Mon, 10 Mar 2025 14:29:00 +0200 Subject: [PATCH 07/22] update model test for anoncreds --- app/tests/models/test_verifier.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/app/tests/models/test_verifier.py b/app/tests/models/test_verifier.py index 6f5b43b223..dc66e2961a 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={} From bca4964dc4f5f28aa8a2a1b7e3d8985a77854a6c Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:38:24 +0200 Subject: [PATCH 08/22] test send proof exception --- app/tests/services/verifier/test_verifier.py | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index abbfdee7c0..43e496576e 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -90,6 +90,39 @@ 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_create_proof_request(mock_tenant_auth: AcaPyAuth): when(VerifierV2).create_proof_request(...).thenReturn( From b4b80219e6498b458bd1a96f12817b5a6b28656d Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:38:46 +0200 Subject: [PATCH 09/22] test send proof no result --- app/tests/services/verifier/test_verifier.py | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index 43e496576e..2d1c596c0c 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -123,6 +123,45 @@ async def test_send_proof_request_v2_exception( ) 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( From ccc86019e06c82f37035b041bb4fd70bcdcb07ea Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:39:15 +0200 Subject: [PATCH 10/22] test create proof exception/no result --- app/tests/services/verifier/test_verifier.py | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index 2d1c596c0c..f0db8a6692 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -177,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, From 50e28f062bbe518bce2e01c69340d2299a843c89 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:39:51 +0200 Subject: [PATCH 11/22] test accept proof no result/exception --- app/tests/services/verifier/test_verifier.py | 70 ++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index f0db8a6692..d85666e81c 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -242,6 +242,76 @@ 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_reject_proof_request( mock_agent_controller: AcaPyClient, From 2e4188ae937be60f593b4aafaaad3c602f2d3a39 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:40:16 +0200 Subject: [PATCH 12/22] test accept proof no connection --- app/tests/services/verifier/test_verifier.py | 41 ++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index d85666e81c..066e006dbc 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -312,6 +312,47 @@ async def test_accept_proof_request_v2_no_result( 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, From 96183ef29517cd609d942eb6a58f2f09566a4586 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:40:37 +0200 Subject: [PATCH 13/22] test reject proof bad state --- app/tests/services/verifier/test_verifier.py | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index 066e006dbc..6b9b3c6936 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -394,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, From 91ac18b35b548cf487f9d1acdc3216ab03f9d56c Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:40:54 +0200 Subject: [PATCH 14/22] test delete proof exception --- app/tests/services/verifier/test_verifier.py | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index 6b9b3c6936..6fce7afb54 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -457,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, From dc3ac640631c7d7bbadc1448cd3b3e05edf54091 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:41:18 +0200 Subject: [PATCH 15/22] test get record no result/exception --- app/tests/services/verifier/test_verifier.py | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index 6fce7afb54..17fd6cc0a0 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -509,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, From 35132d2049ecf4eaea622636c5a60c795d9491e4 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:41:51 +0200 Subject: [PATCH 16/22] test get records ne result/exception --- app/tests/services/verifier/test_verifier.py | 86 ++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index 17fd6cc0a0..933f599889 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -616,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, From dc6bdc9502d89bba0ef8f86e5f4f6aa7600128ce Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 11:42:25 +0200 Subject: [PATCH 17/22] test get cred by proof id exception --- app/tests/services/verifier/test_verifier.py | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/tests/services/verifier/test_verifier.py b/app/tests/services/verifier/test_verifier.py index 933f599889..96a1ae5c87 100644 --- a/app/tests/services/verifier/test_verifier.py +++ b/app/tests/services/verifier/test_verifier.py @@ -761,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) From 30316b44de49aba16c81f3c62e1af3a381cfd16a Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 13:54:34 +0200 Subject: [PATCH 18/22] import anoncreds models --- app/tests/services/verifier/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/tests/services/verifier/utils.py b/app/tests/services/verifier/utils.py index c30baee89a..e80b562a54 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, From 1e21450f4267bb7569eea1d883cb9ad9dbcad725 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 13:55:01 +0200 Subject: [PATCH 19/22] sample anoncreds proof request --- app/tests/services/verifier/utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/tests/services/verifier/utils.py b/app/tests/services/verifier/utils.py index e80b562a54..ce109d084e 100644 --- a/app/tests/services/verifier/utils.py +++ b/app/tests/services/verifier/utils.py @@ -53,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() ) From f5972c7ae4af8f00ab301ad2ba0c042b92d204c3 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 13:55:30 +0200 Subject: [PATCH 20/22] add anoncreds check for create proof --- app/services/verifier/acapy_verifier_v2.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/verifier/acapy_verifier_v2.py b/app/services/verifier/acapy_verifier_v2.py index 0abe6caa75..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}", From 5318781409d039a087e7c714fad95bf7ffab7a53 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 13:57:05 +0200 Subject: [PATCH 21/22] test anoncreds part of model --- app/tests/models/test_verifier.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/tests/models/test_verifier.py b/app/tests/models/test_verifier.py index dc66e2961a..12108c2732 100644 --- a/app/tests/models/test_verifier.py +++ b/app/tests/models/test_verifier.py @@ -98,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 == ( @@ -108,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(): From fafb7c0603a7ca1068cc7d387f07d5656ee55665 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 12 Mar 2025 13:57:23 +0200 Subject: [PATCH 22/22] test anoncreds case --- .../verifier/test_acapy_verifier_v2.py | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) 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", )