From 74653ff8add08a1d63902e910736dbf41a6c0258 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 19 Mar 2013 15:18:29 -0400 Subject: [PATCH 1/9] Factory refactor working for lms lettuce tests. --- .../contentstore/features/common.py | 12 +- .../contentstore/features/factories.py | 34 --- common/djangoapps/student/tests/__init__.py | 0 common/djangoapps/student/tests/factories.py | 198 +++++++++++++++ common/djangoapps/student/tests/tests.py | 107 ++++++++ common/djangoapps/terrain/factories.py | 229 ++++-------------- common/djangoapps/terrain/steps.py | 4 +- 7 files changed, 362 insertions(+), 222 deletions(-) delete mode 100644 cms/djangoapps/contentstore/features/factories.py create mode 100644 common/djangoapps/student/tests/__init__.py create mode 100644 common/djangoapps/student/tests/factories.py create mode 100644 common/djangoapps/student/tests/tests.py diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 2ec0427e1d..820b60123b 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -7,8 +7,6 @@ from selenium.common.exceptions import WebDriverException, StaleElementReference from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By -from terrain.factories import UserFactory, RegistrationFactory, UserProfileFactory -from terrain.factories import CourseFactory, GroupFactory from xmodule.modulestore.django import _MODULESTORES, modulestore from xmodule.templates import update_templates from auth.authz import get_user_by_email @@ -61,7 +59,7 @@ def create_studio_user( email='robot+studio@edx.org', password='test', is_staff=False): - studio_user = UserFactory.build( + studio_user = world.UserFactory.build( username=uname, email=email, password=password, @@ -69,11 +67,11 @@ def create_studio_user( studio_user.set_password(password) studio_user.save() - registration = RegistrationFactory(user=studio_user) + registration = world.RegistrationFactory(user=studio_user) registration.register(studio_user) registration.activate() - user_profile = UserProfileFactory(user=studio_user) + user_profile = world.UserProfileFactory(user=studio_user) def flush_xmodule_store(): @@ -175,11 +173,11 @@ def log_into_studio( def create_a_course(): - c = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') + c = world.CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course') # Add the user to the instructor group of the course # so they will have the permissions to see it in studio - g = GroupFactory.create(name='instructor_MITx/999/Robot_Super_Course') + g = world.GroupFactory.create(name='instructor_MITx/999/Robot_Super_Course') u = get_user_by_email('robot+studio@edx.org') u.groups.add(g) u.save() diff --git a/cms/djangoapps/contentstore/features/factories.py b/cms/djangoapps/contentstore/features/factories.py deleted file mode 100644 index 087ceaaa2d..0000000000 --- a/cms/djangoapps/contentstore/features/factories.py +++ /dev/null @@ -1,34 +0,0 @@ -import factory -from student.models import User, UserProfile, Registration -from datetime import datetime -import uuid - - -class UserProfileFactory(factory.Factory): - FACTORY_FOR = UserProfile - - user = None - name = 'Robot Studio' - courseware = 'course.xml' - - -class RegistrationFactory(factory.Factory): - FACTORY_FOR = Registration - - user = None - activation_key = uuid.uuid4().hex - - -class UserFactory(factory.Factory): - FACTORY_FOR = User - - username = 'robot-studio' - email = 'robot+studio@edx.org' - password = 'test' - first_name = 'Robot' - last_name = 'Studio' - is_staff = False - is_active = True - is_superuser = False - last_login = datetime.now() - date_joined = datetime.now() diff --git a/common/djangoapps/student/tests/__init__.py b/common/djangoapps/student/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py new file mode 100644 index 0000000000..8dd70a7a61 --- /dev/null +++ b/common/djangoapps/student/tests/factories.py @@ -0,0 +1,198 @@ +from student.models import (User, UserProfile, Registration, + CourseEnrollmentAllowed) +from django.contrib.auth.models import Group +from datetime import datetime +from factory import Factory +from xmodule.modulestore import Location +from xmodule.modulestore.django import modulestore +from time import gmtime +from uuid import uuid4 +from xmodule.timeparse import stringify_time +from xmodule.modulestore.inheritance import own_metadata + + +class GroupFactory(Factory): + FACTORY_FOR = Group + + name = 'staff_MITx/999/Robot_Super_Course' + + +class UserProfileFactory(Factory): + FACTORY_FOR = UserProfile + + user = None + name = 'Robot Test' + level_of_education = None + gender = 'm' + mailing_address = None + goals = 'World domination' + + +class RegistrationFactory(Factory): + FACTORY_FOR = Registration + + user = None + activation_key = uuid4().hex + + +class UserFactory(Factory): + FACTORY_FOR = User + + username = 'robot' + email = 'robot+test@edx.org' + password = 'test' + first_name = 'Robot' + last_name = 'Test' + is_staff = False + is_active = True + is_superuser = False + last_login = datetime(2012, 1, 1) + date_joined = datetime(2011, 1, 1) + + +class CourseEnrollmentAllowedFactory(Factory): + FACTORY_FOR = CourseEnrollmentAllowed + + email = 'test@edx.org' + course_id = 'edX/test/2012_Fall' + + +def XMODULE_COURSE_CREATION(class_to_create, **kwargs): + return XModuleCourseFactory._create(class_to_create, **kwargs) + + +def XMODULE_ITEM_CREATION(class_to_create, **kwargs): + return XModuleItemFactory._create(class_to_create, **kwargs) + + +class XModuleCourseFactory(Factory): + """ + Factory for XModule courses. + """ + + ABSTRACT_FACTORY = True + _creation_function = (XMODULE_COURSE_CREATION,) + + @classmethod + def _create(cls, target_class, *args, **kwargs): + + template = Location('i4x', 'edx', 'templates', 'course', 'Empty') + org = kwargs.get('org') + number = kwargs.get('number') + display_name = kwargs.get('display_name') + location = Location('i4x', org, number, + 'course', Location.clean(display_name)) + + store = modulestore('direct') + + # Write the data to the mongo datastore + new_course = store.clone_item(template, location) + + # This metadata code was copied from cms/djangoapps/contentstore/views.py + if display_name is not None: + new_course.display_name = display_name + + new_course.lms.start = gmtime() + new_course.tabs = [{"type": "courseware"}, + {"type": "course_info", "name": "Course Info"}, + {"type": "discussion", "name": "Discussion"}, + {"type": "wiki", "name": "Wiki"}, + {"type": "progress", "name": "Progress"}] + + # Update the data in the mongo datastore + store.update_metadata(new_course.location.url(), own_metadata(new_course)) + + return new_course + + +class Course: + pass + + +class CourseFactory(XModuleCourseFactory): + FACTORY_FOR = Course + + template = 'i4x://edx/templates/course/Empty' + org = 'MITx' + number = '999' + display_name = 'Robot Super Course' + + +class XModuleItemFactory(Factory): + """ + Factory for XModule items. + """ + + ABSTRACT_FACTORY = True + _creation_function = (XMODULE_ITEM_CREATION,) + + @classmethod + def _create(cls, target_class, *args, **kwargs): + """ + Uses *kwargs*: + + *parent_location* (required): the location of the parent module + (e.g. the parent course or section) + + *template* (required): the template to create the item from + (e.g. i4x://templates/section/Empty) + + *data* (optional): the data for the item + (e.g. XML problem definition for a problem item) + + *display_name* (optional): the display name of the item + + *metadata* (optional): dictionary of metadata attributes + + *target_class* is ignored + """ + + DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] + + parent_location = Location(kwargs.get('parent_location')) + template = Location(kwargs.get('template')) + data = kwargs.get('data') + display_name = kwargs.get('display_name') + metadata = kwargs.get('metadata', {}) + + store = modulestore('direct') + + # This code was based off that in cms/djangoapps/contentstore/views.py + parent = store.get_item(parent_location) + + # If a display name is set, use that + dest_name = display_name.replace(" ", "_") if display_name is not None else uuid4().hex + dest_location = parent_location._replace(category=template.category, + name=dest_name) + + new_item = store.clone_item(template, dest_location) + + # replace the display name with an optional parameter passed in from the caller + if display_name is not None: + new_item.display_name = display_name + + # Add additional metadata or override current metadata + item_metadata = own_metadata(new_item) + item_metadata.update(metadata) + store.update_metadata(new_item.location.url(), item_metadata) + + # replace the data with the optional *data* parameter + if data is not None: + store.update_item(new_item.location, data) + + if new_item.location.category not in DETACHED_CATEGORIES: + store.update_children(parent_location, parent.children + [new_item.location.url()]) + + return new_item + + +class Item: + pass + + +class ItemFactory(XModuleItemFactory): + FACTORY_FOR = Item + + parent_location = 'i4x://MITx/999/course/Robot_Super_Course' + template = 'i4x://edx/templates/chapter/Empty' + display_name = 'Section One' diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py new file mode 100644 index 0000000000..6a2d75e3d8 --- /dev/null +++ b/common/djangoapps/student/tests/tests.py @@ -0,0 +1,107 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" +import logging + +from django.test import TestCase +from mock import Mock + +from .models import unique_id_for_user +from .views import process_survey_link, _cert_info + +COURSE_1 = 'edX/toy/2012_Fall' +COURSE_2 = 'edx/full/6.002_Spring_2012' + +log = logging.getLogger(__name__) + + +class CourseEndingTest(TestCase): + """Test things related to course endings: certificates, surveys, etc""" + + def test_process_survey_link(self): + username = "fred" + user = Mock(username=username) + id = unique_id_for_user(user) + link1 = "http://www.mysurvey.com" + self.assertEqual(process_survey_link(link1, user), link1) + + link2 = "http://www.mysurvey.com?unique={UNIQUE_ID}" + link2_expected = "http://www.mysurvey.com?unique={UNIQUE_ID}".format(UNIQUE_ID=id) + self.assertEqual(process_survey_link(link2, user), link2_expected) + + def test_cert_info(self): + user = Mock(username="fred") + survey_url = "http://a_survey.com" + course = Mock(end_of_course_survey_url=survey_url) + + self.assertEqual(_cert_info(user, course, None), + {'status': 'processing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False, }) + + cert_status = {'status': 'unavailable'} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'processing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False}) + + cert_status = {'status': 'generating', 'grade': '67'} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'generating', + 'show_disabled_download_button': True, + 'show_download_url': False, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + cert_status = {'status': 'regenerating', 'grade': '67'} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'generating', + 'show_disabled_download_button': True, + 'show_download_url': False, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + download_url = 'http://s3.edx/cert' + cert_status = {'status': 'downloadable', 'grade': '67', + 'download_url': download_url} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'ready', + 'show_disabled_download_button': False, + 'show_download_url': True, + 'download_url': download_url, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + cert_status = {'status': 'notpassing', 'grade': '67', + 'download_url': download_url} + self.assertEqual(_cert_info(user, course, cert_status), + {'status': 'notpassing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': True, + 'survey_url': survey_url, + 'grade': '67' + }) + + # Test a course that doesn't have a survey specified + course2 = Mock(end_of_course_survey_url=None) + cert_status = {'status': 'notpassing', 'grade': '67', + 'download_url': download_url} + self.assertEqual(_cert_info(user, course2, cert_status), + {'status': 'notpassing', + 'show_disabled_download_button': False, + 'show_download_url': False, + 'show_survey_button': False, + 'grade': '67' + }) diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index c36bf935f1..62b133217d 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -1,190 +1,61 @@ -from student.models import User, UserProfile, Registration -from django.contrib.auth.models import Group -from datetime import datetime -from factory import Factory -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore -from time import gmtime -from uuid import uuid4 -from xmodule.timeparse import stringify_time -from xmodule.modulestore.inheritance import own_metadata +from student.tests.factories import (UserFactory, UserProfileFactory, + RegistrationFactory, GroupFactory, + CourseEnrollmentAllowed, + CourseFactory, ItemFactory) +from lettuce import world -class GroupFactory(Factory): - FACTORY_FOR = Group - - name = 'staff_MITx/999/Robot_Super_Course' - - -class UserProfileFactory(Factory): - FACTORY_FOR = UserProfile - - user = None - name = 'Robot Test' - level_of_education = None - gender = 'm' - mailing_address = None - goals = 'World domination' - - -class RegistrationFactory(Factory): - FACTORY_FOR = Registration - - user = None - activation_key = uuid4().hex - - -class UserFactory(Factory): - FACTORY_FOR = User - - username = 'robot' - email = 'robot+test@edx.org' - password = 'test' - first_name = 'Robot' - last_name = 'Test' - is_staff = False - is_active = True - is_superuser = False - last_login = datetime(2012, 1, 1) - date_joined = datetime(2011, 1, 1) - - -def XMODULE_COURSE_CREATION(class_to_create, **kwargs): - return XModuleCourseFactory._create(class_to_create, **kwargs) - - -def XMODULE_ITEM_CREATION(class_to_create, **kwargs): - return XModuleItemFactory._create(class_to_create, **kwargs) - - -class XModuleCourseFactory(Factory): +@world.absorb +class UserFactory(UserFactory): """ - Factory for XModule courses. - """ - - ABSTRACT_FACTORY = True - _creation_function = (XMODULE_COURSE_CREATION,) - - @classmethod - def _create(cls, target_class, *args, **kwargs): - - template = Location('i4x', 'edx', 'templates', 'course', 'Empty') - org = kwargs.get('org') - number = kwargs.get('number') - display_name = kwargs.get('display_name') - location = Location('i4x', org, number, - 'course', Location.clean(display_name)) - - store = modulestore('direct') - - # Write the data to the mongo datastore - new_course = store.clone_item(template, location) - - # This metadata code was copied from cms/djangoapps/contentstore/views.py - if display_name is not None: - new_course.display_name = display_name - - new_course.lms.start = gmtime() - new_course.tabs = [{"type": "courseware"}, - {"type": "course_info", "name": "Course Info"}, - {"type": "discussion", "name": "Discussion"}, - {"type": "wiki", "name": "Wiki"}, - {"type": "progress", "name": "Progress"}] - - # Update the data in the mongo datastore - store.update_metadata(new_course.location.url(), own_metadata(new_course)) - - return new_course - - -class Course: + User account for lms / cms + """ pass -class CourseFactory(XModuleCourseFactory): - FACTORY_FOR = Course - - template = 'i4x://edx/templates/course/Empty' - org = 'MITx' - number = '999' - display_name = 'Robot Super Course' - - -class XModuleItemFactory(Factory): +@world.absorb +class UserProfileFactory(UserProfileFactory): """ - Factory for XModule items. - """ - - ABSTRACT_FACTORY = True - _creation_function = (XMODULE_ITEM_CREATION,) - - @classmethod - def _create(cls, target_class, *args, **kwargs): - """ - Uses *kwargs*: - - *parent_location* (required): the location of the parent module - (e.g. the parent course or section) - - *template* (required): the template to create the item from - (e.g. i4x://templates/section/Empty) - - *data* (optional): the data for the item - (e.g. XML problem definition for a problem item) - - *display_name* (optional): the display name of the item - - *metadata* (optional): dictionary of metadata attributes - - *target_class* is ignored - """ - - DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] - - parent_location = Location(kwargs.get('parent_location')) - template = Location(kwargs.get('template')) - data = kwargs.get('data') - display_name = kwargs.get('display_name') - metadata = kwargs.get('metadata', {}) - - store = modulestore('direct') - - # This code was based off that in cms/djangoapps/contentstore/views.py - parent = store.get_item(parent_location) - - # If a display name is set, use that - dest_name = display_name.replace(" ", "_") if display_name is not None else uuid4().hex - dest_location = parent_location._replace(category=template.category, - name=dest_name) - - new_item = store.clone_item(template, dest_location) - - # replace the display name with an optional parameter passed in from the caller - if display_name is not None: - new_item.display_name = display_name - - # Add additional metadata or override current metadata - item_metadata = own_metadata(new_item) - item_metadata.update(metadata) - store.update_metadata(new_item.location.url(), item_metadata) - - # replace the data with the optional *data* parameter - if data is not None: - store.update_item(new_item.location, data) - - if new_item.location.category not in DETACHED_CATEGORIES: - store.update_children(parent_location, parent.children + [new_item.location.url()]) - - return new_item - - -class Item: + Demographics etc for the User + """ pass -class ItemFactory(XModuleItemFactory): - FACTORY_FOR = Item +@world.absorb +class RegistrationFactory(RegistrationFactory): + """ + Activation key for registering the user account + """ + pass - parent_location = 'i4x://MITx/999/course/Robot_Super_Course' - template = 'i4x://edx/templates/chapter/Empty' - display_name = 'Section One' + +@world.absorb +class GroupFactory(GroupFactory): + """ + Groups for user permissions for courses + """ + pass + + +@world.absorb +class CourseEnrollmentAllowed(CourseEnrollmentAllowed): + """ + Users allowed to enroll in the course outside of the usual window + """ + pass + + +@world.absorb +class CourseFactory(CourseFactory): + """ + Courseware courses + """ + pass + + +@world.absorb +class ItemFactory(ItemFactory): + """ + Everything included inside a course + """ + pass diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index 52eeb23c4a..e9dcd7db31 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -119,11 +119,11 @@ def create_user(uname): portal_user.set_password('test') portal_user.save() - registration = RegistrationFactory(user=portal_user) + registration = world.RegistrationFactory(user=portal_user) registration.register(portal_user) registration.activate() - user_profile = UserProfileFactory(user=portal_user) + user_profile = world.UserProfileFactory(user=portal_user) @world.absorb From daadaffb61fcbffb46ed555e8efb73740aa62853 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 19 Mar 2013 15:44:01 -0400 Subject: [PATCH 2/9] Add mongo contentstore to acceptance.py for lettuce tests for lms --- lms/envs/acceptance.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py index 3dac545367..5280c7d288 100644 --- a/lms/envs/acceptance.py +++ b/lms/envs/acceptance.py @@ -29,6 +29,14 @@ MODULESTORE = { } } +CONTENTSTORE = { + 'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore', + 'OPTIONS': { + 'host': 'localhost', + 'db': 'test_xcontent', + } +} + # Set this up so that rake lms[acceptance] and running the # harvest command both use the same (test) database # which they can flush without messing up your dev db From 1c4ffcf122e53ad916cba195b8bd5328e48e8b60 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 19 Mar 2013 16:00:15 -0400 Subject: [PATCH 3/9] Fix import statement for test.py --- common/djangoapps/student/tests.py | 107 ----------------------- common/djangoapps/student/tests/tests.py | 4 +- 2 files changed, 2 insertions(+), 109 deletions(-) delete mode 100644 common/djangoapps/student/tests.py diff --git a/common/djangoapps/student/tests.py b/common/djangoapps/student/tests.py deleted file mode 100644 index 6a2d75e3d8..0000000000 --- a/common/djangoapps/student/tests.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -This file demonstrates writing tests using the unittest module. These will pass -when you run "manage.py test". - -Replace this with more appropriate tests for your application. -""" -import logging - -from django.test import TestCase -from mock import Mock - -from .models import unique_id_for_user -from .views import process_survey_link, _cert_info - -COURSE_1 = 'edX/toy/2012_Fall' -COURSE_2 = 'edx/full/6.002_Spring_2012' - -log = logging.getLogger(__name__) - - -class CourseEndingTest(TestCase): - """Test things related to course endings: certificates, surveys, etc""" - - def test_process_survey_link(self): - username = "fred" - user = Mock(username=username) - id = unique_id_for_user(user) - link1 = "http://www.mysurvey.com" - self.assertEqual(process_survey_link(link1, user), link1) - - link2 = "http://www.mysurvey.com?unique={UNIQUE_ID}" - link2_expected = "http://www.mysurvey.com?unique={UNIQUE_ID}".format(UNIQUE_ID=id) - self.assertEqual(process_survey_link(link2, user), link2_expected) - - def test_cert_info(self): - user = Mock(username="fred") - survey_url = "http://a_survey.com" - course = Mock(end_of_course_survey_url=survey_url) - - self.assertEqual(_cert_info(user, course, None), - {'status': 'processing', - 'show_disabled_download_button': False, - 'show_download_url': False, - 'show_survey_button': False, }) - - cert_status = {'status': 'unavailable'} - self.assertEqual(_cert_info(user, course, cert_status), - {'status': 'processing', - 'show_disabled_download_button': False, - 'show_download_url': False, - 'show_survey_button': False}) - - cert_status = {'status': 'generating', 'grade': '67'} - self.assertEqual(_cert_info(user, course, cert_status), - {'status': 'generating', - 'show_disabled_download_button': True, - 'show_download_url': False, - 'show_survey_button': True, - 'survey_url': survey_url, - 'grade': '67' - }) - - cert_status = {'status': 'regenerating', 'grade': '67'} - self.assertEqual(_cert_info(user, course, cert_status), - {'status': 'generating', - 'show_disabled_download_button': True, - 'show_download_url': False, - 'show_survey_button': True, - 'survey_url': survey_url, - 'grade': '67' - }) - - download_url = 'http://s3.edx/cert' - cert_status = {'status': 'downloadable', 'grade': '67', - 'download_url': download_url} - self.assertEqual(_cert_info(user, course, cert_status), - {'status': 'ready', - 'show_disabled_download_button': False, - 'show_download_url': True, - 'download_url': download_url, - 'show_survey_button': True, - 'survey_url': survey_url, - 'grade': '67' - }) - - cert_status = {'status': 'notpassing', 'grade': '67', - 'download_url': download_url} - self.assertEqual(_cert_info(user, course, cert_status), - {'status': 'notpassing', - 'show_disabled_download_button': False, - 'show_download_url': False, - 'show_survey_button': True, - 'survey_url': survey_url, - 'grade': '67' - }) - - # Test a course that doesn't have a survey specified - course2 = Mock(end_of_course_survey_url=None) - cert_status = {'status': 'notpassing', 'grade': '67', - 'download_url': download_url} - self.assertEqual(_cert_info(user, course2, cert_status), - {'status': 'notpassing', - 'show_disabled_download_button': False, - 'show_download_url': False, - 'show_survey_button': False, - 'grade': '67' - }) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 6a2d75e3d8..4638da44b2 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -9,8 +9,8 @@ import logging from django.test import TestCase from mock import Mock -from .models import unique_id_for_user -from .views import process_survey_link, _cert_info +from student.models import unique_id_for_user +from student.views import process_survey_link, _cert_info COURSE_1 = 'edX/toy/2012_Fall' COURSE_2 = 'edx/full/6.002_Spring_2012' From 3576a3154b6386f95f8a48182791e1084d093756 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 19 Mar 2013 16:38:51 -0400 Subject: [PATCH 4/9] Repoint factory references in lettuce tests to world. --- .../features/studio-overview-togglesection.py | 21 +++++++++---------- common/djangoapps/student/tests/factories.py | 11 ++++++++-- common/djangoapps/terrain/factories.py | 10 ++++++++- lms/djangoapps/courseware/features/common.py | 9 ++++---- .../courseware/features/problems.py | 3 +-- .../django_comment_client/tests/test_utils.py | 16 +------------- 6 files changed, 34 insertions(+), 36 deletions(-) diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py index 00aa39455d..060d592cfd 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.py @@ -1,5 +1,4 @@ from lettuce import world, step -from terrain.factories import * from common import * from nose.tools import assert_true, assert_false, assert_equal @@ -10,15 +9,15 @@ logger = getLogger(__name__) @step(u'I have a course with no sections$') def have_a_course(step): clear_courses() - course = CourseFactory.create() + course = world.CourseFactory.create() @step(u'I have a course with 1 section$') def have_a_course_with_1_section(step): clear_courses() - course = CourseFactory.create() - section = ItemFactory.create(parent_location=course.location) - subsection1 = ItemFactory.create( + course = world.CourseFactory.create() + section = world.ItemFactory.create(parent_location=course.location) + subsection1 = world.ItemFactory.create( parent_location=section.location, template='i4x://edx/templates/sequential/Empty', display_name='Subsection One',) @@ -27,20 +26,20 @@ def have_a_course_with_1_section(step): @step(u'I have a course with multiple sections$') def have_a_course_with_two_sections(step): clear_courses() - course = CourseFactory.create() - section = ItemFactory.create(parent_location=course.location) - subsection1 = ItemFactory.create( + course = world.CourseFactory.create() + section = world.ItemFactory.create(parent_location=course.location) + subsection1 = world.ItemFactory.create( parent_location=section.location, template='i4x://edx/templates/sequential/Empty', display_name='Subsection One',) - section2 = ItemFactory.create( + section2 = world.ItemFactory.create( parent_location=course.location, display_name='Section Two',) - subsection2 = ItemFactory.create( + subsection2 = world.ItemFactory.create( parent_location=section2.location, template='i4x://edx/templates/sequential/Empty', display_name='Subsection Alpha',) - subsection3 = ItemFactory.create( + subsection3 = world.ItemFactory.create( parent_location=section2.location, template='i4x://edx/templates/sequential/Empty', display_name='Subsection Beta',) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 8dd70a7a61..2acc235ce2 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -1,8 +1,8 @@ from student.models import (User, UserProfile, Registration, - CourseEnrollmentAllowed) + CourseEnrollmentAllowed, CourseEnrollment) from django.contrib.auth.models import Group from datetime import datetime -from factory import Factory +from factory import Factory, SubFactory from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from time import gmtime @@ -50,6 +50,13 @@ class UserFactory(Factory): date_joined = datetime(2011, 1, 1) +class CourseEnrollmentFactory(Factory): + FACTORY_FOR = CourseEnrollment + + user = SubFactory(UserFactory) + course_id = 'edX/toy/2012_Fall' + + class CourseEnrollmentAllowedFactory(Factory): FACTORY_FOR = CourseEnrollmentAllowed diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index 62b133217d..3a3381c74c 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -1,6 +1,6 @@ from student.tests.factories import (UserFactory, UserProfileFactory, RegistrationFactory, GroupFactory, - CourseEnrollmentAllowed, + CourseEnrollmentAllowed, CourseEnrollment, CourseFactory, ItemFactory) from lettuce import world @@ -37,6 +37,14 @@ class GroupFactory(GroupFactory): pass +@world.absorb +class CourseEnrollment(CourseEnrollment): + """ + Courses that the user is enrolled in + """ + pass + + @world.absorb class CourseEnrollmentAllowed(CourseEnrollmentAllowed): """ diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index 8fb2843656..bdbd952caa 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -5,7 +5,6 @@ from lettuce.django import django_url from django.conf import settings from django.contrib.auth.models import User from student.models import CourseEnrollment -from terrain.factories import CourseFactory, ItemFactory from xmodule.modulestore import Location from xmodule.modulestore.django import _MODULESTORES, modulestore from xmodule.templates import update_templates @@ -101,15 +100,15 @@ def create_course(step, course): # Create the course # We always use the same org and display name, # but vary the course identifier (e.g. 600x or 191x) - course = CourseFactory.create(org=TEST_COURSE_ORG, + course = world.CourseFactory.create(org=TEST_COURSE_ORG, number=course, display_name=TEST_COURSE_NAME) # Add a section to the course to contain problems - section = ItemFactory.create(parent_location=course.location, + section = world.ItemFactory.create(parent_location=course.location, display_name=TEST_SECTION_NAME) - problem_section = ItemFactory.create(parent_location=section.location, + problem_section = world.ItemFactory.create(parent_location=section.location, template='i4x://edx/templates/sequential/Empty', display_name=TEST_SECTION_NAME) @@ -131,7 +130,7 @@ def i_am_registered_for_the_course(step, course): @step(u'The course "([^"]*)" has extra tab "([^"]*)"$') def add_tab_to_course(step, course, extra_tab_name): - section_item = ItemFactory.create(parent_location=course_location(course), + section_item = world.ItemFactory.create(parent_location=course_location(course), template="i4x://edx/templates/static_tab/Empty", display_name=str(extra_tab_name)) diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py index a6575c3d22..a2d37ff7d8 100644 --- a/lms/djangoapps/courseware/features/problems.py +++ b/lms/djangoapps/courseware/features/problems.py @@ -4,7 +4,6 @@ from selenium.webdriver.support.ui import Select import random import textwrap from common import i_am_registered_for_the_course, TEST_SECTION_NAME, section_location -from terrain.factories import ItemFactory from capa.tests.response_xml_factory import OptionResponseXMLFactory, \ ChoiceResponseXMLFactory, MultipleChoiceResponseXMLFactory, \ StringResponseXMLFactory, NumericalResponseXMLFactory, \ @@ -92,7 +91,7 @@ def add_problem_to_course(course, problem_type): # Create a problem item using our generated XML # We set rerandomize=always in the metadata so that the "Reset" button # will appear. - problem_item = ItemFactory.create(parent_location=section_location(course), + problem_item = world.ItemFactory.create(parent_location=section_location(course), template="i4x://edx/templates/problem/Blank_Common_Problem", display_name=str(problem_type), data=problem_xml, diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index cec006e630..fe580f1ebe 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -8,6 +8,7 @@ import factory from django.contrib.auth.models import User from student.models import UserProfile, CourseEnrollment from django_comment_client.models import Role, Permission +from student.tests.factories import UserFactory, CourseEnrollmentFactory import django_comment_client.models as models import django_comment_client.utils as utils @@ -15,21 +16,6 @@ import django_comment_client.utils as utils import xmodule.modulestore.django as django -class UserFactory(factory.Factory): - FACTORY_FOR = User - username = 'robot' - password = '123456' - email = 'robot@edx.org' - is_active = True - is_staff = False - - -class CourseEnrollmentFactory(factory.Factory): - FACTORY_FOR = CourseEnrollment - user = factory.SubFactory(UserFactory) - course_id = 'edX/toy/2012_Fall' - - class RoleFactory(factory.Factory): FACTORY_FOR = Role name = 'Student' From 16773aac638a3055df3cae4185455ec5df5b6201 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Tue, 19 Mar 2013 17:09:57 -0400 Subject: [PATCH 5/9] More factory refactoring. --- common/djangoapps/terrain/factories.py | 4 ++-- lms/djangoapps/courseware/features/common.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index 3a3381c74c..88aa94ef00 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -38,7 +38,7 @@ class GroupFactory(GroupFactory): @world.absorb -class CourseEnrollment(CourseEnrollment): +class CourseEnrollmentFactory(CourseEnrollment): """ Courses that the user is enrolled in """ @@ -46,7 +46,7 @@ class CourseEnrollment(CourseEnrollment): @world.absorb -class CourseEnrollmentAllowed(CourseEnrollmentAllowed): +class CourseEnrollmentAllowedFactory(CourseEnrollmentAllowed): """ Users allowed to enroll in the course outside of the usual window """ diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index bdbd952caa..eff3ce3743 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -1,8 +1,6 @@ from lettuce import world, step -from django.core.management import call_command from nose.tools import assert_equals, assert_in from lettuce.django import django_url -from django.conf import settings from django.contrib.auth.models import User from student.models import CourseEnrollment from xmodule.modulestore import Location @@ -123,6 +121,7 @@ def i_am_registered_for_the_course(step, course): u = User.objects.get(username='robot') # If the user is not already enrolled, enroll the user. + # TODO: change to factory CourseEnrollment.objects.get_or_create(user=u, course_id=course_id(course)) world.log_in('robot@edx.org', 'test') From e45ccbf38929274d0be1a974925b2169c6f58225 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 20 Mar 2013 11:12:54 -0400 Subject: [PATCH 6/9] Leave CourseEnrollment factor refactoring as a TODO. --- common/djangoapps/terrain/factories.py | 10 +--------- .../django_comment_client/tests/test_utils.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index 88aa94ef00..1dedff133b 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -1,6 +1,6 @@ from student.tests.factories import (UserFactory, UserProfileFactory, RegistrationFactory, GroupFactory, - CourseEnrollmentAllowed, CourseEnrollment, + CourseEnrollmentAllowed, CourseFactory, ItemFactory) from lettuce import world @@ -37,14 +37,6 @@ class GroupFactory(GroupFactory): pass -@world.absorb -class CourseEnrollmentFactory(CourseEnrollment): - """ - Courses that the user is enrolled in - """ - pass - - @world.absorb class CourseEnrollmentAllowedFactory(CourseEnrollmentAllowed): """ diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index fe580f1ebe..cec006e630 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -8,7 +8,6 @@ import factory from django.contrib.auth.models import User from student.models import UserProfile, CourseEnrollment from django_comment_client.models import Role, Permission -from student.tests.factories import UserFactory, CourseEnrollmentFactory import django_comment_client.models as models import django_comment_client.utils as utils @@ -16,6 +15,21 @@ import django_comment_client.utils as utils import xmodule.modulestore.django as django +class UserFactory(factory.Factory): + FACTORY_FOR = User + username = 'robot' + password = '123456' + email = 'robot@edx.org' + is_active = True + is_staff = False + + +class CourseEnrollmentFactory(factory.Factory): + FACTORY_FOR = CourseEnrollment + user = factory.SubFactory(UserFactory) + course_id = 'edX/toy/2012_Fall' + + class RoleFactory(factory.Factory): FACTORY_FOR = Role name = 'Student' From 6048bc28320225db3b2f09cf9ff4742ce36519d1 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 20 Mar 2013 11:31:12 -0400 Subject: [PATCH 7/9] Pep8 fixes for factory refactor --- common/djangoapps/student/tests/factories.py | 12 +++--- common/djangoapps/terrain/factories.py | 22 +++++----- lms/djangoapps/courseware/features/common.py | 16 +++---- lms/djangoapps/courseware/features/courses.py | 9 ++-- .../courseware/features/problems.py | 42 +++++++++---------- .../courseware/features/smart-accordion.py | 6 +-- 6 files changed, 53 insertions(+), 54 deletions(-) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 2acc235ce2..1f2378a8c9 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -1,4 +1,4 @@ -from student.models import (User, UserProfile, Registration, +from student.models import (User, UserProfile, Registration, CourseEnrollmentAllowed, CourseEnrollment) from django.contrib.auth.models import Group from datetime import datetime @@ -101,10 +101,10 @@ class XModuleCourseFactory(Factory): new_course.lms.start = gmtime() new_course.tabs = [{"type": "courseware"}, - {"type": "course_info", "name": "Course Info"}, - {"type": "discussion", "name": "Discussion"}, - {"type": "wiki", "name": "Wiki"}, - {"type": "progress", "name": "Progress"}] + {"type": "course_info", "name": "Course Info"}, + {"type": "discussion", "name": "Discussion"}, + {"type": "wiki", "name": "Wiki"}, + {"type": "progress", "name": "Progress"}] # Update the data in the mongo datastore store.update_metadata(new_course.location.url(), own_metadata(new_course)) @@ -170,7 +170,7 @@ class XModuleItemFactory(Factory): # If a display name is set, use that dest_name = display_name.replace(" ", "_") if display_name is not None else uuid4().hex dest_location = parent_location._replace(category=template.category, - name=dest_name) + name=dest_name) new_item = store.clone_item(template, dest_location) diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index 1dedff133b..88e29a18ef 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -1,7 +1,7 @@ from student.tests.factories import (UserFactory, UserProfileFactory, - RegistrationFactory, GroupFactory, - CourseEnrollmentAllowed, - CourseFactory, ItemFactory) + RegistrationFactory, GroupFactory, + CourseEnrollmentAllowed, + CourseFactory, ItemFactory) from lettuce import world @@ -9,7 +9,7 @@ from lettuce import world class UserFactory(UserFactory): """ User account for lms / cms - """ + """ pass @@ -17,7 +17,7 @@ class UserFactory(UserFactory): class UserProfileFactory(UserProfileFactory): """ Demographics etc for the User - """ + """ pass @@ -25,7 +25,7 @@ class UserProfileFactory(UserProfileFactory): class RegistrationFactory(RegistrationFactory): """ Activation key for registering the user account - """ + """ pass @@ -33,7 +33,7 @@ class RegistrationFactory(RegistrationFactory): class GroupFactory(GroupFactory): """ Groups for user permissions for courses - """ + """ pass @@ -41,7 +41,7 @@ class GroupFactory(GroupFactory): class CourseEnrollmentAllowedFactory(CourseEnrollmentAllowed): """ Users allowed to enroll in the course outside of the usual window - """ + """ pass @@ -49,13 +49,13 @@ class CourseEnrollmentAllowedFactory(CourseEnrollmentAllowed): class CourseFactory(CourseFactory): """ Courseware courses - """ + """ pass - + @world.absorb class ItemFactory(ItemFactory): """ Everything included inside a course - """ + """ pass diff --git a/lms/djangoapps/courseware/features/common.py b/lms/djangoapps/courseware/features/common.py index eff3ce3743..d03a59f776 100644 --- a/lms/djangoapps/courseware/features/common.py +++ b/lms/djangoapps/courseware/features/common.py @@ -99,16 +99,16 @@ def create_course(step, course): # We always use the same org and display name, # but vary the course identifier (e.g. 600x or 191x) course = world.CourseFactory.create(org=TEST_COURSE_ORG, - number=course, - display_name=TEST_COURSE_NAME) + number=course, + display_name=TEST_COURSE_NAME) # Add a section to the course to contain problems section = world.ItemFactory.create(parent_location=course.location, - display_name=TEST_SECTION_NAME) + display_name=TEST_SECTION_NAME) problem_section = world.ItemFactory.create(parent_location=section.location, - template='i4x://edx/templates/sequential/Empty', - display_name=TEST_SECTION_NAME) + template='i4x://edx/templates/sequential/Empty', + display_name=TEST_SECTION_NAME) @step(u'I am registered for the course "([^"]*)"$') @@ -130,8 +130,8 @@ def i_am_registered_for_the_course(step, course): @step(u'The course "([^"]*)" has extra tab "([^"]*)"$') def add_tab_to_course(step, course, extra_tab_name): section_item = world.ItemFactory.create(parent_location=course_location(course), - template="i4x://edx/templates/static_tab/Empty", - display_name=str(extra_tab_name)) + template="i4x://edx/templates/static_tab/Empty", + display_name=str(extra_tab_name)) @step(u'I am an edX user$') @@ -159,7 +159,7 @@ def flush_xmodule_store(): def course_id(course_num): return "%s/%s/%s" % (TEST_COURSE_ORG, course_num, - TEST_COURSE_NAME.replace(" ", "_")) + TEST_COURSE_NAME.replace(" ", "_")) def course_location(course_num): diff --git a/lms/djangoapps/courseware/features/courses.py b/lms/djangoapps/courseware/features/courses.py index 4fbbfd24f2..c99fb58b85 100644 --- a/lms/djangoapps/courseware/features/courses.py +++ b/lms/djangoapps/courseware/features/courses.py @@ -83,13 +83,13 @@ def get_courseware_with_tabs(course_id): course = get_course_by_id(course_id) chapters = [chapter for chapter in course.get_children() if not chapter.lms.hide_from_toc] courseware = [{'chapter_name': c.display_name_with_default, - 'sections': [{'section_name': s.display_name_with_default, + 'sections': [{'section_name': s.display_name_with_default, 'clickable_tab_count': len(s.get_children()) if (type(s) == seq_module.SequenceDescriptor) else 0, 'tabs': [{'children_count': len(t.get_children()) if (type(t) == vertical_module.VerticalDescriptor) else 0, - 'class': t.__class__.__name__} - for t in s.get_children()]} + 'class': t.__class__.__name__} + for t in s.get_children()]} for s in c.get_children() if not s.lms.hide_from_toc]} - for c in chapters] + for c in chapters] return courseware @@ -168,7 +168,6 @@ def process_section(element, num_tabs=0): assert False, "Class for element not recognized!!" - def process_problem(element, problem_id): ''' Process problem attempts to diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py index a2d37ff7d8..9f3a483c57 100644 --- a/lms/djangoapps/courseware/features/problems.py +++ b/lms/djangoapps/courseware/features/problems.py @@ -5,9 +5,9 @@ import random import textwrap from common import i_am_registered_for_the_course, TEST_SECTION_NAME, section_location from capa.tests.response_xml_factory import OptionResponseXMLFactory, \ - ChoiceResponseXMLFactory, MultipleChoiceResponseXMLFactory, \ - StringResponseXMLFactory, NumericalResponseXMLFactory, \ - FormulaResponseXMLFactory, CustomResponseXMLFactory + ChoiceResponseXMLFactory, MultipleChoiceResponseXMLFactory, \ + StringResponseXMLFactory, NumericalResponseXMLFactory, \ + FormulaResponseXMLFactory, CustomResponseXMLFactory # Factories from capa.tests.response_xml_factory that we will use # to generate the problem XML, with the keyword args used to configure @@ -77,7 +77,7 @@ PROBLEM_FACTORY_DICT = { a2=0 return (a1+a2)==int(expect) """)}}, - } +} def add_problem_to_course(course, problem_type): @@ -92,10 +92,10 @@ def add_problem_to_course(course, problem_type): # We set rerandomize=always in the metadata so that the "Reset" button # will appear. problem_item = world.ItemFactory.create(parent_location=section_location(course), - template="i4x://edx/templates/problem/Blank_Common_Problem", - display_name=str(problem_type), - data=problem_xml, - metadata={'rerandomize': 'always'}) + template="i4x://edx/templates/problem/Blank_Common_Problem", + display_name=str(problem_type), + data=problem_xml, + metadata={'rerandomize': 'always'}) @step(u'I am viewing a "([^"]*)" problem') @@ -201,21 +201,21 @@ def assert_answer_mark(step, problem_type, correctness): # depending on whether the user selects an incorrect # item or submits without selecting any item) correct_selectors = {'drop down': ['span.correct'], - 'multiple choice': ['label.choicegroup_correct'], - 'checkbox': ['span.correct'], - 'string': ['div.correct'], - 'numerical': ['div.correct'], - 'formula': ['div.correct'], - 'script': ['div.correct'], } + 'multiple choice': ['label.choicegroup_correct'], + 'checkbox': ['span.correct'], + 'string': ['div.correct'], + 'numerical': ['div.correct'], + 'formula': ['div.correct'], + 'script': ['div.correct'], } incorrect_selectors = {'drop down': ['span.incorrect'], 'multiple choice': ['label.choicegroup_incorrect', - 'span.incorrect'], - 'checkbox': ['span.incorrect'], - 'string': ['div.incorrect'], - 'numerical': ['div.incorrect'], - 'formula': ['div.incorrect'], - 'script': ['div.incorrect']} + 'span.incorrect'], + 'checkbox': ['span.incorrect'], + 'string': ['div.incorrect'], + 'numerical': ['div.incorrect'], + 'formula': ['div.incorrect'], + 'script': ['div.incorrect']} assert(correctness in ['correct', 'incorrect', 'unanswered']) assert(problem_type in correct_selectors and problem_type in incorrect_selectors) @@ -257,7 +257,7 @@ def inputfield(problem_type, choice=None, input_num=1): of checkboxes. """ sel = ("input#input_i4x-edx-model_course-problem-%s_2_%s" % - (problem_type.replace(" ", "_"), str(input_num))) + (problem_type.replace(" ", "_"), str(input_num))) if choice is not None: base = "_choice_" if problem_type == "multiple choice" else "_" diff --git a/lms/djangoapps/courseware/features/smart-accordion.py b/lms/djangoapps/courseware/features/smart-accordion.py index 7c4770d632..a7eb782722 100644 --- a/lms/djangoapps/courseware/features/smart-accordion.py +++ b/lms/djangoapps/courseware/features/smart-accordion.py @@ -81,7 +81,7 @@ def browse_course(course_id): num_rendered_sections = len(rendered_sections) msg = ('%d sections expected, %d sections found on page, %s - %d - %s' % - (num_sections, num_rendered_sections, course_id, chapter_it, chapters[chapter_it]['chapter_name'])) + (num_sections, num_rendered_sections, course_id, chapter_it, chapters[chapter_it]['chapter_name'])) #logger.debug(msg) assert num_sections == num_rendered_sections, msg @@ -112,7 +112,7 @@ def browse_course(course_id): num_rendered_tabs = 0 msg = ('%d tabs expected, %d tabs found, %s - %d - %s' % - (num_tabs, num_rendered_tabs, course_id, section_it, sections[section_it]['section_name'])) + (num_tabs, num_rendered_tabs, course_id, section_it, sections[section_it]['section_name'])) #logger.debug(msg) # Save the HTML to a file for later comparison @@ -137,7 +137,7 @@ def browse_course(course_id): rendered_items = world.browser.find_by_css('div#seq_content > section > ol > li > section') num_rendered_items = len(rendered_items) msg = ('%d items expected, %d items found, %s - %d - %s - tab %d' % - (tab_children, num_rendered_items, course_id, section_it, sections[section_it]['section_name'], tab_it)) + (tab_children, num_rendered_items, course_id, section_it, sections[section_it]['section_name'], tab_it)) #logger.debug(msg) assert tab_children == num_rendered_items, msg From 192b99133461ec107a0af4240d54d24b45872746 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 20 Mar 2013 11:52:15 -0400 Subject: [PATCH 8/9] Pylint for lettuce factory refactor. --- common/djangoapps/terrain/browser.py | 14 +++++++++---- common/djangoapps/terrain/factories.py | 27 +++++++++++++++----------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py index 6394959532..c8cc0c9e4b 100644 --- a/common/djangoapps/terrain/browser.py +++ b/common/djangoapps/terrain/browser.py @@ -1,7 +1,6 @@ from lettuce import before, after, world from splinter.browser import Browser from logging import getLogger -import time # Let the LMS and CMS do their one-time setup # For example, setting up mongo caches @@ -16,6 +15,9 @@ from django.core.management import call_command @before.harvest def initial_setup(server): + ''' + Launch the browser once before executing the tests + ''' # Launch the browser app (choose one of these below) world.browser = Browser('chrome') # world.browser = Browser('phantomjs') @@ -24,14 +26,18 @@ def initial_setup(server): @before.each_scenario def reset_data(scenario): - # Clean out the django test database defined in the - # envs/acceptance.py file: mitx_all/db/test_mitx.db + ''' + Clean out the django test database defined in the + envs/acceptance.py file: mitx_all/db/test_mitx.db + ''' logger.debug("Flushing the test database...") call_command('flush', interactive=False) @after.all def teardown_browser(total): - # Quit firefox + ''' + Quit the browser after executing the tests + ''' world.browser.quit() pass diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index 88e29a18ef..d7a1de6780 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -1,12 +1,17 @@ -from student.tests.factories import (UserFactory, UserProfileFactory, - RegistrationFactory, GroupFactory, - CourseEnrollmentAllowed, - CourseFactory, ItemFactory) +''' +Factories are defined in other modules and absorbed here into the +lettuce world so that they can be used by both unit tests +and integration / BDD tests. + +TODO: move the course and item factories out of student and into +xmodule/modulestore +''' +import student.tests.factories as sf from lettuce import world @world.absorb -class UserFactory(UserFactory): +class UserFactory(sf.UserFactory): """ User account for lms / cms """ @@ -14,7 +19,7 @@ class UserFactory(UserFactory): @world.absorb -class UserProfileFactory(UserProfileFactory): +class UserProfileFactory(sf.UserProfileFactory): """ Demographics etc for the User """ @@ -22,7 +27,7 @@ class UserProfileFactory(UserProfileFactory): @world.absorb -class RegistrationFactory(RegistrationFactory): +class RegistrationFactory(sf.RegistrationFactory): """ Activation key for registering the user account """ @@ -30,7 +35,7 @@ class RegistrationFactory(RegistrationFactory): @world.absorb -class GroupFactory(GroupFactory): +class GroupFactory(sf.GroupFactory): """ Groups for user permissions for courses """ @@ -38,7 +43,7 @@ class GroupFactory(GroupFactory): @world.absorb -class CourseEnrollmentAllowedFactory(CourseEnrollmentAllowed): +class CourseEnrollmentAllowedFactory(sf.CourseEnrollmentAllowed): """ Users allowed to enroll in the course outside of the usual window """ @@ -46,7 +51,7 @@ class CourseEnrollmentAllowedFactory(CourseEnrollmentAllowed): @world.absorb -class CourseFactory(CourseFactory): +class CourseFactory(sf.CourseFactory): """ Courseware courses """ @@ -54,7 +59,7 @@ class CourseFactory(CourseFactory): @world.absorb -class ItemFactory(ItemFactory): +class ItemFactory(sf.ItemFactory): """ Everything included inside a course """ From 5eba299dca0d3d3904d3b9e8c6295acc59a656cd Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 20 Mar 2013 12:10:18 -0400 Subject: [PATCH 9/9] Move course and item factories to xmodule.modulestore.tests --- common/djangoapps/student/tests/factories.py | 146 ------------------ common/djangoapps/terrain/factories.py | 8 +- .../xmodule/modulestore/tests/factories.py | 41 ++++- 3 files changed, 36 insertions(+), 159 deletions(-) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 1f2378a8c9..f74188725a 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -3,12 +3,7 @@ from student.models import (User, UserProfile, Registration, from django.contrib.auth.models import Group from datetime import datetime from factory import Factory, SubFactory -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore -from time import gmtime from uuid import uuid4 -from xmodule.timeparse import stringify_time -from xmodule.modulestore.inheritance import own_metadata class GroupFactory(Factory): @@ -62,144 +57,3 @@ class CourseEnrollmentAllowedFactory(Factory): email = 'test@edx.org' course_id = 'edX/test/2012_Fall' - - -def XMODULE_COURSE_CREATION(class_to_create, **kwargs): - return XModuleCourseFactory._create(class_to_create, **kwargs) - - -def XMODULE_ITEM_CREATION(class_to_create, **kwargs): - return XModuleItemFactory._create(class_to_create, **kwargs) - - -class XModuleCourseFactory(Factory): - """ - Factory for XModule courses. - """ - - ABSTRACT_FACTORY = True - _creation_function = (XMODULE_COURSE_CREATION,) - - @classmethod - def _create(cls, target_class, *args, **kwargs): - - template = Location('i4x', 'edx', 'templates', 'course', 'Empty') - org = kwargs.get('org') - number = kwargs.get('number') - display_name = kwargs.get('display_name') - location = Location('i4x', org, number, - 'course', Location.clean(display_name)) - - store = modulestore('direct') - - # Write the data to the mongo datastore - new_course = store.clone_item(template, location) - - # This metadata code was copied from cms/djangoapps/contentstore/views.py - if display_name is not None: - new_course.display_name = display_name - - new_course.lms.start = gmtime() - new_course.tabs = [{"type": "courseware"}, - {"type": "course_info", "name": "Course Info"}, - {"type": "discussion", "name": "Discussion"}, - {"type": "wiki", "name": "Wiki"}, - {"type": "progress", "name": "Progress"}] - - # Update the data in the mongo datastore - store.update_metadata(new_course.location.url(), own_metadata(new_course)) - - return new_course - - -class Course: - pass - - -class CourseFactory(XModuleCourseFactory): - FACTORY_FOR = Course - - template = 'i4x://edx/templates/course/Empty' - org = 'MITx' - number = '999' - display_name = 'Robot Super Course' - - -class XModuleItemFactory(Factory): - """ - Factory for XModule items. - """ - - ABSTRACT_FACTORY = True - _creation_function = (XMODULE_ITEM_CREATION,) - - @classmethod - def _create(cls, target_class, *args, **kwargs): - """ - Uses *kwargs*: - - *parent_location* (required): the location of the parent module - (e.g. the parent course or section) - - *template* (required): the template to create the item from - (e.g. i4x://templates/section/Empty) - - *data* (optional): the data for the item - (e.g. XML problem definition for a problem item) - - *display_name* (optional): the display name of the item - - *metadata* (optional): dictionary of metadata attributes - - *target_class* is ignored - """ - - DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] - - parent_location = Location(kwargs.get('parent_location')) - template = Location(kwargs.get('template')) - data = kwargs.get('data') - display_name = kwargs.get('display_name') - metadata = kwargs.get('metadata', {}) - - store = modulestore('direct') - - # This code was based off that in cms/djangoapps/contentstore/views.py - parent = store.get_item(parent_location) - - # If a display name is set, use that - dest_name = display_name.replace(" ", "_") if display_name is not None else uuid4().hex - dest_location = parent_location._replace(category=template.category, - name=dest_name) - - new_item = store.clone_item(template, dest_location) - - # replace the display name with an optional parameter passed in from the caller - if display_name is not None: - new_item.display_name = display_name - - # Add additional metadata or override current metadata - item_metadata = own_metadata(new_item) - item_metadata.update(metadata) - store.update_metadata(new_item.location.url(), item_metadata) - - # replace the data with the optional *data* parameter - if data is not None: - store.update_item(new_item.location, data) - - if new_item.location.category not in DETACHED_CATEGORIES: - store.update_children(parent_location, parent.children + [new_item.location.url()]) - - return new_item - - -class Item: - pass - - -class ItemFactory(XModuleItemFactory): - FACTORY_FOR = Item - - parent_location = 'i4x://MITx/999/course/Robot_Super_Course' - template = 'i4x://edx/templates/chapter/Empty' - display_name = 'Section One' diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index d7a1de6780..768c51b25e 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -2,11 +2,9 @@ Factories are defined in other modules and absorbed here into the lettuce world so that they can be used by both unit tests and integration / BDD tests. - -TODO: move the course and item factories out of student and into -xmodule/modulestore ''' import student.tests.factories as sf +import xmodule.modulestore.tests.factories as xf from lettuce import world @@ -51,7 +49,7 @@ class CourseEnrollmentAllowedFactory(sf.CourseEnrollmentAllowed): @world.absorb -class CourseFactory(sf.CourseFactory): +class CourseFactory(xf.CourseFactory): """ Courseware courses """ @@ -59,7 +57,7 @@ class CourseFactory(sf.CourseFactory): @world.absorb -class ItemFactory(sf.ItemFactory): +class ItemFactory(xf.ItemFactory): """ Everything included inside a course """ diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index b842ffe9dd..1a82e1b708 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -25,8 +25,7 @@ class XModuleCourseFactory(Factory): @classmethod def _create(cls, target_class, *args, **kwargs): - # This logic was taken from the create_new_course method in - # cms/djangoapps/contentstore/views.py + template = Location('i4x', 'edx', 'templates', 'course', 'Empty') org = kwargs.get('org') number = kwargs.get('number') @@ -43,8 +42,7 @@ class XModuleCourseFactory(Factory): if display_name is not None: new_course.display_name = display_name - new_course.start = gmtime() - + new_course.lms.start = gmtime() new_course.tabs = [{"type": "courseware"}, {"type": "course_info", "name": "Course Info"}, {"type": "discussion", "name": "Discussion"}, @@ -81,21 +79,41 @@ class XModuleItemFactory(Factory): @classmethod def _create(cls, target_class, *args, **kwargs): """ - kwargs must include parent_location, template. Can contain display_name - target_class is ignored + Uses *kwargs*: + + *parent_location* (required): the location of the parent module + (e.g. the parent course or section) + + *template* (required): the template to create the item from + (e.g. i4x://templates/section/Empty) + + *data* (optional): the data for the item + (e.g. XML problem definition for a problem item) + + *display_name* (optional): the display name of the item + + *metadata* (optional): dictionary of metadata attributes + + *target_class* is ignored """ DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info'] parent_location = Location(kwargs.get('parent_location')) template = Location(kwargs.get('template')) + data = kwargs.get('data') display_name = kwargs.get('display_name') + metadata = kwargs.get('metadata', {}) store = modulestore('direct') # This code was based off that in cms/djangoapps/contentstore/views.py parent = store.get_item(parent_location) - dest_location = parent_location._replace(category=template.category, name=uuid4().hex) + + # If a display name is set, use that + dest_name = display_name.replace(" ", "_") if display_name is not None else uuid4().hex + dest_location = parent_location._replace(category=template.category, + name=dest_name) new_item = store.clone_item(template, dest_location) @@ -103,7 +121,14 @@ class XModuleItemFactory(Factory): if display_name is not None: new_item.display_name = display_name - store.update_metadata(new_item.location.url(), own_metadata(new_item)) + # Add additional metadata or override current metadata + item_metadata = own_metadata(new_item) + item_metadata.update(metadata) + store.update_metadata(new_item.location.url(), item_metadata) + + # replace the data with the optional *data* parameter + if data is not None: + store.update_item(new_item.location, data) if new_item.location.category not in DETACHED_CATEGORIES: store.update_children(parent_location, parent.children + [new_item.location.url()])