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

Expose query params for get_credentials_by_proof_id #997

Merged
merged 19 commits into from
Aug 30, 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
6 changes: 1 addition & 5 deletions app/models/issuer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
from enum import Enum
from typing import Any, Dict, List, Optional

from aries_cloudcontroller import (
LDProofVCDetail,
TransactionRecord,
TxnOrPublishRevocationsResult,
)
from aries_cloudcontroller import LDProofVCDetail, TxnOrPublishRevocationsResult
from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator

from shared.exceptions import CloudApiValueError
Expand Down
12 changes: 6 additions & 6 deletions app/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 16 additions & 2 deletions app/routes/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,19 +496,29 @@ async def delete_proof(
)
async def get_credentials_by_proof_id(
proof_id: str,
referent: Optional[str] = None,
limit: Optional[int] = limit_query_parameter,
offset: Optional[int] = offset_query_parameter,
auth: AcaPyAuth = Depends(acapy_auth_from_header),
) -> List[IndyCredPrecis]:
"""
Get matching credentials for a presentation exchange
---
This endpoint returns a list of possible credentials that the prover can use to respond to a given proof request.

The `presentation_referents` field indicates which of the fields in the proof request that credential satisfies.
The `presentation_referents` field (in the response) indicates which of the fields
in the proof request that credential satisfies.

Parameters:
---
proof_id: str
The relevant proof exchange ID for the prover
referent: Optional str
The presentation_referent of the proof to match, comma separated str of presentation_referents
limit: Optional int
The number of credentials to fetch
offset: Optional int
The index to start fetching credentials from

Returns:
---
Expand All @@ -524,7 +534,11 @@ async def get_credentials_by_proof_id(
async with client_from_auth(auth) as aries_controller:
bound_logger.debug("Fetching credentials for request")
result = await verifier.get_credentials_by_proof_id(
controller=aries_controller, proof_id=proof_id
controller=aries_controller,
proof_id=proof_id,
referent=referent,
count=str(limit),
start=str(offset),
ff137 marked this conversation as resolved.
Show resolved Hide resolved
)
except CloudApiException as e:
bound_logger.info("Could not get matching credentials: {}.", e)
Expand Down
10 changes: 9 additions & 1 deletion app/services/verifier/acapy_verifier_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,12 @@ async def delete_proof(cls, controller: AcaPyClient, proof_id: str) -> None:

@classmethod
async def get_credentials_by_proof_id(
cls, controller: AcaPyClient, proof_id: str
cls,
controller: AcaPyClient,
proof_id: str,
referent: Optional[str] = None,
count: Optional[str] = None,
start: Optional[str] = None,
) -> List[IndyCredPrecis]:
bound_logger = logger.bind(body={"proof_id": proof_id})
pres_ex_id = pres_id_no_version(proof_id=proof_id)
Expand All @@ -275,6 +280,9 @@ async def get_credentials_by_proof_id(
logger=bound_logger,
acapy_call=controller.present_proof_v1_0.get_matching_credentials,
pres_ex_id=pres_ex_id,
referent=referent,
count=count,
start=start,
)
except CloudApiException as e:
raise CloudApiException(
Expand Down
10 changes: 9 additions & 1 deletion app/services/verifier/acapy_verifier_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,12 @@ async def delete_proof(cls, controller: AcaPyClient, proof_id: str) -> None:

@classmethod
async def get_credentials_by_proof_id(
cls, controller: AcaPyClient, proof_id: str
cls,
controller: AcaPyClient,
proof_id: str,
referent: Optional[str] = None,
count: Optional[str] = None,
start: Optional[str] = None,
) -> List[IndyCredPrecis]:
bound_logger = logger.bind(body={"proof_id": proof_id})
pres_ex_id = pres_id_no_version(proof_id=proof_id)
Expand All @@ -290,6 +295,9 @@ async def get_credentials_by_proof_id(
logger=bound_logger,
acapy_call=controller.present_proof_v2_0.get_matching_credentials,
pres_ex_id=pres_ex_id,
referent=referent,
count=count,
start=start,
)
except CloudApiException as e:
raise CloudApiException(
Expand Down
1 change: 0 additions & 1 deletion app/tests/e2e/issuer/test_save_exchange_record.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import asyncio
import json
import time

import pytest
from fastapi import HTTPException
Expand Down
1 change: 0 additions & 1 deletion app/tests/e2e/test_wallet_dids.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
from typing import List

import pytest
from aries_cloudcontroller import DID, AcaPyClient
Expand Down
82 changes: 82 additions & 0 deletions app/tests/e2e/verifier/test_get_credentials_by_proof_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from typing import List
Dismissed Show dismissed Hide dismissed

import pytest

from app.routes.verifier import router
from app.tests.util.connections import AcmeAliceConnect
from app.tests.util.regression_testing import TestMode
from app.tests.util.verifier import send_proof_request
from app.tests.util.webhooks import check_webhook_state
from shared import RichAsyncClient
from shared.models.credential_exchange import CredentialExchange

VERIFIER_BASE_PATH = router.prefix


@pytest.mark.anyio
@pytest.mark.skipif(
TestMode.regression_run in TestMode.fixture_params,
reason="Temporarily skip; existing tests on dev don't clean up old records yet",
)
async def test_limit_and_offset(
issue_alice_creds: List[CredentialExchange], # pylint: disable=unused-argument
acme_and_alice_connection: AcmeAliceConnect,
acme_client: RichAsyncClient,
alice_member_client: RichAsyncClient,
):
request_body = {
"connection_id": acme_and_alice_connection.acme_connection_id,
"protocol_version": "v2",
"indy_proof_request": {
"name": "Proof Request",
"version": "1.0.0",
"requested_attributes": {
"0_speed_uuid": {
"name": "speed",
},
"0_name_uuid": {
"name": "name",
},
},
"requested_predicates": {},
},
}
send_proof_response = await send_proof_request(acme_client, request_body)

acme_proof_id = send_proof_response["proof_id"]
thread_id = send_proof_response["thread_id"]

alice_payload = await check_webhook_state(
client=alice_member_client,
topic="proofs",
state="request-received",
filter_map={
"thread_id": thread_id,
},
)

alice_proof_id = alice_payload["proof_id"]

try:
for limit, offset, expected_length in [
[1, 0, 1],
[3, 0, 3],
[4, 0, 3],
[1, 0, 1],
[1, 1, 1],
[1, 3, 0],
]:
requested_credentials = (
await alice_member_client.get(
f"{VERIFIER_BASE_PATH}/proofs/{alice_proof_id}/credentials",
params={"limit": limit, "offset": offset},
)
).json()

assert len(requested_credentials) == expected_length

finally:
await alice_member_client.delete(
f"{VERIFIER_BASE_PATH}/proofs/{alice_proof_id}"
)
await acme_client.delete(f"{VERIFIER_BASE_PATH}/proofs/{acme_proof_id}")
90 changes: 85 additions & 5 deletions app/tests/services/verifier/test_verifier.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from unittest.mock import AsyncMock
from unittest.mock import AsyncMock, patch

import pytest
from aries_cloudcontroller import AcaPyClient, ConnRecord, IndyCredInfo, IndyCredPrecis
from fastapi.testclient import TestClient
from mockito import verify, when
from pytest_mock import MockerFixture

import app.routes.verifier as test_module
from app.dependencies.auth import AcaPyAuth
from app.exceptions.cloudapi_exception import CloudApiException
from app.main import app
from app.routes.verifier import acapy_auth_from_header, get_credentials_by_proof_id
from app.services.verifier.acapy_verifier_v1 import VerifierV1
from app.services.verifier.acapy_verifier_v2 import VerifierV2
from app.tests.services.verifier.utils import indy_pres_spec, sample_indy_proof_request
Expand Down Expand Up @@ -503,30 +506,107 @@ async def test_get_credentials_by_proof_id(
)
# V1
when(VerifierV1).get_credentials_by_proof_id(
controller=mock_agent_controller, proof_id="v1-abcd"
controller=mock_agent_controller,
proof_id="v1-abcd",
referent=None,
count="100",
start="0",
).thenReturn(to_async([cred_precis]))

result = await test_module.get_credentials_by_proof_id(
proof_id="v1-abcd",
auth=mock_tenant_auth,
referent=None,
limit=100,
offset=0,
)

assert result == [cred_precis]
verify(VerifierV1).get_credentials_by_proof_id(
controller=mock_agent_controller, proof_id="v1-abcd"
controller=mock_agent_controller,
proof_id="v1-abcd",
referent=None,
count="100",
start="0",
)

# V2
when(VerifierV2).get_credentials_by_proof_id(
controller=mock_agent_controller, proof_id="v2-abcd"
controller=mock_agent_controller,
proof_id="v2-abcd",
referent=None,
count="100",
start="0",
).thenReturn(to_async([cred_precis]))

result = await test_module.get_credentials_by_proof_id(
proof_id="v2-abcd",
auth=mock_tenant_auth,
referent=None,
limit=100,
offset=0,
)

assert result == [cred_precis]
verify(VerifierV2).get_credentials_by_proof_id(
controller=mock_agent_controller, proof_id="v2-abcd"
controller=mock_agent_controller,
proof_id="v2-abcd",
referent=None,
count="100",
start="0",
)


@pytest.mark.anyio
async def test_get_credentials_by_proof_id_bad_limit():
client = TestClient(app)

def override_auth():
return "mocked_auth"

app.dependency_overrides[acapy_auth_from_header] = override_auth
try:
response = client.get(
"/v1/verifier/proofs/v2-abcd/credentials",
params={"limit": 10001, "offset": 0},
headers={"x-api-key": "mocked_auth"},
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "less_than_equal",
"loc": ["query", "limit"],
"msg": "Input should be less than or equal to 10000",
"input": "10001",
"ctx": {"le": 10000},
}
]
}
finally:
app.dependency_overrides.clear()


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

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

await get_credentials_by_proof_id(
proof_id="v2-abcd",
auth="mocked_auth",
referent=None,
limit=2,
offset=1,
)

mock_aries_controller.present_proof_v2_0.get_matching_credentials.assert_called_once_with(
pres_ex_id="abcd",
referent=None,
count="2",
start="1",
)
6 changes: 3 additions & 3 deletions endorser/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions trustregistry/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading