feat(mobile_api): Add course access object to mobile course info API (#34273)
* feat: include access serializer into mobile info api view * test: add tests for serializer and view methods * test: move tests to common directory and update test case * fix: cr fixes and use snake case for functions * test: fix additional get call assertion * feat: add required course access messages to mobile endpoint * test: [AXM-229] Improve test coverage * style: [AXM-229] Try to fix linters * fix: remove redundant comment * refactor: change names for the test files --------- Co-authored-by: KyryloKireiev <kirillkireev888@gmail.com>
This commit is contained in:
@@ -2,13 +2,25 @@
|
||||
Course Info serializers
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from typing import Union
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from common.djangoapps.util.course import get_encoded_course_sharing_utm_params, get_link_for_about_page
|
||||
from common.djangoapps.util.milestones_helpers import (
|
||||
get_pre_requisite_courses_not_completed,
|
||||
)
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.access import administrative_accesses_to_course_for_user
|
||||
from lms.djangoapps.courseware.access_utils import check_course_open_for_learner
|
||||
from lms.djangoapps.mobile_api.users.serializers import ModeSerializer
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.features.course_duration_limits.access import get_user_course_expiration_date
|
||||
|
||||
|
||||
class CourseInfoOverviewSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for serialize additional fields in BlocksInfoInCourseView.
|
||||
Serializer for additional course fields that should be returned in BlocksInfoInCourseView.
|
||||
"""
|
||||
|
||||
name = serializers.CharField(source='display_name')
|
||||
@@ -16,6 +28,9 @@ class CourseInfoOverviewSerializer(serializers.ModelSerializer):
|
||||
org = serializers.CharField(source='display_org_with_default')
|
||||
is_self_paced = serializers.BooleanField(source='self_paced')
|
||||
media = serializers.SerializerMethodField()
|
||||
course_sharing_utm_parameters = serializers.SerializerMethodField()
|
||||
course_about = serializers.SerializerMethodField('get_course_about_url')
|
||||
course_modes = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = CourseOverview
|
||||
@@ -29,8 +44,86 @@ class CourseInfoOverviewSerializer(serializers.ModelSerializer):
|
||||
'end',
|
||||
'is_self_paced',
|
||||
'media',
|
||||
'course_sharing_utm_parameters',
|
||||
'course_about',
|
||||
'course_modes',
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_media(obj):
|
||||
"""
|
||||
Return course images in the correct format.
|
||||
"""
|
||||
return {'image': obj.image_urls}
|
||||
|
||||
def get_course_sharing_utm_parameters(self, obj):
|
||||
return get_encoded_course_sharing_utm_params()
|
||||
|
||||
def get_course_about_url(self, course_overview):
|
||||
return get_link_for_about_page(course_overview)
|
||||
|
||||
def get_course_modes(self, course_overview):
|
||||
"""
|
||||
Retrieve course modes associated with the course.
|
||||
"""
|
||||
course_modes = CourseMode.modes_for_course(
|
||||
course_overview.id,
|
||||
only_selectable=False
|
||||
)
|
||||
return [
|
||||
ModeSerializer(mode).data
|
||||
for mode in course_modes
|
||||
]
|
||||
|
||||
|
||||
class MobileCourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Serializer for the CourseEnrollment object used in the BlocksInfoInCourseView.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
fields = ('created', 'mode', 'is_active')
|
||||
model = CourseEnrollment
|
||||
lookup_field = 'username'
|
||||
|
||||
|
||||
class CourseAccessSerializer(serializers.Serializer):
|
||||
"""
|
||||
Get info whether a user should be able to view course material.
|
||||
"""
|
||||
|
||||
has_unmet_prerequisites = serializers.SerializerMethodField(method_name='get_has_unmet_prerequisites')
|
||||
is_too_early = serializers.SerializerMethodField(method_name='get_is_too_early')
|
||||
is_staff = serializers.SerializerMethodField(method_name='get_is_staff')
|
||||
audit_access_expires = serializers.SerializerMethodField()
|
||||
courseware_access = serializers.SerializerMethodField()
|
||||
|
||||
def get_has_unmet_prerequisites(self, data: dict) -> bool:
|
||||
"""
|
||||
Check whether or not a course has unmet prerequisites.
|
||||
"""
|
||||
return any(get_pre_requisite_courses_not_completed(data.get('user'), [data.get('course_id')]))
|
||||
|
||||
def get_is_too_early(self, data: dict) -> bool:
|
||||
"""
|
||||
Determine if the course is open to a learner (course has started or user has early beta access).
|
||||
"""
|
||||
return not check_course_open_for_learner(data.get('user'), data.get('course'))
|
||||
|
||||
def get_is_staff(self, data: dict) -> bool:
|
||||
"""
|
||||
Determine whether a user has staff access to this course.
|
||||
"""
|
||||
return any(administrative_accesses_to_course_for_user(data.get('user'), data.get('course_id')))
|
||||
|
||||
def get_audit_access_expires(self, data: dict) -> Union[str, None]:
|
||||
"""
|
||||
Returns expiration date for a course audit expiration, if any or null
|
||||
"""
|
||||
return get_user_course_expiration_date(data.get('user'), data.get('course'))
|
||||
|
||||
def get_courseware_access(self, data: dict) -> dict:
|
||||
"""
|
||||
Determine if the learner has access to the course, otherwise show error message.
|
||||
"""
|
||||
return has_access(data.get('user'), 'load_mobile', data.get('course')).to_json()
|
||||
|
||||
@@ -3,20 +3,28 @@ Views for course info API
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, Union
|
||||
|
||||
import django
|
||||
from django.contrib.auth import get_user_model
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment, User as StudentUser
|
||||
from common.djangoapps.static_replace import make_static_urls_absolute
|
||||
from lms.djangoapps.certificates.api import certificate_downloadable_status
|
||||
from lms.djangoapps.courseware.courses import get_course_info_section_block
|
||||
from lms.djangoapps.course_goals.models import UserActivity
|
||||
from lms.djangoapps.course_api.blocks.views import BlocksInCourseView
|
||||
from lms.djangoapps.mobile_api.course_info.serializers import CourseInfoOverviewSerializer
|
||||
from lms.djangoapps.mobile_api.course_info.serializers import (
|
||||
CourseInfoOverviewSerializer,
|
||||
CourseAccessSerializer,
|
||||
MobileCourseEnrollmentSerializer
|
||||
)
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
from openedx.core.lib.xblock_utils import get_course_update_items
|
||||
@@ -26,6 +34,8 @@ from ..decorators import mobile_course_access, mobile_view
|
||||
User = get_user_model()
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
UserType = Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser, StudentUser]
|
||||
|
||||
|
||||
@mobile_view()
|
||||
class CourseUpdatesList(generics.ListAPIView):
|
||||
@@ -271,31 +281,52 @@ class BlocksInfoInCourseView(BlocksInCourseView):
|
||||
* 404 if the course is not available or cannot be seen.
|
||||
"""
|
||||
|
||||
def get_certificate(self, request, course_id):
|
||||
def get_requested_user(self, user: UserType, username: Optional[str] = None) -> Union[UserType, None]:
|
||||
"""
|
||||
Returns the information about the user's certificate in the course.
|
||||
Return a user for whom the course blocks are fetched.
|
||||
|
||||
Arguments:
|
||||
user: current user from request.
|
||||
username: string with username.
|
||||
Returns: A user object or None.
|
||||
"""
|
||||
if user.is_anonymous:
|
||||
return None
|
||||
|
||||
if not username or (username and user.username == username):
|
||||
return user
|
||||
if username and (user.is_staff or user.is_superuser):
|
||||
try:
|
||||
return User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
log.warning('Provided username does not correspond to an existing user %s', username)
|
||||
return None
|
||||
|
||||
def get_certificate(self, request, user, course_id):
|
||||
"""
|
||||
Return the information about the user's certificate in the course.
|
||||
|
||||
Arguments:
|
||||
request (Request): The request object.
|
||||
user (User): The user object.
|
||||
course_id (str): The identifier of the course.
|
||||
Returns:
|
||||
(dict): A dict containing information about location of the user's certificate
|
||||
or an empty dictionary, if there is no certificate.
|
||||
"""
|
||||
if request.user.is_authenticated:
|
||||
certificate_info = certificate_downloadable_status(request.user, course_id)
|
||||
if certificate_info['is_downloadable']:
|
||||
return {
|
||||
'url': request.build_absolute_uri(
|
||||
certificate_info['download_url']
|
||||
),
|
||||
}
|
||||
certificate_info = certificate_downloadable_status(user, course_id)
|
||||
if certificate_info['is_downloadable']:
|
||||
return {
|
||||
'url': request.build_absolute_uri(
|
||||
certificate_info['download_url']
|
||||
),
|
||||
}
|
||||
return {}
|
||||
|
||||
def list(self, request, **kwargs): # pylint: disable=W0221
|
||||
"""
|
||||
REST API endpoint for listing all the blocks information in the course and
|
||||
information about the course while regarding user access and roles.
|
||||
information about the course considering user access and roles.
|
||||
|
||||
Arguments:
|
||||
request - Django request object
|
||||
@@ -304,13 +335,48 @@ class BlocksInfoInCourseView(BlocksInCourseView):
|
||||
response = super().list(request, kwargs)
|
||||
|
||||
if request.GET.get('return_type', 'dict') == 'dict':
|
||||
api_version = self.kwargs.get('api_version')
|
||||
course_id = request.query_params.get('course_id', None)
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course_overview = CourseOverview.get_from_id(course_key)
|
||||
requested_username = request.query_params.get('username', None)
|
||||
|
||||
course_data = {
|
||||
'id': course_id,
|
||||
'certificate': self.get_certificate(request, course_key),
|
||||
'course_updates': reverse(
|
||||
'course-updates-list',
|
||||
kwargs={'api_version': api_version, 'course_id': course_id},
|
||||
request=request,
|
||||
),
|
||||
'course_handouts': reverse(
|
||||
'course-handouts-list',
|
||||
kwargs={'api_version': api_version, 'course_id': course_id},
|
||||
request=request,
|
||||
),
|
||||
}
|
||||
course_data.update(CourseInfoOverviewSerializer(course_overview).data)
|
||||
|
||||
course_info_context = {}
|
||||
if requested_user := self.get_requested_user(request.user, requested_username):
|
||||
course_info_context = {
|
||||
'user': requested_user
|
||||
}
|
||||
user_enrollment = CourseEnrollment.get_enrollment(user=requested_user, course_key=course_key)
|
||||
course_data.update({
|
||||
'discussion_url': reverse(
|
||||
'discussion_course',
|
||||
kwargs={'course_id': course_id},
|
||||
request=request,
|
||||
) if course_overview.is_discussion_tab_enabled(requested_user) else None,
|
||||
'course_access_details': CourseAccessSerializer({
|
||||
'user': requested_user,
|
||||
'course': course_overview,
|
||||
'course_id': course_key
|
||||
}).data,
|
||||
'certificate': self.get_certificate(request, requested_user, course_key),
|
||||
'enrollment_details': MobileCourseEnrollmentSerializer(user_enrollment).data,
|
||||
})
|
||||
|
||||
course_data.update(CourseInfoOverviewSerializer(course_overview, context=course_info_context).data)
|
||||
|
||||
response.data.update(course_data)
|
||||
return response
|
||||
|
||||
171
lms/djangoapps/mobile_api/tests/test_course_info_serializers.py
Normal file
171
lms/djangoapps/mobile_api/tests/test_course_info_serializers.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Tests for serializers for the Mobile Course Info
|
||||
"""
|
||||
|
||||
import ddt
|
||||
from django.test import TestCase
|
||||
from mock import MagicMock, Mock, patch
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.mobile_api.course_info.serializers import (
|
||||
CourseAccessSerializer,
|
||||
CourseInfoOverviewSerializer,
|
||||
)
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestCourseAccessSerializer(TestCase):
|
||||
"""
|
||||
Tests for the CourseAccessSerializer.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.course = CourseOverviewFactory()
|
||||
|
||||
@ddt.data(
|
||||
([{'course_id': {}}], True),
|
||||
([], False),
|
||||
)
|
||||
@ddt.unpack
|
||||
@patch('lms.djangoapps.mobile_api.course_info.serializers.get_pre_requisite_courses_not_completed')
|
||||
def test_has_unmet_prerequisites(
|
||||
self,
|
||||
mock_return_value: List[Dict],
|
||||
has_unmet_prerequisites: bool,
|
||||
mock_get_prerequisites: MagicMock,
|
||||
) -> None:
|
||||
mock_get_prerequisites.return_value = mock_return_value
|
||||
|
||||
output_data = CourseAccessSerializer({
|
||||
'user': self.user,
|
||||
'course': self.course,
|
||||
'course_id': self.course.id,
|
||||
}).data
|
||||
|
||||
self.assertEqual(output_data['has_unmet_prerequisites'], has_unmet_prerequisites)
|
||||
mock_get_prerequisites.assert_called_once_with(self.user, [self.course.id])
|
||||
|
||||
@ddt.data(
|
||||
(True, False),
|
||||
(False, True),
|
||||
)
|
||||
@ddt.unpack
|
||||
@patch('lms.djangoapps.mobile_api.course_info.serializers.check_course_open_for_learner')
|
||||
def test_is_too_early(
|
||||
self,
|
||||
mock_return_value: bool,
|
||||
is_too_early: bool,
|
||||
mock_check_course_open: MagicMock,
|
||||
) -> None:
|
||||
mock_check_course_open.return_value = mock_return_value
|
||||
|
||||
output_data = CourseAccessSerializer({
|
||||
'user': self.user,
|
||||
'course': self.course,
|
||||
'course_id': self.course.id
|
||||
}).data
|
||||
|
||||
self.assertEqual(output_data['is_too_early'], is_too_early)
|
||||
mock_check_course_open.assert_called_once_with(self.user, self.course)
|
||||
|
||||
@ddt.data(
|
||||
((False, False, False), False),
|
||||
((True, True, True), True),
|
||||
((True, False, False), True),
|
||||
)
|
||||
@ddt.unpack
|
||||
@patch('lms.djangoapps.mobile_api.course_info.serializers.administrative_accesses_to_course_for_user')
|
||||
def test_is_staff(
|
||||
self,
|
||||
mock_return_value: Tuple[bool],
|
||||
is_staff: bool,
|
||||
mock_administrative_access: MagicMock,
|
||||
) -> None:
|
||||
mock_administrative_access.return_value = mock_return_value
|
||||
|
||||
output_data = CourseAccessSerializer({
|
||||
'user': self.user,
|
||||
'course': self.course,
|
||||
'course_id': self.course.id
|
||||
}).data
|
||||
|
||||
self.assertEqual(output_data['is_staff'], is_staff)
|
||||
mock_administrative_access.assert_called_once_with(self.user, self.course.id)
|
||||
|
||||
@ddt.data(None, 'mocked_user_course_expiration_date')
|
||||
@patch('lms.djangoapps.mobile_api.course_info.serializers.get_user_course_expiration_date')
|
||||
def test_get_audit_access_expires(
|
||||
self,
|
||||
mock_return_value: Union[str, None],
|
||||
mock_get_user_course_expiration_date: MagicMock,
|
||||
) -> None:
|
||||
mock_get_user_course_expiration_date.return_value = mock_return_value
|
||||
|
||||
output_data = CourseAccessSerializer({
|
||||
'user': self.user,
|
||||
'course': self.course,
|
||||
'course_id': self.course.id
|
||||
}).data
|
||||
|
||||
self.assertEqual(output_data['audit_access_expires'], mock_return_value)
|
||||
mock_get_user_course_expiration_date.assert_called_once_with(self.user, self.course)
|
||||
|
||||
@patch('lms.djangoapps.mobile_api.course_info.serializers.has_access')
|
||||
def test_get_courseware_access(self, mock_has_access: MagicMock) -> None:
|
||||
mocked_access = {
|
||||
'has_access': True,
|
||||
'error_code': None,
|
||||
'developer_message': None,
|
||||
'user_message': None,
|
||||
'additional_context_user_message': None,
|
||||
'user_fragment': None
|
||||
}
|
||||
mock_has_access.return_value = Mock(to_json=Mock(return_value=mocked_access))
|
||||
|
||||
output_data = CourseAccessSerializer({
|
||||
'user': self.user,
|
||||
'course': self.course,
|
||||
'course_id': self.course.id
|
||||
}).data
|
||||
|
||||
self.assertDictEqual(output_data['courseware_access'], mocked_access)
|
||||
mock_has_access.assert_called_once_with(self.user, 'load_mobile', self.course)
|
||||
mock_has_access.return_value.to_json.assert_called_once_with()
|
||||
|
||||
|
||||
class TestCourseInfoOverviewSerializer(TestCase):
|
||||
"""
|
||||
Tests for the CourseInfoOverviewSerializer.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.course_overview = CourseOverviewFactory()
|
||||
|
||||
def test_get_media(self):
|
||||
output_data = CourseInfoOverviewSerializer(self.course_overview, context={'user': self.user}).data
|
||||
|
||||
self.assertIn('media', output_data)
|
||||
self.assertIn('image', output_data['media'])
|
||||
self.assertIn('raw', output_data['media']['image'])
|
||||
self.assertIn('small', output_data['media']['image'])
|
||||
self.assertIn('large', output_data['media']['image'])
|
||||
|
||||
@patch('lms.djangoapps.mobile_api.course_info.serializers.get_link_for_about_page', return_value='mock_about_link')
|
||||
def test_get_course_sharing_utm_parameters(self, mock_get_link_for_about_page: MagicMock) -> None:
|
||||
output_data = CourseInfoOverviewSerializer(self.course_overview, context={'user': self.user}).data
|
||||
|
||||
self.assertEqual(output_data['course_about'], mock_get_link_for_about_page.return_value)
|
||||
mock_get_link_for_about_page.assert_called_once_with(self.course_overview)
|
||||
|
||||
def test_get_course_modes(self):
|
||||
expected_course_modes = [{'slug': 'audit', 'sku': None, 'android_sku': None, 'ios_sku': None, 'min_price': 0}]
|
||||
|
||||
output_data = CourseInfoOverviewSerializer(self.course_overview, context={'user': self.user}).data
|
||||
|
||||
self.assertListEqual(output_data['course_modes'], expected_course_modes)
|
||||
@@ -5,26 +5,33 @@ Tests for course_info
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.test import RequestFactory
|
||||
from django.urls import reverse
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from mock import patch
|
||||
from rest_framework.test import APIClient # pylint: disable=unused-import
|
||||
from rest_framework import status
|
||||
|
||||
from common.djangoapps.student.models import CourseEnrollment # pylint: disable=unused-import
|
||||
from common.djangoapps.student.tests.factories import UserFactory # pylint: disable=unused-import
|
||||
from common.djangoapps.util.course import get_link_for_about_page
|
||||
from lms.djangoapps.mobile_api.testutils import MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin
|
||||
from lms.djangoapps.mobile_api.utils import API_V1, API_V05
|
||||
from lms.djangoapps.mobile_api.course_info.views import BlocksInfoInCourseView
|
||||
from lms.djangoapps.course_api.blocks.tests.test_views import TestBlocksInCourseView
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.features.course_experience import ENABLE_COURSE_GOALS
|
||||
from xmodule.html_block import CourseInfoBlock # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=unused-import, wrong-import-order
|
||||
from xmodule.modulestore.xml_importer import import_course_from_xml # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin, MilestonesTestCaseMixin):
|
||||
"""
|
||||
@@ -259,9 +266,9 @@ class TestCourseGoalsUserActivityAPI(MobileAPITestCase, SharedModuleStoreTestCas
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestBlocksInfoInCourseView(TestBlocksInCourseView): # lint-amnesty, pylint: disable=test-inherits-tests
|
||||
class TestBlocksInfoInCourseView(TestBlocksInCourseView, MilestonesTestCaseMixin): # lint-amnesty, pylint: disable=test-inherits-tests
|
||||
"""
|
||||
Test class for BlocksInfoInCourseView
|
||||
Test class for BlocksInfoInCourseView
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -269,6 +276,70 @@ class TestBlocksInfoInCourseView(TestBlocksInCourseView): # lint-amnesty, pylin
|
||||
self.url = reverse('blocks_info_in_course', kwargs={
|
||||
'api_version': 'v3',
|
||||
})
|
||||
self.request = RequestFactory().get(self.url)
|
||||
self.student_user = UserFactory.create(username="student_user")
|
||||
|
||||
@ddt.data(
|
||||
('anonymous', None, None),
|
||||
('staff', 'student_user', 'student_user'),
|
||||
('student', 'student_user', 'student_user'),
|
||||
('student', None, 'student_user'),
|
||||
('student', 'other_student', None),
|
||||
)
|
||||
@ddt.unpack
|
||||
@patch('lms.djangoapps.mobile_api.course_info.views.User.objects.get')
|
||||
def test_get_requested_user(self, user_role, username, expected_username, mock_get):
|
||||
"""
|
||||
Test get_requested_user utility from the BlocksInfoInCourseView.
|
||||
|
||||
Parameters:
|
||||
user_role: type of the user that making a request.
|
||||
username: username query parameter from the request.
|
||||
expected_username: username of the returned user.
|
||||
"""
|
||||
if user_role == 'anonymous':
|
||||
request_user = AnonymousUser()
|
||||
elif user_role == 'staff':
|
||||
request_user = self.admin_user
|
||||
elif user_role == 'student':
|
||||
request_user = self.student_user
|
||||
|
||||
self.request.user = request_user
|
||||
|
||||
if expected_username == 'student_user':
|
||||
mock_user = self.student_user
|
||||
mock_get.return_value = mock_user
|
||||
|
||||
result_user = BlocksInfoInCourseView().get_requested_user(self.request.user, username)
|
||||
if expected_username:
|
||||
self.assertEqual(result_user.username, expected_username)
|
||||
if username and request_user.username != username:
|
||||
mock_get.assert_called_with(username=username)
|
||||
else:
|
||||
self.assertIsNone(result_user)
|
||||
|
||||
@ddt.data(
|
||||
({'is_downloadable': True, 'download_url': 'https://test_certificate_url'},
|
||||
{'url': 'https://test_certificate_url'}),
|
||||
({'is_downloadable': False}, {}),
|
||||
)
|
||||
@ddt.unpack
|
||||
@patch('lms.djangoapps.mobile_api.course_info.views.certificate_downloadable_status')
|
||||
def test_get_certificate(self, certificate_status_return, expected_output, mock_certificate_status):
|
||||
"""
|
||||
Test get_certificate utility from the BlocksInfoInCourseView.
|
||||
|
||||
Parameters:
|
||||
certificate_status_return: returned value of the mocked certificate_downloadable_status function.
|
||||
expected_output: return_value of the get_certificate function with specified mock return_value.
|
||||
"""
|
||||
mock_certificate_status.return_value = certificate_status_return
|
||||
self.request.user = self.user
|
||||
|
||||
certificate_info = BlocksInfoInCourseView().get_certificate(
|
||||
self.request, self.user, 'course-v1:Test+T101+2021_T1'
|
||||
)
|
||||
self.assertEqual(certificate_info, expected_output)
|
||||
|
||||
@patch('lms.djangoapps.mobile_api.course_info.views.certificate_downloadable_status')
|
||||
def test_additional_info_response(self, mock_certificate_downloadable_status):
|
||||
@@ -302,3 +373,52 @@ class TestBlocksInfoInCourseView(TestBlocksInCourseView): # lint-amnesty, pylin
|
||||
assert response.data['certificate'] == {'url': certificate_url}
|
||||
assert response.data['is_self_paced'] is False
|
||||
mock_certificate_downloadable_status.assert_called_once()
|
||||
|
||||
def test_course_access_details(self):
|
||||
response = self.verify_response(url=self.url)
|
||||
|
||||
expected_course_access_details = {
|
||||
'has_unmet_prerequisites': False,
|
||||
'is_too_early': False,
|
||||
'is_staff': False,
|
||||
'audit_access_expires': None,
|
||||
'courseware_access': {
|
||||
'has_access': True,
|
||||
'error_code': None,
|
||||
'developer_message': None,
|
||||
'user_message': None,
|
||||
'additional_context_user_message': None,
|
||||
'user_fragment': None
|
||||
}
|
||||
}
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertDictEqual(response.data['course_access_details'], expected_course_access_details)
|
||||
|
||||
def test_course_sharing_utm_parameters(self):
|
||||
response = self.verify_response(url=self.url)
|
||||
|
||||
expected_course_sharing_utm_parameters = {
|
||||
'facebook': 'utm_medium=social&utm_campaign=social-sharing-db&utm_source=facebook',
|
||||
'twitter': 'utm_medium=social&utm_campaign=social-sharing-db&utm_source=twitter'
|
||||
}
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertDictEqual(response.data['course_sharing_utm_parameters'], expected_course_sharing_utm_parameters)
|
||||
|
||||
def test_course_about_url(self):
|
||||
response = self.verify_response(url=self.url)
|
||||
|
||||
course_overview = CourseOverview.objects.get(id=self.course.course_id)
|
||||
expected_course_about_link = get_link_for_about_page(course_overview)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['course_about'], expected_course_about_link)
|
||||
|
||||
def test_course_modes(self):
|
||||
response = self.verify_response(url=self.url)
|
||||
|
||||
expected_course_modes = [{'slug': 'audit', 'sku': None, 'android_sku': None, 'ios_sku': None, 'min_price': 0}]
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertListEqual(response.data['course_modes'], expected_course_modes)
|
||||
Reference in New Issue
Block a user