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

Added V2 functionality to handle queries against a program_uuid inste… #136

Merged
merged 1 commit into from
Jan 9, 2017
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
11 changes: 0 additions & 11 deletions credentials/apps/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@
from credentials.apps.credentials.models import UserCredential


class ProgramFilter(django_filters.FilterSet):
""" Allows for filtering program credentials by their program_id and status
using a query string argument.
"""
program_id = django_filters.NumberFilter(name="program_credentials__program_id")

class Meta:
model = UserCredential
fields = ['program_id', 'status']


class CourseFilter(django_filters.FilterSet):
""" Allows for filtering course credentials by their course_id, status and
certificate_type using a query string argument.
Expand Down
4 changes: 2 additions & 2 deletions credentials/apps/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ def to_internal_value(self, data):
try:
if 'program_id' in data and data.get('program_id'):
credential_id = data['program_id']
return ProgramCertificate.objects.get(program_id=data['program_id'], is_active=True)
return ProgramCertificate.objects.get(program_id=credential_id, is_active=True)
elif 'course_id' in data and data.get('course_id') and data.get('certificate_type'):
credential_id = data['course_id']
return CourseCertificate.objects.get(
course_id=data['course_id'],
course_id=credential_id,
certificate_type=data['certificate_type'],
is_active=True
)
Expand Down
59 changes: 59 additions & 0 deletions credentials/apps/api/tests/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
Mixins for Credentials API tests.
"""
from time import time
import json

from django.conf import settings
from django.contrib.auth.models import Group
from rest_framework.test import APIRequestFactory
import jwt

from credentials.apps.api.serializers import UserCredentialSerializer
from credentials.apps.core.constants import Role
from credentials.apps.core.tests.factories import UserFactory

JWT_AUTH = 'JWT_AUTH'

Expand Down Expand Up @@ -51,3 +57,56 @@ def default_payload(self, user, admin=False, ttl=1):
"given_name": "",
"family_name": "",
}


class CredentialViewSetTestsMixin(object):
""" Base Class for ProgramCredentialViewSetTests and CourseCredentialViewSetTests. """

list_path = None
user_credential = None

# pylint: disable=no-member
def setUp(self):
super(CredentialViewSetTestsMixin, self).setUp()

self.user = UserFactory()
self.user.groups.add(Group.objects.get(name=Role.ADMINS))
self.client.force_authenticate(self.user)
self.request = APIRequestFactory().get('/')

def assert_permission_required(self, data):
"""
Ensure access to these APIs is restricted to those with explicit model
permissions.
"""
self.client.force_authenticate(user=UserFactory())
response = self.client.get(self.list_path, data)
self.assertEqual(response.status_code, 403)

def assert_list_without_id_filter(self, path, expected, data=None):
"""Helper method used for making request and assertions. """
response = self.client.get(path, data)

self.assertEqual(response.status_code, 400)
self.assertEqual(response.data, expected)

def assert_list_with_id_filter(self, data=None, should_exist=True):
"""Helper method used for making request and assertions. """
expected = self._generate_results(should_exist)
response = self.client.get(self.list_path, data)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, expected)

def assert_list_with_status_filter(self, data, should_exist=True):
"""Helper method for making request and assertions. """
expected = self._generate_results(should_exist)
response = self.client.get(self.list_path, data, expected)
self.assertEqual(json.loads(response.content), expected)

def _generate_results(self, exists=True):
if exists:
return {'count': 1, 'next': None, 'previous': None,
'results': [UserCredentialSerializer(self.user_credential, context={'request': self.request}).data]}

return {'count': 0, 'next': None, 'previous': None, 'results': []}
114 changes: 15 additions & 99 deletions credentials/apps/api/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
"""
Tests for credentials service views.
"""
# pylint: disable=no-member
from __future__ import unicode_literals
import json

import ddt
from django.contrib.auth.models import Group, Permission
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase, APIRequestFactory
from rest_framework.test import APIRequestFactory
from testfixtures import LogCapture

from credentials.apps.api.serializers import UserCredentialSerializer
Expand All @@ -17,20 +13,20 @@
from credentials.apps.credentials.models import UserCredential
from credentials.apps.credentials.tests import factories


JSON_CONTENT_TYPE = 'application/json'
LOGGER_NAME = 'credentials.apps.credentials.issuers'
LOGGER_NAME_SERIALIZER = 'credentials.apps.api.serializers'


@ddt.ddt
class UserCredentialViewSetTests(APITestCase):
class BaseUserCredentialViewSetTests(object):
""" Tests for GenerateCredentialView. """
# pylint: disable=no-member

list_path = reverse("api:v1:usercredential-list")
list_path = None

def setUp(self):
super(UserCredentialViewSetTests, self).setUp()
super(BaseUserCredentialViewSetTests, self).setUp()

