Commit 141e0a93 by Brian Beggs

Merge branch 'master' into dj18-release-merge

parents 0e66fad1 290de455
...@@ -35,9 +35,6 @@ var CourseDetails = Backbone.Model.extend({ ...@@ -35,9 +35,6 @@ var CourseDetails = Backbone.Model.extend({
if (newattrs.start_date === null) { if (newattrs.start_date === null) {
errors.start_date = gettext("The course must have an assigned start date."); errors.start_date = gettext("The course must have an assigned start date.");
} }
if (this.hasChanged("start_date") && this.get("has_cert_config") === false){
errors.start_date = gettext("The course must have at least one active certificate configuration before it can be started.");
}
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = gettext("The course end date must be later than the course start date."); errors.end_date = gettext("The course end date must be later than the course start date.");
} }
......
...@@ -72,13 +72,6 @@ define([ ...@@ -72,13 +72,6 @@ define([
); );
}); });
it('Changing course start date without active certificate configuration should result in error', function () {
this.view.$el.find('#course-start-date')
.val('10/06/2014')
.trigger('change');
expect(this.view.$el.find('span.message-error').text()).toContain("course must have at least one active certificate configuration");
});
it('Selecting a course in pre-requisite drop down should save it as part of course details', function () { it('Selecting a course in pre-requisite drop down should save it as part of course details', function () {
var pre_requisite_courses = ['test/CSS101/2012_T1']; var pre_requisite_courses = ['test/CSS101/2012_T1'];
var requests = AjaxHelpers.requests(this), var requests = AjaxHelpers.requests(this),
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
Tests the "preview" selector in the LMS that allows changing between Staff, Student, and Content Groups. Tests the "preview" selector in the LMS that allows changing between Staff, Student, and Content Groups.
""" """
from nose.plugins.attrib import attr
from ..helpers import UniqueCourseTest, create_user_partition_json from ..helpers import UniqueCourseTest, create_user_partition_json
from ...pages.studio.auto_auth import AutoAuthPage from ...pages.studio.auto_auth import AutoAuthPage
from ...pages.lms.courseware import CoursewarePage from ...pages.lms.courseware import CoursewarePage
...@@ -14,6 +17,7 @@ from xmodule.partitions.partitions import Group ...@@ -14,6 +17,7 @@ from xmodule.partitions.partitions import Group
from textwrap import dedent from textwrap import dedent
@attr('shard_3')
class StaffViewTest(UniqueCourseTest): class StaffViewTest(UniqueCourseTest):
""" """
Tests that verify the staff view. Tests that verify the staff view.
...@@ -51,6 +55,7 @@ class StaffViewTest(UniqueCourseTest): ...@@ -51,6 +55,7 @@ class StaffViewTest(UniqueCourseTest):
return staff_page return staff_page
@attr('shard_3')
class CourseWithoutContentGroupsTest(StaffViewTest): class CourseWithoutContentGroupsTest(StaffViewTest):
""" """
Setup for tests that have no content restricted to specific content groups. Setup for tests that have no content restricted to specific content groups.
...@@ -81,6 +86,7 @@ class CourseWithoutContentGroupsTest(StaffViewTest): ...@@ -81,6 +86,7 @@ class CourseWithoutContentGroupsTest(StaffViewTest):
) )
@attr('shard_3')
class StaffViewToggleTest(CourseWithoutContentGroupsTest): class StaffViewToggleTest(CourseWithoutContentGroupsTest):
""" """
Tests for the staff view toggle button. Tests for the staff view toggle button.
...@@ -97,6 +103,7 @@ class StaffViewToggleTest(CourseWithoutContentGroupsTest): ...@@ -97,6 +103,7 @@ class StaffViewToggleTest(CourseWithoutContentGroupsTest):
self.assertFalse(course_page.has_tab('Instructor')) self.assertFalse(course_page.has_tab('Instructor'))
@attr('shard_3')
class StaffDebugTest(CourseWithoutContentGroupsTest): class StaffDebugTest(CourseWithoutContentGroupsTest):
""" """
Tests that verify the staff debug info. Tests that verify the staff debug info.
...@@ -228,6 +235,7 @@ class StaffDebugTest(CourseWithoutContentGroupsTest): ...@@ -228,6 +235,7 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
'for user {}'.format(self.USERNAME), msg) 'for user {}'.format(self.USERNAME), msg)
@attr('shard_3')
class CourseWithContentGroupsTest(StaffViewTest): class CourseWithContentGroupsTest(StaffViewTest):
""" """
Verifies that changing the "View this course as" selector works properly for content groups. Verifies that changing the "View this course as" selector works properly for content groups.
......
""" """
Acceptance tests for the Import and Export pages Acceptance tests for the Import and Export pages
""" """
from nose.plugins.attrib import attr
from datetime import datetime from datetime import datetime
from abc import abstractmethod from abc import abstractmethod
...@@ -33,6 +34,7 @@ class ExportTestMixin(object): ...@@ -33,6 +34,7 @@ class ExportTestMixin(object):
self.assertTrue(is_tarball_mimetype) self.assertTrue(is_tarball_mimetype)
@attr('shard_4')
class TestCourseExport(ExportTestMixin, StudioCourseTest): class TestCourseExport(ExportTestMixin, StudioCourseTest):
""" """
Export tests for courses. Export tests for courses.
...@@ -55,6 +57,7 @@ class TestCourseExport(ExportTestMixin, StudioCourseTest): ...@@ -55,6 +57,7 @@ class TestCourseExport(ExportTestMixin, StudioCourseTest):
self.assertEqual(self.export_page.header_text, 'Course Export') self.assertEqual(self.export_page.header_text, 'Course Export')
@attr('shard_4')
class TestLibraryExport(ExportTestMixin, StudioLibraryTest): class TestLibraryExport(ExportTestMixin, StudioLibraryTest):
""" """
Export tests for libraries. Export tests for libraries.
...@@ -103,6 +106,7 @@ class BadExportMixin(object): ...@@ -103,6 +106,7 @@ class BadExportMixin(object):
) )
@attr('shard_4')
class TestLibraryBadExport(BadExportMixin, StudioLibraryTest): class TestLibraryBadExport(BadExportMixin, StudioLibraryTest):
""" """
Verify exporting a bad library causes an error. Verify exporting a bad library causes an error.
...@@ -126,6 +130,7 @@ class TestLibraryBadExport(BadExportMixin, StudioLibraryTest): ...@@ -126,6 +130,7 @@ class TestLibraryBadExport(BadExportMixin, StudioLibraryTest):
) )
@attr('shard_4')
class TestCourseBadExport(BadExportMixin, StudioCourseTest): class TestCourseBadExport(BadExportMixin, StudioCourseTest):
""" """
Verify exporting a bad course causes an error. Verify exporting a bad course causes an error.
...@@ -157,6 +162,7 @@ class TestCourseBadExport(BadExportMixin, StudioCourseTest): ...@@ -157,6 +162,7 @@ class TestCourseBadExport(BadExportMixin, StudioCourseTest):
) )
@attr('shard_4')
class ImportTestMixin(object): class ImportTestMixin(object):
""" """
Tests to run for both course and library import pages. Tests to run for both course and library import pages.
...@@ -271,6 +277,7 @@ class ImportTestMixin(object): ...@@ -271,6 +277,7 @@ class ImportTestMixin(object):
self.import_page.wait_for_tasks(fail_on='Updating') self.import_page.wait_for_tasks(fail_on='Updating')
@attr('shard_4')
class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest): class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
""" """
Tests the Course import page Tests the Course import page
...@@ -316,6 +323,7 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest): ...@@ -316,6 +323,7 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
) )
@attr('shard_4')
class TestCourseImport(ImportTestMixin, StudioCourseTest): class TestCourseImport(ImportTestMixin, StudioCourseTest):
""" """
Tests the Course import page Tests the Course import page
...@@ -357,6 +365,7 @@ class TestCourseImport(ImportTestMixin, StudioCourseTest): ...@@ -357,6 +365,7 @@ class TestCourseImport(ImportTestMixin, StudioCourseTest):
self.assertEqual(self.import_page.header_text, 'Course Import') self.assertEqual(self.import_page.header_text, 'Course Import')
@attr('shard_4')
class TestLibraryImport(ImportTestMixin, StudioLibraryTest): class TestLibraryImport(ImportTestMixin, StudioLibraryTest):
""" """
Tests the Library import page Tests the Library import page
......
...@@ -312,6 +312,7 @@ class EditContainerTest(NestedVerticalTest): ...@@ -312,6 +312,7 @@ class EditContainerTest(NestedVerticalTest):
self.assertEqual(component.student_content, "modified content") self.assertEqual(component.student_content, "modified content")
@attr('shard_3')
class EditVisibilityModalTest(ContainerBase): class EditVisibilityModalTest(ContainerBase):
""" """
Tests of the visibility settings modal for components on the unit Tests of the visibility settings modal for components on the unit
...@@ -397,6 +398,7 @@ class EditVisibilityModalTest(ContainerBase): ...@@ -397,6 +398,7 @@ class EditVisibilityModalTest(ContainerBase):
# Re-open the modal and inspect its selected inputs # Re-open the modal and inspect its selected inputs
visibility_editor = self.edit_component_visibility(component) visibility_editor = self.edit_component_visibility(component)
self.verify_selected_labels(visibility_editor, expected_labels) self.verify_selected_labels(visibility_editor, expected_labels)
visibility_editor.save()
def verify_component_validation_error(self, component): def verify_component_validation_error(self, component):
""" """
...@@ -427,14 +429,13 @@ class EditVisibilityModalTest(ContainerBase): ...@@ -427,14 +429,13 @@ class EditVisibilityModalTest(ContainerBase):
self.browser.refresh() self.browser.refresh()
self.container_page.wait_for_page() self.container_page.wait_for_page()
def remove_missing_groups(self, component): def remove_missing_groups(self, visibility_editor, component):
""" """
Deselect the missing groups for a component. After save, Deselect the missing groups for a component. After save,
verify that there are no missing group messages in the modal verify that there are no missing group messages in the modal
and that there is no validation error on the component. and that there is no validation error on the component.
""" """
visibility_editor = self.edit_component_visibility(component) for option in visibility_editor.selected_options:
for option in self.edit_component_visibility(component).selected_options:
if option.text == self.MISSING_GROUP_LABEL: if option.text == self.MISSING_GROUP_LABEL:
option.click() option.click()
visibility_editor.save() visibility_editor.save()
...@@ -541,7 +542,7 @@ class EditVisibilityModalTest(ContainerBase): ...@@ -541,7 +542,7 @@ class EditVisibilityModalTest(ContainerBase):
self.verify_component_validation_error(self.html_component) self.verify_component_validation_error(self.html_component)
visibility_editor = self.edit_component_visibility(self.html_component) visibility_editor = self.edit_component_visibility(self.html_component)
self.verify_selected_labels(visibility_editor, [self.MISSING_GROUP_LABEL] * 2) self.verify_selected_labels(visibility_editor, [self.MISSING_GROUP_LABEL] * 2)
self.remove_missing_groups(self.html_component) self.remove_missing_groups(visibility_editor, self.html_component)
self.verify_visibility_set(self.html_component, False) self.verify_visibility_set(self.html_component, False)
def test_found_and_missing_groups(self): def test_found_and_missing_groups(self):
...@@ -565,7 +566,7 @@ class EditVisibilityModalTest(ContainerBase): ...@@ -565,7 +566,7 @@ class EditVisibilityModalTest(ContainerBase):
self.verify_component_validation_error(self.html_component) self.verify_component_validation_error(self.html_component)
visibility_editor = self.edit_component_visibility(self.html_component) visibility_editor = self.edit_component_visibility(self.html_component)
self.verify_selected_labels(visibility_editor, ['Dogs', 'Cats'] + [self.MISSING_GROUP_LABEL] * 2) self.verify_selected_labels(visibility_editor, ['Dogs', 'Cats'] + [self.MISSING_GROUP_LABEL] * 2)
self.remove_missing_groups(self.html_component) self.remove_missing_groups(visibility_editor, self.html_component)
visibility_editor = self.edit_component_visibility(self.html_component) visibility_editor = self.edit_component_visibility(self.html_component)
self.verify_selected_labels(visibility_editor, ['Dogs', 'Cats']) self.verify_selected_labels(visibility_editor, ['Dogs', 'Cats'])
self.verify_visibility_set(self.html_component, True) self.verify_visibility_set(self.html_component, True)
...@@ -1041,6 +1042,7 @@ class UnitPublishingTest(ContainerBase): ...@@ -1041,6 +1042,7 @@ class UnitPublishingTest(ContainerBase):
# self.assertEqual('discussion', self.courseware.xblock_component_type(1)) # self.assertEqual('discussion', self.courseware.xblock_component_type(1))
@attr('shard_3')
class DisplayNameTest(ContainerBase): class DisplayNameTest(ContainerBase):
""" """
Test consistent use of display_name_with_default Test consistent use of display_name_with_default
...@@ -1077,6 +1079,7 @@ class DisplayNameTest(ContainerBase): ...@@ -1077,6 +1079,7 @@ class DisplayNameTest(ContainerBase):
self.assertEqual(container.name, title_on_unit_page) self.assertEqual(container.name, title_on_unit_page)
@attr('shard_3')
class ProblemCategoryTabsTest(ContainerBase): class ProblemCategoryTabsTest(ContainerBase):
""" """
Test to verify tabs in problem category. Test to verify tabs in problem category.
......
...@@ -1755,6 +1755,7 @@ class DeprecationWarningMessageTest(CourseOutlineTest): ...@@ -1755,6 +1755,7 @@ class DeprecationWarningMessageTest(CourseOutlineTest):
) )
@attr('shard_4')
class SelfPacedOutlineTest(CourseOutlineTest): class SelfPacedOutlineTest(CourseOutlineTest):
"""Test the course outline for a self-paced course.""" """Test the course outline for a self-paced course."""
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Acceptance tests for Studio's Settings Details pages Acceptance tests for Studio's Settings Details pages
""" """
from datetime import datetime, timedelta from datetime import datetime, timedelta
from nose.plugins.attrib import attr
from unittest import skip from unittest import skip
from .base_studio_test import StudioCourseTest from .base_studio_test import StudioCourseTest
...@@ -18,6 +19,7 @@ from ..helpers import ( ...@@ -18,6 +19,7 @@ from ..helpers import (
) )
@attr('shard_4')
class StudioSettingsDetailsTest(StudioCourseTest): class StudioSettingsDetailsTest(StudioCourseTest):
"""Base class for settings and details page tests.""" """Base class for settings and details page tests."""
...@@ -35,6 +37,7 @@ class StudioSettingsDetailsTest(StudioCourseTest): ...@@ -35,6 +37,7 @@ class StudioSettingsDetailsTest(StudioCourseTest):
self.assertTrue(self.settings_detail.is_browser_on_page()) self.assertTrue(self.settings_detail.is_browser_on_page())
@attr('shard_4')
class SettingsMilestonesTest(StudioSettingsDetailsTest): class SettingsMilestonesTest(StudioSettingsDetailsTest):
""" """
Tests for milestones feature in Studio's settings tab Tests for milestones feature in Studio's settings tab
...@@ -201,6 +204,7 @@ class SettingsMilestonesTest(StudioSettingsDetailsTest): ...@@ -201,6 +204,7 @@ class SettingsMilestonesTest(StudioSettingsDetailsTest):
)) ))
@attr('shard_4')
class CoursePacingTest(StudioSettingsDetailsTest): class CoursePacingTest(StudioSettingsDetailsTest):
"""Tests for setting a course to self-paced.""" """Tests for setting a course to self-paced."""
......
...@@ -38,6 +38,11 @@ class BadgeAssertionFactory(DjangoModelFactory): ...@@ -38,6 +38,11 @@ class BadgeAssertionFactory(DjangoModelFactory):
model = BadgeAssertion model = BadgeAssertion
mode = 'honor' mode = 'honor'
data = {
'image': 'http://www.example.com/image.png',
'json': {'id': 'http://www.example.com/assertion.json'},
'issuer': 'http://www.example.com/issuer.json',
}
class BadgeImageConfigurationFactory(DjangoModelFactory): class BadgeImageConfigurationFactory(DjangoModelFactory):
...@@ -75,7 +80,8 @@ class CertificateHtmlViewConfigurationFactory(DjangoModelFactory): ...@@ -75,7 +80,8 @@ class CertificateHtmlViewConfigurationFactory(DjangoModelFactory):
}, },
"honor": { "honor": {
"certificate_type": "Honor Code", "certificate_type": "Honor Code",
"certificate_title": "Certificate of Achievement" "certificate_title": "Certificate of Achievement",
"logo_url": "http://www.edx.org/honor_logo.png"
}, },
"verified": { "verified": {
"certificate_type": "Verified", "certificate_type": "Verified",
...@@ -84,6 +90,13 @@ class CertificateHtmlViewConfigurationFactory(DjangoModelFactory): ...@@ -84,6 +90,13 @@ class CertificateHtmlViewConfigurationFactory(DjangoModelFactory):
"xseries": { "xseries": {
"certificate_title": "XSeries Certificate of Achievement", "certificate_title": "XSeries Certificate of Achievement",
"certificate_type": "XSeries" "certificate_type": "XSeries"
},
"microsites": {
"testmicrosite": {
"company_about_url": "http://www.testmicrosite.org/about-us",
"company_privacy_url": "http://www.testmicrosite.org/edx-privacy-policy",
"company_tos_url": "http://www.testmicrosite.org/edx-terms-service"
}
} }
}""" }"""
......
...@@ -187,16 +187,6 @@ class UpdateExampleCertificateViewTest(TestCase): ...@@ -187,16 +187,6 @@ class UpdateExampleCertificateViewTest(TestCase):
self.assertEqual(content['return_code'], 0) self.assertEqual(content['return_code'], 0)
def fakemicrosite(name, default=None):
"""
This is a test mocking function to return a microsite configuration
"""
if name == 'microsite_config_key':
return 'test_microsite'
else:
return default
@attr('shard_1') @attr('shard_1')
class MicrositeCertificatesViewsTests(ModuleStoreTestCase): class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
""" """
...@@ -270,7 +260,6 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase): ...@@ -270,7 +260,6 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
self.course.save() self.course.save()
self.store.update_item(self.course, self.user.id) self.store.update_item(self.course, self.user.id)
@patch("microsite_configuration.microsite.get_value", fakemicrosite)
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
def test_html_view_for_microsite(self): def test_html_view_for_microsite(self):
test_configuration_string = """{ test_configuration_string = """{
...@@ -285,18 +274,20 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase): ...@@ -285,18 +274,20 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
"logo_src": "/static/certificates/images/logo-edx.svg", "logo_src": "/static/certificates/images/logo-edx.svg",
"logo_url": "http://www.edx.org" "logo_url": "http://www.edx.org"
}, },
"test_microsite": { "microsites": {
"accomplishment_class_append": "accomplishment-certificate", "testmicrosite": {
"platform_name": "platform_microsite", "accomplishment_class_append": "accomplishment-certificate",
"company_about_url": "http://www.microsite.org/about-us", "platform_name": "platform_microsite",
"company_privacy_url": "http://www.microsite.org/edx-privacy-policy", "company_about_url": "http://www.microsite.org/about-us",
"company_tos_url": "http://www.microsite.org/microsite-terms-service", "company_privacy_url": "http://www.microsite.org/edx-privacy-policy",
"company_verified_certificate_url": "http://www.microsite.org/verified-certificate", "company_tos_url": "http://www.microsite.org/microsite-terms-service",
"document_stylesheet_url_application": "/static/certificates/sass/main-ltr.css", "company_verified_certificate_url": "http://www.microsite.org/verified-certificate",
"logo_src": "/static/certificates/images/logo-microsite.svg", "document_stylesheet_url_application": "/static/certificates/sass/main-ltr.css",
"logo_url": "http://www.microsite.org", "logo_src": "/static/certificates/images/logo-microsite.svg",
"company_about_description": "This is special microsite aware company_about_description content", "logo_url": "http://www.microsite.org",
"company_about_title": "Microsite title" "company_about_description": "This is special microsite aware company_about_description content",
"company_about_title": "Microsite title"
}
}, },
"honor": { "honor": {
"certificate_type": "Honor Code" "certificate_type": "Honor Code"
...@@ -310,13 +301,12 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase): ...@@ -310,13 +301,12 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
course_id=unicode(self.course.id) course_id=unicode(self.course.id)
) )
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
response = self.client.get(test_url) response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertIn('platform_microsite', response.content) self.assertIn('platform_microsite', response.content)
self.assertIn('http://www.microsite.org', response.content) self.assertIn('http://www.microsite.org', response.content)
self.assertIn('This is special microsite aware company_about_description content', response.content) self.assertIn('This is special microsite aware company_about_description content', response.content)
self.assertIn('Microsite title', response.content) self.assertIn('Microsite title', response.content)
@patch("microsite_configuration.microsite.get_value", fakemicrosite)
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
def test_html_view_microsite_configuration_missing(self): def test_html_view_microsite_configuration_missing(self):
test_configuration_string = """{ test_configuration_string = """{
...@@ -343,7 +333,7 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase): ...@@ -343,7 +333,7 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
course_id=unicode(self.course.id) course_id=unicode(self.course.id)
) )
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
response = self.client.get(test_url) response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertIn('edX', response.content) self.assertIn('edX', response.content)
self.assertNotIn('platform_microsite', response.content) self.assertNotIn('platform_microsite', response.content)
self.assertNotIn('http://www.microsite.org', response.content) self.assertNotIn('http://www.microsite.org', response.content)
......
...@@ -23,7 +23,6 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase ...@@ -23,7 +23,6 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from certificates.api import get_certificate_url from certificates.api import get_certificate_url
from certificates.models import ( from certificates.models import (
GeneratedCertificate, GeneratedCertificate,
BadgeAssertion,
CertificateStatuses, CertificateStatuses,
CertificateSocialNetworks, CertificateSocialNetworks,
CertificateTemplate, CertificateTemplate,
...@@ -33,6 +32,7 @@ from certificates.models import ( ...@@ -33,6 +32,7 @@ from certificates.models import (
from certificates.tests.factories import ( from certificates.tests.factories import (
CertificateHtmlViewConfigurationFactory, CertificateHtmlViewConfigurationFactory,
LinkedInAddToProfileConfigurationFactory, LinkedInAddToProfileConfigurationFactory,
BadgeAssertionFactory,
) )
from util import organizations_helpers as organizations_api from util import organizations_helpers as organizations_api
from django.test.client import RequestFactory from django.test.client import RequestFactory
...@@ -222,6 +222,104 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -222,6 +222,104 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
self.assertIn('logo_test1.png', response.content) self.assertIn('logo_test1.png', response.content)
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED) @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
@patch.dict("django.conf.settings.SOCIAL_SHARING_SETTINGS", {
"CERTIFICATE_TWITTER": True,
"CERTIFICATE_FACEBOOK": True,
})
def test_rendering_maximum_data(self):
"""
Tests at least one data item from different context update methods to
make sure every context update method is invoked while rendering certificate template.
"""
long_org_name = 'Long org name'
short_org_name = 'short_org_name'
test_organization_data = {
'name': long_org_name,
'short_name': short_org_name,
'description': 'Test Organization Description',
'active': True,
'logo': '/logo_test1.png'
}
test_org = organizations_api.add_organization(organization_data=test_organization_data)
organizations_api.add_organization_course(organization_data=test_org, course_id=unicode(self.course.id))
self._add_course_certificates(count=1, signatory_count=1, is_active=True)
BadgeAssertionFactory.create(
user=self.user, course_id=self.course_id,
)
self.course.cert_html_view_overrides = {
"logo_src": "/static/certificates/images/course_override_logo.png"
}
self.course.save()
self.store.update_item(self.course, self.user.id)
test_url = get_certificate_url(
user_id=self.user.id,
course_id=unicode(self.course.id)
)
response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
# Test an item from basic info
self.assertIn(
'Terms of Service & Honor Code',
response.content
)
self.assertIn(
'Certificate ID Number',
response.content
)
# Test an item from html cert configuration
self.assertIn(
'<a class="logo" href="http://www.edx.org/honor_logo.png">',
response.content
)
# Test an item from course info
self.assertIn(
'course_title_0',
response.content
)
# Test an item from user info
self.assertIn(
"{fullname}, you've earned a certificate!".format(fullname=self.user.profile.name),
response.content
)
# Test an item from social info
self.assertIn(
"Post on Facebook",
response.content
)
self.assertIn(
"Share on Twitter",
response.content
)
# Test an item from certificate/org info
self.assertIn(
"a course of study offered by {partner_short_name}, "
"an online learning initiative of {partner_long_name} "
"through {platform_name}.".format(
partner_short_name=short_org_name,
partner_long_name=long_org_name,
platform_name='Test Microsite'
),
response.content
)
# Test item from badge info
self.assertIn(
"Add to Mozilla Backpack",
response.content
)
# Test item from microsite info
self.assertIn(
"http://www.testmicrosite.org/about-us",
response.content
)
# Test course overrides
self.assertIn(
"/static/certificates/images/course_override_logo.png",
response.content
)
@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
def test_render_html_view_valid_certificate(self): def test_render_html_view_valid_certificate(self):
test_url = get_certificate_url( test_url = get_certificate_url(
user_id=self.user.id, user_id=self.user.id,
...@@ -398,7 +496,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -398,7 +496,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
course_id=unicode(self.course.id) course_id=unicode(self.course.id)
) )
response = self.client.get(test_url + '?preview=honor') response = self.client.get(test_url + '?preview=honor')
#accessing certificate web view in preview mode without # accessing certificate web view in preview mode without
# staff or instructor access should show invalid certificate # staff or instructor access should show invalid certificate
self.assertIn('Cannot Find Certificate', response.content) self.assertIn('Cannot Find Certificate', response.content)
...@@ -495,16 +593,9 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -495,16 +593,9 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
test_url = '{}?evidence_visit=1'.format(cert_url) test_url = '{}?evidence_visit=1'.format(cert_url)
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
self.recreate_tracker() self.recreate_tracker()
assertion = BadgeAssertion( assertion = BadgeAssertionFactory.create(
user=self.user, course_id=self.course_id, mode='honor', user=self.user, course_id=self.course_id,
data={
'image': 'http://www.example.com/image.png',
'json': {'id': 'http://www.example.com/assertion.json'},
'issuer': 'http://www.example.com/issuer.json',
}
) )
assertion.save()
response = self.client.get(test_url) response = self.client.get(test_url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
assert_event_matches( assert_event_matches(
......
...@@ -25,13 +25,7 @@ from discussion_api.permissions import ( ...@@ -25,13 +25,7 @@ from discussion_api.permissions import (
get_initializable_thread_fields, get_initializable_thread_fields,
) )
from discussion_api.serializers import CommentSerializer, ThreadSerializer, get_context from discussion_api.serializers import CommentSerializer, ThreadSerializer, get_context
from django_comment_client.base.views import ( from django_comment_client.base.views import track_comment_created_event, track_thread_created_event
THREAD_CREATED_EVENT_NAME,
get_comment_created_event_data,
get_comment_created_event_name,
get_thread_created_event_data,
track_forum_event,
)
from django_comment_common.signals import ( from django_comment_common.signals import (
thread_created, thread_created,
thread_edited, thread_edited,
...@@ -566,13 +560,7 @@ def create_thread(request, thread_data): ...@@ -566,13 +560,7 @@ def create_thread(request, thread_data):
api_thread = serializer.data api_thread = serializer.data
_do_extra_actions(api_thread, cc_thread, thread_data.keys(), actions_form, context) _do_extra_actions(api_thread, cc_thread, thread_data.keys(), actions_form, context)
track_forum_event( track_thread_created_event(request, course, cc_thread, actions_form.cleaned_data["following"])
request,
THREAD_CREATED_EVENT_NAME,
course,
cc_thread,
get_thread_created_event_data(cc_thread, followed=actions_form.cleaned_data["following"])
)
return api_thread return api_thread
...@@ -616,13 +604,7 @@ def create_comment(request, comment_data): ...@@ -616,13 +604,7 @@ def create_comment(request, comment_data):
api_comment = serializer.data api_comment = serializer.data
_do_extra_actions(api_comment, cc_comment, comment_data.keys(), actions_form, context) _do_extra_actions(api_comment, cc_comment, comment_data.keys(), actions_form, context)
track_forum_event( track_comment_created_event(request, context["course"], cc_comment, cc_thread["commentable_id"], followed=False)
request,
get_comment_created_event_name(cc_comment),
context["course"],
cc_comment,
get_comment_created_event_data(cc_comment, cc_thread["commentable_id"], followed=False)
)
return api_comment return api_comment
......
...@@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse ...@@ -13,7 +13,7 @@ from django.core.urlresolvers import reverse
from request_cache.middleware import RequestCache from request_cache.middleware import RequestCache
from mock import patch, ANY, Mock from mock import patch, ANY, Mock
from nose.tools import assert_true, assert_equal # pylint: disable=no-name-in-module from nose.tools import assert_true, assert_equal # pylint: disable=no-name-in-module
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.keys import CourseKey
from lms.lib.comment_client import Thread from lms.lib.comment_client import Thread
from common.test.utils import MockSignalHandlerMixin, disable_signal from common.test.utils import MockSignalHandlerMixin, disable_signal
...@@ -1641,6 +1641,40 @@ class ForumEventTestCase(ModuleStoreTestCase, MockRequestSetupMixin): ...@@ -1641,6 +1641,40 @@ class ForumEventTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
self.assertEqual(name, event_name) self.assertEqual(name, event_name)
self.assertEqual(event['team_id'], team.team_id) self.assertEqual(event['team_id'], team.team_id)
@ddt.data(
('vote_for_thread', 'thread_id', 'thread'),
('undo_vote_for_thread', 'thread_id', 'thread'),
('vote_for_comment', 'comment_id', 'response'),
('undo_vote_for_comment', 'comment_id', 'response'),
)
@ddt.unpack
@patch('eventtracking.tracker.emit')
@patch('lms.lib.comment_client.utils.requests.request')
def test_thread_voted_event(self, view_name, obj_id_name, obj_type, mock_request, mock_emit):
undo = view_name.startswith('undo')
self._set_mock_request_data(mock_request, {
'closed': False,
'commentable_id': 'test_commentable_id',
'username': 'gumprecht',
})
request = RequestFactory().post('dummy_url', {})
request.user = self.student
request.view_name = view_name
view_function = getattr(views, view_name)
kwargs = dict(course_id=unicode(self.course.id))
kwargs[obj_id_name] = obj_id_name
if not undo:
kwargs.update(value='up')
view_function(request, **kwargs)
self.assertTrue(mock_emit.called)
event_name, event = mock_emit.call_args[0]
self.assertEqual(event_name, 'edx.forum.{}.voted'.format(obj_type))
self.assertEqual(event['target_username'], 'gumprecht')
self.assertEqual(event['undo_vote'], undo)
self.assertEqual(event['vote_value'], 'up')
class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin): class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
...@@ -1699,7 +1733,7 @@ class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin): ...@@ -1699,7 +1733,7 @@ class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
self.assertNotIn("users", content) self.assertNotIn("users", content)
def test_course_does_not_exist(self): def test_course_does_not_exist(self):
course_id = SlashSeparatedCourseKey.from_deprecated_string("does/not/exist") course_id = CourseKey.from_string("does/not/exist")
response = self.make_request(course_id=course_id, username="other") response = self.make_request(course_id=course_id, username="other")
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
......
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.courses import get_course from courseware.courses import get_course
...@@ -16,10 +15,7 @@ class Command(BaseCommand): ...@@ -16,10 +15,7 @@ class Command(BaseCommand):
raise CommandError("Only one course id may be specifiied") raise CommandError("Only one course id may be specifiied")
course_id = args[0] course_id = args[0]
try: course_key = CourseKey.from_string(course_id)
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course(course_key) course = get_course(course_key)
if not course: if not course:
......
...@@ -3,7 +3,7 @@ Management command to seed default permissions and roles. ...@@ -3,7 +3,7 @@ Management command to seed default permissions and roles.
""" """
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django_comment_common.utils import seed_permissions_roles from django_comment_common.utils import seed_permissions_roles
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.keys import CourseKey
class Command(BaseCommand): class Command(BaseCommand):
...@@ -15,6 +15,6 @@ class Command(BaseCommand): ...@@ -15,6 +15,6 @@ class Command(BaseCommand):
raise CommandError("Please provide a course id") raise CommandError("Please provide a course id")
if len(args) > 1: if len(args) > 1:
raise CommandError("Too many arguments") raise CommandError("Too many arguments")
course_id = SlashSeparatedCourseKey.from_deprecated_string(args[0]) course_id = CourseKey.from_string(args[0])
seed_permissions_roles(course_id) seed_permissions_roles(course_id)
...@@ -3,7 +3,7 @@ Tests for the django comment client integration models ...@@ -3,7 +3,7 @@ Tests for the django comment client integration models
""" """
from django.test.testcases import TestCase from django.test.testcases import TestCase
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_TOY_MODULESTORE from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_TOY_MODULESTORE
import django_comment_common.models as models import django_comment_common.models as models
...@@ -23,7 +23,7 @@ class RoleClassTestCase(ModuleStoreTestCase): ...@@ -23,7 +23,7 @@ class RoleClassTestCase(ModuleStoreTestCase):
# For course ID, syntax edx/classname/classdate is important # For course ID, syntax edx/classname/classdate is important
# because xmodel.course_module.id_to_location looks for a string to split # because xmodel.course_module.id_to_location looks for a string to split
self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.course_id = CourseKey.from_string("edX/toy/2012_Fall")
self.student_role = models.Role.objects.get_or_create(name="Student", self.student_role = models.Role.objects.get_or_create(name="Student",
course_id=self.course_id)[0] course_id=self.course_id)[0]
self.student_role.add_permission("delete_thread") self.student_role.add_permission("delete_thread")
...@@ -31,7 +31,7 @@ class RoleClassTestCase(ModuleStoreTestCase): ...@@ -31,7 +31,7 @@ class RoleClassTestCase(ModuleStoreTestCase):
course_id=self.course_id)[0] course_id=self.course_id)[0]
self.TA_role = models.Role.objects.get_or_create(name="Community TA", self.TA_role = models.Role.objects.get_or_create(name="Community TA",
course_id=self.course_id)[0] course_id=self.course_id)[0]
self.course_id_2 = SlashSeparatedCourseKey("edx", "6.002x", "2012_Fall") self.course_id_2 = CourseKey.from_string("edX/6.002x/2012_Fall")
self.TA_role_2 = models.Role.objects.get_or_create(name="Community TA", self.TA_role_2 = models.Role.objects.get_or_create(name="Community TA",
course_id=self.course_id_2)[0] course_id=self.course_id_2)[0]
......
...@@ -206,6 +206,16 @@ define([ ...@@ -206,6 +206,16 @@ define([
expectPaymentButtonEnabled( true ); expectPaymentButtonEnabled( true );
}); });
it('displays an error if no payment processors are available', function () {
var view = createView({processors: []});
expect(view.errorModel.get('shown')).toBe(true);
expect(view.errorModel.get('errorTitle')).toEqual(
'All payment options are currently unavailable.'
);
expect(view.errorModel.get('errorMsg')).toEqual(
'Try the transaction again in a few minutes.'
);
});
}); });
} }
); );
...@@ -56,7 +56,8 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/ ...@@ -56,7 +56,8 @@ define(['jquery', 'common/js/spec_helpers/template_helpers', 'js/verify_student/
var createView = function( displaySteps, currentStep ) { var createView = function( displaySteps, currentStep ) {
return new PayAndVerifyView({ return new PayAndVerifyView({
displaySteps: displaySteps, displaySteps: displaySteps,
currentStep: currentStep currentStep: currentStep,
errorModel: new ( Backbone.Model.extend({}) )()
}).render(); }).render();
}; };
......
...@@ -105,10 +105,20 @@ var edx = edx || {}; ...@@ -105,10 +105,20 @@ var edx = edx || {};
self._getProductText( templateContext.courseModeSlug, templateContext.upgrade ) self._getProductText( templateContext.courseModeSlug, templateContext.upgrade )
); );
// create a button for each payment processor if (processors.length === 0) {
_.each(processors.reverse(), function(processorName) { // No payment processors are enabled at the moment, so show an error message
$( 'div.payment-buttons' ).append( self._getPaymentButtonHtml(processorName) ); this.errorModel.set({
}); errorTitle: gettext('All payment options are currently unavailable.'),
errorMsg: gettext('Try the transaction again in a few minutes.'),
shown: true
})
}
else {
// create a button for each payment processor
_.each(processors.reverse(), function(processorName) {
$( 'div.payment-buttons' ).append( self._getPaymentButtonHtml(processorName) );
});
}
// Handle payment submission // Handle payment submission
$( '.payment-button' ).on( 'click', _.bind( this.createOrder, this ) ); $( '.payment-button' ).on( 'click', _.bind( this.createOrder, this ) );
......
...@@ -273,8 +273,8 @@ def run_jshint(options): ...@@ -273,8 +273,8 @@ def run_jshint(options):
_prepare_report_dir(jshint_report_dir) _prepare_report_dir(jshint_report_dir)
sh( sh(
"jshint {root} --config .jshintrc >> {jshint_report}".format( "jshint . --config .jshintrc >> {jshint_report}".format(
root=Env.REPO_ROOT, jshint_report=jshint_report jshint_report=jshint_report
), ),
ignore_error=True ignore_error=True
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment