test_textbooks.py 13.1 KB
Newer Older
David Baumgold committed
1 2
import json
from unittest import TestCase
3
from contentstore.tests.utils import CourseTestCase
4
from contentstore.utils import reverse_course_url
David Baumgold committed
5 6

from contentstore.views.course import (
David Baumgold committed
7
    validate_textbooks_json, validate_textbook_json, TextbookValidationError)
David Baumgold committed
8 9


David Baumgold committed
10
class TextbookIndexTestCase(CourseTestCase):
David Baumgold committed
11
    "Test cases for the textbook index page"
David Baumgold committed
12
    def setUp(self):
David Baumgold committed
13
        "Set the URL for tests"
David Baumgold committed
14
        super(TextbookIndexTestCase, self).setUp()
15
        self.url = reverse_course_url('textbooks_list_handler', self.course.id)
David Baumgold committed
16 17

    def test_view_index(self):
David Baumgold committed
18
        "Basic check that the textbook index page responds correctly"
David Baumgold committed
19
        resp = self.client.get(self.url)
20
        self.assertEqual(resp.status_code, 200)
David Baumgold committed
21 22 23 24 25 26
        # we don't have resp.context right now,
        # due to bugs in our testing harness :(
        if resp.context:
            self.assertEqual(resp.context['course'], self.course)

    def test_view_index_xhr(self):
David Baumgold committed
27
        "Check that we get a JSON response when requested via AJAX"
