tests.py 13.8 KB
Newer Older
1
"""
Arthur Barrett committed
2
Unit tests for the notes app.
3 4 5
"""

from django.test import TestCase
Arthur Barrett committed
6 7 8
from django.test.client import Client
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
Arthur Barrett committed
9
from django.core.exceptions import ValidationError
Arthur Barrett committed
10

Arthur Barrett committed
11
import collections
Arthur Barrett committed
12 13 14 15
import json

from . import utils, api, models

Arthur Barrett committed
16

Arthur Barrett committed
17
class UtilsTest(TestCase):
Arthur Barrett committed
18 19
    def setUp(self):
        '''
Arthur Barrett committed
20
        Setup a dummy course-like object with a tabs field that can be
Arthur Barrett committed
21
        accessed via attribute lookup.
Arthur Barrett committed
22
        '''
Arthur Barrett committed
23
        self.course = collections.namedtuple('DummyCourse', ['tabs'])
Arthur Barrett committed
24 25 26 27 28 29 30 31 32 33 34 35 36 37
        self.course.tabs = []

    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."
        '''
Arthur Barrett committed
38 39 40
        self.course.tabs = [{'type': 'foo'},
                            {'name': 'My Notes', 'type': 'notes'},
                            {'type': 'bar'}]
41

Arthur Barrett committed
42 43
        self.assertTrue(utils.notes_enabled_for_course(self.course))

Arthur Barrett committed
44

Arthur Barrett committed
45
class ApiTest(TestCase):
46

Arthur Barrett committed
47 48 49
    def setUp(self):
        self.client = Client()

50
        # Mocks
Arthur Barrett committed
51
        api.api_enabled = self.mock_api_enabled(True)
52

Arthur Barrett committed
53 54 55
        # Create two accounts
        self.password = 'abc'
        self.student = User.objects.create_user('student', 'student@test.com', self.password)
Arthur Barrett committed
56
        self.student2 = User.objects.create_user('student2', 'student2@test.com', self.password)
Arthur Barrett committed
57 58 59
        self.instructor = User.objects.create_user('instructor', 'instructor@test.com', self.password)
        self.course_id = 'HarvardX/CB22x/The_Ancient_Greek_Hero'
        self.note = {
Arthur Barrett committed
60 61 62 63 64 65 66 67 68 69
            'user': self.student,
            'course_id': self.course_id,
            'uri': '/',
            'text': 'foo',
            'quote': 'bar',
            'range_start': 0,
            'range_start_offset': 0,
            'range_end': 100,
            'range_end_offset': 0,
            'tags': 'a,b,c'
Arthur Barrett committed
70 71
        }

72 73 74
        # Make sure no note with this ID ever exists for testing purposes
        self.NOTE_ID_DOES_NOT_EXIST = 99999

Arthur Barrett committed
75 76
    def mock_api_enabled(self, is_enabled):
        return (lambda request, course_id: is_enabled)
Arthur Barrett committed
77

Arthur Barrett committed
78 79 80
    def login(self, as_student=None):
        username = None
        password = self.password
Arthur Barrett committed
81

Arthur Barrett committed
82 83 84 85 86 87 88 89
        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={}):
Arthur Barrett committed
90
        args.update({'course_id': self.course_id})
Arthur Barrett committed
91 92 93 94 95 96 97 98 99
        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)
Arthur Barrett committed
100 101 102 103 104 105
        return notes

    def test_root(self):
        self.login()

        resp = self.client.get(self.url('notes_api_root'))
Arthur Barrett committed
106
        self.assertEqual(resp.status_code, 200)
Arthur Barrett committed
107 108 109 110
        self.assertNotEqual(resp.content, '')

        content = json.loads(resp.content)

Arthur Barrett committed
111
        self.assertEqual(set(('name', 'version')), set(content.keys()))
Arthur Barrett committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125
        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):
Arthur Barrett committed
126
        num_notes = 3
Arthur Barrett committed
127 128 129 130 131 132 133 134
        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)
Arthur Barrett committed
135
        self.assertIsInstance(content, list)
Arthur Barrett committed
136 137 138 139 140
        self.assertEqual(len(content), num_notes)

    def test_index_max_notes(self):
        self.login()

Arthur Barrett committed
141
        MAX_LIMIT = api.API_SETTINGS.get('MAX_NOTE_LIMIT')
Arthur Barrett committed
142 143
        num_notes = MAX_LIMIT + 1
        self.create_notes(num_notes)
144

Arthur Barrett committed
145 146 147
        resp = self.client.get(self.url('notes_api_notes'))
        self.assertEqual(resp.status_code, 200)
        self.assertNotEqual(resp.content, '')
148

Arthur Barrett committed
149
        content = json.loads(resp.content)
Arthur Barrett committed
150
        self.assertIsInstance(content, list)
Arthur Barrett committed
151
        self.assertEqual(len(content), MAX_LIMIT)
Arthur Barrett committed
152 153 154 155 156 157 158 159 160

    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']
Arthur Barrett committed
161
        note = dict([(k, v) for k, v in note_dict.items() if k not in excluded_fields])
Arthur Barrett committed
162

Arthur Barrett committed
163 164 165 166
        resp = self.client.post(self.url('notes_api_notes'),
                                json.dumps(note),
                                content_type='application/json',
                                HTTP_X_REQUESTED_WITH='XMLHttpRequest')
Arthur Barrett committed
167 168 169 170 171 172 173 174

        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, [], '']:
Arthur Barrett committed
175 176 177 178
            resp = self.client.post(self.url('notes_api_notes'),
                                    json.dumps(empty_test),
                                    content_type='application/json',
                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest')
179
            self.assertEqual(resp.status_code, 400)
Arthur Barrett committed
180 181 182 183 184 185 186 187 188

    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']
Arthur Barrett committed
189
        note = dict([(k, v) for k, v in note_dict.items() if k not in excluded_fields])
Arthur Barrett committed
190

Arthur Barrett committed
191 192 193 194
        resp = self.client.post(self.url('notes_api_notes'),
                                json.dumps(note),
                                content_type='application/json',
                                HTTP_X_REQUESTED_WITH='XMLHttpRequest')
195
        self.assertEqual(resp.status_code, 400)
Arthur Barrett committed
196

Arthur Barrett committed
197
    def test_read_note(self):
Arthur Barrett committed
198 199 200 201 202 203
        self.login()

        notes = self.create_notes(3)
        self.assertEqual(len(notes), 3)

        for note in notes:
204
            resp = self.client.get(self.url('notes_api_note', {'note_id': note.pk}))
Arthur Barrett committed
205 206 207 208
            self.assertEqual(resp.status_code, 200)
            self.assertNotEqual(resp.content, '')

            content = json.loads(resp.content)
209
            self.assertEqual(content['id'], note.pk)
Arthur Barrett committed
210 211 212 213 214
            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', {
215
            'note_id': self.NOTE_ID_DOES_NOT_EXIST
Arthur Barrett committed
216 217 218 219 220 221 222 223 224 225 226
        }))
        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)
227
        resp = self.client.get(self.url('notes_api_note', {'note_id': note.pk}))
Arthur Barrett committed
228 229 230
        self.assertEqual(resp.status_code, 403)
        self.assertEqual(resp.content, '')

231 232 233 234 235 236 237 238
    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', {
239
            'note_id': note.pk
240 241 242 243 244
        }))
        self.assertEqual(resp.status_code, 204)
        self.assertEqual(resp.content, '')

        with self.assertRaises(models.Note.DoesNotExist):
245
            models.Note.objects.get(pk=note.pk)
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262

    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', {
263
            'note_id': note.pk
264 265 266 267 268
        }))
        self.assertEqual(resp.status_code, 403)
        self.assertEqual(resp.content, '')

        try:
269
            models.Note.objects.get(pk=note.pk)
270 271 272
        except models.Note.DoesNotExist:
            self.fail('note should exist and not be deleted because the student does not have permission to do so')

Arthur Barrett committed
273
    def test_update_note(self):
274 275 276 277 278 279 280 281 282 283
        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()
284
        resp = self.client.put(self.url('notes_api_note', {'note_id': note.pk}),
285 286 287 288 289 290
                               json.dumps(updated_dict),
                               content_type='application/json',
                               HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEqual(resp.status_code, 303)
        self.assertEqual(resp.content, '')

291
        actual = models.Note.objects.get(pk=note.pk)
292 293 294
        actual_dict = actual.as_dict()
        for field in ['text', 'tags']:
            self.assertEqual(actual_dict[field], updated_dict[field])
Arthur Barrett committed
295

Arthur Barrett committed
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
    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)
Arthur Barrett committed
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360


class NoteTest(TestCase):
    def setUp(self):
        self.password = 'abc'
        self.student = User.objects.create_user('student', 'student@test.com', self.password)
        self.course_id = 'HarvardX/CB22x/The_Ancient_Greek_Hero'
        self.note = {
            'user': self.student,
            'course_id': self.course_id,
            '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):
361
        reference_note = models.Note(**self.note)
Arthur Barrett committed
362 363
        body = reference_note.as_dict()

364
        note = models.Note(course_id=self.course_id, user=self.student)
Arthur Barrett committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378
        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):
379
        note = models.Note(course_id=self.course_id, user=self.student)
Arthur Barrett committed
380 381 382 383 384 385 386 387 388 389 390 391
        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):
392
        note = models.Note(course_id=self.course_id, user=self.student)
Arthur Barrett committed
393 394 395 396
        d = note.as_dict()
        self.assertNotIsInstance(d, basestring)
        self.assertEqual(d['user_id'], self.student.id)
        self.assertTrue('course_id' not in d)