self.user = UserFactory()
self.client.force_authenticate(self.user)
Expand Down Expand Up @@ -449,10 +445,13 @@ def test_users_lists_access_by_authenticated_users(self):


@ddt.ddt
class UserCredentialViewSetPermissionsTests(APITestCase):
class BaseUserCredentialViewSetPermissionsTests(object):
"""
Thoroughly exercise the custom view- and object-level permissions for this viewset.
"""
# pylint: disable=no-member

list_path = None

def make_user(self, group=None, perm=None, **kwargs):
""" DRY helper to create users with specific groups and/or permissions. """
Expand All @@ -478,10 +477,9 @@ def test_list(self, user_kwargs, expected_status):
The list method (GET) requires either 'view' permission, or for the
'username' query parameter to match that of the requesting user.
"""
list_path = reverse("api:v1:usercredential-list")

self.client.force_authenticate(self.make_user(**user_kwargs))
response = self.client.get(list_path, {'username': 'test-user'})
response = self.client.get(self.list_path, {'username': 'test-user'})
self.assertEqual(response.status_code, expected_status)

@ddt.data(
Expand All @@ -497,7 +495,6 @@ def test_create(self, user_kwargs, expected_status):
"""
The creation (POST) method requires the 'add' permission.
"""
list_path = reverse('api:v1:usercredential-list')
program_certificate = factories.ProgramCertificateFactory()
post_data = {
'username': 'test-user',
Expand All @@ -508,7 +505,7 @@ def test_create(self, user_kwargs, expected_status):
}

self.client.force_authenticate(self.make_user(**user_kwargs))
response = self.client.post(list_path, data=json.dumps(post_data), content_type=JSON_CONTENT_TYPE)
response = self.client.post(self.list_path, data=json.dumps(post_data), content_type=JSON_CONTENT_TYPE)
self.assertEqual(response.status_code, expected_status)

@ddt.data(
Expand Down Expand Up @@ -563,95 +560,14 @@ def test_partial_update(self, user_kwargs, expected_status):
self.assertEqual(response.status_code, expected_status)


class CredentialViewSetTests(APITestCase):
""" Base Class for ProgramCredentialViewSetTests and CourseCredentialViewSetTests. """

list_path = None
user_credential = None

def setUp(self):
super(CredentialViewSetTests, self).setUp()

self.user = UserFactory()
self.user.groups.add(Group.objects.get(name=Role.ADMINS))
self.client.force_authenticate(self.user)
self.request = APIRequestFactory().get('/')

def assert_permission_required(self, data):
"""
Ensure access to these APIs is restricted to those with explicit model
permissions.
"""
self.client.force_authenticate(user=UserFactory())
response = self.client.get(self.list_path, data)
self.assertEqual(response.status_code, 403)

def assert_list_without_id_filter(self, path, expected):
"""Helper method used for making request and assertions. """
response = self.client.get(path)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data, expected)

def assert_list_with_id_filter(self, data):
"""Helper method used for making request and assertions. """
expected = {'count': 1, 'next': None, 'previous': None,
'results': [UserCredentialSerializer(self.user_credential, context={'request': self.request}).data]}
response = self.client.get(self.list_path, data)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, expected)

def assert_list_with_status_filter(self, data):
"""Helper method for making request and assertions. """
expected = {'count': 1, 'next': None, 'previous': None,
'results': [UserCredentialSerializer(self.user_credential, context={'request': self.request}).data]}
response = self.client.get(self.list_path, data, expected)
self.assertEqual(json.loads(response.content), expected)


class ProgramCredentialViewSetTests(CredentialViewSetTests):
""" Tests for ProgramCredentialViewSetTests. """

list_path = reverse("api:v1:programcredential-list")

def setUp(self):
super(ProgramCredentialViewSetTests, self).setUp()

self.program_certificate = factories.ProgramCertificateFactory()
self.program_id = self.program_certificate.program_id
self.user_credential = factories.UserCredentialFactory.create(credential=self.program_certificate)
self.request = APIRequestFactory().get('/')

def test_list_without_program_id(self):
""" Verify a list end point of program credentials will work only with
program_id filter.
"""
self.assert_list_without_id_filter(path=self.list_path, expected={
'error': 'A program_id query string parameter is required for filtering program credentials.'
})

def test_list_with_program_id_filter(self):
""" Verify the list endpoint supports filter data by program_id."""
program_cert = factories.ProgramCertificateFactory(program_id=001)
factories.UserCredentialFactory.create(credential=program_cert)
self.assert_list_with_id_filter(data={'program_id': self.program_id})

def test_list_with_status_filter(self):
""" Verify the list endpoint supports filtering by status."""
factories.UserCredentialFactory.create_batch(2, status="revoked", username=self.user_credential.username)
self.assert_list_with_status_filter(data={'program_id': self.program_id, 'status': UserCredential.AWARDED}, )

def test_permission_required(self):
""" Verify that requests require explicit model permissions. """
self.assert_permission_required({'program_id': self.program_id, 'status': UserCredential.AWARDED})


class CourseCredentialViewSetTests(CredentialViewSetTests):
class BaseCourseCredentialViewSetTests(object):
""" Tests for CourseCredentialViewSetTests. """
# pylint: disable=no-member

list_path = reverse("api:v1:coursecredential-list")
list_path = None

def setUp(self):
super(CourseCredentialViewSetTests, self).setUp()
super(BaseCourseCredentialViewSetTests, self).setUp()

self.course_certificate = factories.CourseCertificateFactory()
self.course_id = self.course_certificate.course_id
Expand Down
1 change: 1 addition & 0 deletions credentials/apps/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@

urlpatterns = [
url(r'^v1/', include('credentials.apps.api.v1.urls', namespace='v1')),
url(r'^v2/', include('credentials.apps.api.v2.urls', namespace='v2')),
]
14 changes: 14 additions & 0 deletions credentials/apps/api/v1/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import django_filters

from credentials.apps.credentials.models import UserCredential


class UserCredentialFilter(django_filters.FilterSet):
""" Allows for filtering program credentials by their program_id and status
using a query string argument.
"""
program_id = django_filters.NumberFilter(name="program_credentials__program_id")

class Meta:
model = UserCredential
fields = ['program_id', 'status']
1 change: 0 additions & 1 deletion credentials/apps/api/v1/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
# Create your tests in sub-packages prefixed with "test_" (e.g. test_views).
76 changes: 76 additions & 0 deletions credentials/apps/api/v1/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Tests for credentials service views.
"""
# pylint: disable=no-member
from __future__ import unicode_literals

