Skip to content

Commit

Permalink
feat: [AXM-1143] extend VC API with course credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
kyrylo-kh committed Dec 5, 2024
1 parent 2e14f53 commit 1660299
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from credentials.apps.verifiable_credentials.issuance import IssuanceException
from credentials.apps.verifiable_credentials.issuance.tests.factories import IssuanceLineFactory
from credentials.apps.verifiable_credentials.storages.learner_credential_wallet import LCWallet
from credentials.apps.verifiable_credentials.utils import get_user_program_credentials_data
from credentials.apps.verifiable_credentials.utils import get_user_credentials_data


JSON_CONTENT_TYPE = "application/json"
Expand Down Expand Up @@ -73,22 +73,39 @@ def setUp(self):

def test_deny_unauthenticated_user(self):
self.client.logout()
response = self.client.get("/verifiable_credentials/api/v1/program_credentials/")
response = self.client.get("/verifiable_credentials/api/v1/credentials/")
self.assertEqual(response.status_code, 401)

def test_allow_authenticated_user(self):
"""Verify the endpoint requires an authenticated user."""
self.client.logout()
self.client.login(username=self.user.username, password=USER_PASSWORD)
response = self.client.get("/verifiable_credentials/api/v1/program_credentials/")
response = self.client.get("/verifiable_credentials/api/v1/credentials/")
self.assertEqual(response.status_code, 200)

def test_get(self):
def test_get_without_query_params(self):
self.client.login(username=self.user.username, password=USER_PASSWORD)
response = self.client.get("/verifiable_credentials/api/v1/program_credentials/")
response = self.client.get("/verifiable_credentials/api/v1/credentials/")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["program_credentials"], get_user_program_credentials_data(self.user.username))
self.assertEqual(response.data["program_credentials"], get_user_credentials_data(self.user.username, "programcertificate"))
self.assertEqual(response.data["course_credentials"], get_user_credentials_data(self.user.username, "coursecertificate"))

def test_get_with_query_params(self):
self.client.login(username=self.user.username, password=USER_PASSWORD)
response = self.client.get("/verifiable_credentials/api/v1/credentials/?types=programcertificate")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["program_credentials"], get_user_credentials_data(self.user.username, "programcertificate"))
self.assertNotIn("course_credentials", response.data)

response = self.client.get("/verifiable_credentials/api/v1/credentials/?types=coursecertificate")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["course_credentials"], get_user_credentials_data(self.user.username, "coursecertificate"))
self.assertNotIn("program_credentials", response.data)

response = self.client.get("/verifiable_credentials/api/v1/credentials/?types=programcertificate,coursecertificate")
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["program_credentials"], get_user_credentials_data(self.user.username, "programcertificate"))
self.assertEqual(response.data["course_credentials"], get_user_credentials_data(self.user.username, "coursecertificate"))

class InitIssuanceViewTestCase(SiteMixin, TestCase):
url_path = reverse("verifiable_credentials:api:v1:credentials-init")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


router = routers.DefaultRouter()
router.register(r"program_credentials", views.ProgramCredentialsViewSet, basename="program_credentials")
router.register(r"credentials", views.CredentialsViewSet, basename="credentials")

