From 813c22d1382262349c3ccaa06efe4ef581acf016 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Mon, 27 May 2013 18:09:29 +0300 Subject: [PATCH] Adds integration tests for word_cloud module --- lms/djangoapps/courseware/tests/__init__.py | 6 +- .../courseware/tests/test_word_cloud.py | 256 ++++++++++++++++++ 2 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 lms/djangoapps/courseware/tests/test_word_cloud.py diff --git a/lms/djangoapps/courseware/tests/__init__.py b/lms/djangoapps/courseware/tests/__init__.py index 31fe376d69..33c8d12701 100644 --- a/lms/djangoapps/courseware/tests/__init__.py +++ b/lms/djangoapps/courseware/tests/__init__.py @@ -26,17 +26,19 @@ class BaseTestXmodule(ModuleStoreTestCase): This class prepares course and users for tests: 1. create test course; - 2. create, enrol and login users for this course; + 2. create, enroll and login users for this course; Any xmodule should overwrite only next parameters for test: 1. CATEGORY 2. DATA 3. MODEL_DATA + 4. COURSE_DATA and USER_COUNT if needed This class should not contain any tests, because CATEGORY should be defined in child class. """ USER_COUNT = 2 + COURSE_DATA = {} # Data from YAML common/lib/xmodule/xmodule/templates/NAME/default.yaml CATEGORY = "" @@ -45,7 +47,7 @@ class BaseTestXmodule(ModuleStoreTestCase): def setUp(self): - self.course = CourseFactory.create() + self.course = CourseFactory.create(data=self.COURSE_DATA) # Turn off cache. modulestore().request_cache = None diff --git a/lms/djangoapps/courseware/tests/test_word_cloud.py b/lms/djangoapps/courseware/tests/test_word_cloud.py new file mode 100644 index 0000000000..7c214f3458 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_word_cloud.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +"""Word cloud integration tests using mongo modulestore.""" + +import json +from operator import itemgetter + +from . import BaseTestXmodule + + +class TestWordCloud(BaseTestXmodule): + """Integration test for word cloud xmodule.""" + CATEGORY = "word_cloud" + + def _get_users_state(self): + """Return current state for each user: + + {username: json_state} + """ + # check word cloud response for every user + users_state = {} + + for user in self.users: + response = self.clients[user.username].post(self.get_url('get_state')) + users_state[user.username] = json.loads(response.content) + + return users_state + + def _post_words(self, words): + """Post `words` and return current state for each user: + + {username: json_state} + """ + users_state = {} + + for user in self.users: + response = self.clients[user.username].post( + self.get_url('submit'), + {'student_words[]': words}, + HTTP_X_REQUESTED_WITH='XMLHttpRequest' + ) + users_state[user.username] = json.loads(response.content) + + return users_state + + def _check_response(self, response_contents, correct_jsons): + """Utility function that compares correct and real responses.""" + for username, content in response_contents.items(): + + # Used in debugger for comparing objects. + # self.maxDiff = None + + # We should compare top_words for manually, + # because they are unsorted. + keys_to_compare = set(content.keys()).difference(set(['top_words'])) + self.assertDictEqual( + {k: content[k] for k in keys_to_compare}, + {k: correct_jsons[username][k] for k in keys_to_compare}) + + # comparing top_words: + top_words_content = sorted( + content['top_words'], + key=itemgetter('text') + ) + top_words_correct = sorted( + correct_jsons[username]['top_words'], + key=itemgetter('text') + ) + self.assertListEqual(top_words_content, top_words_correct) + + def test_initial_state(self): + """Inital state of word cloud is correct. Those state that + is sended from server to frontend, when students load word + cloud page. + """ + users_state = self._get_users_state() + + self.assertEqual( + ''.join(set([ + content['status'] + for _, content in users_state.items() + ])), + 'success') + + # correct initial data: + correct_initial_data = { + u'status': u'success', + u'student_words': {}, + u'total_count': 0, + u'submitted': False, + u'top_words': {}, + u'display_student_percents': False + } + + for _, response_content in users_state.items(): + self.assertEquals(response_content, correct_initial_data) + + def test_post_words(self): + """Students can submit data succesfully. + Word cloud data properly updates after students submit. + """ + input_words = [ + "small", + "BIG", + " Spaced ", + " few words", + ] + + correct_words = [ + u"small", + u"big", + u"spaced", + u"few words", + ] + + users_state = self._post_words(input_words) + + self.assertEqual( + ''.join(set([ + content['status'] + for _, content in users_state.items() + ])), + 'success') + + correct_state = {} + for index, user in enumerate(self.users): + + correct_state[user.username] = { + u'status': u'success', + u'submitted': True, + u'display_student_percents': True, + u'student_words': {word: 1 + index for word in correct_words}, + u'total_count': len(input_words) * (1 + index), + u'top_words': [ + { + u'text': word, u'percent': 100 / len(input_words), + u'size': (1 + index) + } + for word in correct_words + ] + } + + self._check_response(users_state, correct_state) + + def test_collective_users_submits(self): + """Test word cloud data flow per single and collective users submits. + + Make sures that: + + 1. Inital state of word cloud is correct. Those state that + is sended from server to frontend, when students load word + cloud page. + + 2. Students can submit data succesfully. + + 3. Next submits produce "already voted" error. Next submits for user + are not allowed by user interface, but techically it possible, and + word_cloud should properly react. + + 4. State of word cloud after #3 is still as after #2. + """ + + # 1. + users_state = self._get_users_state() + + self.assertEqual( + ''.join(set([ + content['status'] + for _, content in users_state.items() + ])), + 'success') + + # 2. + # Invcemental state per user. + users_state_after_post = self._post_words(['word1', 'word2']) + + self.assertEqual( + ''.join(set([ + content['status'] + for _, content in users_state_after_post.items() + ])), + 'success') + + # Final state after all posts. + users_state_before_fail = self._get_users_state() + + # 3. + users_state_after_post = self._post_words( + ['word1', 'word2', 'word3']) + + self.assertEqual( + ''.join(set([ + content['status'] + for _, content in users_state_after_post.items() + ])), + 'fail') + + # 4. + current_users_state = self._get_users_state() + self._check_response(users_state_before_fail, current_users_state) + + def test_unicode(self): + input_words = [u" this is unicode Юникод"] + correct_words = [u"this is unicode юникод"] + + users_state = self._post_words(input_words) + + self.assertEqual( + ''.join(set([ + content['status'] + for _, content in users_state.items() + ])), + 'success') + + for user in self.users: + self.assertListEqual( + users_state[user.username]['student_words'].keys(), + correct_words) + + def test_handle_ajax_incorrect_dispatch(self): + responses = { + user.username: self.clients[user.username].post( + self.get_url('whatever'), + {}, + HTTP_X_REQUESTED_WITH='XMLHttpRequest') + for user in self.users + } + + self.assertEqual( + set([ + response.status_code + for _, response in responses.items() + ]).pop(), + 200) + + for user in self.users: + self.assertDictEqual( + json.loads(responses[user.username].content), + { + 'status': 'fail', + 'error': 'Unknown Command!' + }) + + def test_word_cloud_constructor(self): + """Make sure that all parameters extracted correclty from xml""" + # `get_html` return only context, cause we + # overwrite `system.render_template` + context = self.item_module.get_html() + + expected_context = { + 'ajax_url': self.item_module.system.ajax_url, + 'element_class': self.item_module.location.category, + 'element_id': self.item_module.location.html_id(), + 'num_inputs': 5, # default value + 'submitted': False # default value + } + self.assertDictEqual(context, expected_context)