306 lines
11 KiB
Python
306 lines
11 KiB
Python
"""
|
|
Tests for users API
|
|
"""
|
|
import datetime
|
|
from django.utils import timezone
|
|
|
|
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
|
|
from student.models import CourseEnrollment
|
|
from certificates.models import CertificateStatuses
|
|
from certificates.tests.factories import GeneratedCertificateFactory
|
|
|
|
from .. import errors
|
|
from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileAuthUserTestMixin, MobileCourseAccessTestMixin
|
|
from .serializers import CourseEnrollmentSerializer
|
|
|
|
|
|
class TestUserDetailApi(MobileAPITestCase, MobileAuthUserTestMixin):
|
|
"""
|
|
Tests for /api/mobile/v0.5/users/<user_name>...
|
|
"""
|
|
REVERSE_INFO = {'name': 'user-detail', 'params': ['username']}
|
|
|
|
def test_success(self):
|
|
self.login()
|
|
|
|
response = self.api_response()
|
|
self.assertEqual(response.data['username'], self.user.username)
|
|
self.assertEqual(response.data['email'], self.user.email)
|
|
|
|
|
|
class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
|
|
"""
|
|
Tests for /api/mobile/v0.5/my_user_info
|
|
"""
|
|
def reverse_url(self, reverse_args=None, **kwargs):
|
|
return '/api/mobile/v0.5/my_user_info'
|
|
|
|
def test_success(self):
|
|
"""Verify the endpoint redirects to the user detail endpoint"""
|
|
self.login()
|
|
|
|
response = self.api_response(expected_response_code=302)
|
|
self.assertTrue(self.username in response['location'])
|
|
|
|
|
|
class TestUserEnrollmentApi(MobileAPITestCase, MobileAuthUserTestMixin, MobileCourseAccessTestMixin):
|
|
"""
|
|
Tests for /api/mobile/v0.5/users/<user_name>/course_enrollments/
|
|
"""
|
|
REVERSE_INFO = {'name': 'courseenrollment-detail', 'params': ['username']}
|
|
ALLOW_ACCESS_TO_UNRELEASED_COURSE = True
|
|
ALLOW_ACCESS_TO_MILESTONE_COURSE = True
|
|
|
|
def verify_success(self, response):
|
|
super(TestUserEnrollmentApi, self).verify_success(response)
|
|
courses = response.data
|
|
self.assertEqual(len(courses), 1)
|
|
|
|
found_course = courses[0]['course']
|
|
self.assertTrue('video_outline' in found_course)
|
|
self.assertTrue('course_handouts' in found_course)
|
|
self.assertEqual(found_course['id'], unicode(self.course.id))
|
|
self.assertEqual(courses[0]['mode'], 'honor')
|
|
self.assertEqual(courses[0]['course']['subscription_id'], self.course.clean_id(padding_char='_'))
|
|
|
|
def verify_failure(self, response):
|
|
self.assertEqual(response.status_code, 200)
|
|
courses = response.data
|
|
self.assertEqual(len(courses), 0)
|
|
|
|
def test_sort_order(self):
|
|
self.login()
|
|
|
|
num_courses = 3
|
|
courses = []
|
|
for course_num in range(num_courses):
|
|
courses.append(CourseFactory.create(mobile_available=True))
|
|
self.enroll(courses[course_num].id)
|
|
|
|
# verify courses are returned in the order of enrollment, with most recently enrolled first.
|
|
response = self.api_response()
|
|
for course_num in range(num_courses):
|
|
self.assertEqual(
|
|
response.data[course_num]['course']['id'], # pylint: disable=no-member
|
|
unicode(courses[num_courses - course_num - 1].id)
|
|
)
|
|
|
|
def test_no_certificate(self):
|
|
self.login_and_enroll()
|
|
|
|
response = self.api_response()
|
|
certificate_data = response.data[0]['certificate'] # pylint: disable=no-member
|
|
self.assertDictEqual(certificate_data, {})
|
|
|
|
def test_certificate(self):
|
|
self.login_and_enroll()
|
|
|
|
certificate_url = "http://test_certificate_url"
|
|
GeneratedCertificateFactory.create(
|
|
user=self.user,
|
|
course_id=self.course.id,
|
|
status=CertificateStatuses.downloadable,
|
|
mode='verified',
|
|
download_url=certificate_url,
|
|
)
|
|
|
|
response = self.api_response()
|
|
certificate_data = response.data[0]['certificate'] # pylint: disable=no-member
|
|
self.assertEquals(certificate_data['url'], certificate_url)
|
|
|
|
def test_no_facebook_url(self):
|
|
self.login_and_enroll()
|
|
|
|
response = self.api_response()
|
|
course_data = response.data[0]['course'] # pylint: disable=no-member
|
|
self.assertIsNone(course_data['social_urls']['facebook'])
|
|
|
|
def test_facebook_url(self):
|
|
self.login_and_enroll()
|
|
|
|
self.course.facebook_url = "http://facebook.com/test_group_page"
|
|
self.store.update_item(self.course, self.user.id)
|
|
|
|
response = self.api_response()
|
|
course_data = response.data[0]['course'] # pylint: disable=no-member
|
|
self.assertEquals(course_data['social_urls']['facebook'], self.course.facebook_url)
|
|
|
|
|
|
class CourseStatusAPITestCase(MobileAPITestCase):
|
|
"""
|
|
Base test class for /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
|
|
"""
|
|
REVERSE_INFO = {'name': 'user-course-status', 'params': ['username', 'course_id']}
|
|
|
|
def setUp(self):
|
|
"""
|
|
Creates a basic course structure for our course
|
|
"""
|
|
super(CourseStatusAPITestCase, self).setUp()
|
|
|
|
self.section = ItemFactory.create(
|
|
parent=self.course,
|
|
category='chapter',
|
|
)
|
|
self.sub_section = ItemFactory.create(
|
|
parent=self.section,
|
|
category='sequential',
|
|
)
|
|
self.unit = ItemFactory.create(
|
|
parent=self.sub_section,
|
|
category='vertical',
|
|
)
|
|
self.other_sub_section = ItemFactory.create(
|
|
parent=self.section,
|
|
category='sequential',
|
|
)
|
|
self.other_unit = ItemFactory.create(
|
|
parent=self.other_sub_section,
|
|
category='vertical',
|
|
)
|
|
|
|
|
|
class TestCourseStatusGET(CourseStatusAPITestCase, MobileAuthUserTestMixin, MobileCourseAccessTestMixin):
|
|
"""
|
|
Tests for GET of /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
|
|
"""
|
|
def test_success(self):
|
|
self.login_and_enroll()
|
|
|
|
response = self.api_response()
|
|
self.assertEqual(
|
|
response.data["last_visited_module_id"], # pylint: disable=no-member
|
|
unicode(self.sub_section.location)
|
|
)
|
|
self.assertEqual(
|
|
response.data["last_visited_module_path"], # pylint: disable=no-member
|
|
[unicode(module.location) for module in [self.sub_section, self.section, self.course]]
|
|
)
|
|
|
|
|
|
class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin, MobileCourseAccessTestMixin):
|
|
"""
|
|
Tests for PATCH of /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
|
|
"""
|
|
def url_method(self, url, **kwargs):
|
|
# override implementation to use PATCH method.
|
|
return self.client.patch(url, data=kwargs.get('data', None)) # pylint: disable=no-member
|
|
|
|
def test_success(self):
|
|
self.login_and_enroll()
|
|
response = self.api_response(data={"last_visited_module_id": unicode(self.other_unit.location)})
|
|
self.assertEqual(
|
|
response.data["last_visited_module_id"], # pylint: disable=no-member
|
|
unicode(self.other_sub_section.location)
|
|
)
|
|
|
|
def test_invalid_module(self):
|
|
self.login_and_enroll()
|
|
response = self.api_response(data={"last_visited_module_id": "abc"}, expected_response_code=400)
|
|
self.assertEqual(
|
|
response.data, # pylint: disable=no-member
|
|
errors.ERROR_INVALID_MODULE_ID
|
|
)
|
|
|
|
def test_nonexistent_module(self):
|
|
self.login_and_enroll()
|
|
non_existent_key = self.course.id.make_usage_key('video', 'non-existent')
|
|
response = self.api_response(data={"last_visited_module_id": non_existent_key}, expected_response_code=400)
|
|
self.assertEqual(
|
|
response.data, # pylint: disable=no-member
|
|
errors.ERROR_INVALID_MODULE_ID
|
|
)
|
|
|
|
def test_no_timezone(self):
|
|
self.login_and_enroll()
|
|
past_date = datetime.datetime.now()
|
|
response = self.api_response(
|
|
data={
|
|
"last_visited_module_id": unicode(self.other_unit.location),
|
|
"modification_date": past_date.isoformat() # pylint: disable=maybe-no-member
|
|
},
|
|
expected_response_code=400
|
|
)
|
|
self.assertEqual(
|
|
response.data, # pylint: disable=no-member
|
|
errors.ERROR_INVALID_MODIFICATION_DATE
|
|
)
|
|
|
|
def _date_sync(self, date, initial_unit, update_unit, expected_subsection):
|
|
"""
|
|
Helper for test cases that use a modification to decide whether
|
|
to update the course status
|
|
"""
|
|
self.login_and_enroll()
|
|
|
|
# save something so we have an initial date
|
|
self.api_response(data={"last_visited_module_id": unicode(initial_unit.location)})
|
|
|
|
# now actually update it
|
|
response = self.api_response(
|
|
data={
|
|
"last_visited_module_id": unicode(update_unit.location),
|
|
"modification_date": date.isoformat()
|
|
}
|
|
)
|
|
self.assertEqual(
|
|
response.data["last_visited_module_id"], # pylint: disable=no-member
|
|
unicode(expected_subsection.location)
|
|
)
|
|
|
|
def test_old_date(self):
|
|
self.login_and_enroll()
|
|
date = timezone.now() + datetime.timedelta(days=-100)
|
|
self._date_sync(date, self.unit, self.other_unit, self.sub_section)
|
|
|
|
def test_new_date(self):
|
|
self.login_and_enroll()
|
|
date = timezone.now() + datetime.timedelta(days=100)
|
|
self._date_sync(date, self.unit, self.other_unit, self.other_sub_section)
|
|
|
|
def test_no_initial_date(self):
|
|
self.login_and_enroll()
|
|
response = self.api_response(
|
|
data={
|
|
"last_visited_module_id": unicode(self.other_unit.location),
|
|
"modification_date": timezone.now().isoformat()
|
|
}
|
|
)
|
|
self.assertEqual(
|
|
response.data["last_visited_module_id"], # pylint: disable=no-member
|
|
unicode(self.other_sub_section.location)
|
|
)
|
|
|
|
def test_invalid_date(self):
|
|
self.login_and_enroll()
|
|
response = self.api_response(data={"modification_date": "abc"}, expected_response_code=400)
|
|
self.assertEqual(
|
|
response.data, # pylint: disable=no-member
|
|
errors.ERROR_INVALID_MODIFICATION_DATE
|
|
)
|
|
|
|
|
|
class TestCourseEnrollmentSerializer(MobileAPITestCase):
|
|
"""
|
|
Test the course enrollment serializer
|
|
"""
|
|
def test_success(self):
|
|
self.login_and_enroll()
|
|
|
|
serialized = CourseEnrollmentSerializer(CourseEnrollment.enrollments_for_user(self.user)[0]).data # pylint: disable=no-member
|
|
self.assertEqual(serialized['course']['video_outline'], None)
|
|
self.assertEqual(serialized['course']['name'], self.course.display_name)
|
|
self.assertEqual(serialized['course']['number'], self.course.id.course)
|
|
self.assertEqual(serialized['course']['org'], self.course.id.org)
|
|
|
|
def test_with_display_overrides(self):
|
|
self.login_and_enroll()
|
|
|
|
self.course.display_coursenumber = "overridden_number"
|
|
self.course.display_organization = "overridden_org"
|
|
self.store.update_item(self.course, self.user.id)
|
|
|
|
serialized = CourseEnrollmentSerializer(CourseEnrollment.enrollments_for_user(self.user)[0]).data # pylint: disable=no-member
|
|
self.assertEqual(serialized['course']['number'], self.course.display_coursenumber)
|
|
self.assertEqual(serialized['course']['org'], self.course.display_organization)
|