Commit 167710dc by Clinton Blackburn

Merge pull request #265 from edx/clintonb/js-coverage

Reporting JS coverage alongside Python
parents 620f99b9 501956f7
...@@ -7,10 +7,15 @@ sudo: false ...@@ -7,10 +7,15 @@ sudo: false
cache: cache:
directories: directories:
- $HOME/.cache/pip - $HOME/.cache/pip
addons:
apt:
packages:
- lcov
before_install: before_install:
- "export DISPLAY=:99.0" - "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start" - "sh -e /etc/init.d/xvfb start"
install: install:
- gem install coveralls-lcov
- pip install -r requirements/test.txt - pip install -r requirements/test.txt
- pip install coveralls - pip install coveralls
- make requirements.js - make requirements.js
...@@ -21,4 +26,5 @@ branches: ...@@ -21,4 +26,5 @@ branches:
only: only:
- master - master
after_success: after_success:
coveralls - coveralls-lcov -v -n coverage/report-lcov/lcov.info > js-coverage.json
- coveralls --merge=js-coverage.json
...@@ -41,9 +41,10 @@ serve: ...@@ -41,9 +41,10 @@ serve:
clean: clean:
find . -name '*.pyc' -delete find . -name '*.pyc' -delete
coverage erase coverage erase
rm -rf assets/ ecommerce/static/build rm -rf assets/ ecommerce/static/build coverage
validate_js: validate_js:
rm -rf coverage
gulp test gulp test
gulp lint gulp lint
gulp jscs gulp jscs
......
class PublishFailed(Exception):
""" Raised when a publish operation fails. """
pass
...@@ -82,3 +82,9 @@ class CheckoutPageTest(UserMixin, CourseCatalogTestMixin, TestCase): ...@@ -82,3 +82,9 @@ class CheckoutPageTest(UserMixin, CourseCatalogTestMixin, TestCase):
response, response,
'Purchase {} credits from'.format(self.credit_hours) 'Purchase {} credits from'.format(self.credit_hours)
) )
def test_course_not_found(self):
""" The view should return HTTP 404 if the course cannot be found. """
path = reverse('credit:checkout', args=['course/not/found'])
response = self.client.get(path)
self.assertEqual(response.status_code, 404)
import httpretty import ddt
from django.test import TestCase from django.test import TestCase
import httpretty
import mock
import requests
from requests import ConnectionError, Timeout
from ecommerce.settings.base import get_lms_url
from ecommerce.extensions.checkout.utils import get_provider_data from ecommerce.extensions.checkout.utils import get_provider_data
from ecommerce.settings.base import get_lms_url
@ddt.ddt
class UtilTests(TestCase): class UtilTests(TestCase):
@httpretty.activate @httpretty.activate
def test_get_provider_data(self): def test_get_provider_data(self):
...@@ -31,3 +35,9 @@ class UtilTests(TestCase): ...@@ -31,3 +35,9 @@ class UtilTests(TestCase):
) )
provider_data = get_provider_data('ABC') provider_data = get_provider_data('ABC')
self.assertEqual(provider_data, None) self.assertEqual(provider_data, None)
@ddt.data(ConnectionError, Timeout)
def test_exceptions(self, exception):
""" Verify the function returns None when a request exception is raised. """
with mock.patch.object(requests, 'get', mock.Mock(side_effect=exception)):
self.assertIsNone(get_provider_data('ABC'))
// jscs:disable requireCapitalizedConstructors
define([ define([
'backbone', 'backbone'
'models/product_model',
'models/course_seats/course_seat',
'utils/course_utils'
], ],
function (Backbone, function (Backbone) {
Product,
CourseSeat,
CourseUtils) {
'use strict'; 'use strict';
return Backbone.Collection.extend({ return Backbone.Collection.extend({});
model: function (attrs, options) {
var modelClass = Product;
if (attrs.product_class === 'Seat') {
modelClass = CourseUtils.getCourseSeatModel(CourseUtils.getSeatType(attrs));
}
/*jshint newcap: false */
return new modelClass(attrs, options);
/*jshint newcap: true */
}
});
} }
); );
...@@ -138,7 +138,7 @@ define([ ...@@ -138,7 +138,7 @@ define([
*/ */
removeParentProducts: function () { removeParentProducts: function () {
var products = this.get('products'), var products = this.get('products'),
parents = _.where(products, {structure: 'parent'}); parents = products.where({structure: 'parent'});
products.remove(parents); products.remove(parents);
}, },
......
...@@ -18,7 +18,7 @@ define([ ...@@ -18,7 +18,7 @@ define([
return Backbone.RelationalModel.extend({ return Backbone.RelationalModel.extend({
urlRoot: '/api/v2/products/', urlRoot: '/api/v2/products/',
nestedAttributes: ['certificate_type', 'id_verification_required', 'course_key'], nestedAttributes: ['certificate_type', 'course_key', 'id_verification_required'],
parse: function (response) { parse: function (response) {
// Un-nest the attributes // Un-nest the attributes
......
define([
'underscore',
'models/course_model'
],
function (_,
Course) {
'use strict';
var model,
data = {
id: 'edX/DemoX/Demo_Course',
url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/',
name: 'edX Demonstration Course',
verification_deadline: null,
type: 'verified',
products_url: 'http://ecommerce.local:8002/api/v2/courses/edX/DemoX/Demo_Course/products/',
last_edited: '2015-07-27T00:27:23Z',
products: [
{
id: 9,
url: 'http://ecommerce.local:8002/api/v2/products/9/',
structure: 'child',
product_class: 'Seat',
title: 'Seat in edX Demonstration Course with honor certificate',
price: '0.00',
expires: null,
attribute_values: [
{
name: 'certificate_type',
value: 'honor'
},
{
name: 'course_key',
value: 'edX/DemoX/Demo_Course'
},
{
name: 'id_verification_required',
value: false
}
],
is_available_to_buy: true
},
{
id: 8,
url: 'http://ecommerce.local:8002/api/v2/products/8/',
structure: 'child',
product_class: 'Seat',
title: 'Seat in edX Demonstration Course with verified certificate (and ID verification)',
price: '15.00',
expires: null,
attribute_values: [
{
name: 'certificate_type',
value: 'verified'
},
{
name: 'course_key',
value: 'edX/DemoX/Demo_Course'
},
{
name: 'id_verification_required',
value: true
}
],
is_available_to_buy: true
},
{
id: 7,
url: 'http://ecommerce.local:8002/api/v2/products/7/',
structure: 'parent',
product_class: 'Seat',
title: 'Seat in edX Demonstration Course',
price: null,
expires: null,
attribute_values: [
{
name: 'course_key',
value: 'edX/DemoX/Demo_Course'
}
],
is_available_to_buy: false
}
]
};
beforeEach(function () {
model = Course.findOrCreate(data, {parse: true});
});
describe('removeParentProducts', function () {
it('should remove all parent products from the products collection', function () {
var products = model.get('products');
// Sanity check to ensure the products were properly parsed
expect(products.length).toEqual(3);
// Remove the parent products
model.removeParentProducts();
// Only the children survived...
expect(products.length).toEqual(2);
expect(products.where({structure: 'child'}).length).toEqual(2);
});
});
// NOTE (CCB): There is a bug preventing this from being called 'toJSON'.
// See https://github.com/karma-runner/karma/issues/1534.
describe('#toJSON', function () {
it('should not modify verification_deadline if verification_deadline is empty', function () {
var json,
values = [null, ''];
_.each(values, function (value) {
model.set('verification_deadline', value);
json = model.toJSON();
expect(json.verification_deadline).toEqual(value);
});
});
it('should add a timezone to verification_deadline if verification_deadline is not empty', function () {
var json,
deadline = '2015-01-01T00:00:00';
model.set('verification_deadline', deadline);
json = model.toJSON();
expect(json.verification_deadline).toEqual(deadline + '+00:00');
});
});
}
);
define([
'underscore',
'models/course_seats/course_seat'
],
function (_,
CourseSeat) {
'use strict';
var model,
data = {
id: 9,
url: 'http://ecommerce.local:8002/api/v2/products/9/',
structure: 'child',
product_class: 'Seat',
title: 'Seat in edX Demonstration Course with honor certificate',
price: '0.00',
expires: null,
attribute_values: [
{
name: 'certificate_type',
value: 'honor'
},
{
name: 'course_key',
value: 'edX/DemoX/Demo_Course'
},
{
name: 'id_verification_required',
value: false
}
],
is_available_to_buy: true
};
beforeEach(function () {
model = CourseSeat.findOrCreate(data, {parse: true});
});
describe('getSeatType', function () {
it('should return a seat type corresponding to the certificate type', function () {
var mappings = {
'credit': 'credit',
'honor': 'honor',
'no-id-professional': 'professional',
'professional': 'professional',
'verified': 'verified'
};
_.each(mappings, function (seatType, certificateType) {
model.set('certificate_type', certificateType);
expect(model.getSeatType()).toEqual(seatType);
});
});
it('should return audit for empty certificate types', function () {
var certificate_types = ['', null];
_.each(certificate_types, function (certificateType) {
model.set('certificate_type', certificateType);
expect(model.getSeatType()).toEqual('audit');
});
});
});
describe('getSeatTypeDisplayName', function () {
it('should return a value corresponding to the certificate type', function () {
var mappings = {
'credit': 'Credit',
'honor': 'Honor',
'no-id-professional': 'Professional',
'professional': 'Professional',
'verified': 'Verified'
};
_.each(mappings, function (seatType, certificateType) {
model.set('certificate_type', certificateType);
expect(model.getSeatTypeDisplayName()).toEqual(seatType);
});
});
it('should return Audit for empty certificate types', function () {
var certificate_types = ['', null];
_.each(certificate_types, function (certificateType) {
model.set('certificate_type', certificateType);
expect(model.getSeatTypeDisplayName()).toEqual('Audit');
});
});
});
describe('getCertificateDisplayName', function () {
it('should return a value corresponding to the certificate type', function () {
var mappings = {
'credit': 'Verified Certificate',
'honor': 'Honor Certificate',
'no-id-professional': 'Professional Certificate',
'professional': 'Professional Certificate',
'verified': 'Verified Certificate'
};
_.each(mappings, function (seatType, certificateType) {
model.set('certificate_type', certificateType);
expect(model.getCertificateDisplayName()).toEqual(seatType);
});
});
it('should return (No Certificate) for empty certificate types', function () {
var certificate_types = ['', null];
_.each(certificate_types, function (certificateType) {
model.set('certificate_type', certificateType);
expect(model.getCertificateDisplayName()).toEqual('(No Certificate)');
});
});
});
}
);
define([
'underscore',
'models/product_model'
],
function (_,
Product) {
'use strict';
var model,
data = {
id: 8,
url: 'http://ecommerce.local:8002/api/v2/products/8/',
structure: 'child',
product_class: 'Seat',
title: 'Seat in edX Demonstration Course with verified certificate (and ID verification)',
price: '15.00',
expires: '2016-01-01T00:00:00Z',
attribute_values: [
{
name: 'certificate_type',
value: 'verified'
},
{
name: 'course_key',
value: 'edX/DemoX/Demo_Course'
},
{
name: 'id_verification_required',
value: true
}
],
is_available_to_buy: true
};
beforeEach(function () {
model = Product.findOrCreate(data, {parse: true});
});
// NOTE (CCB): There is a bug preventing this from being called 'toJSON'.
// See https://github.com/karma-runner/karma/issues/1534.
describe('#toJSON', function () {
it('should not modify expires if expires is empty', function () {
var json,
values = [null, ''];
_.each(values, function (value) {
model.set('expires', value);
json = model.toJSON();
expect(json.expires).toEqual(value);
});
});
it('should add a timezone to expires if expires is not empty', function () {
var json,
deadline = '2015-01-01T00:00:00';
model.set('expires', deadline);
json = model.toJSON();
expect(json.expires).toEqual(deadline + '+00:00');
});
it('should re-nest the un-nested attributes', function () {
var json = model.toJSON();
// Sanity check
expect(model.get('certificate_type')).toEqual('verified');
expect(model.get('course_key')).toEqual('edX/DemoX/Demo_Course');
expect(model.get('id_verification_required')).toEqual(true);
// Very the attributes have been re-nested
expect(json.attribute_values).toEqual(data.attribute_values);
});
});
}
);
define([
'underscore',
'utils/course_utils',
'models/course_seats/audit_seat',
'models/course_seats/course_seat',
'models/course_seats/honor_seat',
'models/course_seats/professional_seat',
'models/course_seats/verified_seat'
],
function (_,
CourseUtils,
AuditSeat,
CourseSeat,
HonorSeat,
ProfessionalSeat,
VerifiedSeat) {
'use strict';
describe('getCourseSeatModel', function () {
it('should return the CourseSeat child class corresponding to a seat type', function () {
expect(CourseUtils.getCourseSeatModel('audit')).toEqual(AuditSeat);
expect(CourseUtils.getCourseSeatModel('honor')).toEqual(HonorSeat);
expect(CourseUtils.getCourseSeatModel('professional')).toEqual(ProfessionalSeat);
expect(CourseUtils.getCourseSeatModel('verified')).toEqual(VerifiedSeat);
});
it('should return CourseSeat if the seat type is unknown', function () {
expect(CourseUtils.getCourseSeatModel(null)).toEqual(CourseSeat);
});
});
describe('orderSeatTypesForDisplay', function () {
it('should return a list ordered seat types', function () {
var data = [
['audit', 'professional', 'credit'],
['audit', 'honor', 'verified', 'professional', 'credit']
];
_.each(data, function (expected) {
expect(CourseUtils.orderSeatTypesForDisplay(_.shuffle(expected))).toEqual(expected);
});
});
});
}
);
define([ define([
'utils/utils'], 'utils/utils'
],
function (Utils) { function (Utils) {
'use strict'; 'use strict';
......
...@@ -41,40 +41,6 @@ define([ ...@@ -41,40 +41,6 @@ define([
}, },
/** /**
* Returns the seat type for a given model.
*
* @param {Backbone.Model|Object} seat
* @returns {String|null}
*/
getSeatType: function (seat) {
var seatType = seat.seatType;
if (!seatType) {
// Fall back to using certificate type
switch (seat.get('certificate_type') || seat.certificate_type) {
case 'verified':
seatType = 'verified';
break;
case 'credit':
seatType = 'credit';
break;
case 'professional':
case 'no-id-professional':
seatType = 'professional';
break;
case 'honor':
seatType = 'honor';
break;
default:
seatType = 'audit';
break;
}
}
return seatType;
},
/**
* Returns an array of CourseSeats, ordered as they should be displayed. * Returns an array of CourseSeats, ordered as they should be displayed.
* @param {CourseSeat[]} seats * @param {CourseSeat[]} seats
* @returns {CourseSeat[]} * @returns {CourseSeat[]}
......
...@@ -46,7 +46,10 @@ module.exports = function(config) { ...@@ -46,7 +46,10 @@ module.exports = function(config) {
// Karma coverage config // Karma coverage config
coverageReporter: { coverageReporter: {
type : 'text' reporters: [
{type: 'text'},
{ type: 'lcov', subdir: 'report-lcov' }
]
}, },
// test results reporter to use // test results reporter to use
......
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