Files
edx-platform/lms/djangoapps/notes/tests.py
2015-06-02 15:05:16 -04:00

449 lines
16 KiB
Python

"""
Unit tests for the notes app.
"""
from mock import patch, Mock
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from django.test import TestCase, RequestFactory
from django.test.client import Client
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
import json
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from courseware.tabs import get_course_tab_list, CourseTab
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from notes import utils, api, models
class UtilsTest(ModuleStoreTestCase):
""" Tests for the notes utils. """
def setUp(self):
'''
Setup a dummy course-like object with a tabs field that can be
accessed via attribute lookup.
'''
super(UtilsTest, self).setUp()
self.course = CourseFactory.create()
def test_notes_not_enabled(self):
'''
Tests that notes are disabled when the course tab configuration does NOT
contain a tab with type "notes."
'''
self.assertFalse(utils.notes_enabled_for_course(self.course))
def test_notes_enabled(self):
'''
Tests that notes are enabled when the course tab configuration contains
a tab with type "notes."
'''
with self.settings(FEATURES={'ENABLE_STUDENT_NOTES': True}):
self.course.advanced_modules = ["notes"]
self.assertTrue(utils.notes_enabled_for_course(self.course))
class CourseTabTest(ModuleStoreTestCase):
"""
Test that the course tab shows up the way we expect.
"""
def setUp(self):
'''
Setup a dummy course-like object with a tabs field that can be
accessed via attribute lookup.
'''
super(CourseTabTest, self).setUp()
self.course = CourseFactory.create()
self.user = UserFactory()
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id)
def enable_notes(self):
"""Enable notes and add the tab to the course."""
self.course.tabs.append(CourseTab.load("notes"))
self.course.advanced_modules = ["notes"]
def has_notes_tab(self, course, user):
""" Returns true if the current course and user have a notes tab, false otherwise. """
request = RequestFactory().request()
request.user = user
all_tabs = get_course_tab_list(request, course)
return any([tab.name == u'My Notes' for tab in all_tabs])
def test_course_tab_not_visible(self):
# module not enabled in the course
self.assertFalse(self.has_notes_tab(self.course, self.user))
with self.settings(FEATURES={'ENABLE_STUDENT_NOTES': False}):
# setting not enabled and the module is not enabled
self.assertFalse(self.has_notes_tab(self.course, self.user))
# module is enabled and the setting is not enabled
self.course.advanced_modules = ["notes"]
self.assertFalse(self.has_notes_tab(self.course, self.user))
def test_course_tab_visible(self):
self.enable_notes()
self.assertTrue(self.has_notes_tab(self.course, self.user))
self.course.advanced_modules = []
self.assertFalse(self.has_notes_tab(self.course, self.user))
class ApiTest(TestCase):
def setUp(self):
super(ApiTest, self).setUp()
self.client = Client()
# Mocks
patcher = patch.object(api, 'api_enabled', Mock(return_value=True))
patcher.start()
self.addCleanup(patcher.stop)
# Create two accounts
self.password = 'abc'
self.student = User.objects.create_user('student', 'student@test.com', self.password)
self.student2 = User.objects.create_user('student2', 'student2@test.com', self.password)
self.instructor = User.objects.create_user('instructor', 'instructor@test.com', self.password)
self.course_key = SlashSeparatedCourseKey('HarvardX', 'CB22x', 'The_Ancient_Greek_Hero')
self.note = {
'user': self.student,
'course_id': self.course_key,
'uri': '/',
'text': 'foo',
'quote': 'bar',
'range_start': 0,
'range_start_offset': 0,
'range_end': 100,
'range_end_offset': 0,
'tags': 'a,b,c'
}
# Make sure no note with this ID ever exists for testing purposes
self.NOTE_ID_DOES_NOT_EXIST = 99999
def login(self, as_student=None):
username = None
password = self.password
if as_student is None:
username = self.student.username
else:
username = as_student.username
self.client.login(username=username, password=password)
def url(self, name, args={}):
args.update({'course_id': self.course_key.to_deprecated_string()})
return reverse(name, kwargs=args)
def create_notes(self, num_notes, create=True):
notes = []
for n in range(num_notes):
note = models.Note(**self.note)
if create:
note.save()
notes.append(note)
return notes
def test_root(self):
self.login()
resp = self.client.get(self.url('notes_api_root'))
self.assertEqual(resp.status_code, 200)
self.assertNotEqual(resp.content, '')
content = json.loads(resp.content)
self.assertEqual(set(('name', 'version')), set(content.keys()))
self.assertIsInstance(content['version'], int)
self.assertEqual(content['name'], 'Notes API')
def test_index_empty(self):
self.login()
resp = self.client.get(self.url('notes_api_notes'))
self.assertEqual(resp.status_code, 200)
self.assertNotEqual(resp.content, '')
content = json.loads(resp.content)
self.assertEqual(len(content), 0)
def test_index_with_notes(self):
num_notes = 3
self.login()
self.create_notes(num_notes)
resp = self.client.get(self.url('notes_api_notes'))
self.assertEqual(resp.status_code, 200)
self.assertNotEqual(resp.content, '')
content = json.loads(resp.content)
self.assertIsInstance(content, list)
self.assertEqual(len(content), num_notes)
def test_index_max_notes(self):
self.login()
MAX_LIMIT = api.API_SETTINGS.get('MAX_NOTE_LIMIT')
num_notes = MAX_LIMIT + 1
self.create_notes(num_notes)
resp = self.client.get(self.url('notes_api_notes'))
self.assertEqual(resp.status_code, 200)
self.assertNotEqual(resp.content, '')
content = json.loads(resp.content)
self.assertIsInstance(content, list)
self.assertEqual(len(content), MAX_LIMIT)
def test_create_note(self):
self.login()
notes = self.create_notes(1)
self.assertEqual(len(notes), 1)
note_dict = notes[0].as_dict()
excluded_fields = ['id', 'user_id', 'created', 'updated']
note = dict([(k, v) for k, v in note_dict.items() if k not in excluded_fields])
resp = self.client.post(self.url('notes_api_notes'),
json.dumps(note),
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(resp.status_code, 303)
self.assertEqual(len(resp.content), 0)
def test_create_empty_notes(self):
self.login()
for empty_test in [None, [], '']:
resp = self.client.post(self.url('notes_api_notes'),
json.dumps(empty_test),
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(resp.status_code, 400)
def test_create_note_missing_ranges(self):
self.login()
notes = self.create_notes(1)
self.assertEqual(len(notes), 1)
note_dict = notes[0].as_dict()
excluded_fields = ['id', 'user_id', 'created', 'updated'] + ['ranges']
note = dict([(k, v) for k, v in note_dict.items() if k not in excluded_fields])
resp = self.client.post(self.url('notes_api_notes'),
json.dumps(note),
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(resp.status_code, 400)
def test_read_note(self):
self.login()
notes = self.create_notes(3)
self.assertEqual(len(notes), 3)
for note in notes:
resp = self.client.get(self.url('notes_api_note', {'note_id': note.pk}))
self.assertEqual(resp.status_code, 200)
self.assertNotEqual(resp.content, '')
content = json.loads(resp.content)
self.assertEqual(content['id'], note.pk)
self.assertEqual(content['user_id'], note.user_id)
def test_note_doesnt_exist_to_read(self):
self.login()
resp = self.client.get(self.url('notes_api_note', {
'note_id': self.NOTE_ID_DOES_NOT_EXIST
}))
self.assertEqual(resp.status_code, 404)
self.assertEqual(resp.content, '')
def test_student_doesnt_have_permission_to_read_note(self):
notes = self.create_notes(1)
self.assertEqual(len(notes), 1)
note = notes[0]
# set the student id to a different student (not the one that created the notes)
self.login(as_student=self.student2)
resp = self.client.get(self.url('notes_api_note', {'note_id': note.pk}))
self.assertEqual(resp.status_code, 403)
self.assertEqual(resp.content, '')
def test_delete_note(self):
self.login()
notes = self.create_notes(1)
self.assertEqual(len(notes), 1)
note = notes[0]
resp = self.client.delete(self.url('notes_api_note', {
'note_id': note.pk
}))
self.assertEqual(resp.status_code, 204)
self.assertEqual(resp.content, '')
with self.assertRaises(models.Note.DoesNotExist):
models.Note.objects.get(pk=note.pk)
def test_note_does_not_exist_to_delete(self):
self.login()
resp = self.client.delete(self.url('notes_api_note', {
'note_id': self.NOTE_ID_DOES_NOT_EXIST
}))
self.assertEqual(resp.status_code, 404)
self.assertEqual(resp.content, '')
def test_student_doesnt_have_permission_to_delete_note(self):
notes = self.create_notes(1)
self.assertEqual(len(notes), 1)
note = notes[0]
self.login(as_student=self.student2)
resp = self.client.delete(self.url('notes_api_note', {
'note_id': note.pk
}))
self.assertEqual(resp.status_code, 403)
self.assertEqual(resp.content, '')
try:
models.Note.objects.get(pk=note.pk)
except models.Note.DoesNotExist:
self.fail('note should exist and not be deleted because the student does not have permission to do so')
def test_update_note(self):
notes = self.create_notes(1)
note = notes[0]
updated_dict = note.as_dict()
updated_dict.update({
'text': 'itchy and scratchy',
'tags': ['simpsons', 'cartoons', 'animation']
})
self.login()
resp = self.client.put(self.url('notes_api_note', {'note_id': note.pk}),
json.dumps(updated_dict),
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(resp.status_code, 303)
self.assertEqual(resp.content, '')
actual = models.Note.objects.get(pk=note.pk)
actual_dict = actual.as_dict()
for field in ['text', 'tags']:
self.assertEqual(actual_dict[field], updated_dict[field])
def test_search_note_params(self):
self.login()
total = 3
notes = self.create_notes(total)
invalid_uri = ''.join([note.uri for note in notes])
tests = [{'limit': 0, 'offset': 0, 'expected_rows': total},
{'limit': 0, 'offset': 2, 'expected_rows': total - 2},
{'limit': 0, 'offset': total, 'expected_rows': 0},
{'limit': 1, 'offset': 0, 'expected_rows': 1},
{'limit': 2, 'offset': 0, 'expected_rows': 2},
{'limit': total, 'offset': 2, 'expected_rows': 1},
{'limit': total, 'offset': total, 'expected_rows': 0},
{'limit': total + 1, 'offset': total + 1, 'expected_rows': 0},
{'limit': total + 1, 'offset': 0, 'expected_rows': total},
{'limit': 0, 'offset': 0, 'uri': invalid_uri, 'expected_rows': 0, 'expected_total': 0}]
for test in tests:
params = dict([(k, str(test[k]))
for k in ('limit', 'offset', 'uri')
if k in test])
resp = self.client.get(self.url('notes_api_search'),
params,
content_type='application/json',
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(resp.status_code, 200)
self.assertNotEqual(resp.content, '')
content = json.loads(resp.content)
for expected_key in ('total', 'rows'):
self.assertTrue(expected_key in content)
if 'expected_total' in test:
self.assertEqual(content['total'], test['expected_total'])
else:
self.assertEqual(content['total'], total)
self.assertEqual(len(content['rows']), test['expected_rows'])
for row in content['rows']:
self.assertTrue('id' in row)
class NoteTest(TestCase):
def setUp(self):
super(NoteTest, self).setUp()
self.password = 'abc'
self.student = User.objects.create_user('student', 'student@test.com', self.password)
self.course_key = SlashSeparatedCourseKey('HarvardX', 'CB22x', 'The_Ancient_Greek_Hero')
self.note = {
'user': self.student,
'course_id': self.course_key,
'uri': '/',
'text': 'foo',
'quote': 'bar',
'range_start': 0,
'range_start_offset': 0,
'range_end': 100,
'range_end_offset': 0,
'tags': 'a,b,c'
}
def test_clean_valid_note(self):
reference_note = models.Note(**self.note)
body = reference_note.as_dict()
note = models.Note(course_id=self.course_key, user=self.student)
try:
note.clean(json.dumps(body))
self.assertEqual(note.uri, body['uri'])
self.assertEqual(note.text, body['text'])
self.assertEqual(note.quote, body['quote'])
self.assertEqual(note.range_start, body['ranges'][0]['start'])
self.assertEqual(note.range_start_offset, body['ranges'][0]['startOffset'])
self.assertEqual(note.range_end, body['ranges'][0]['end'])
self.assertEqual(note.range_end_offset, body['ranges'][0]['endOffset'])
self.assertEqual(note.tags, ','.join(body['tags']))
except ValidationError:
self.fail('a valid note should not raise an exception')
def test_clean_invalid_note(self):
note = models.Note(course_id=self.course_key, user=self.student)
for empty_type in (None, '', 0, []):
with self.assertRaises(ValidationError):
note.clean(None)
with self.assertRaises(ValidationError):
note.clean(json.dumps({
'text': 'foo',
'quote': 'bar',
'ranges': [{} for i in range(10)] # too many ranges
}))
def test_as_dict(self):
note = models.Note(course_id=self.course_key, user=self.student)
d = note.as_dict()
self.assertNotIsInstance(d, basestring)
self.assertEqual(d['user_id'], self.student.id)
self.assertTrue('course_id' not in d)