from django.core.urlresolvers import reverse
from rest_framework.test import APIRequestFactory, APITestCase

from credentials.apps.api.tests.mixins import CredentialViewSetTestsMixin
from credentials.apps.api.tests.test_views import (
BaseUserCredentialViewSetTests, BaseUserCredentialViewSetPermissionsTests, BaseCourseCredentialViewSetTests,
)
from credentials.apps.credentials.models import UserCredential
from credentials.apps.credentials.tests import factories


class ProgramCredentialViewSetTests(CredentialViewSetTestsMixin, APITestCase):
""" Tests for ProgramCredentialViewSetTests. """

list_path = reverse("api:v1:programcredential-list")

def setUp(self):
super(ProgramCredentialViewSetTests, self).setUp()

self.program_certificate = factories.ProgramCertificateFactory()
self.program_id = self.program_certificate.program_id
self.user_credential = factories.UserCredentialFactory.create(credential=self.program_certificate)
self.request = APIRequestFactory().get('/')

def test_list_without_program_id(self):
""" Verify a list end point of program credentials will work only with
program_id filter.
"""
self.assert_list_without_id_filter(path=self.list_path, expected={
'error': 'A program_id query string parameter is required for filtering program credentials.'
})

def test_list_with_program_id_filter(self):
""" Verify the list endpoint supports filter data by program_id."""
program_cert = factories.ProgramCertificateFactory(program_id=1)
factories.UserCredentialFactory.create(credential=program_cert)
self.assert_list_with_id_filter(data={'program_id': self.program_id})

def test_list_with_program_invalid_id_filter(self):
""" Verify the list endpoint supports filter data by program_id."""
program_cert = factories.ProgramCertificateFactory(program_id=1)
factories.UserCredentialFactory.create(credential=program_cert)
self.assert_list_with_id_filter(data={'program_id': 50}, should_exist=False)

def test_list_with_status_filter(self):
""" Verify the list endpoint supports filtering by status."""
factories.UserCredentialFactory.create_batch(2, status=UserCredential.REVOKED,
username=self.user_credential.username)
self.assert_list_with_status_filter(data={'program_id': self.program_id, 'status': UserCredential.AWARDED})

def test_list_with_bad_status_filter(self):
""" Verify the list endpoint supports filtering by status."""
self.assert_list_with_status_filter(data={'program_id': self.program_id, 'status': UserCredential.REVOKED},
should_exist=False)

def test_permission_required(self):
""" Verify that requests require explicit model permissions. """
self.assert_permission_required({'program_id': self.program_id, 'status': UserCredential.AWARDED})


class UserCredentialViewSetTests(BaseUserCredentialViewSetTests, APITestCase):
list_path = reverse("api:v1:usercredential-list")


class UserCredentialViewSetPermissionsTests(BaseUserCredentialViewSetPermissionsTests, APITestCase):
list_path = reverse("api:v1:usercredential-list")


class CourseCredentialViewSetTests(BaseCourseCredentialViewSetTests, CredentialViewSetTestsMixin, APITestCase):
list_path = reverse("api:v1:coursecredential-list")
Loading