David Baumgold committed
28 29 30 31 32
        resp = self.client.get(
            self.url,
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
33
        self.assertEqual(resp.status_code, 200)
David Baumgold committed
34 35 36
        obj = json.loads(resp.content)
        self.assertEqual(self.course.pdf_textbooks, obj)

37
    def test_view_index_xhr_content(self):
David Baumgold committed
38
        "Check that the response maps to the content of the modulestore"
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
        content = [
            {
                "tab_title": "my textbook",
                "url": "/abc.pdf",
                "id": "992"
            }, {
                "tab_title": "pineapple",
                "id": "0pineapple",
                "chapters": [
                    {
                        "title": "The Fruit",
                        "url": "/a/b/fruit.pdf",
                    }, {
                        "title": "The Legend",
                        "url": "/b/c/legend.pdf",
                    }
                ]
            }
        ]
        self.course.pdf_textbooks = content
59
        self.save_course()
60 61 62 63 64 65

        resp = self.client.get(
            self.url,
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
66
        self.assertEqual(resp.status_code, 200)
67
        obj = json.loads(resp.content)
68

69 70
        self.assertEqual(content, obj)

71
    def test_view_index_xhr_put(self):
David Baumgold committed
72
        "Check that you can save information to the server"
David Baumgold committed
73 74 75 76
        textbooks = [
            {"tab_title": "Hi, mom!"},
            {"tab_title": "Textbook 2"},
        ]
77
        resp = self.client.put(
David Baumgold committed
78 79 80 81 82 83
            self.url,
            data=json.dumps(textbooks),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
84
        self.assertEqual(resp.status_code, 200)
David Baumgold committed
85

David Baumgold committed
86 87
        # should be the same, except for added ID
        no_ids = []
88 89
        self.reload_course()
        for textbook in self.course.pdf_textbooks:
David Baumgold committed
90 91 92
            del textbook["id"]
            no_ids.append(textbook)
        self.assertEqual(no_ids, textbooks)
David Baumgold committed
93

94
    def test_view_index_xhr_put_invalid(self):
David Baumgold committed
95
        "Check that you can't save invalid JSON"
96
        resp = self.client.put(
97 98 99 100 101 102
            self.url,
            data="invalid",
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
103
        self.assertEqual(resp.status_code, 400)
104 105 106
        obj = json.loads(resp.content)
        self.assertIn("error", obj)

David Baumgold committed
107

David Baumgold committed
108
class TextbookCreateTestCase(CourseTestCase):
David Baumgold committed
109 110
    "Test cases for creating a new PDF textbook"

David Baumgold committed
111
    def setUp(self):
David Baumgold committed
112
        "Set up a url and some textbook content for tests"
David Baumgold committed
113
        super(TextbookCreateTestCase, self).setUp()
114 115
        self.url = reverse_course_url('textbooks_list_handler', self.course.id)

David Baumgold committed
116 117 118 119 120 121 122 123
        self.textbook = {
            "tab_title": "Economics",
            "chapters": {
                "title": "Chapter 1",
                "url": "/a/b/c/ch1.pdf",
            }
        }

David Baumgold committed
124
    def test_happy_path(self):
David Baumgold committed
125
        "Test that you can create a textbook"
David Baumgold committed
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
        resp = self.client.post(
            self.url,
            data=json.dumps(self.textbook),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(resp.status_code, 201)
        self.assertIn("Location", resp)
        textbook = json.loads(resp.content)
        self.assertIn("id", textbook)
        del textbook["id"]
        self.assertEqual(self.textbook, textbook)

    def test_valid_id(self):
David Baumgold committed
141
        "Textbook IDs must begin with a number; try a valid one"
David Baumgold committed
142 143 144 145 146 147 148 149 150 151 152 153 154
        self.textbook["id"] = "7x5"
        resp = self.client.post(
            self.url,
            data=json.dumps(self.textbook),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(resp.status_code, 201)
        textbook = json.loads(resp.content)
        self.assertEqual(self.textbook, textbook)

    def test_invalid_id(self):
David Baumgold committed
155
        "Textbook IDs must begin with a number; try an invalid one"
David Baumgold committed
156 157 158 159 160 161 162 163
        self.textbook["id"] = "xxx"
        resp = self.client.post(
            self.url,
            data=json.dumps(self.textbook),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
164
        self.assertEqual(resp.status_code, 400)
David Baumgold committed
165 166 167
        self.assertNotIn("Location", resp)


168 169
class TextbookDetailTestCase(CourseTestCase):
    "Test cases for the `textbook_detail_handler` view"
David Baumgold committed
170

David Baumgold committed
171
    def setUp(self):
David Baumgold committed
172
        "Set some useful content and URLs for tests"
173
        super(TextbookDetailTestCase, self).setUp()
David Baumgold committed
174 175 176 177 178 179
        self.textbook1 = {
            "tab_title": "Economics",
            "id": 1,
            "chapters": {
                "title": "Chapter 1",
                "url": "/a/b/c/ch1.pdf",
David Baumgold committed
180
            }
David Baumgold committed
181
        }
182 183
        self.url1 = self.get_details_url("1")

David Baumgold committed
184 185 186 187 188 189 190 191
        self.textbook2 = {
            "tab_title": "Algebra",
            "id": 2,
            "chapters": {
                "title": "Chapter 11",
                "url": "/a/b/ch11.pdf",
            }
        }
192
        self.url2 = self.get_details_url("2")
David Baumgold committed
193
        self.course.pdf_textbooks = [self.textbook1, self.textbook2]
194 195
        # Save the data that we've just changed to the underlying
        # MongoKeyValueStore before we update the mongo datastore.
196
        self.save_course()
197 198 199 200 201 202 203 204 205 206 207
        self.url_nonexist = self.get_details_url("1=20")

    def get_details_url(self, textbook_id):
        """
        Returns the URL for textbook detail handler.
        """
        return reverse_course_url(
            'textbooks_detail_handler',
            self.course.id,
            kwargs={'textbook_id': textbook_id}
        )
David Baumgold committed
208 209

    def test_get_1(self):
David Baumgold committed
210
        "Get the first textbook"
David Baumgold committed
211
        resp = self.client.get(self.url1)
212
        self.assertEqual(resp.status_code, 200)
David Baumgold committed
213 214 215 216
        compare = json.loads(resp.content)
        self.assertEqual(compare, self.textbook1)

    def test_get_2(self):
David Baumgold committed
217
        "Get the second textbook"
David Baumgold committed
218
        resp = self.client.get(self.url2)
219
        self.assertEqual(resp.status_code, 200)
David Baumgold committed
220 221 222 223
        compare = json.loads(resp.content)
        self.assertEqual(compare, self.textbook2)

    def test_get_nonexistant(self):
David Baumgold committed
224
        "Get a nonexistent textbook"
David Baumgold committed
225 226 227 228
        resp = self.client.get(self.url_nonexist)
        self.assertEqual(resp.status_code, 404)

    def test_delete(self):
David Baumgold committed
229
        "Delete a textbook by ID"
David Baumgold committed
230
        resp = self.client.delete(self.url1)
231
        self.assertEqual(resp.status_code, 204)
232 233
        self.reload_course()
        self.assertEqual(self.course.pdf_textbooks, [self.textbook2])
David Baumgold committed
234 235

    def test_delete_nonexistant(self):
David Baumgold committed
236
        "Delete a textbook by ID, when the ID doesn't match an existing textbook"
David Baumgold committed
237 238
        resp = self.client.delete(self.url_nonexist)
        self.assertEqual(resp.status_code, 404)
239 240
        self.reload_course()
        self.assertEqual(self.course.pdf_textbooks, [self.textbook1, self.textbook2])
David Baumgold committed
241 242

    def test_create_new_by_id(self):
David Baumgold committed
243
        "Create a textbook by ID"
David Baumgold committed
244 245 246 247 248
        textbook = {
            "tab_title": "a new textbook",
            "url": "supercool.pdf",
            "id": "1supercool",
        }
249
        url = self.get_details_url("1supercool")
David Baumgold committed
250 251 252 253 254 255 256
        resp = self.client.post(
            url,
            data=json.dumps(textbook),
            content_type="application/json",
        )
        self.assertEqual(resp.status_code, 201)
        resp2 = self.client.get(url)
257
        self.assertEqual(resp2.status_code, 200)
David Baumgold committed
258 259
        compare = json.loads(resp2.content)
        self.assertEqual(compare, textbook)
260
        self.reload_course()
David Baumgold committed
261
        self.assertEqual(
262
            self.course.pdf_textbooks,
David Baumgold committed
263 264 265 266
            [self.textbook1, self.textbook2, textbook]
        )

    def test_replace_by_id(self):
David Baumgold committed
267
        "Create a textbook by ID, overwriting an existing textbook ID"
David Baumgold committed
268 269 270 271 272 273 274 275 276 277 278 279
        replacement = {
            "tab_title": "You've been replaced!",
            "url": "supercool.pdf",
            "id": "2",
        }
        resp = self.client.post(
            self.url2,
            data=json.dumps(replacement),
            content_type="application/json",
        )
        self.assertEqual(resp.status_code, 201)
        resp2 = self.client.get(self.url2)
280
        self.assertEqual(resp2.status_code, 200)
David Baumgold committed
281 282 283 284 285 286 287 288 289 290
        compare = json.loads(resp2.content)
        self.assertEqual(compare, replacement)
        course = self.store.get_item(self.course.location)
        self.assertEqual(
            course.pdf_textbooks,
            [self.textbook1, replacement]
        )


class TextbookValidationTestCase(TestCase):
David Baumgold committed
291 292
    "Tests for the code to validate the structure of a PDF textbook"

David Baumgold committed
293
    def setUp(self):
David Baumgold committed
294
        "Set some useful content for tests"
David Baumgold committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
        self.tb1 = {
            "tab_title": "Hi, mom!",
            "url": "/mom.pdf"
        }
        self.tb2 = {
            "tab_title": "Hi, dad!",
            "chapters": [
                {
                    "title": "Baseball",
                    "url": "baseball.pdf",
                }, {
                    "title": "Basketball",
                    "url": "crazypants.pdf",
                }
            ]
        }
        self.textbooks = [self.tb1, self.tb2]

    def test_happy_path_plural(self):
David Baumgold committed
314
        "Test that the plural validator works properly"
David Baumgold committed
315 316 317 318
        result = validate_textbooks_json(json.dumps(self.textbooks))
        self.assertEqual(self.textbooks, result)

    def test_happy_path_singular_1(self):
David Baumgold committed
319
        "Test that the singular validator works properly"
David Baumgold committed
320 321 322 323
        result = validate_textbook_json(json.dumps(self.tb1))
        self.assertEqual(self.tb1, result)

    def test_happy_path_singular_2(self):
David Baumgold committed
324
        "Test that the singular validator works properly, with different data"
David Baumgold committed
325 326
        result = validate_textbook_json(json.dumps(self.tb2))
        self.assertEqual(self.tb2, result)
David Baumgold committed
327

David Baumgold committed
328
    def test_valid_id(self):
David Baumgold committed
329
        "Test that a valid ID doesn't trip the validator, and comes out unchanged"
David Baumgold committed
330 331 332 333 334
        self.tb1["id"] = 1
        result = validate_textbook_json(json.dumps(self.tb1))
        self.assertEqual(self.tb1, result)

    def test_invalid_id(self):
David Baumgold committed
335
        "Test that an invalid ID trips the validator"
David Baumgold committed
336 337 338 339 340
        self.tb1["id"] = "abc"
        with self.assertRaises(TextbookValidationError):
            validate_textbook_json(json.dumps(self.tb1))

    def test_invalid_json_plural(self):
David Baumgold committed
341
        "Test that invalid JSON trips the plural validator"
David Baumgold committed
342 343 344 345
        with self.assertRaises(TextbookValidationError):
            validate_textbooks_json("[{'abc'}]")

    def test_invalid_json_singular(self):
David Baumgold committed
346
        "Test that invalid JSON trips the singluar validator"
David Baumgold committed
347 348 349 350
        with self.assertRaises(TextbookValidationError):
            validate_textbook_json("[{1]}")

    def test_wrong_json_plural(self):
David Baumgold committed
351
        "Test that a JSON object trips the plural validators (requires a list)"
David Baumgold committed
352 353 354 355
        with self.assertRaises(TextbookValidationError):
            validate_textbooks_json('{"tab_title": "Hi, mom!"}')

    def test_wrong_json_singular(self):
David Baumgold committed
356
        "Test that a JSON list trips the plural validators (requires an object)"
David Baumgold committed
357 358
        with self.assertRaises(TextbookValidationError):
            validate_textbook_json('[{"tab_title": "Hi, mom!"}, {"tab_title": "Hi, dad!"}]')
David Baumgold committed
359

David Baumgold committed
360
    def test_no_tab_title_plural(self):
David Baumgold committed
361
        "Test that `tab_title` is required for the plural validator"
David Baumgold committed
362
        with self.assertRaises(TextbookValidationError):
David Baumgold committed
363
            validate_textbooks_json('[{"url": "/textbook.pdf"}]')
David Baumgold committed
364

David Baumgold committed
365
    def test_no_tab_title_singular(self):
David Baumgold committed
366
        "Test that `tab_title` is required for the singular validator"
David Baumgold committed
367
        with self.assertRaises(TextbookValidationError):
David Baumgold committed
368
            validate_textbook_json('{"url": "/textbook.pdf"}')
David Baumgold committed
369

David Baumgold committed
370
    def test_duplicate_ids(self):
David Baumgold committed
371
        "Test that duplicate IDs in the plural validator trips the validator"
David Baumgold committed
372 373 374 375 376 377 378 379 380
        textbooks = [{
            "tab_title": "name one",
            "url": "one.pdf",
            "id": 1,
        }, {
            "tab_title": "name two",
            "url": "two.pdf",
            "id": 1,
        }]
David Baumgold committed
381
        with self.assertRaises(TextbookValidationError):
David Baumgold committed
382
            validate_textbooks_json(json.dumps(textbooks))