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

test: [AXM-229] Improve test coverage #2543

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
26 changes: 10 additions & 16 deletions lms/djangoapps/mobile_api/course_info/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ class Meta:
@staticmethod
def get_media(obj):
"""
Return course images in the correct forrmat.
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()
return get_encoded_course_sharing_utm_params()

def get_course_about_url(self, course_overview):
return get_link_for_about_page(course_overview)
Expand Down Expand Up @@ -87,35 +87,35 @@ class Meta:
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")
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")]))
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"))
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")))
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
Expand All @@ -126,10 +126,4 @@ 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()


return has_access(data.get('user'), 'load_mobile', data.get('course')).to_json()
6 changes: 5 additions & 1 deletion lms/djangoapps/mobile_api/course_info/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
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, CourseAccessSerializer, MobileCourseEnrollmentSerializer
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
Expand Down
145 changes: 121 additions & 24 deletions lms/djangoapps/mobile_api/tests/test_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,74 @@
"""

import ddt
from mock import patch
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 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,
)
from lms.djangoapps.mobile_api.course_info.serializers import CourseAccessSerializer
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory


@ddt.ddt
class TestCourseAccessSerializer(TestCase):
"""Tests for the CourseAccessSerializer"""
"""
Tests for the CourseAccessSerializer.
"""

def setUp(self):
super().setUp()
self.user = UserFactory()
self.course = CourseOverviewFactory()

@ddt.data(
([{"course_id": {}}], True),
([{'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, has_unmet_prerequisites, mock_get_prerequisites):
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,
'user': self.user,
'course': self.course,
'course_id': self.course.id,
}).data

self.assertEqual(output_data['hasUnmetPrerequisites'], has_unmet_prerequisites)
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, is_too_early, mock_check_course_open):
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
'user': self.user,
'course': self.course,
'course_id': self.course.id
}).data

self.assertEqual(output_data['isTooEarly'], is_too_early)
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),
Expand All @@ -63,12 +79,93 @@ def test_is_too_early(self, mock_return_value, is_too_early, mock_check_course_o
)
@ddt.unpack
@patch('lms.djangoapps.mobile_api.course_info.serializers.administrative_accesses_to_course_for_user')
def test_is_staff(self, mock_return_value, is_staff, mock_administrative_access):
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
'user': self.user,
'course': self.course,
'course_id': self.course.id
}).data

self.assertEqual(output_data['isStaff'], is_staff)
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)
74 changes: 63 additions & 11 deletions lms/djangoapps/mobile_api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
from edx_toggles.toggles.testutils import override_waffle_flag
from milestones.tests.utils import MilestonesTestCaseMixin
from mock import patch
from rest_framework import status

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
Expand Down Expand Up @@ -263,7 +266,7 @@ def test_flag_disabled(self, mock_logger):


@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
"""
Expand All @@ -277,14 +280,14 @@ def setUp(self):
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),
('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")
@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.
Expand All @@ -294,16 +297,16 @@ def test_get_requested_user(self, user_role, username, expected_username, mock_g
username: username query parameter from the request.
expected_username: username of the returned user.
"""
if user_role == "anonymous":
if user_role == 'anonymous':
request_user = AnonymousUser()
elif user_role == "staff":
elif user_role == 'staff':
request_user = self.admin_user
elif user_role == "student":
elif user_role == 'student':
request_user = self.student_user

self.request.user = request_user

if expected_username == "student_user":
if expected_username == 'student_user':
mock_user = self.student_user
mock_get.return_value = mock_user

Expand Down Expand Up @@ -370,3 +373,52 @@ def test_additional_info_response(self, mock_certificate_downloadable_status):
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)
Loading