Commit 882c2fa3 by Clinton Blackburn

Merge pull request #10030 from edx/hotfix/2015-10-05

Hotfix for 2015-10-05
parents a3f8ed3f 775ca26c
...@@ -86,7 +86,8 @@ class CreditCourseDashboardTest(ModuleStoreTestCase): ...@@ -86,7 +86,8 @@ class CreditCourseDashboardTest(ModuleStoreTestCase):
def test_not_eligible_for_credit(self): def test_not_eligible_for_credit(self):
# The user is not yet eligible for credit, so no additional information should be displayed on the dashboard. # The user is not yet eligible for credit, so no additional information should be displayed on the dashboard.
response = self._load_dashboard() response = self._load_dashboard()
self.assertNotContains(response, "credit") self.assertNotContains(response, "credit-eligibility-msg")
self.assertNotContains(response, "purchase-credit-btn")
def test_eligible_for_credit(self): def test_eligible_for_credit(self):
# Simulate that the user has completed the only requirement in the course # Simulate that the user has completed the only requirement in the course
......
...@@ -413,7 +413,7 @@ FEATURES = { ...@@ -413,7 +413,7 @@ FEATURES = {
'ENABLE_OPENBADGES': False, 'ENABLE_OPENBADGES': False,
# Credit course API # Credit course API
'ENABLE_CREDIT_API': False, 'ENABLE_CREDIT_API': True,
# The block types to disable need to be specified in "x block disable config" in django admin. # The block types to disable need to be specified in "x block disable config" in django admin.
'ENABLE_DISABLING_XBLOCK_TYPES': True, 'ENABLE_DISABLING_XBLOCK_TYPES': True,
...@@ -2122,7 +2122,7 @@ if FEATURES.get('CLASS_DASHBOARD'): ...@@ -2122,7 +2122,7 @@ if FEATURES.get('CLASS_DASHBOARD'):
INSTALLED_APPS += ('class_dashboard',) INSTALLED_APPS += ('class_dashboard',)
################ Enable credit eligibility feature #################### ################ Enable credit eligibility feature ####################
ENABLE_CREDIT_ELIGIBILITY = False ENABLE_CREDIT_ELIGIBILITY = True
FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY
######################## CAS authentication ########################### ######################## CAS authentication ###########################
......
/**
* Credit-related utilities
*/
var edx = edx || {};
(function ($, _) {
'use strict';
edx.commerce = edx.commerce || {};
edx.commerce.credit = edx.commerce.credit || {};
edx.commerce.credit.createCreditRequest = function (providerId, courseKey, username) {
return $.ajax({
url: '/api/credit/v1/providers/' + providerId + '/request/',
type: 'POST',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
data: JSON.stringify({
'course_key': courseKey,
'username': username
}),
context: this,
success: function (requestData) {
var $form = $('<form>', {
'action': requestData.url,
'method': 'POST',
'accept-method': 'UTF-8'
});
_.each(requestData.parameters, function (value, key) {
$('<textarea>').attr({
name: key,
value: value
}).appendTo($form);
});
$form.submit();
}
});
};
})(jQuery, _);
...@@ -50,12 +50,12 @@ var edx = edx || {}; ...@@ -50,12 +50,12 @@ var edx = edx || {};
this.getProviderData(providerId).then(this.renderProvider, this.renderError) this.getProviderData(providerId).then(this.renderProvider, this.renderError)
} }
}, },
renderCourseNamePlaceholder: function(courseId) { renderCourseNamePlaceholder: function (courseId) {
// Display the course Id or name (if available) in the placeholder // Display the course Id or name (if available) in the placeholder
var $courseNamePlaceholder = $(".course_name_placeholder"); var $courseNamePlaceholder = $(".course_name_placeholder");
$courseNamePlaceholder.text(courseId); $courseNamePlaceholder.text(courseId);
this.getCourseData(courseId).then(function(responseData) { this.getCourseData(courseId).then(function (responseData) {
$courseNamePlaceholder.text(responseData.name); $courseNamePlaceholder.text(responseData.name);
}); });
}, },
...@@ -77,7 +77,7 @@ var edx = edx || {}; ...@@ -77,7 +77,7 @@ var edx = edx || {};
var self = this, var self = this,
orderId = $.url('?basket_id') || $.url('?payment-order-num'); orderId = $.url('?basket_id') || $.url('?payment-order-num');
if (orderId && this.$el.data('is-payment-complete')==='True') { if (orderId && this.$el.data('is-payment-complete') === 'True') {
// Get the order details // Get the order details
self.$el.removeClass('hidden'); self.$el.removeClass('hidden');
self.getReceiptData(orderId).then(self.renderReceipt, self.renderError); self.getReceiptData(orderId).then(self.renderReceipt, self.renderError);
...@@ -168,7 +168,7 @@ var edx = edx || {}; ...@@ -168,7 +168,7 @@ var edx = edx || {};
billedTo: null billedTo: null
}; };
if (order.billing_address){ if (order.billing_address) {
receiptContext.billedTo = { receiptContext.billedTo = {
firstName: order.billing_address.first_name, firstName: order.billing_address.first_name,
lastName: order.billing_address.last_name, lastName: order.billing_address.last_name,
...@@ -263,8 +263,8 @@ var edx = edx || {}; ...@@ -263,8 +263,8 @@ var edx = edx || {};
line = order.lines[0]; line = order.lines[0];
if (this.useEcommerceApi) { if (this.useEcommerceApi) {
attributeValues = _.find(line.product.attribute_values, function (attribute) { attributeValues = _.find(line.product.attribute_values, function (attribute) {
return attribute.name === 'credit_provider' return attribute.name === 'credit_provider';
}); });
// This method assumes that all items in the order are related to a single course. // This method assumes that all items in the order are related to a single course.
if (attributeValues != undefined) { if (attributeValues != undefined) {
...@@ -273,7 +273,7 @@ var edx = edx || {}; ...@@ -273,7 +273,7 @@ var edx = edx || {};
} }
return null; return null;
}, }
}); });
new edx.commerce.ReceiptView({ new edx.commerce.ReceiptView({
...@@ -282,16 +282,11 @@ var edx = edx || {}; ...@@ -282,16 +282,11 @@ var edx = edx || {};
})(jQuery, _, _.str, Backbone); })(jQuery, _, _.str, Backbone);
function completeOrder(event) { // jshint ignore:line
function completeOrder (event) {
var courseKey = $(event).data("course-key"), var courseKey = $(event).data("course-key"),
username = $(event).data("username"), username = $(event).data("username"),
providerId = $(event).data("provider"), providerId = $(event).data("provider"),
postData = { $errorContainer = $("#error-container");
'course_key': courseKey,
'username': username
},
errorContainer = $("#error-container");
analytics.track( analytics.track(
"edx.bi.credit.clicked_complete_credit", "edx.bi.credit.clicked_complete_credit",
...@@ -301,34 +296,7 @@ function completeOrder (event) { ...@@ -301,34 +296,7 @@ function completeOrder (event) {
} }
); );
$.ajax({ edx.commerce.credit.createCreditRequest(providerId, courseKey, username).fail(function () {
url: '/api/credit/v1/providers/' + providerId + '/request/', $errorContainer.removeClass("hidden");
type: 'POST',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
data: JSON.stringify(postData) ,
context: this,
success: function(requestData){
var form = $('#complete-order-form');
$('input', form).remove();
form.attr( 'action', requestData.url );
form.attr( 'method', 'POST' );
_.each( requestData.parameters, function( value, key ) {
$('<input>').attr({
type: 'hidden',
name: key,
value: value
}).appendTo(form);
});
form.submit();
},
error: function(xhr){
errorContainer.removeClass("is-hidden");
errorContainer.removeClass("hidden");
}
}); });
} }
(function($, analytics) { /**
* Student dashboard credit messaging.
*/
var edx = edx || {};
(function ($, analytics) {
'use strict'; 'use strict';
$(document).ready(function() { $(document).ready(function () {
var errorContainer = $(".credit-error-msg"), var $errorContainer = $(".credit-error-msg"),
creditStatusError = errorContainer.data("credit-error"); creditStatusError = $errorContainer.data("credit-error");
if (creditStatusError == "True"){ if (creditStatusError === "True") {
errorContainer.toggleClass("is-hidden"); $errorContainer.toggleClass("is-hidden");
} }
// Fire analytics events when the "purchase credit" button is clicked // Fire analytics events when the "purchase credit" button is clicked
$(".purchase-credit-btn").on("click", function(event) { $(".purchase-credit-btn").on("click", function (event) {
var courseKey = $(event.target).data("course-key"); var courseKey = $(event.target).data("course-key");
analytics.track( analytics.track(
"edx.bi.credit.clicked_purchase_credit", "edx.bi.credit.clicked_purchase_credit",
...@@ -20,46 +26,19 @@ ...@@ -20,46 +26,19 @@
} }
); );
}); });
// This event invokes credit request endpoint. It will initiate
// a credit request for the credit course for the provided user.
$(".pending-credit-btn").on("click", function(event){
var courseKey = $(event.target).data("course-key"),
username = $(event.target).data("user"),
provider_id = $(event.target).data("provider"),
postData = {
'course_key': courseKey,
'username': username
};
$.ajax({
url: 'api/credit/v1/providers/' + provider_id + '/request/',
type: 'POST',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
data: JSON.stringify(postData) ,
context: this,
success: function(requestData){
var form = $('#credit-pending-form');
$('input', form).remove();
form.attr( 'action', requestData.url ); // This event invokes credit request endpoint. It will initiate
form.attr( 'method', 'POST' ); // a credit request for the credit course for the provided user.
$(".pending-credit-btn").on("click", function (event) {
var $target = $(event.target),
courseKey = $target.data("course-key"),
username = $target.data("user"),
providerId = $target.data("provider");
_.each( requestData.parameters, function( value, key ) { edx.commerce.credit.createCreditRequest(providerId, courseKey, username).fail(function () {
$('<input>').attr({ $(".credit-action").hide();
type: 'hidden', $errorContainer.toggleClass("is-hidden");
name: key,
value: value
}).appendTo(form);
});
form.submit();
},
error: function(xhr){
$(".credit-request-pending-msg").hide("is-hidden");
$(".pending-credit-btn").hide();
errorContainer.toggleClass("is-hidden");
}
}); });
}); });
}); });
......
...@@ -548,24 +548,6 @@ ...@@ -548,24 +548,6 @@
padding: 0; padding: 0;
} }
.credit-btn{
@include float(right);
}
.denied-credit-btn{
@include float(right);
}
.credit-request-pending-msg{
display: inline-block;
}
.credit-btn {
@extend %btn-pl-yellow-base;
background-image: none ;
text-shadow: none;
box-shadow: none;
}
.message { .message {
@extend %ui-depth1; @extend %ui-depth1;
border-radius: 3px; border-radius: 3px;
...@@ -750,19 +732,21 @@ ...@@ -750,19 +732,21 @@
font-weight: bold; font-weight: bold;
} }
} }
.credit-eligibility-msg {
@include float(left);
margin-top: 10px;
}
.credit-request-pending-msg { .credit-action {
@include float(left); .credit-msg {
margin-top: 10px; @include float(left);
} width: flex-grid(9, 12);
}
.credit-request-approved-msg{ .credit-btn {
width: flex-grid(10, 12); @extend %btn-pl-yellow-base;
@include float(left); @include float(right);
background-image: none ;
text-shadow: none;
box-shadow: none;
text-transform: none;
}
} }
.actions { .actions {
......
...@@ -21,6 +21,7 @@ from django.utils.translation import ugettext as _ ...@@ -21,6 +21,7 @@ from django.utils.translation import ugettext as _
<script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script> <script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script> <script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script> <script src="${static.url('js/src/tooltip_manager.js')}"></script>
<script src="${static.url('js/commerce/credit.js')}"></script>
<script src="${static.url('js/commerce/views/receipt_view.js')}"></script> <script src="${static.url('js/commerce/views/receipt_view.js')}"></script>
</%block> </%block>
...@@ -56,7 +57,6 @@ from django.utils.translation import ugettext as _ ...@@ -56,7 +57,6 @@ from django.utils.translation import ugettext as _
<h1>${_("Loading Order Data...")}</h1> <h1>${_("Loading Order Data...")}</h1>
<span>${ _("Please wait while we retrieve your order details.") }</span> <span>${ _("Please wait while we retrieve your order details.") }</span>
</div> </div>
<form id="complete-order-form"></form>
</section> </section>
</div> </div>
</%block> </%block>
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
<%= interpolate("<img src='%s' alt='%s'></image>", [thumbnail_url, display_name]) %> <%= interpolate("<img src='%s' alt='%s'></image>", [thumbnail_url, display_name]) %>
</div> </div>
<div class="complete-order"> <div class="complete-order">
<%= interpolate('<button data-provider="%s" data-course-key="%s" data-username="%s" class="complete-course" onClick=completeOrder(this)>%s</button>', [provider_id, course_key, username, gettext( "Complete Order")]) %> <%= interpolate('<button data-provider="%s" data-course-key="%s" data-username="%s" class="complete-course" onClick=completeOrder(this)>%s</button>', [provider_id, course_key, username,
gettext( "Get Credit")]) %>
</div> </div>
</div> </div>
...@@ -35,6 +35,7 @@ import json ...@@ -35,6 +35,7 @@ import json
</%block> </%block>
<%block name="js_extra"> <%block name="js_extra">
<script src="${static.url('js/commerce/credit.js')}"></script>
<%static:js group='dashboard'/> <%static:js group='dashboard'/>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
......
...@@ -19,61 +19,47 @@ ...@@ -19,61 +19,47 @@
) )
)} )}
</p> </p>
% if not credit_status["purchased"] and not credit_status["error"] : <div class="credit-action">
<p class="message-copy credit-eligibility-msg"> % if not credit_status["purchased"] and not credit_status["error"] :
${_("You are now eligible for credit. <b>Congratulations!</b>")} <p class="message-copy credit-msg credit-eligibility-msg">
## Translators: provider_name is the name of a credit provider or university (e.g. State University)
${_("You are now eligible to purchase course credit from {provider_name} for this course. Click <strong>Get Credit</strong> to get started.").format(
provider_name=credit_status["provider_name"],
)}
</p>
<div class="purchase_credit">
<a class="btn credit-btn purchase-credit-btn" href="${settings.ECOMMERCE_PUBLIC_URL_ROOT}/credit/checkout/${credit_status['course_key']}" target="_blank" data-course-key="${credit_status['course_key']}">${_("Get Credit")}</a>
</div>
% elif credit_status["request_status"] in [None, "pending"] and not credit_status["error"]:
<p class="message-copy credit-msg credit-request-pending-msg">
## Translators: provider_name is the name of a credit provider or university (e.g. State University)
${_("Thank you for your payment. To receive course credit, you must now request credit at the {provider_name} website.").format(
provider_name=credit_status["provider_name"],
)
}
</p>
<button class="btn credit-btn pending-credit-btn" data-course-key="${credit_status['course_key']}" data-user="${user.username}" data-provider="${credit_status['provider_id']}">${_("Finalize Credit")}</button>
</p> % elif credit_status["request_status"] == "approved" and not credit_status["error"] :
<div class="purchase_credit"> <p class="message-copy credit-msg credit-request-approved-msg">
<a class="btn credit-btn purchase-credit-btn" href="${settings.ECOMMERCE_PUBLIC_URL_ROOT}/credit/checkout/${credit_status['course_key']}" target="_blank" data-course-key="${credit_status['course_key']}">${_("Get credit")}</a> ## Translators: provider_name is the name of a credit provider or university (e.g. State University)
</div> ${_("<strong>Congratulations!</strong> {provider_name} has converted your course credit. To see your course credit, click <strong>Access Credit</strong>.").format(
% elif credit_status["request_status"] in [None, "pending"] and not credit_status["error"] : provider_name=credit_status["provider_name"],
% if credit_status["request_status"] == "pending":
<p class="message-copy credit-request-pending-msg">
## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a
## credit provider, such as 'State University' or 'Happy Fun Company'
${_("Thank you. Your credit is processing. Please see {link_to_provider_site} for more information.").format(
link_to_provider_site=provider_link,
) )
} }
</p> </p>
<button class="btn credit-btn pending-credit-btn" data-course-key="${credit_status['course_key']}" data-user="${user.username}" data-provider="${credit_status['provider_id']}">${_("Learn more")}</button> <a class="btn credit-btn access-credit-btn" href="${credit_status['provider_status_url']}" target="_blank">${_("Access Credit")}</a>
% elif credit_status["request_status"] is None: % elif credit_status["request_status"] == "rejected" and not credit_status["error"] :
<p class="message-copy credit-request-pending-msg"> <p class="message-copy credit-msg credit-request-rejected-msg">
## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a ## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a
## credit provider, such as 'State University' or 'Happy Fun Company' ## credit provider, such as 'State University' or 'Happy Fun Company'. provider_name is the name of credit provider.
${_("Thank you for your purchase. Please proceed to {link_to_provider_site} to finalize your credit.").format( ${_("{provider_name} did not approve your request for course credit. For more information, contact {link_to_provider_site} directly.").format(
provider_name=credit_status["provider_name"],
link_to_provider_site=provider_link, link_to_provider_site=provider_link,
) )
} }
</p> </p>
<button class="btn credit-btn pending-credit-btn" data-course-key="${credit_status['course_key']}" data-user="${user.username}" data-provider="${credit_status['provider_id']}">${_("Finalize credit")}</button>
% endif % endif
</div>
<form id="credit-pending-form"> </form>
% elif credit_status["request_status"] == "approved" and not credit_status["error"] :
<p class="message-copy credit-request-approved-msg">
## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a
## credit provider, such as 'State University' or 'Happy Fun Company'
${_("Your credit has been processed and approved. <b>Congratulations!</b>. Please see {link_to_provider_site} for more information.").format(
link_to_provider_site=provider_link,
)
}
</p>
<a class="btn credit-btn access-credit-btn" href="${credit_status['provider_status_url']}" target="_blank">${_("Access credit")}</a>
% elif credit_status["request_status"] == "rejected" and not credit_status["error"] :
<p class="message-copy credit-request-rejected-msg">
## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a
## credit provider, such as 'State University' or 'Happy Fun Company'
${_("Your credit has been processed but denied. Please contact {link_to_provider_site} for more information.").format(
link_to_provider_site=provider_link,
)
}
</p>
<a class="btn credit-btn denied-credit-btn" href="${credit_status['provider_status_url']}" target="_blank">
${_("Contact {provider}").format(provider=u'credit_status["provider_name"]')
}
</a>
% endif
</div> </div>
% endif % endif
...@@ -242,13 +242,20 @@ def create_credit_request(course_key, provider_id, username): ...@@ -242,13 +242,20 @@ def create_credit_request(course_key, provider_id, username):
# Retrieve the final grade from the eligibility table # Retrieve the final grade from the eligibility table
try: try:
final_grade = unicode(CreditRequirementStatus.objects.get( final_grade = CreditRequirementStatus.objects.get(
username=username, username=username,
requirement__namespace="grade", requirement__namespace="grade",
requirement__name="grade", requirement__name="grade",
requirement__course__course_key=course_key, requirement__course__course_key=course_key,
status="satisfied" status="satisfied"
).reason["final_grade"]) ).reason["final_grade"]
# NOTE (CCB): Limiting the grade to seven characters is a hack for ASU.
if len(unicode(final_grade)) > 7:
final_grade = u'{:.5f}'.format(final_grade)
else:
final_grade = unicode(final_grade)
except (CreditRequirementStatus.DoesNotExist, TypeError, KeyError): except (CreditRequirementStatus.DoesNotExist, TypeError, KeyError):
log.exception( log.exception(
"Could not retrieve final grade from the credit eligibility table " "Could not retrieve final grade from the credit eligibility table "
......
...@@ -54,10 +54,10 @@ def signature(params, shared_secret): ...@@ -54,10 +54,10 @@ def signature(params, shared_secret):
str: The 32-character signature. str: The 32-character signature.
""" """
encoded_params = "".join([ encoded_params = u"".join([
"{key}:{value}".format(key=key, value=params[key]) u"{key}:{value}".format(key=key, value=params[key])
for key in sorted(params.keys()) for key in sorted(params.keys())
if key != "signature" if key != u"signature"
]) ])
hasher = hmac.new(shared_secret, encoded_params, hashlib.sha256) hasher = hmac.new(shared_secret, encoded_params.encode('utf-8'), hashlib.sha256)
return hasher.hexdigest() return hasher.hexdigest()
...@@ -2,21 +2,21 @@ ...@@ -2,21 +2,21 @@
Tests for the API functions in the credit app. Tests for the API functions in the credit app.
""" """
import datetime import datetime
import ddt
import json import json
from mock import patch
import pytz
import unittest import unittest
import ddt
from django.conf import settings from django.conf import settings
from django.core import mail from django.core import mail
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.db import connection, transaction from django.db import connection, transaction
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
from unittest import skipUnless from mock import patch
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
import pytz
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from util.date_utils import from_timestamp from util.date_utils import from_timestamp
from openedx.core.djangoapps.credit import api from openedx.core.djangoapps.credit import api
...@@ -36,9 +36,6 @@ from openedx.core.djangoapps.credit.models import ( ...@@ -36,9 +36,6 @@ from openedx.core.djangoapps.credit.models import (
CreditEligibility CreditEligibility
) )
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6" TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6"
...@@ -95,7 +92,7 @@ class CreditApiTestBase(ModuleStoreTestCase): ...@@ -95,7 +92,7 @@ class CreditApiTestBase(ModuleStoreTestCase):
return credit_course return credit_course
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS') @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
@ddt.ddt @ddt.ddt
class CreditRequirementApiTests(CreditApiTestBase): class CreditRequirementApiTests(CreditApiTestBase):
""" """
...@@ -432,7 +429,7 @@ class CreditRequirementApiTests(CreditApiTestBase): ...@@ -432,7 +429,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
self.assertEqual(mail.outbox[0].subject, 'Course Credit Eligibility') self.assertEqual(mail.outbox[0].subject, 'Course Credit Eligibility')
# Now verify them email content # Now verify them email content
email_payload_first = mail.outbox[0].attachments[0]._payload # pylint: disable=protected-access email_payload_first = mail.outbox[0].attachments[0]._payload # pylint: disable=protected-access
# Test that email has two payloads [multipart (plain text and html # Test that email has two payloads [multipart (plain text and html
# content), attached image] # content), attached image]
...@@ -446,7 +443,7 @@ class CreditRequirementApiTests(CreditApiTestBase): ...@@ -446,7 +443,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
# Now check that html email content has same logo image 'Content-ID' # Now check that html email content has same logo image 'Content-ID'
# as the attached logo image 'Content-ID' # as the attached logo image 'Content-ID'
email_image = email_payload_first[1] email_image = email_payload_first[1]
html_content_first = email_payload_first[0]._payload[1]._payload # pylint: disable=protected-access html_content_first = email_payload_first[0]._payload[1]._payload # pylint: disable=protected-access
# strip enclosing angle brackets from 'logo_image' cache 'Content-ID' # strip enclosing angle brackets from 'logo_image' cache 'Content-ID'
image_id = email_image.get('Content-ID', '')[1:-1] image_id = email_image.get('Content-ID', '')[1:-1]
...@@ -468,8 +465,8 @@ class CreditRequirementApiTests(CreditApiTestBase): ...@@ -468,8 +465,8 @@ class CreditRequirementApiTests(CreditApiTestBase):
self.assertEqual(len(mail.outbox), 2) self.assertEqual(len(mail.outbox), 2)
# Now check that on sending eligibility notification again cached # Now check that on sending eligibility notification again cached
# logo image is used # logo image is used
email_payload_second = mail.outbox[1].attachments[0]._payload # pylint: disable=protected-access email_payload_second = mail.outbox[1].attachments[0]._payload # pylint: disable=protected-access
html_content_second = email_payload_second[0]._payload[1]._payload # pylint: disable=protected-access html_content_second = email_payload_second[0]._payload[1]._payload # pylint: disable=protected-access
self.assertIn(image_id, html_content_second) self.assertIn(image_id, html_content_second)
# The user should remain eligible even if the requirement status is later changed # The user should remain eligible even if the requirement status is later changed
...@@ -644,6 +641,21 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase): ...@@ -644,6 +641,21 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
self.assertIn(param_key, parameters) self.assertIn(param_key, parameters)
self.assertEqual(parameters[param_key], self.USER_INFO[key]) self.assertEqual(parameters[param_key], self.USER_INFO[key])
def test_create_credit_request_grade_length(self):
""" Verify the length of the final grade is limited to seven (7) characters total.
This is a hack for ASU.
"""
# Update the user's grade
status = CreditRequirementStatus.objects.get(username=self.USER_INFO["username"])
status.status = "satisfied"
status.reason = {"final_grade": 1.0 / 3.0}
status.save()
# Initiate a credit request
request = api.create_credit_request(self.course_key, self.PROVIDER_ID, self.USER_INFO['username'])
self.assertEqual(request['parameters']['final_grade'], u'0.33333')
def test_credit_request_disable_integration(self): def test_credit_request_disable_integration(self):
CreditProvider.objects.all().update(enable_integration=False) CreditProvider.objects.all().update(enable_integration=False)
...@@ -842,6 +854,7 @@ class CreditApiFeatureFlagTest(UrlResetMixin, TestCase): ...@@ -842,6 +854,7 @@ class CreditApiFeatureFlagTest(UrlResetMixin, TestCase):
""" """
Base class to test the credit api urls. Base class to test the credit api urls.
""" """
def setUp(self, **kwargs): def setUp(self, **kwargs):
enable_credit_api = kwargs.get('enable_credit_api', False) enable_credit_api = kwargs.get('enable_credit_api', False)
with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREDIT_API': enable_credit_api}): with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREDIT_API': enable_credit_api}):
......
# coding=utf-8
""" """
Tests for digital signatures used to validate messages to/from credit providers. Tests for digital signatures used to validate messages to/from credit providers.
""" """
...@@ -9,14 +10,14 @@ from django.test.utils import override_settings ...@@ -9,14 +10,14 @@ from django.test.utils import override_settings
from openedx.core.djangoapps.credit import signature from openedx.core.djangoapps.credit import signature
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
"asu": u'abcd1234'
})
class SignatureTest(TestCase): class SignatureTest(TestCase):
""" """
Tests for digital signatures. Tests for digital signatures.
""" """
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
"asu": u'abcd1234'
})
def test_unicode_secret_key(self): def test_unicode_secret_key(self):
# Test a key that has type `unicode` but consists of ASCII characters # Test a key that has type `unicode` but consists of ASCII characters
# (This can happen, for example, when loading the key from a JSON configuration file) # (This can happen, for example, when loading the key from a JSON configuration file)
...@@ -35,3 +36,9 @@ class SignatureTest(TestCase): ...@@ -35,3 +36,9 @@ class SignatureTest(TestCase):
# so we can fix the misconfiguration. # so we can fix the misconfiguration.
key = signature.get_shared_secret_key("asu") key = signature.get_shared_secret_key("asu")
self.assertIs(key, None) self.assertIs(key, None)
def test_unicode_data(self):
""" Verify the signature generation method supports Unicode data. """
key = signature.get_shared_secret_key("asu")
sig = signature.signature({'name': u'Ed Xavíer'}, key)
self.assertEqual(sig, "76b6c9a657000829253d7c23977b35b34ad750c5681b524d7fdfb25cd5273cec")
...@@ -58,7 +58,7 @@ git+https://github.com/edx/edx-lint.git@c5745631d2eee4e2efe8c31fa7b42fe2c12a0755 ...@@ -58,7 +58,7 @@ git+https://github.com/edx/edx-lint.git@c5745631d2eee4e2efe8c31fa7b42fe2c12a0755
git+https://github.com/edx/ecommerce-api-client.git@1.1.0#egg=ecommerce-api-client==1.1.0 git+https://github.com/edx/ecommerce-api-client.git@1.1.0#egg=ecommerce-api-client==1.1.0
-e git+https://github.com/edx/edx-user-state-client.git@30c0ad4b9f57f8d48d6943eb585ec8a9205f4469#egg=edx-user-state-client -e git+https://github.com/edx/edx-user-state-client.git@30c0ad4b9f57f8d48d6943eb585ec8a9205f4469#egg=edx-user-state-client
git+https://github.com/edx/edx-organizations.git@release-2015-09-22#egg=edx-organizations==0.1.6 git+https://github.com/edx/edx-organizations.git@release-2015-09-22#egg=edx-organizations==0.1.6
git+https://github.com/edx/edx-proctoring.git@0.9.14#egg=edx-proctoring==0.9.14 git+https://github.com/edx/edx-proctoring.git@0.9.16#egg=edx-proctoring==0.9.16
# Third Party XBlocks # Third Party XBlocks
-e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga -e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga
......
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