Skip to content

Commit

Permalink
Merge pull request #339 from bcgov/feature/334-unit-test-critical-cod…
Browse files Browse the repository at this point in the history
…e-sections

#334 Add unit tests for critical code sections [2.0]
  • Loading branch information
esune authored Sep 25, 2023
2 parents 1e3ec92 + 36efe56 commit 35316a9
Show file tree
Hide file tree
Showing 13 changed files with 643 additions and 17 deletions.
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


@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

# 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
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

0 comments on commit 35316a9

Please sign in to comment.