urlpatterns = [
path(r"credentials/init/", views.InitIssuanceView.as_view(), name="credentials-init"),
Expand Down
28 changes: 22 additions & 6 deletions credentials/apps/verifiable_credentials/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from credentials.apps.verifiable_credentials.storages.utils import get_available_storages, get_storage
from credentials.apps.verifiable_credentials.utils import (
generate_base64_qr_code,
get_user_program_credentials_data,
get_user_credentials_data,
is_valid_uuid,
)

Expand All @@ -35,7 +35,7 @@
User = get_user_model()


class ProgramCredentialsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
class CredentialsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
authentication_classes = (
JwtAuthentication,
SessionAuthentication,
Expand All @@ -45,15 +45,31 @@ class ProgramCredentialsViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):

def list(self, request, *args, **kwargs):
"""
List data for all the user's issued program credentials.
GET: /verifiable_credentials/api/v1/program_credentials/
List data for all the user's issued credentials.
GET: /verifiable_credentials/api/v1/credentials?types=coursecertificate,programcertificate
Arguments:
request: A request to control data returned in endpoint response
Returns:
response(dict): Information about the user's program credentials
"""
program_credentials = get_user_program_credentials_data(request.user.username)
return Response({"program_credentials": program_credentials})
types = self.request.query_params.get('types')
response = {}

credential_types_map = {
"programcertificate": "program_credentials",
"coursecertificate": "course_credentials",
}

if types:
types = types.split(',')
else:
types = credential_types_map.keys()

for type in types:
if type in credential_types_map:
response[credential_types_map[type]] = get_user_credentials_data(request.user.username, type)

return Response(response)


class InitIssuanceView(APIView):
Expand Down
56 changes: 45 additions & 11 deletions credentials/apps/verifiable_credentials/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from credentials.apps.verifiable_credentials.utils import (
capitalize_first,
generate_base64_qr_code,
get_user_program_credentials_data,
get_user_credentials_data,
)


Expand Down Expand Up @@ -73,25 +73,25 @@ def setUp(self):

def test_get_user_program_credentials_data_not_completed(self):
self.program_user_credential.delete()
result = get_user_program_credentials_data(self.user.username)
result = get_user_credentials_data(self.user.username, "programcertificate")
assert result == []

def test_get_user_program_credentials_data_zero_programs(self):
self.program_cert.delete()
self.program.delete()
self.program_user_credential.delete()
result = get_user_program_credentials_data(self.user.username)
result = get_user_credentials_data(self.user.username, "programcertificate")
assert result == []

def test_get_user_program_credentials_data_one_program(self):
result = get_user_program_credentials_data(self.user.username)
result = get_user_credentials_data(self.user.username, "programcertificate")
assert result[0]["uuid"] == str(self.program_user_credential.uuid).replace("-", "")
assert result[0]["status"] == self.program_user_credential.status
assert result[0]["username"] == self.program_user_credential.username
assert result[0]["download_url"] == self.program_user_credential.download_url
assert result[0]["credential_id"] == self.program_user_credential.credential_id
assert result[0]["program_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "")
assert result[0]["program_title"] == self.program_user_credential.credential.program.title
assert result[0]["credential_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "")
assert result[0]["credential_title"] == self.program_user_credential.credential.program.title

def test_get_user_program_credentials_data_multiple_programs(self):
self.program2 = ProgramFactory(
Expand All @@ -108,23 +108,57 @@ def test_get_user_program_credentials_data_multiple_programs(self):
credential_content_type=self.program_credential_content_type,
credential=self.program_cert2,
)
result = get_user_program_credentials_data(self.user.username)
result = get_user_credentials_data(self.user.username, "programcertificate")
assert result[0]["uuid"] == str(self.program_user_credential.uuid).replace("-", "")
assert result[0]["status"] == self.program_user_credential.status
assert result[0]["username"] == self.program_user_credential.username
assert result[0]["download_url"] == self.program_user_credential.download_url
assert result[0]["credential_id"] == self.program_user_credential.credential_id
assert result[0]["program_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "")
assert result[0]["program_title"] == self.program_user_credential.credential.program.title
assert result[0]["credential_uuid"] == str(self.program_user_credential.credential.program_uuid).replace("-", "")
assert result[0]["credential_title"] == self.program_user_credential.credential.program.title

assert result[1]["uuid"] == str(self.program_user_credential2.uuid).replace("-", "")
assert result[1]["status"] == self.program_user_credential2.status
assert result[1]["username"] == self.program_user_credential2.username
assert result[1]["download_url"] == self.program_user_credential2.download_url
assert result[1]["credential_id"] == self.program_user_credential2.credential_id
assert result[1]["program_uuid"] == str(self.program_user_credential2.credential.program_uuid).replace("-", "")
assert result[1]["program_title"] == self.program_user_credential2.credential.program.title
assert result[1]["credential_uuid"] == str(self.program_user_credential2.credential.program_uuid).replace("-", "")
assert result[1]["credential_title"] == self.program_user_credential2.credential.program.title

def test_get_user_course_credentials_data_zero_courses(self):
self.course_user_credentials[0].delete()
self.course_user_credentials[1].delete()
result = get_user_credentials_data(self.user.username, "coursecertificate")
assert result == []

def test_get_user_course_credentials_data_one_course(self):
self.course_user_credentials[1].delete()
result = get_user_credentials_data(self.user.username, "coursecertificate")
assert result[0]["uuid"] == str(self.course_user_credentials[0].uuid).replace("-", "")
assert result[0]["status"] == self.course_user_credentials[0].status
assert result[0]["username"] == self.course_user_credentials[0].username
assert result[0]["download_url"] == self.course_user_credentials[0].download_url
assert result[0]["credential_id"] == self.course_user_credentials[0].credential_id
assert result[0]["credential_uuid"] == self.course_user_credentials[0].credential.course_id
assert result[0]["credential_title"] == self.course_user_credentials[0].credential.title

def test_get_user_course_credentials_data_multiple_courses(self):
result = get_user_credentials_data(self.user.username, "coursecertificate")
assert result[0]["uuid"] == str(self.course_user_credentials[0].uuid).replace("-", "")
assert result[0]["status"] == self.course_user_credentials[0].status
assert result[0]["username"] == self.course_user_credentials[0].username
assert result[0]["download_url"] == self.course_user_credentials[0].download_url
assert result[0]["credential_id"] == self.course_user_credentials[0].credential_id
assert result[0]["credential_uuid"] == self.course_user_credentials[0].credential.course_id
assert result[0]["credential_title"] == self.course_user_credentials[0].credential.title

assert result[1]["uuid"] == str(self.course_user_credentials[1].uuid).replace("-", "")
assert result[1]["status"] == self.course_user_credentials[1].status
assert result[1]["username"] == self.course_user_credentials[1].username
assert result[1]["download_url"] == self.course_user_credentials[1].download_url
assert result[1]["credential_id"] == self.course_user_credentials[1].credential_id
assert result[1]["credential_uuid"] == self.course_user_credentials[1].credential.course_id
assert result[1]["credential_title"] == self.course_user_credentials[1].credential.title

class TestGenerateBase64QRCode(TestCase):
def test_correct_output_format(self):
Expand Down
40 changes: 26 additions & 14 deletions credentials/apps/verifiable_credentials/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,49 @@
from credentials.apps.credentials.data import UserCredentialStatus


def get_user_program_credentials_data(username):
def get_user_credentials_data(username, model):
"""
Translates a list of UserCredentials (for programs) into context data.
Arguments:
request_username(str): Username for whom we are getting UserCredential objects for
model(str): The model for content type (programcertificate | coursecertificate)
Returns:
list(dict): A list of dictionaries, each dictionary containing information for a credential that the
user awarded
"""
program_cert_content_type = ContentType.objects.get(app_label="credentials", model="programcertificate")
program_credentials = get_user_credentials_by_content_type(
username, [program_cert_content_type], UserCredentialStatus.AWARDED.value
credential_cert_content_type = ContentType.objects.get(app_label="credentials", model=model)
credentials = get_user_credentials_by_content_type(
username, [credential_cert_content_type], UserCredentialStatus.AWARDED.value
)
return [
{

data = []
for credential in credentials:
if model == "programcertificate":
credential_uuid = credential.credential.program_uuid.hex
credential_title = credential.credential.program.title
credential_org = ", ".join(
credential.credential.program.authoring_organizations.values_list("name", flat=True)
)
elif model == "coursecertificate":
credential_uuid = credential.credential.course_id
credential_title = credential.credential.title
credential_org = ""

data.append({
"uuid": credential.uuid.hex,
"status": credential.status,
"username": credential.username,
"download_url": credential.download_url,
"credential_id": credential.credential_id,
"program_uuid": credential.credential.program_uuid.hex,
"program_title": credential.credential.program.title,
"program_org": ", ".join(
credential.credential.program.authoring_organizations.values_list("name", flat=True)
),
"credential_uuid": credential_uuid ,
"credential_title": credential_title,
"credential_org": credential_org,
"modified_date": credential.modified.date().isoformat(),
}
for credential in program_credentials
]
})

return data


def generate_base64_qr_code(text):
Expand Down

0 comments on commit 1660299

Please sign in to comment.