From 8a2d08bbd6625fa0e8a8cb1ca2f9e6012293b30f Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 17 Apr 2013 12:58:41 -0400 Subject: [PATCH 1/5] Refactor choosing the browser for lettuce tests to settings.py --- cms/envs/acceptance.py | 1 + common/djangoapps/terrain/browser.py | 19 +++++++++++++------ lms/envs/acceptance.py | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py index 26a8adc92c..1e7a32dc68 100644 --- a/cms/envs/acceptance.py +++ b/cms/envs/acceptance.py @@ -36,3 +36,4 @@ DATABASES = { INSTALLED_APPS += ('lettuce.django',) LETTUCE_APPS = ('contentstore',) LETTUCE_SERVER_PORT = 8001 +LETTUCE_BROWSER = 'chrome' diff --git a/common/djangoapps/terrain/browser.py b/common/djangoapps/terrain/browser.py index c8cc0c9e4b..1d371a3242 100644 --- a/common/djangoapps/terrain/browser.py +++ b/common/djangoapps/terrain/browser.py @@ -1,6 +1,8 @@ from lettuce import before, after, world from splinter.browser import Browser from logging import getLogger +from django.core.management import call_command +from django.conf import settings # Let the LMS and CMS do their one-time setup # For example, setting up mongo caches @@ -10,18 +12,14 @@ from cms import one_time_startup logger = getLogger(__name__) logger.info("Loading the lettuce acceptance testing terrain file...") -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') - # world.browser = Browser('firefox') + browser_driver = getattr(settings, 'LETTUCE_BROWSER', 'chrome') + world.browser = Browser(browser_driver) @before.each_scenario @@ -34,6 +32,15 @@ def reset_data(scenario): call_command('flush', interactive=False) +@after.each_scenario +def screenshot_on_error(scenario): + ''' + Save a screenshot to help with debugging + ''' + if scenario.failed: + world.browser.driver.save_screenshot('/tmp/last_failed_scenario.png') + + @after.all def teardown_browser(total): ''' diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py index 5f416cd189..2c51dda5e6 100644 --- a/lms/envs/acceptance.py +++ b/lms/envs/acceptance.py @@ -67,3 +67,4 @@ MITX_FEATURES['STUB_VIDEO_FOR_TESTING'] = True # Include the lettuce app for acceptance testing, including the 'harvest' django-admin command INSTALLED_APPS += ('lettuce.django',) LETTUCE_APPS = ('courseware',) +LETTUCE_BROWSER = 'chrome' From 8a852f90cb15ff848066a02d0aeb9bc193be327a Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 17 Apr 2013 13:27:33 -0400 Subject: [PATCH 2/5] Fix or skip lettuce tests to run under phantomjs and firefox --- .../contentstore/features/advanced-settings.py | 13 +++++-------- .../contentstore/features/checklists.feature | 5 ++++- cms/djangoapps/contentstore/features/checklists.py | 7 +++---- common/djangoapps/terrain/steps.py | 2 ++ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.py b/cms/djangoapps/contentstore/features/advanced-settings.py index 6fb102faea..ea5b24b21f 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.py +++ b/cms/djangoapps/contentstore/features/advanced-settings.py @@ -3,10 +3,7 @@ from lettuce import world, step from common import * -import time -from terrain.steps import reload_the_page - -from nose.tools import assert_true, assert_false, assert_equal +from nose.tools import assert_false, assert_equal """ http://selenium.googlecode.com/svn/trunk/docs/api/py/webdriver/selenium.webdriver.common.keys.html @@ -18,8 +15,8 @@ VALUE_CSS = 'textarea.json' DISPLAY_NAME_KEY = "display_name" DISPLAY_NAME_VALUE = '"Robot Super Course"' -############### ACTIONS #################### +############### ACTIONS #################### @step('I select the Advanced Settings$') def i_select_advanced_settings(step): expand_icon_css = 'li.nav-course-settings i.icon-expand' @@ -38,7 +35,7 @@ def i_am_on_advanced_course_settings(step): @step(u'I press the "([^"]*)" notification button$') def press_the_notification_button(step, name): css = 'a.%s-button' % name.lower() - world.css_click_at(css) + world.css_click(css) @step(u'I edit the value of a policy key$') @@ -52,7 +49,7 @@ def edit_the_value_of_a_policy_key(step): @step(u'I edit the value of a policy key and save$') -def edit_the_value_of_a_policy_key(step): +def edit_the_value_of_a_policy_key_and_save(step): change_display_name_value(step, '"foo"') @@ -90,7 +87,7 @@ def it_is_formatted(step): @step('it is displayed as a string') -def it_is_formatted(step): +def it_is_displayed_as_string(step): assert_policy_entries([DISPLAY_NAME_KEY], ['"quote me"']) diff --git a/cms/djangoapps/contentstore/features/checklists.feature b/cms/djangoapps/contentstore/features/checklists.feature index bccb80b8d7..ddf1adf263 100644 --- a/cms/djangoapps/contentstore/features/checklists.feature +++ b/cms/djangoapps/contentstore/features/checklists.feature @@ -10,6 +10,8 @@ Feature: Course checklists Then I can check and uncheck tasks in a checklist And They are correctly selected after I reload the page + @skip-phantom + @skip-firefox Scenario: A task can link to a location within Studio Given I have opened Checklists When I select a link to the course outline @@ -17,8 +19,9 @@ Feature: Course checklists And I press the browser back button Then I am brought back to the course outline in the correct state + @skip-phantom + @skip-firefox Scenario: A task can link to a location outside Studio Given I have opened Checklists When I select a link to help page Then I am brought to the help page in a new window - diff --git a/cms/djangoapps/contentstore/features/checklists.py b/cms/djangoapps/contentstore/features/checklists.py index 489544f424..d433dbbf0d 100644 --- a/cms/djangoapps/contentstore/features/checklists.py +++ b/cms/djangoapps/contentstore/features/checklists.py @@ -89,8 +89,6 @@ def i_am_brought_to_help_page_in_new_window(step): assert_equal('http://help.edge.edx.org/', world.browser.url) - - ############### HELPER METHODS #################### def verifyChecklist2Status(completed, total, percentage): def verify_count(driver): @@ -107,9 +105,11 @@ def verifyChecklist2Status(completed, total, percentage): def toggleTask(checklist, task): - world.css_click('#course-checklist' + str(checklist) +'-task' + str(task)) + world.css_click('#course-checklist' + str(checklist) + '-task' + str(task)) +# TODO: figure out a way to do this in phantom and firefox +# For now we will mark the scenerios that use this method as skipped def clickActionLink(checklist, task, actionText): # toggle checklist item to make sure that the link button is showing toggleTask(checklist, task) @@ -121,4 +121,3 @@ def clickActionLink(checklist, task, actionText): world.wait_for(verify_action_link_text) action_link.click() - diff --git a/common/djangoapps/terrain/steps.py b/common/djangoapps/terrain/steps.py index a2db80712f..fdab514177 100644 --- a/common/djangoapps/terrain/steps.py +++ b/common/djangoapps/terrain/steps.py @@ -132,6 +132,8 @@ def i_am_logged_in(step): world.create_user('robot') world.log_in('robot', 'test') world.browser.visit(django_url('/')) + # You should not see the login link + assert_equals(world.browser.find_by_css('a#login'), []) @step(u'I am an edX user$') From 0426761bede3c0938e08c438914e9cbb9a9419fb Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 17 Apr 2013 15:49:29 -0400 Subject: [PATCH 3/5] Update lettuce and factory-boy versions. --- common/djangoapps/student/tests/factories.py | 26 +++++++++----------- requirements.txt | 4 +-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index adb51954e8..952a8c016e 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -2,17 +2,17 @@ from student.models import (User, UserProfile, Registration, CourseEnrollmentAllowed, CourseEnrollment) from django.contrib.auth.models import Group from datetime import datetime -from factory import Factory, SubFactory, post_generation +from factory import DjangoModelFactory, Factory, SubFactory, PostGenerationMethodCall from uuid import uuid4 -class GroupFactory(Factory): +class GroupFactory(DjangoModelFactory): FACTORY_FOR = Group name = 'staff_MITx/999/Robot_Super_Course' -class UserProfileFactory(Factory): +class UserProfileFactory(DjangoModelFactory): FACTORY_FOR = UserProfile user = None @@ -23,19 +23,20 @@ class UserProfileFactory(Factory): goals = 'World domination' -class RegistrationFactory(Factory): +class RegistrationFactory(DjangoModelFactory): FACTORY_FOR = Registration user = None activation_key = uuid4().hex -class UserFactory(Factory): +class UserFactory(DjangoModelFactory): FACTORY_FOR = User username = 'robot' email = 'robot+test@edx.org' - password = 'test' + password = PostGenerationMethodCall('set_password', + 'test') first_name = 'Robot' last_name = 'Test' is_staff = False @@ -44,26 +45,21 @@ class UserFactory(Factory): last_login = datetime(2012, 1, 1) date_joined = datetime(2011, 1, 1) - @post_generation - def set_password(self, create, extracted, **kwargs): - self._raw_password = self.password - self.set_password(self.password) - if create: - self.save() +class AdminFactory(Factory): + FACTORY_FOR = User -class AdminFactory(UserFactory): is_staff = True -class CourseEnrollmentFactory(Factory): +class CourseEnrollmentFactory(DjangoModelFactory): FACTORY_FOR = CourseEnrollment user = SubFactory(UserFactory) course_id = 'edX/toy/2012_Fall' -class CourseEnrollmentAllowedFactory(Factory): +class CourseEnrollmentAllowedFactory(DjangoModelFactory): FACTORY_FOR = CourseEnrollmentAllowed email = 'test@edx.org' diff --git a/requirements.txt b/requirements.txt index a626ac1944..08aaecc71e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -61,8 +61,8 @@ sphinx==1.1.3 # Used for testing coverage==3.6 -factory_boy==1.3.0 -lettuce==0.2.15 +factory_boy==2.0.2 +lettuce==0.2.16 mock==0.8.0 nosexcover==1.0.7 pep8==1.4.5 From 29ce700de6e1b7c68dc532898f14893ed0ff6da3 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Wed, 17 Apr 2013 17:16:01 -0400 Subject: [PATCH 4/5] Tag CMS lettuce tests with @skip-phantom if they aren't working under phantomjs right now. --- .../features/advanced-settings.feature | 4 +++ .../features/course-settings.feature | 3 ++ .../contentstore/features/section.feature | 1 + .../studio-overview-togglesection.feature | 31 ++++++++++--------- .../contentstore/features/subsection.feature | 17 +++++----- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/cms/djangoapps/contentstore/features/advanced-settings.feature b/cms/djangoapps/contentstore/features/advanced-settings.feature index 558294e890..ca5b62e596 100644 --- a/cms/djangoapps/contentstore/features/advanced-settings.feature +++ b/cms/djangoapps/contentstore/features/advanced-settings.feature @@ -11,6 +11,7 @@ Feature: Advanced (manual) course policy Given I am on the Advanced Course Settings page in Studio Then the settings are alphabetized + @skip-phantom Scenario: Test cancel editing key value Given I am on the Advanced Course Settings page in Studio When I edit the value of a policy key @@ -19,6 +20,7 @@ Feature: Advanced (manual) course policy And I reload the page Then the policy key value is unchanged + @skip-phantom Scenario: Test editing key value Given I am on the Advanced Course Settings page in Studio When I edit the value of a policy key and save @@ -26,6 +28,7 @@ Feature: Advanced (manual) course policy And I reload the page Then the policy key value is changed + @skip-phantom Scenario: Test how multi-line input appears Given I am on the Advanced Course Settings page in Studio When I create a JSON object as a value @@ -33,6 +36,7 @@ Feature: Advanced (manual) course policy And I reload the page Then it is displayed as formatted + @skip-phantom Scenario: Test automatic quoting of non-JSON values Given I am on the Advanced Course Settings page in Studio When I create a non-JSON value not in quotes diff --git a/cms/djangoapps/contentstore/features/course-settings.feature b/cms/djangoapps/contentstore/features/course-settings.feature index e869bfe47a..fc9641cb46 100644 --- a/cms/djangoapps/contentstore/features/course-settings.feature +++ b/cms/djangoapps/contentstore/features/course-settings.feature @@ -1,17 +1,20 @@ Feature: Course Settings As a course author, I want to be able to configure my course settings. + @skip-phantom Scenario: User can set course dates Given I have opened a new course in Studio When I select Schedule and Details And I set course dates Then I see the set dates on refresh + @skip-phantom Scenario: User can clear previously set course dates (except start date) Given I have set course dates And I clear all the dates except start Then I see cleared dates on refresh + @skip-phantom Scenario: User cannot clear the course start date Given I have set course dates And I clear the course start date diff --git a/cms/djangoapps/contentstore/features/section.feature b/cms/djangoapps/contentstore/features/section.feature index 08d38367bc..24cbeb3db9 100644 --- a/cms/djangoapps/contentstore/features/section.feature +++ b/cms/djangoapps/contentstore/features/section.feature @@ -3,6 +3,7 @@ Feature: Create Section As a course author I want to create and edit sections + @skip-phantom Scenario: Add a new section to a course Given I have opened a new course in Studio When I click the New Section link diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature index 762dea6838..a0e0a48f9e 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature +++ b/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature @@ -1,32 +1,33 @@ Feature: Overview Toggle Section In order to quickly view the details of a course's section or to scan the inventory of sections - As a course author - I want to toggle the visibility of each section's subsection details in the overview listing + As a course author + I want to toggle the visibility of each section's subsection details in the overview listing Scenario: The default layout for the overview page is to show sections in expanded view Given I have a course with multiple sections - When I navigate to the course overview page - Then I see the "Collapse All Sections" link - And all sections are expanded + When I navigate to the course overview page + Then I see the "Collapse All Sections" link + And all sections are expanded Scenario: Expand /collapse for a course with no sections Given I have a course with no sections - When I navigate to the course overview page - Then I do not see the "Collapse All Sections" link + When I navigate to the course overview page + Then I do not see the "Collapse All Sections" link + @skip-phantom Scenario: Collapse link appears after creating first section of a course Given I have a course with no sections - When I navigate to the course overview page - And I add a section - Then I see the "Collapse All Sections" link - And all sections are expanded + When I navigate to the course overview page + And I add a section + Then I see the "Collapse All Sections" link + And all sections are expanded @skip-phantom Scenario: Collapse link is not removed after last section of a course is deleted Given I have a course with 1 section - And I navigate to the course overview page - When I press the "section" delete icon - And I confirm the alert + And I navigate to the course overview page + When I press the "section" delete icon + And I confirm the alert Then I see the "Collapse All Sections" link Scenario: Collapsing all sections when all sections are expanded @@ -57,4 +58,4 @@ Feature: Overview Toggle Section When I expand the first section And I click the "Expand All Sections" link Then I see the "Collapse All Sections" link - And all sections are expanded \ No newline at end of file + And all sections are expanded diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature index cc3b2b1cbb..28285bf8a1 100644 --- a/cms/djangoapps/contentstore/features/subsection.feature +++ b/cms/djangoapps/contentstore/features/subsection.feature @@ -3,13 +3,15 @@ Feature: Create Subsection As a course author I want to create and edit subsections - Scenario: Add a new subsection to a section + @skip-phantom + Scenario: Add a new subsection to a section Given I have opened a new course section in Studio When I click the New Subsection link And I enter the subsection name and click save Then I see my subsection on the Courseware page - Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216) + @skip-phantom + Scenario: Add a new subsection (with a name containing a quote) to a section (bug #216) Given I have opened a new course section in Studio When I click the New Subsection link And I enter a subsection name with a quote and click save @@ -17,7 +19,7 @@ Feature: Create Subsection And I click to edit the subsection name Then I see the complete subsection name with a quote in the editor - Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258) + Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258) Given I have opened a new course section in Studio And I have added a new subsection And I mark it as Homework @@ -25,20 +27,19 @@ Feature: Create Subsection And I reload the page Then I see it marked as Homework - Scenario: Set a due date in a different year (bug #256) + @skip-phantom + Scenario: Set a due date in a different year (bug #256) Given I have opened a new subsection in Studio And I have set a release date and due date in different years Then I see the correct dates And I reload the page Then I see the correct dates - @skip-phantom - Scenario: Delete a subsection + @skip-phantom + Scenario: Delete a subsection Given I have opened a new course section in Studio And I have added a new subsection And I see my subsection on the Courseware page When I press the "subsection" delete icon And I confirm the alert Then the subsection does not exist - - From be76f731071b9a7f30844dd4820a0c7d5dda8a95 Mon Sep 17 00:00:00 2001 From: Jay Zoldak Date: Thu, 18 Apr 2013 13:20:51 -0400 Subject: [PATCH 5/5] Consolidate and upgrade factories --- common/djangoapps/student/tests/factories.py | 4 +- lms/djangoapps/courseware/tests/factories.py | 91 ++++++++++++------- .../courseware/tests/test_model_data.py | 67 +++----------- .../django_comment_client/tests/test_utils.py | 32 +------ lms/djangoapps/licenses/tests.py | 25 +++-- 5 files changed, 93 insertions(+), 126 deletions(-) diff --git a/common/djangoapps/student/tests/factories.py b/common/djangoapps/student/tests/factories.py index 952a8c016e..0d9621fc01 100644 --- a/common/djangoapps/student/tests/factories.py +++ b/common/djangoapps/student/tests/factories.py @@ -46,9 +46,7 @@ class UserFactory(DjangoModelFactory): date_joined = datetime(2011, 1, 1) -class AdminFactory(Factory): - FACTORY_FOR = User - +class AdminFactory(UserFactory): is_staff = True diff --git a/lms/djangoapps/courseware/tests/factories.py b/lms/djangoapps/courseware/tests/factories.py index df072c015c..af33ba1211 100644 --- a/lms/djangoapps/courseware/tests/factories.py +++ b/lms/djangoapps/courseware/tests/factories.py @@ -1,62 +1,85 @@ -import factory -from student.models import (User, UserProfile, Registration, - CourseEnrollmentAllowed) -from courseware.models import StudentModule -from django.contrib.auth.models import Group from datetime import datetime -import uuid +import json +from functools import partial + +from factory import DjangoModelFactory, SubFactory +from student.tests.factories import UserFactory as StudentUserFactory +from student.tests.factories import GroupFactory as StudentGroupFactory +from student.tests.factories import UserProfileFactory as StudentUserProfileFactory +from student.tests.factories import CourseEnrollmentAllowedFactory as StudentCourseEnrollmentAllowedFactory +from student.tests.factories import RegistrationFactory as StudentRegistrationFactory +from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField +from courseware.models import XModuleStudentInfoField, XModuleStudentPrefsField + +from xmodule.modulestore import Location + +location = partial(Location, 'i4x', 'edX', 'test_course', 'problem') -class UserProfileFactory(factory.Factory): - FACTORY_FOR = UserProfile - - user = None +class UserProfileFactory(StudentUserProfileFactory): name = 'Robot Studio' courseware = 'course.xml' -class RegistrationFactory(factory.Factory): - FACTORY_FOR = Registration - - user = None - activation_key = uuid.uuid4().hex +class RegistrationFactory(StudentRegistrationFactory): + pass -class UserFactory(factory.Factory): - FACTORY_FOR = User - - username = 'robot' +class UserFactory(StudentUserFactory): email = 'robot@edx.org' - password = 'test' - first_name = 'Robot' last_name = 'Tester' - is_staff = False - is_active = True - is_superuser = False last_login = datetime.now() date_joined = datetime.now() -class GroupFactory(factory.Factory): - FACTORY_FOR = Group - +class GroupFactory(StudentGroupFactory): name = 'test_group' -class CourseEnrollmentAllowedFactory(factory.Factory): - FACTORY_FOR = CourseEnrollmentAllowed - - email = 'test@edx.org' - course_id = 'edX/test/2012_Fall' +class CourseEnrollmentAllowedFactory(StudentCourseEnrollmentAllowedFactory): + pass -class StudentModuleFactory(factory.Factory): +class StudentModuleFactory(DjangoModelFactory): FACTORY_FOR = StudentModule module_type = "problem" - student = factory.SubFactory(UserFactory) + student = SubFactory(UserFactory) course_id = "MITx/999/Robot_Super_Course" state = None grade = None max_grade = None done = 'na' + + +class ContentFactory(DjangoModelFactory): + FACTORY_FOR = XModuleContentField + + field_name = 'existing_field' + value = json.dumps('old_value') + definition_id = location('def_id').url() + + +class SettingsFactory(DjangoModelFactory): + FACTORY_FOR = XModuleSettingsField + + field_name = 'existing_field' + value = json.dumps('old_value') + usage_id = '%s-%s' % ('edX/test_course/test', location('def_id').url()) + + +class StudentPrefsFactory(DjangoModelFactory): + FACTORY_FOR = XModuleStudentPrefsField + + field_name = 'existing_field' + value = json.dumps('old_value') + student = SubFactory(UserFactory) + module_type = 'problem' + + +class StudentInfoFactory(DjangoModelFactory): + FACTORY_FOR = XModuleStudentInfoField + + field_name = 'existing_field' + value = json.dumps('old_value') + student = SubFactory(UserFactory) diff --git a/lms/djangoapps/courseware/tests/test_model_data.py b/lms/djangoapps/courseware/tests/test_model_data.py index 65eaa5a4bd..0966fb1aeb 100644 --- a/lms/djangoapps/courseware/tests/test_model_data.py +++ b/lms/djangoapps/courseware/tests/test_model_data.py @@ -1,15 +1,19 @@ -import factory import json from mock import Mock -from django.contrib.auth.models import User - from functools import partial -from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError, ModelDataCache -from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField +from courseware.model_data import LmsKeyValueStore, InvalidWriteError +from courseware.model_data import InvalidScopeError, ModelDataCache +from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField +from courseware.models import XModuleStudentInfoField, XModuleStudentPrefsField + +from student.tests.factories import UserFactory +from courseware.tests.factories import StudentModuleFactory as cmfStudentModuleFactory +from courseware.tests.factories import ContentFactory, SettingsFactory +from courseware.tests.factories import StudentPrefsFactory, StudentInfoFactory + from xblock.core import Scope, BlockScope from xmodule.modulestore import Location - from django.test import TestCase @@ -19,6 +23,7 @@ def mock_field(scope, name): field.name = name return field + def mock_descriptor(fields=[], lms_fields=[]): descriptor = Mock() descriptor.stores_state = True @@ -37,53 +42,9 @@ prefs_key = partial(LmsKeyValueStore.Key, Scope.preferences, 'user', 'problem') user_info_key = partial(LmsKeyValueStore.Key, Scope.user_info, 'user', None) -class UserFactory(factory.Factory): - FACTORY_FOR = User - - username = 'user' - - -class StudentModuleFactory(factory.Factory): - FACTORY_FOR = StudentModule - - module_type = 'problem' +class StudentModuleFactory(cmfStudentModuleFactory): module_state_key = location('def_id').url() - student = factory.SubFactory(UserFactory) course_id = course_id - state = None - - -class ContentFactory(factory.Factory): - FACTORY_FOR = XModuleContentField - - field_name = 'existing_field' - value = json.dumps('old_value') - definition_id = location('def_id').url() - - -class SettingsFactory(factory.Factory): - FACTORY_FOR = XModuleSettingsField - - field_name = 'existing_field' - value = json.dumps('old_value') - usage_id = '%s-%s' % (course_id, location('def_id').url()) - - -class StudentPrefsFactory(factory.Factory): - FACTORY_FOR = XModuleStudentPrefsField - - field_name = 'existing_field' - value = json.dumps('old_value') - student = factory.SubFactory(UserFactory) - module_type = 'problem' - - -class StudentInfoFactory(factory.Factory): - FACTORY_FOR = XModuleStudentInfoField - - field_name = 'existing_field' - value = json.dumps('old_value') - student = factory.SubFactory(UserFactory) class TestDescriptorFallback(TestCase): @@ -114,7 +75,7 @@ class TestDescriptorFallback(TestCase): class TestInvalidScopes(TestCase): def setUp(self): self.desc_md = {} - self.user = UserFactory.create() + self.user = UserFactory.create(username='user') self.mdc = ModelDataCache([mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc) @@ -180,7 +141,7 @@ class TestStudentModuleStorage(TestCase): class TestMissingStudentModule(TestCase): def setUp(self): - self.user = UserFactory.create() + self.user = UserFactory.create(username='user') self.desc_md = {} self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user) self.kvs = LmsKeyValueStore(self.desc_md, self.mdc) diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py index cec006e630..80b8419d5a 100644 --- a/lms/djangoapps/django_comment_client/tests/test_utils.py +++ b/lms/djangoapps/django_comment_client/tests/test_utils.py @@ -1,42 +1,18 @@ -import string -import random -import collections - from django.test import TestCase +from factory import DjangoModelFactory +from student.tests.factories import UserFactory, CourseEnrollmentFactory -import factory -from django.contrib.auth.models import User -from student.models import UserProfile, CourseEnrollment from django_comment_client.models import Role, Permission - -import django_comment_client.models as models 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): +class RoleFactory(DjangoModelFactory): FACTORY_FOR = Role name = 'Student' course_id = 'edX/toy/2012_Fall' -class PermissionFactory(factory.Factory): +class PermissionFactory(DjangoModelFactory): FACTORY_FOR = Permission name = 'create_comment' diff --git a/lms/djangoapps/licenses/tests.py b/lms/djangoapps/licenses/tests.py index 5cf5e44dde..151a0faa9d 100644 --- a/lms/djangoapps/licenses/tests.py +++ b/lms/djangoapps/licenses/tests.py @@ -5,15 +5,19 @@ import json from uuid import uuid4 from random import shuffle from tempfile import NamedTemporaryFile -from factory import Factory, SubFactory +from factory import DjangoModelFactory, SubFactory from django.test import TestCase +from django.test.client import Client from django.test.utils import override_settings from django.core.management import call_command from django.core.urlresolvers import reverse +from nose.tools import assert_true + from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE from licenses.models import CourseSoftware, UserLicense -from courseware.tests.tests import LoginEnrollmentTestCase, get_user + +from student.tests.factories import UserFactory from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -27,7 +31,7 @@ SERIAL_1 = '123456abcde' log = logging.getLogger(__name__) -class CourseSoftwareFactory(Factory): +class CourseSoftwareFactory(DjangoModelFactory): '''Factory for generating CourseSoftware objects in database''' FACTORY_FOR = CourseSoftware @@ -37,7 +41,7 @@ class CourseSoftwareFactory(Factory): course_id = COURSE_1 -class UserLicenseFactory(Factory): +class UserLicenseFactory(DjangoModelFactory): ''' Factory for generating UserLicense objects in database @@ -46,19 +50,24 @@ class UserLicenseFactory(Factory): ''' FACTORY_FOR = UserLicense + user = None software = SubFactory(CourseSoftwareFactory) serial = SERIAL_1 -class LicenseTestCase(LoginEnrollmentTestCase): +class LicenseTestCase(TestCase): '''Tests for licenses.views''' def setUp(self): '''creates a user and logs in''' - self.setup_viewtest_user() + # self.setup_viewtest_user() + self.user = UserFactory(username='test', + email='test@edx.org', password='test_password') + self.client = Client() + assert_true(self.client.login(username='test', password='test_password')) self.software = CourseSoftwareFactory() def test_get_license(self): - UserLicenseFactory(user=get_user(self.viewtest_email), software=self.software) + UserLicenseFactory(user=self.user, software=self.software) response = self.client.post(reverse('user_software_license'), {'software': SOFTWARE_1, 'generate': 'false'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest', @@ -125,7 +134,7 @@ class LicenseTestCase(LoginEnrollmentTestCase): self.assertEqual(404, response.status_code) def test_get_license_without_login(self): - self.logout() + self.client.logout() response = self.client.post(reverse('user_software_license'), {'software': SOFTWARE_1, 'generate': 'false'}, HTTP_X_REQUESTED_WITH='XMLHttpRequest',