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

#334 Add unit tests for critical code sections [2.0] #339

Merged
merged 1 commit into from
Sep 25, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/controller_unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11"]
python-version: ["3.11"]

steps:
- uses: actions/checkout@v3
Expand Down
6 changes: 6 additions & 0 deletions oidc-controller/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[run]
omit =
**/tests/*
*test*
*__init__*

9 changes: 9 additions & 0 deletions oidc-controller/api/core/acapy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ def create_presentation_request(
headers=self.agent_config.get_headers(),
json=present_proof_payload,
)

# TODO: Determine if this should assert it received a json object
assert resp_raw.status_code == 200, resp_raw.content

resp = json.loads(resp_raw.content)
result = CreatePresentationResponse.parse_obj(resp)

Expand All @@ -66,7 +69,10 @@ def get_presentation_request(self, presentation_exchange_id: Union[UUID, str]):
+ str(presentation_exchange_id),
headers=self.agent_config.get_headers(),
)

# TODO: Determine if this should assert it received a json object
assert resp_raw.status_code == 200, resp_raw.content

resp = json.loads(resp_raw.content)

logger.debug(f"<<< get_presentation_request -> {resp}")
Expand Down Expand Up @@ -102,9 +108,12 @@ def get_wallet_did(self, public=False) -> WalletDid:
url,
headers=self.agent_config.get_headers(),
)

# TODO: Determine if this should assert it received a json object
assert (
resp_raw.status_code == 200
), f"{resp_raw.status_code}::{resp_raw.content}"

resp = json.loads(resp_raw.content)

if public:
Expand Down
50 changes: 50 additions & 0 deletions oidc-controller/api/core/acapy/tests/__mocks__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
presentation_request_configuration = {
'name': 'proof_requested',
'version': '0.0.1',
'requested_attributes': {
'req_attr_0': {
'names': ['email'],
'restrictions': [
{'schema_name': 'verified-email',
'issuer_did': 'MTYqmTBoLT7KLP5RNfgK3b'}
],
'non_revoked': {
'from': 1695320203, 'to': 1695320203
}
}
},
'requested_predicates': {}
}

presentation_request = {
'nonce': '136042354083201173353396',
'name': 'proof_requested',
'version': '0.0.1',
'requested_attributes':{
'req_attr_0': {
'non_revoked': {'from': 1695321803, 'to': 1695321803},
'restrictions': [{'schema_name': 'verified-email', 'issuer_did': 'MTYqmTBoLT7KLP5RNfgK3b'}],
'names': ['email']
}
},
'requested_predicates': {}
}

create_presentation_response_http = {
'updated_at': '2023-09-21T18:43:23.470373Z',
'role': 'verifier',
'presentation_exchange_id': 'b2945790-79c4-4059-9f93-6bd43b2186f7',
'created_at': '2023-09-21T18:43:23.470373Z',
'trace': False,
'thread_id': 'ab2e3f02-6e16-4e08-8165-5ddc7aad3090',
'initiator': 'self',
'state': 'request_sent',
'presentation_request': presentation_request,
'auto_verify': True,
'presentation_request_dict': {
'@type': 'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/present-proof/1.0/request-presentation',
'@id': 'ab2e3f02-6e16-4e08-8165-5ddc7aad3090',
'request_presentations~attach': [{'@id': 'libindy-request-presentation-0', 'mime-type': 'application/json', 'data': {'base64': 'eyJuYW1lIjogInByb29mX3JlcXVlc3RlZCIsICJ2ZXJzaW9uIjogIjAuMC4xIiwgInJlcXVlc3RlZF9hdHRyaWJ1dGVzIjogeyJyZXFfYXR0cl8wIjogeyJuYW1lcyI6IFsiZW1haWwiXSwgInJlc3RyaWN0aW9ucyI6IFt7InNjaGVtYV9uYW1lIjogInZlcmlmaWVkLWVtYWlsIiwgImlzc3Vlcl9kaWQiOiAiTVRZcW1UQm9MVDdLTFA1Uk5mZ0szYiJ9XSwgIm5vbl9yZXZva2VkIjogeyJmcm9tIjogMTY5NTMyMTgwMywgInRvIjogMTY5NTMyMTgwM319fSwgInJlcXVlc3RlZF9wcmVkaWNhdGVzIjoge30sICJub25jZSI6ICIxMzYwNDIzNTQwODMyMDExNzMzNTMzOTYifQ=='}}]
},
'auto_present': False
}
216 changes: 216 additions & 0 deletions oidc-controller/api/core/acapy/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import json

import mock
import pytest
from api.core.acapy.client import (CREATE_PRESENTATION_REQUEST_URL,
PRESENT_PROOF_RECORDS,
PUBLIC_WALLET_DID_URI, WALLET_DID_URI,
AcapyClient)
from api.core.acapy.config import MultiTenantAcapy, SingleTenantAcapy
from api.core.acapy.models import CreatePresentationResponse, WalletDid
from api.core.acapy.tests.__mocks__ import (create_presentation_response_http,
presentation_request_configuration)
from api.core.config import settings


@pytest.mark.asyncio
@mock.patch.object(settings, "ACAPY_TENANCY", None)
async def test_init_no_setting_returns_client_with_single_tenancy_config():
client = AcapyClient()
assert client is not None
assert isinstance(client.agent_config, SingleTenantAcapy) is True


@pytest.mark.asyncio
@mock.patch.object(settings, "ACAPY_TENANCY", "single")
async def test_init_single_returns_client_with_single_tenancy_config():
client = AcapyClient()
assert client is not None
assert isinstance(client.agent_config, SingleTenantAcapy) is True


@pytest.mark.asyncio
@mock.patch.object(settings, "ACAPY_TENANCY", "multi")
async def test_init_multi_returns_client_with_multi_tenancy_config():
client = AcapyClient()
assert client is not None
assert isinstance(client.agent_config, MultiTenantAcapy) is True


@pytest.mark.asyncio
async def test_create_presentation_returns_sucessfully_with_valid_data(requests_mock):
requests_mock.post(
settings.ACAPY_ADMIN_URL + CREATE_PRESENTATION_REQUEST_URL,
headers={},
json=json.dumps(create_presentation_response_http),
status_code=200,
)

with mock.patch.object(CreatePresentationResponse, "parse_obj", return_value={'result': 'success'}):
client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
presentation_request = client.create_presentation_request(
presentation_request_configuration)
assert presentation_request is not None
esune marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.asyncio
async def test_create_presentation_throws_assertion_error_with_non_200_response_from_acapy(requests_mock):
requests_mock.post(
settings.ACAPY_ADMIN_URL + CREATE_PRESENTATION_REQUEST_URL,
headers={},
json=json.dumps(create_presentation_response_http),
status_code=400,
)

with mock.patch.object(CreatePresentationResponse, "parse_obj", return_value={'result': 'success'}):
client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
try:
presentation_request = client.create_presentation_request(
presentation_request_configuration)
assert presentation_request is not None
except AssertionError as e:
assert e is not None
esune marked this conversation as resolved.
Show resolved Hide resolved

# TODO: determine if this function should assert a valid json response
@pytest.mark.asyncio
async def test_create_presentation_throws_error_with_non_json_from_acapy(requests_mock):
requests_mock.post(
settings.ACAPY_ADMIN_URL + CREATE_PRESENTATION_REQUEST_URL,
headers={},
status_code=200,
)

with mock.patch.object(CreatePresentationResponse, "parse_obj", return_value={'result': 'success'}):
client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
try:
presentation_request = client.create_presentation_request(
presentation_request_configuration)
assert presentation_request is not None
except json.JSONDecodeError as e:
assert e is not None


@pytest.mark.asyncio
async def test_get_presentation_returns_sucessfully_with_valid_data(requests_mock):
requests_mock.get(
settings.ACAPY_ADMIN_URL + PRESENT_PROOF_RECORDS + "/" + "1234-567890",
headers={},
json={"result": "success"},
status_code=200,
)

client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
presentation = client.get_presentation_request("1234-567890")
assert presentation is not None


@pytest.mark.asyncio
async def test_get_presentation_throws_assertion_error_for_non_200_response_from_acapy(requests_mock):
requests_mock.get(
settings.ACAPY_ADMIN_URL + PRESENT_PROOF_RECORDS + "/" + "1234-567890",
headers={},
json={"result": "success"},
status_code=400,
)

client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
try:
client.get_presentation_request("1234-567890")
except AssertionError as e:
assert e is not None


@pytest.mark.asyncio
async def test_verify_presentation_returns_sucessfully_with_valid_data(requests_mock):
requests_mock.post(
settings.ACAPY_ADMIN_URL + PRESENT_PROOF_RECORDS +
"/" + "1234-567890" + "/verify-presentation",
headers={},
json={"result": "success"},
status_code=200,
)

client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
verification = client.verify_presentation("1234-567890")
assert verification is not None


@pytest.mark.asyncio
async def test_verify_presentation_throws_assertion_error_for_non_200_response_from_acapy(requests_mock):
requests_mock.post(
settings.ACAPY_ADMIN_URL + PRESENT_PROOF_RECORDS +
"/" + "1234-567890" + "/verify-presentation",
headers={},
json={"result": "success"},
status_code=400,
)

client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
try:
client.verify_presentation("1234-567890")
except AssertionError as e:
assert e is not None


@pytest.mark.asyncio
async def test_get_wallet_did_public_returns_sucessfully_on_public_url_and_simple_resp(requests_mock):
requests_mock.get(
settings.ACAPY_ADMIN_URL + PUBLIC_WALLET_DID_URI,
headers={},
json={"result": "success"},
status_code=200,
)
with mock.patch.object(WalletDid, "parse_obj", return_value={'result': 'success'}):
client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
wallet_resp = client.get_wallet_did(public=True)
assert wallet_resp is not None


@pytest.mark.asyncio
async def test_get_wallet_did_public_throws_assertion_error_on_non_200_response(requests_mock):
requests_mock.get(
settings.ACAPY_ADMIN_URL + PUBLIC_WALLET_DID_URI,
headers={},
json={"result": "success"},
status_code=400,
)
with mock.patch.object(WalletDid, "parse_obj", return_value={'result': 'success'}):
client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
try:
client.get_wallet_did(public=True)
except AssertionError as e:
assert e is not None


@pytest.mark.asyncio
async def test_get_wallet_did_not_public_returns_sucessfully_on_correct_url_and_processes_array(requests_mock):
requests_mock.get(
settings.ACAPY_ADMIN_URL + WALLET_DID_URI,
headers={},
json={"results": ["success"]},
status_code=200,
)
with mock.patch.object(WalletDid, "parse_obj", return_value={'result': 'success'}):
client = AcapyClient()
client.agent_config.get_headers = mock.MagicMock(
return_value={'x-api-key': ''})
wallet_resp = client.get_wallet_did(public=False)
assert wallet_resp is not None
51 changes: 51 additions & 0 deletions oidc-controller/api/core/acapy/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import mock
import pytest
from api.core.acapy.config import MultiTenantAcapy, SingleTenantAcapy
from api.core.config import settings


@pytest.mark.asyncio
@mock.patch.object(settings, "ST_ACAPY_ADMIN_API_KEY_NAME", 'name')
@mock.patch.object(settings, "ST_ACAPY_ADMIN_API_KEY", 'key')
async def test_single_tenant_has_expected_headers():
acapy = SingleTenantAcapy()
headers = acapy.get_headers()
assert headers == {'name': 'key'}


@pytest.mark.asyncio
async def test_multi_tenant_get_headers_returns_bearer_token_auth(requests_mock):
acapy = MultiTenantAcapy()
acapy.get_wallet_token = mock.MagicMock(return_value='token')
headers = acapy.get_headers()
assert headers == {"Authorization": "Bearer token"}


@pytest.mark.asyncio
async def test_multi_tenant_get_wallet_token_returns_token_at_token_key(requests_mock):
requests_mock.post(
settings.ACAPY_ADMIN_URL + "/multitenancy/wallet/wallet_id/token",
headers={},
json={'token': 'token'},
status_code=200,
)
acapy = MultiTenantAcapy()
acapy.wallet_id = 'wallet_id'
token = acapy.get_wallet_token()
assert token == 'token'


@pytest.mark.asyncio
async def test_multi_tenant_throws_assertion_error_for_non_200_response(requests_mock):
requests_mock.post(
settings.ACAPY_ADMIN_URL + "/multitenancy/wallet/wallet_id/token",
headers={},
json={'token': 'token'},
status_code=400,
)
acapy = MultiTenantAcapy()
acapy.wallet_id = 'wallet_id'
try:
acapy.get_wallet_token()
except AssertionError as e:
assert e is not None
5 changes: 3 additions & 2 deletions oidc-controller/api/core/oidc/issue_token_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class Token(BaseModel):

@classmethod
def get_claims(
cls, pres_exch: Dict, auth_session: AuthSession, ver_config: VerificationConfig
esune marked this conversation as resolved.
Show resolved Hide resolved
cls, auth_session: AuthSession, ver_config: VerificationConfig
) -> dict[str, str]:
"""Converts vc presentation values to oidc claims"""
oidc_claims: List[Claim] = [
Expand Down Expand Up @@ -108,7 +108,8 @@ def get_claims(
{c.type: c.value for c in presentation_claims.values()}
)
return result


# TODO: Determine if this is useful to keep, and remove it if it's not. It is currently unused.
# renames and calculates dict members appropriate to
# https://openid.net/specs/openid-connect-core-1_0.html#IDToken
# and
Expand Down
Loading