Skip to content

Commit

Permalink
test: [AXM-229] Improve test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
KyryloKireiev committed Apr 17, 2024
1 parent 88f4d7c commit 84202da
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 51 deletions.
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()
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)

0 comments on commit 84202da

Please sign in to comment.