317 lines
12 KiB
Python
317 lines
12 KiB
Python
"""
|
|
Tests for users API
|
|
"""
|
|
|
|
import datetime
|
|
import ddt
|
|
import json
|
|
|
|
from rest_framework.test import APITestCase
|
|
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
|
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
|
from xmodule.modulestore.django import modulestore
|
|
from courseware.tests.factories import UserFactory
|
|
from django.core.urlresolvers import reverse
|
|
from django.utils import timezone
|
|
from mobile_api.users.serializers import CourseEnrollmentSerializer
|
|
from mobile_api import errors
|
|
from student.models import CourseEnrollment
|
|
from mobile_api.tests import ROLE_CASES
|
|
|
|
|
|
@ddt.ddt
|
|
class TestUserApi(ModuleStoreTestCase, APITestCase):
|
|
"""
|
|
Test the user info API
|
|
"""
|
|
def setUp(self):
|
|
super(TestUserApi, self).setUp()
|
|
self.course = CourseFactory.create(mobile_available=True)
|
|
self.user = UserFactory.create()
|
|
self.password = 'test'
|
|
self.username = self.user.username
|
|
|
|
def tearDown(self):
|
|
super(TestUserApi, self).tearDown()
|
|
self.client.logout()
|
|
|
|
def _enrollment_url(self):
|
|
"""
|
|
api url that gets the current user's course enrollments
|
|
"""
|
|
return reverse('courseenrollment-detail', kwargs={'username': self.user.username})
|
|
|
|
def _enroll(self, course):
|
|
"""
|
|
enroll test user in test course
|
|
"""
|
|
resp = self.client.post(reverse('change_enrollment'), {
|
|
'enrollment_action': 'enroll',
|
|
'course_id': course.id.to_deprecated_string(),
|
|
'check_access': True,
|
|
})
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
def _verify_single_course_enrollment(self, course, should_succeed):
|
|
"""
|
|
check that enrolling in course adds us to it
|
|
"""
|
|
|
|
url = self._enrollment_url()
|
|
self.client.login(username=self.username, password=self.password)
|
|
self._enroll(course)
|
|
response = self.client.get(url)
|
|
|
|
courses = response.data # pylint: disable=maybe-no-member
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
if should_succeed:
|
|
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(course.id))
|
|
self.assertEqual(courses[0]['mode'], 'honor')
|
|
else:
|
|
self.assertEqual(len(courses), 0)
|
|
|
|
@ddt.data(*ROLE_CASES)
|
|
@ddt.unpack
|
|
def test_non_mobile_enrollments(self, role, should_succeed):
|
|
non_mobile_course = CourseFactory.create(mobile_available=False)
|
|
|
|
if role:
|
|
role(non_mobile_course.id).add_users(self.user)
|
|
|
|
self._verify_single_course_enrollment(non_mobile_course, should_succeed)
|
|
|
|
def test_mobile_enrollments(self):
|
|
self._verify_single_course_enrollment(self.course, True)
|
|
|
|
def test_user_overview(self):
|
|
self.client.login(username=self.username, password=self.password)
|
|
url = reverse('user-detail', kwargs={'username': self.user.username})
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.data # pylint: disable=maybe-no-member
|
|
self.assertEqual(data['username'], self.user.username)
|
|
self.assertEqual(data['email'], self.user.email)
|
|
|
|
def test_overview_anon(self):
|
|
# anonymous disallowed
|
|
url = reverse('user-detail', kwargs={'username': self.user.username})
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 401)
|
|
# can't get info on someone else
|
|
other = UserFactory.create()
|
|
self.client.login(username=self.username, password=self.password)
|
|
response = self.client.get(reverse('user-detail', kwargs={'username': other.username}))
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_redirect_userinfo(self):
|
|
url = '/api/mobile/v0.5/my_user_info'
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 401)
|
|
|
|
self.client.login(username=self.username, password=self.password)
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertTrue(self.username in response['location'])
|
|
|
|
def test_course_serializer(self):
|
|
self.client.login(username=self.username, password=self.password)
|
|
self._enroll(self.course)
|
|
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_course_serializer_with_display_overrides(self):
|
|
self.course.display_coursenumber = "overridden_number"
|
|
self.course.display_organization = "overridden_org"
|
|
modulestore().update_item(self.course, self.user.id)
|
|
|
|
self.client.login(username=self.username, password=self.password)
|
|
self._enroll(self.course)
|
|
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)
|
|
|
|
# Tests for user-course-status
|
|
|
|
def _course_status_url(self):
|
|
"""
|
|
Convenience to fetch the url for our user and course
|
|
"""
|
|
return reverse('user-course-status', kwargs={'username': self.username, 'course_id': unicode(self.course.id)})
|
|
|
|
def _setup_course_skeleton(self):
|
|
"""
|
|
Creates a basic course structure for our course
|
|
"""
|
|
section = ItemFactory.create(
|
|
parent_location=self.course.location,
|
|
)
|
|
sub_section = ItemFactory.create(
|
|
parent_location=section.location,
|
|
)
|
|
unit = ItemFactory.create(
|
|
parent_location=sub_section.location,
|
|
)
|
|
other_unit = ItemFactory.create(
|
|
parent_location=sub_section.location,
|
|
)
|
|
|
|
return section, sub_section, unit, other_unit
|
|
|
|
def test_course_status_course_not_found(self):
|
|
self.client.login(username=self.username, password=self.password)
|
|
url = reverse('user-course-status', kwargs={'username': self.username, 'course_id': 'a/b/c'})
|
|
response = self.client.get(url)
|
|
json_data = json.loads(response.content)
|
|
self.assertEqual(response.status_code, 404)
|
|
self.assertEqual(json_data, errors.ERROR_INVALID_COURSE_ID)
|
|
|
|
def test_course_status_wrong_user(self):
|
|
url = reverse('user-course-status', kwargs={'username': 'other_user', 'course_id': unicode(self.course.id)})
|
|
self.client.login(username=self.username, password=self.password)
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_course_status_no_auth(self):
|
|
url = self._course_status_url()
|
|
response = self.client.get(url)
|
|
self.assertEqual(response.status_code, 401)
|
|
|
|
def test_default_value(self):
|
|
(section, sub_section, unit, __) = self._setup_course_skeleton()
|
|
self.client.login(username=self.username, password=self.password)
|
|
|
|
url = self._course_status_url()
|
|
result = self.client.get(url)
|
|
json_data = json.loads(result.content)
|
|
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assertEqual(json_data["last_visited_module_id"], unicode(unit.location))
|
|
self.assertEqual(
|
|
json_data["last_visited_module_path"],
|
|
[unicode(module.location) for module in [unit, sub_section, section, self.course]]
|
|
)
|
|
|
|
def test_course_update_no_args(self):
|
|
self.client.login(username=self.username, password=self.password)
|
|
|
|
url = self._course_status_url()
|
|
result = self.client.patch(url) # pylint: disable=no-member
|
|
self.assertEqual(result.status_code, 200)
|
|
|
|
def test_course_update(self):
|
|
(__, __, __, other_unit) = self._setup_course_skeleton()
|
|
self.client.login(username=self.username, password=self.password)
|
|
|
|
url = self._course_status_url()
|
|
result = self.client.patch( # pylint: disable=no-member
|
|
url,
|
|
{"last_visited_module_id": unicode(other_unit.location)}
|
|
)
|
|
self.assertEqual(result.status_code, 200)
|
|
result = self.client.get(url)
|
|
json_data = json.loads(result.content)
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assertEqual(json_data["last_visited_module_id"], unicode(other_unit.location))
|
|
|
|
def test_course_update_bad_module(self):
|
|
self.client.login(username=self.username, password=self.password)
|
|
|
|
url = self._course_status_url()
|
|
result = self.client.patch( # pylint: disable=no-member
|
|
url,
|
|
{"last_visited_module_id": "abc"},
|
|
)
|
|
json_data = json.loads(result.content)
|
|
self.assertEqual(result.status_code, 400)
|
|
self.assertEqual(json_data, errors.ERROR_INVALID_MODULE_ID)
|
|
|
|
def test_course_update_no_timezone(self):
|
|
(__, __, __, other_unit) = self._setup_course_skeleton()
|
|
self.client.login(username=self.username, password=self.password)
|
|
url = self._course_status_url()
|
|
past_date = datetime.datetime.now()
|
|
result = self.client.patch( # pylint: disable=no-member
|
|
url,
|
|
{
|
|
"last_visited_module_id": unicode(other_unit.location),
|
|
"modification_date": past_date.isoformat() # pylint: disable=maybe-no-member
|
|
},
|
|
)
|
|
|
|
json_data = json.loads(result.content)
|
|
self.assertEqual(result.status_code, 400)
|
|
self.assertEqual(json_data, errors.ERROR_INVALID_MODIFICATION_DATE)
|
|
|
|
def _test_course_update_date_sync(self, date, initial_unit, update_unit, expected_unit):
|
|
"""
|
|
Helper for test cases that use a modification to decide whether
|
|
to update the course status
|
|
"""
|
|
self.client.login(username=self.username, password=self.password)
|
|
url = self._course_status_url()
|
|
# save something so we have an initial date
|
|
self.client.patch( # pylint: disable=no-member
|
|
url,
|
|
{"last_visited_module_id": unicode(initial_unit.location)}
|
|
)
|
|
|
|
# now actually update it
|
|
result = self.client.patch( # pylint: disable=no-member
|
|
url,
|
|
{
|
|
"last_visited_module_id": unicode(update_unit.location),
|
|
"modification_date": date.isoformat()
|
|
},
|
|
)
|
|
|
|
json_data = json.loads(result.content)
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assertEqual(json_data["last_visited_module_id"], unicode(expected_unit.location))
|
|
|
|
def test_course_update_old_date(self):
|
|
(__, __, unit, other_unit) = self._setup_course_skeleton()
|
|
date = timezone.now() + datetime.timedelta(days=-100)
|
|
self._test_course_update_date_sync(date, unit, other_unit, unit)
|
|
|
|
def test_course_update_new_date(self):
|
|
(__, __, unit, other_unit) = self._setup_course_skeleton()
|
|
|
|
date = timezone.now() + datetime.timedelta(days=100)
|
|
self._test_course_update_date_sync(date, unit, other_unit, other_unit)
|
|
|
|
def test_course_update_no_initial_date(self):
|
|
(__, __, _, other_unit) = self._setup_course_skeleton()
|
|
self.client.login(username=self.username, password=self.password)
|
|
url = self._course_status_url()
|
|
result = self.client.patch( # pylint: disable=no-member
|
|
url,
|
|
{
|
|
"last_visited_module_id": unicode(other_unit.location),
|
|
"modification_date": timezone.now().isoformat()
|
|
}
|
|
)
|
|
json_data = json.loads(result.content)
|
|
self.assertEqual(result.status_code, 200)
|
|
self.assertEqual(json_data["last_visited_module_id"], unicode(other_unit.location))
|
|
|
|
def test_course_update_invalid_date(self):
|
|
self.client.login(username=self.username, password=self.password)
|
|
|
|
url = self._course_status_url()
|
|
result = self.client.patch( # pylint: disable=no-member
|
|
url,
|
|
{"modification_date": "abc"}
|
|
)
|
|
json_data = json.loads(result.content)
|
|
self.assertEqual(result.status_code, 400)
|
|
self.assertEqual(json_data, errors.ERROR_INVALID_MODIFICATION_DATE)
|