Merge pull request #10030 from edx/hotfix/2015-10-05
Hotfix for 2015-10-05
This commit is contained in:
@@ -86,7 +86,8 @@ class CreditCourseDashboardTest(ModuleStoreTestCase):
|
||||
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.
|
||||
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):
|
||||
# Simulate that the user has completed the only requirement in the course
|
||||
|
||||
@@ -413,7 +413,7 @@ FEATURES = {
|
||||
'ENABLE_OPENBADGES': False,
|
||||
|
||||
# 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.
|
||||
'ENABLE_DISABLING_XBLOCK_TYPES': True,
|
||||
@@ -2122,7 +2122,7 @@ if FEATURES.get('CLASS_DASHBOARD'):
|
||||
INSTALLED_APPS += ('class_dashboard',)
|
||||
|
||||
################ Enable credit eligibility feature ####################
|
||||
ENABLE_CREDIT_ELIGIBILITY = False
|
||||
ENABLE_CREDIT_ELIGIBILITY = True
|
||||
FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY
|
||||
|
||||
######################## CAS authentication ###########################
|
||||
|
||||
42
lms/static/js/commerce/credit.js
Normal file
42
lms/static/js/commerce/credit.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 || {};
|
||||
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
|
||||
var $courseNamePlaceholder = $(".course_name_placeholder");
|
||||
$courseNamePlaceholder.text(courseId);
|
||||
|
||||
this.getCourseData(courseId).then(function(responseData) {
|
||||
this.getCourseData(courseId).then(function (responseData) {
|
||||
$courseNamePlaceholder.text(responseData.name);
|
||||
});
|
||||
},
|
||||
@@ -77,7 +77,7 @@ var edx = edx || {};
|
||||
var self = this,
|
||||
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
|
||||
self.$el.removeClass('hidden');
|
||||
self.getReceiptData(orderId).then(self.renderReceipt, self.renderError);
|
||||
@@ -168,7 +168,7 @@ var edx = edx || {};
|
||||
billedTo: null
|
||||
};
|
||||
|
||||
if (order.billing_address){
|
||||
if (order.billing_address) {
|
||||
receiptContext.billedTo = {
|
||||
firstName: order.billing_address.first_name,
|
||||
lastName: order.billing_address.last_name,
|
||||
@@ -263,8 +263,8 @@ var edx = edx || {};
|
||||
line = order.lines[0];
|
||||
if (this.useEcommerceApi) {
|
||||
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.
|
||||
if (attributeValues != undefined) {
|
||||
@@ -273,7 +273,7 @@ var edx = edx || {};
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
new edx.commerce.ReceiptView({
|
||||
@@ -282,16 +282,11 @@ var edx = edx || {};
|
||||
|
||||
})(jQuery, _, _.str, Backbone);
|
||||
|
||||
|
||||
function completeOrder (event) {
|
||||
function completeOrder(event) { // jshint ignore:line
|
||||
var courseKey = $(event).data("course-key"),
|
||||
username = $(event).data("username"),
|
||||
providerId = $(event).data("provider"),
|
||||
postData = {
|
||||
'course_key': courseKey,
|
||||
'username': username
|
||||
},
|
||||
errorContainer = $("#error-container");
|
||||
$errorContainer = $("#error-container");
|
||||
|
||||
analytics.track(
|
||||
"edx.bi.credit.clicked_complete_credit",
|
||||
@@ -301,34 +296,7 @@ function completeOrder (event) {
|
||||
}
|
||||
);
|
||||
|
||||
$.ajax({
|
||||
url: '/api/credit/v1/providers/' + providerId + '/request/',
|
||||
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");
|
||||
}
|
||||
edx.commerce.credit.createCreditRequest(providerId, courseKey, username).fail(function () {
|
||||
$errorContainer.removeClass("hidden");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
(function($, analytics) {
|
||||
/**
|
||||
* Student dashboard credit messaging.
|
||||
*/
|
||||
|
||||
var edx = edx || {};
|
||||
|
||||
(function ($, analytics) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready(function() {
|
||||
var errorContainer = $(".credit-error-msg"),
|
||||
creditStatusError = errorContainer.data("credit-error");
|
||||
$(document).ready(function () {
|
||||
var $errorContainer = $(".credit-error-msg"),
|
||||
creditStatusError = $errorContainer.data("credit-error");
|
||||
|
||||
if (creditStatusError == "True"){
|
||||
errorContainer.toggleClass("is-hidden");
|
||||
if (creditStatusError === "True") {
|
||||
$errorContainer.toggleClass("is-hidden");
|
||||
}
|
||||
|
||||
// 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");
|
||||
analytics.track(
|
||||
"edx.bi.credit.clicked_purchase_credit",
|
||||
@@ -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');
|
||||
$(".pending-credit-btn").on("click", function (event) {
|
||||
var $target = $(event.target),
|
||||
courseKey = $target.data("course-key"),
|
||||
username = $target.data("user"),
|
||||
providerId = $target.data("provider");
|
||||
|
||||
$('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){
|
||||
$(".credit-request-pending-msg").hide("is-hidden");
|
||||
$(".pending-credit-btn").hide();
|
||||
errorContainer.toggleClass("is-hidden");
|
||||
}
|
||||
edx.commerce.credit.createCreditRequest(providerId, courseKey, username).fail(function () {
|
||||
$(".credit-action").hide();
|
||||
$errorContainer.toggleClass("is-hidden");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -548,24 +548,6 @@
|
||||
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 {
|
||||
@extend %ui-depth1;
|
||||
border-radius: 3px;
|
||||
@@ -750,19 +732,21 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.credit-eligibility-msg {
|
||||
@include float(left);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.credit-request-pending-msg {
|
||||
@include float(left);
|
||||
margin-top: 10px;
|
||||
}
|
||||
.credit-action {
|
||||
.credit-msg {
|
||||
@include float(left);
|
||||
width: flex-grid(9, 12);
|
||||
}
|
||||
|
||||
.credit-request-approved-msg{
|
||||
width: flex-grid(10, 12);
|
||||
@include float(left);
|
||||
.credit-btn {
|
||||
@extend %btn-pl-yellow-base;
|
||||
@include float(right);
|
||||
background-image: none ;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -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/underscore.string.min.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>
|
||||
</%block>
|
||||
|
||||
@@ -56,7 +57,6 @@ from django.utils.translation import ugettext as _
|
||||
<h1>${_("Loading Order Data...")}</h1>
|
||||
<span>${ _("Please wait while we retrieve your order details.") }</span>
|
||||
</div>
|
||||
<form id="complete-order-form"></form>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<%= interpolate("<img src='%s' alt='%s'></image>", [thumbnail_url, display_name]) %>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
@@ -35,6 +35,7 @@ import json
|
||||
</%block>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script src="${static.url('js/commerce/credit.js')}"></script>
|
||||
<%static:js group='dashboard'/>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
||||
@@ -19,61 +19,47 @@
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
% if not credit_status["purchased"] and not credit_status["error"] :
|
||||
<p class="message-copy credit-eligibility-msg">
|
||||
${_("You are now eligible for credit. <b>Congratulations!</b>")}
|
||||
<div class="credit-action">
|
||||
% if not credit_status["purchased"] and not credit_status["error"] :
|
||||
<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>
|
||||
<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"] :
|
||||
% if credit_status["request_status"] == "pending":
|
||||
<p class="message-copy credit-request-pending-msg">
|
||||
% elif credit_status["request_status"] == "approved" and not credit_status["error"] :
|
||||
<p class="message-copy credit-msg credit-request-approved-msg">
|
||||
## Translators: provider_name is the name of a credit provider or university (e.g. State University)
|
||||
${_("<strong>Congratulations!</strong> {provider_name} has converted your course credit. To see your course credit, click <strong>Access Credit</strong>.").format(
|
||||
provider_name=credit_status["provider_name"],
|
||||
)
|
||||
}
|
||||
</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-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
|
||||
## 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(
|
||||
## credit provider, such as 'State University' or 'Happy Fun Company'. provider_name is the name of credit provider.
|
||||
${_("{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,
|
||||
)
|
||||
}
|
||||
</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>
|
||||
% elif credit_status["request_status"] is None:
|
||||
<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 for your purchase. Please proceed to {link_to_provider_site} to finalize your credit.").format(
|
||||
link_to_provider_site=provider_link,
|
||||
)
|
||||
}
|
||||
</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
|
||||
|
||||
<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
|
||||
|
||||
@@ -242,13 +242,20 @@ def create_credit_request(course_key, provider_id, username):
|
||||
|
||||
# Retrieve the final grade from the eligibility table
|
||||
try:
|
||||
final_grade = unicode(CreditRequirementStatus.objects.get(
|
||||
final_grade = CreditRequirementStatus.objects.get(
|
||||
username=username,
|
||||
requirement__namespace="grade",
|
||||
requirement__name="grade",
|
||||
requirement__course__course_key=course_key,
|
||||
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):
|
||||
log.exception(
|
||||
"Could not retrieve final grade from the credit eligibility table "
|
||||
|
||||
@@ -54,10 +54,10 @@ def signature(params, shared_secret):
|
||||
str: The 32-character signature.
|
||||
|
||||
"""
|
||||
encoded_params = "".join([
|
||||
"{key}:{value}".format(key=key, value=params[key])
|
||||
encoded_params = u"".join([
|
||||
u"{key}:{value}".format(key=key, value=params[key])
|
||||
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()
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
Tests for the API functions in the credit app.
|
||||
"""
|
||||
import datetime
|
||||
import ddt
|
||||
import json
|
||||
from mock import patch
|
||||
import pytz
|
||||
import unittest
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.db import connection, transaction
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
from unittest import skipUnless
|
||||
|
||||
from mock import patch
|
||||
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 openedx.core.djangoapps.credit import api
|
||||
@@ -36,9 +36,6 @@ from openedx.core.djangoapps.credit.models import (
|
||||
CreditEligibility
|
||||
)
|
||||
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"
|
||||
|
||||
@@ -95,7 +92,7 @@ class CreditApiTestBase(ModuleStoreTestCase):
|
||||
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
|
||||
class CreditRequirementApiTests(CreditApiTestBase):
|
||||
"""
|
||||
@@ -432,7 +429,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
|
||||
self.assertEqual(mail.outbox[0].subject, 'Course Credit Eligibility')
|
||||
|
||||
# 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
|
||||
# content), attached image]
|
||||
@@ -446,7 +443,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
|
||||
# Now check that html email content has same logo image 'Content-ID'
|
||||
# as the attached logo image 'Content-ID'
|
||||
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'
|
||||
image_id = email_image.get('Content-ID', '')[1:-1]
|
||||
@@ -468,8 +465,8 @@ class CreditRequirementApiTests(CreditApiTestBase):
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
# Now check that on sending eligibility notification again cached
|
||||
# logo image is used
|
||||
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
|
||||
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
|
||||
self.assertIn(image_id, html_content_second)
|
||||
|
||||
# The user should remain eligible even if the requirement status is later changed
|
||||
@@ -644,6 +641,21 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
|
||||
self.assertIn(param_key, parameters)
|
||||
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):
|
||||
CreditProvider.objects.all().update(enable_integration=False)
|
||||
|
||||
@@ -842,6 +854,7 @@ class CreditApiFeatureFlagTest(UrlResetMixin, TestCase):
|
||||
"""
|
||||
Base class to test the credit api urls.
|
||||
"""
|
||||
|
||||
def setUp(self, **kwargs):
|
||||
enable_credit_api = kwargs.get('enable_credit_api', False)
|
||||
with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREDIT_API': enable_credit_api}):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# coding=utf-8
|
||||
"""
|
||||
Tests for digital signatures used to validate messages to/from credit providers.
|
||||
"""
|
||||
@@ -9,14 +10,14 @@ from django.test.utils import override_settings
|
||||
from openedx.core.djangoapps.credit import signature
|
||||
|
||||
|
||||
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
|
||||
"asu": u'abcd1234'
|
||||
})
|
||||
class SignatureTest(TestCase):
|
||||
"""
|
||||
Tests for digital signatures.
|
||||
"""
|
||||
|
||||
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
|
||||
"asu": u'abcd1234'
|
||||
})
|
||||
def test_unicode_secret_key(self):
|
||||
# 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)
|
||||
@@ -35,3 +36,9 @@ class SignatureTest(TestCase):
|
||||
# so we can fix the misconfiguration.
|
||||
key = signature.get_shared_secret_key("asu")
|
||||
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
|
||||
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
|
||||
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
|
||||
-e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga
|
||||
|
||||
Reference in New Issue
Block a user