Course enroll page

Will add a prepaid purchase once the prepaid-v2 branch is merged into
master.
This commit is contained in:
Matt Lott 2015-09-03 11:04:40 -07:00
parent 46ee12ff9d
commit 9131d8668f
17 changed files with 628 additions and 7 deletions

View file

@ -58,11 +58,12 @@ module.exports = class CocoRouter extends Backbone.Router
'contribute/diplomat': go('contribute/DiplomatView')
'contribute/scribe': go('contribute/ScribeView')
'courses': go('courses/CoursesView')
'courses/mock1': go('courses/mock1/CoursesView')
'courses/mock1/enroll/:courseID': go('courses/mock1/CourseEnrollView')
'courses/mock1/:courseID': go('courses/mock1/CourseDetailsView')
'courses/mock1/:courseID/info': go('courses/mock1/CourseInfoView')
'courses': go('courses/CoursesView')
'courses/enroll(/:courseID)': go('courses/CourseEnrollView')
'courses/:courseID': go('courses/CourseDetailsView')
'db/*path': 'routeToServer'
'demo(/*subpath)': go('DemoView')

View file

@ -198,3 +198,11 @@ module.exports.getSponsoredSubsAmount = getSponsoredSubsAmount = (price=999, sub
Math.round((1 - offset) * price + (subCount - 1 + offset) * price * 0.8)
else
Math.round((1 - offset) * price + 10 * price * 0.8 + (subCount - 11 + offset) * price * 0.6)
module.exports.getCoursesPrice = getSponsoredSubsAmount = (courses, seats=20) ->
totalPricePerSeat = courses.reduce ((a, b) -> a + b.get('pricePerSeat')), 0
if courses.length > 2
pricePerSeat = Math.round(totalPricePerSeat / 2.0)
else
pricePerSeat = parseInt(totalPricePerSeat)
seats * pricePerSeat

View file

@ -7,6 +7,7 @@ _.extend CourseSchema.properties,
campaignID: c.objectId()
concepts: c.array {title: 'Programming Concepts', uniqueItems: true}, c.concept
description: {type: 'string'}
pricePerSeat: {type: 'number', description: 'Price per seat in USD cents.'}
screenshot: c.url {title: 'URL', description: 'Link to course screenshot.'}
c.extendBasicProperties CourseSchema, 'Course'

View file

@ -1,11 +1,12 @@
c = require './../schemas'
CourseInstanceSchema = c.object {title: 'Course Instance'}
c.extendNamedProperties CourseInstanceSchema # name first
_.extend CourseInstanceSchema.properties,
courseID: c.objectId()
description: {type: 'string'}
members: c.array {title: 'Members'}, c.objectId()
name: {type: 'string'}
ownerID: c.objectId()
prepaidID: c.objectId()

View file

@ -0,0 +1,7 @@
#course-details-view
.edit-description-input
width: 100%
.edit-name-input
width: 50%

View file

@ -0,0 +1,14 @@
#course-enroll-view
.btn-buy
margin: 20px 0px
.center
text-align: center
.enroll-container
margin: 5% 20%
width: 60%
.class-name
width: 300px

View file

@ -0,0 +1,31 @@
extends /templates/base
block content
//- DO NOT localize / i18n
div
span *UNDER CONSTRUCTION, send feedback to
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
div(style='border-bottom: 1px solid black;')
h1(style='text-align: center;') Course
if course
div= course.get('name')
div= course.get('description')
div= course.get('campaignID')
div= course.get('concepts')
else
div No course found.
h1(style='text-align: center;') Class
if courseInstance
p
div= courseInstance.get('name') || 'Class Name'
div= courseInstance.get('description')
div= courseInstance.get('courseID')
div= courseInstance.get('ownerID')
div= courseInstance.get('members')
div= courseInstance.get('prepaidID')
else
p No classes found.

View file

@ -0,0 +1,77 @@
extends /templates/base
block content
//- DO NOT localize / i18n
div
span *UNDER CONSTRUCTION, send feedback to
a.spl(href='mailto:team@codecombat.com') team@codecombat.com
div(style='border-bottom: 1px solid black')
if state === 'declined' || state === 'unknown_error'
p
.alert.alert-danger ERROR #{stateMessage}
if state === 'creating'
p
.alert.alert-info Creating class...
else if state === 'purchasing'
p
.alert.alert-info Purchasing course...
else
.well.well-lg.enroll-container
if price > 0
h1.center Buy Course
else
h1.center Create Class
h3 1. Course
if courses.length > 2
p Select 'All Courses' for a 50% discount!
.form-group
select.form-control.course-select
each course in courses
option(value="#{course.id}")= course.get('name')
if courses.length > 1
option(value="All Courses") All Courses
h3 2. Number of students
p Enter the number of students you need for this class.
input.input-seats(type='text', value="#{seats}")
h3 3. Name your class
p This will be displayed on the course page for you and your students. It can be changed later.
input.class-name(type='text', placeholder="Mrs. Smith's 4th Period", value="#{className ? className : ''}")
if price > 0
h3 4. Buy
else
h3 4. Create Class
p
if price > 0
span.spr You are purchasing a license for
else
span.spr You are creating a class for
strong.spr #{selectedCourseTitle}
span.spr for
strong #{seats} students
| .
p Afterwards you will receive an unlock code to distribute to your students, which they can use to enroll in your class.
p.center
if price > 0
button.btn.btn-success.btn-lg.btn-buy $#{(price / 100.0).toFixed(2)}
else
button.btn.btn-success.btn-lg.btn-buy Create Class
+trial-and-questions
mixin trial-and-questions
h3 Free trial for teachers!
p
span.spr Please fill out our
a(href='/teachers/freetrial', data-i18n="teachers.teacher_subs_2")
span.spl to get individual access to all courses for evalutaion purposes.
h3 Questions?
p
span Please contact
a.spl(href='mailto:team@codecombat.com') team@codecombat.com

View file

@ -0,0 +1,38 @@
RootView = require 'views/core/RootView'
template = require 'templates/courses/course-details'
CocoCollection = require 'collections/CocoCollection'
Course = require 'models/Course'
CourseInstance = require 'models/CourseInstance'
# TODO: logged out experience
# TODO: no course instances
# TODO: no course instance selected
module.exports = class CourseDetailsView extends RootView
id: 'course-details-view'
template: template
constructor: (options, @courseID) ->
super options
@courseInstanceID = options.courseInstanceID
@course = new Course _id: @courseID
@supermodel.loadModel @course, 'course', cache: false
if @courseInstanceID
@courseInstance = new CourseInstance _id: @courseInstanceID
@supermodel.loadModel @courseInstance, 'course_instance', cache: false
else if !me.isAnonymous()
@courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance})
@listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
@supermodel.loadCollection(@courseInstances, 'course_instances')
getRenderData: ->
context = super()
context.course = @course
context.courseInstance = @courseInstance
context
onCourseInstancesLoaded: ->
if @courseInstances.models.length is 1
@courseInstance = @courseInstances.models[0]
else if @courseInstances.models.length > 0
@courseInstance = @courseInstances.models[0]

View file

@ -0,0 +1,133 @@
app = require 'core/application'
AuthModal = require 'views/core/AuthModal'
CocoCollection = require 'collections/CocoCollection'
Course = require 'models/Course'
{getCoursesPrice} = require 'core/utils'
RootView = require 'views/core/RootView'
stripeHandler = require 'core/services/stripe'
template = require 'templates/courses/course-enroll'
module.exports = class CourseEnrollView extends RootView
id: 'course-enroll-view'
template: template
events:
'click .btn-buy': 'onClickBuy'
'change .class-name': 'onNameChange'
'change .course-select': 'onChangeCourse'
'change .input-seats': 'onSeatsChange'
subscriptions:
'stripe:received-token': 'onStripeReceivedToken'
constructor: (options, @courseID=0) ->
super options
@courseID ?= options.courseID
@seats = 20
@courses = new CocoCollection([], { url: "/db/course", model: Course})
@listenTo @courses, 'sync', @onCoursesLoaded
@supermodel.loadCollection(@courses, 'courses')
getRenderData: ->
context = super()
context.className = @className
context.courses = @courses.models
context.price = @price ? 0
context.seats = @seats
context.selectedCourse = @selectedCourse
context.selectedCourseTitle = @selectedCourse?.get('name') ? 'All Courses'
context.state = @state
context.stateMessage = @stateMessage
context
afterRender: ->
super()
if @selectedCourse
@$el.find('.course-select').val(@selectedCourse.id)
else
@$el.find('.course-select').val('All Courses')
onCoursesLoaded: ->
if @courseID
@selectedCourse = _.find @courses.models, (a) => a.id is @courseID
else if @courses.models.length > 0
@selectedCourse = @courses.models[0]
@renderNewPrice()
onClickBuy: (e) ->
return @openModalView new AuthModal() if me.isAnonymous()
if @seats < 1 or not _.isFinite(@seats)
alert("Please enter the maximum number of students needed for your class.")
return
if @price is 0
@state = 'creating'
@createClass()
return
@state = undefined
@stateMessage = undefined
@render()
# Show Stripe handler
courseTitle = @selectedCourse?.get('name') ? 'All Courses'
application.tracker?.trackEvent 'Started course purchase', {course: courseTitle, price: @price, seats: @seats}
stripeHandler.open
amount: @price
description: "#{courseTitle} for #{@seats} students"
bitcoin: true
alipay: if me.get('chinaVersion') or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
onStripeReceivedToken: (e) ->
@state = 'purchasing'
@render?()
@createClass(e.token.id)
onChangeCourse: (e) ->
@selectedCourse = _.find @courses.models, (a) -> a.id is $(e.target).val()
@renderNewPrice()
onNameChange: (e) ->
@className = $('.class-name').val()
onSeatsChange: (e) ->
@seats = $(e.target).val()
@seats = 20 if @seats < 1 or not _.isFinite(@seats)
@renderNewPrice()
createClass: (token) ->
data =
name: $('.class-name').val()
seats: @seats
token: token
data.courseID = @selectedCourse.id if @selectedCourse
jqxhr = $.post('/db/course_instance/-/create', data)
jqxhr.done (data, textStatus, jqXHR) =>
application.tracker?.trackEvent 'Finished course purchase', {course: @selectedCourse?.get('name') ? 'All Courses', price: @price, seats: @seats}
# TODO: handle fetch errors
me.fetch(cache: false).always =>
courseID = @selectedCourse?.id ? @courses.models[0]?.id
viewArgs = if data?.length > 0 then [courseInstanceID: data[0]._id, courseID] else [{}, courseID]
Backbone.Mediator.publish 'router:navigate',
route: "/courses/#{courseID}"
viewClass: 'views/courses/CourseDetailsView'
viewArgs: viewArgs
jqxhr.fail (xhr, textStatus, errorThrown) =>
console.error 'Got an error purchasing a course:', textStatus, errorThrown
application.tracker?.trackEvent 'Failed course purchase', status: textStatus
if xhr.status is 402
@state = 'declined'
@stateMessage = arguments[2]
else
@state = 'unknown_error'
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
@render?()
renderNewPrice: ->
if @selectedCourse
@price = getCoursesPrice([@selectedCourse], @seats)
else
@price = getCoursesPrice(@courses.models, @seats)
@render?()

View file

@ -4,6 +4,8 @@
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// NOTE: uses name as unique identifier, so changing the name will insert a new course
// NOTE: concepts should match actual campaign levels
// NOTE: pricePerSeat in USD cents
var documents =
[
@ -13,7 +15,26 @@ var documents =
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d"),
concepts: ['basic_syntax', 'arguments', 'while_loops', 'strings', 'variables'],
description: "Learn basic syntax, while loops, and the CodeCombat environment.",
pricePerSeat: NumberInt(0),
screenshot: "/images/pages/courses/101_info.png"
},
{
name: "Computer Science 2",
slug: "computer-science-2",
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d"),
concepts: ['basic_syntax', 'arguments', 'while_loops', 'strings', 'variables', 'if_statements'],
description: "Introduce Arguments, Variables, If Statements, and Arithmetic.",
pricePerSeat: NumberInt(400),
screenshot: "/images/pages/courses/102_info.png"
},
{
name: "Computer Science 3",
slug: "computer-science-3",
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d"),
concepts: ['if_statements', 'arithmetic'],
description: "Learn how to handle input.",
pricePerSeat: NumberInt(400),
screenshot: "/images/pages/courses/103_info.png"
}
];

View file

@ -75,6 +75,7 @@ module.exports = class Handler
props
# sending functions
sendUnauthorizedError: (res) -> errors.unauthorized(res)
sendForbiddenError: (res) -> errors.forbidden(res)
sendNotFoundError: (res, message) -> errors.notFound(res, message)
sendMethodNotAllowed: (res, message) -> errors.badMethod(res, @allowedMethods, message)

View file

@ -5,9 +5,6 @@ jsonSchema = require '../../app/schemas/models/course_instance.schema'
CourseInstanceSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref}
CourseInstanceSchema.plugin plugins.NamedPlugin
CourseInstanceSchema.plugin plugins.SearchablePlugin, {searchable: ['name', 'description']}
CourseInstanceSchema.statics.privateProperties = []
CourseInstanceSchema.statics.editableProperties = [
'description'

View file

@ -1,13 +1,98 @@
mongoose = require 'mongoose'
async = require 'async'
Handler = require '../commons/Handler'
{getCoursesPrice} = require '../../app/core/utils'
Course = require './Course'
CourseInstance = require './CourseInstance'
Prepaid = require '../prepaids/Prepaid'
CourseInstanceHandler = class CourseInstanceHandler extends Handler
modelClass: CourseInstance
jsonSchema: require '../../app/schemas/models/course_instance.schema'
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
logError: (user, msg) ->
console.warn "Course error: #{user.get('slug')} (#{user._id}): '#{msg}'"
hasAccess: (req) ->
req.method is 'GET' or req.user?.isAdmin()
hasAccessToDocument: (req, document, method=null) ->
return true if _.find document?.get('members'), (a) -> a.equals(req.user?.get('_id'))
req.user?.isAdmin()
getByRelationship: (req, res, args...) ->
relationship = args[1]
return @createAPI(req, res) if relationship is 'create'
super arguments...
createAPI: (req, res) ->
return @sendUnauthorizedError(res) unless req.user?
# Required Input
seats = req.body.seats
unless seats > 0
@logError(req.user, 'Course create API missing required seats count')
return @sendBadInputError(res, 'Missing required seats count')
# Optional - unspecified means create instances for all courses
courseID = req.body.courseID
# Optional
name = req.body.name
# Optional - as long as course(s) are all free
stripeToken = req.body.token
@getCourses courseID, (err, courses) =>
if err
@logError(req.user, err)
return @sendDatabaseError(res, err)
price = getCoursesPrice(courses, seats)
if price > 0 and not stripeToken
@logError(req.user, 'Course create API missing required Stripe token')
return @sendBadInputError(res, 'Missing required Stripe token')
# TODO: purchase prepaid for courses, price, and seats
Prepaid.generateNewCode (code) =>
return @sendDatabaseError(res, 'Database error.') unless code
prepaid = new Prepaid
creator: req.user.get('_id')
type: 'course'
code: code
properties:
courseIDs: (course.get('_id') for course in courses)
prepaid.set('maxRedeemers', seats) if seats
prepaid.save (err) =>
return @sendDatabaseError(res, err) if err
courseInstances = []
makeCreateInstanceFn = (course, name, prepaid) =>
(done) =>
@createInstance req, course, name, prepaid, (err, newInstance)=>
courseInstances.push newInstance unless err
done(err)
# tasks = []
# tasks.push(makeCreateInstanceFn(course, name, prepaid)) for course in courses
tasks = (makeCreateInstanceFn(course, name, prepaid) for course in courses)
async.parallel tasks, (err, results) =>
return @sendDatabaseError(res, err) if err
@sendCreated(res, courseInstances)
createInstance: (req, course, name, prepaid, done) =>
courseInstance = new CourseInstance
courseID: course.get('_id')
members: [req.user.get('_id')]
name: name
ownerID: req.user.get('_id')
prepaidID: prepaid.get('_id')
courseInstance.save (err, newInstance) =>
done(err, newInstance)
getCourses: (courseID, done) =>
if courseID
Course.findById courseID, (err, document) =>
done(err, [document])
else
Course.find {}, (err, documents) =>
done(err, documents)
module.exports = new CourseInstanceHandler()

View file

@ -11,6 +11,7 @@ log = require 'winston'
moment = require 'moment'
AnalyticsLogEvent = require '../analytics/AnalyticsLogEvent'
Clan = require '../clans/Clan'
CourseInstance = require '../courses/CourseInstance'
LevelSession = require '../levels/sessions/LevelSession'
LevelSessionHandler = require '../levels/sessions/level_session_handler'
Payment = require '../payments/Payment'
@ -309,6 +310,7 @@ UserHandler = class UserHandler extends Handler
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
return @getCandidates(req, res) if args[1] is 'candidates'
return @getClans(req, res, args[0]) if args[1] is 'clans'
return @getCourseInstances(req, res, args[0]) if args[1] is 'course_instances'
return @getEmployers(req, res) if args[1] is 'employers'
return @getSimulatorLeaderboard(req, res, args[0]) if args[1] is 'simulatorLeaderboard'
return @getMySimulatorLeaderboardRank(req, res, args[0]) if args[1] is 'simulator_leaderboard_rank'
@ -605,6 +607,13 @@ UserHandler = class UserHandler extends Handler
return @sendDatabaseError(res, err) if err
@sendSuccess(res, documents)
getCourseInstances: (req, res, userIDOrSlug) ->
@getDocumentForIdOrSlug userIDOrSlug, (err, user) =>
return @sendNotFoundError(res) unless user
CourseInstance.find {members: {$in: [user.get('_id')]}}, (err, documents) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, documents)
formatCandidate: (authorized, document) ->
fields = if authorized then ['name', 'jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['_id','jobProfile', 'jobProfileApproved']
obj = _.pick document.toObject(), fields

View file

@ -30,6 +30,8 @@ models_path = [
'../../server/articles/Article'
'../../server/campaigns/Campaign'
'../../server/clans/Clan'
'../../server/courses/Course'
'../../server/courses/CourseInstance'
'../../server/levels/Level'
'../../server/levels/components/LevelComponent'
'../../server/levels/systems/LevelSystem'

View file

@ -0,0 +1,195 @@
async = require 'async'
config = require '../../../server_config'
require '../common'
stripe = require('stripe')(config.stripe.secretKey)
# TODO: add permissiosn tests
describe 'CourseInstance', ->
courseInstanceURL = getURL('/db/course_instance/-/create')
userURL = getURL('/db/user')
nameCount = 0
createName = (name) -> name + nameCount++
createCourse = (pricePerSeat, done) ->
name = createName 'course '
course = new Course
name: name
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d")
concepts: ['basic_syntax', 'arguments', 'while_loops', 'strings', 'variables']
description: "Learn basic syntax, while loops, and the CodeCombat environment."
pricePerSeat: pricePerSeat
screenshot: "/images/pages/courses/101_info.png"
course.save (err, course) ->
return done(err) if err
done(err, course)
createCourseInstances = (user, courseID, seats, token, done) ->
name = createName 'course instance '
requestBody =
courseID: courseID
name: name
seats: seats
token: token
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(201)
CourseInstance.find {name: name}, (err, courseInstances) ->
expect(err).toBeNull()
makeCourseInstanceVerifyFn = (courseInstance) ->
(done) ->
expect(courseInstance.get('name')).toEqual(name)
expect(courseInstance.get('ownerID')).toEqual(user.get('_id'))
expect(courseInstance.get('members')).toContain(user.get('_id'))
query = {$and: [{creator: user.get('_id')}]}
query.$and.push {'properties.courseIDs': {$in: [courseID]}} if courseID
Prepaid.find query, (err, prepaids) ->
expect(err).toBeNull()
return done(err) if err
expect(prepaids?.length).toEqual(1)
return done() unless prepaids?.length > 0
expect(prepaids[0].get('type')).toEqual('course')
expect(prepaids[0].get('maxRedeemers')).toEqual(seats) if seats
# TODO: verify Payment
done(err)
tasks = []
for courseInstance in courseInstances
tasks.push makeCourseInstanceVerifyFn(courseInstance)
async.parallel tasks, (err) =>
return done(err) if err
done(err, courseInstances)
it 'Clear database users and clans', (done) ->
clearModels [User, Course, CourseInstance, Prepaid], (err) ->
throw err if err
done()
describe 'Single courses', ->
it 'Create for free course 1 seat', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
createCourse 0, (err, course) ->
expect(err).toBeNull()
return done(err) if err
createCourseInstances user1, course.get('_id'), 1, token.id, (err, courseInstances) ->
expect(err).toBeNull()
return done(err) if err
expect(courseInstances.length).toEqual(1)
done()
it 'Create for free course no seats', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
createCourse 0, (err, course) ->
expect(err).toBeNull()
return done(err) if err
name = createName 'course instance '
requestBody =
courseID: course.get('_id')
name: createName('course instance ')
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(422)
done()
it 'Create for free course no token', (done) ->
loginNewUser (user1) ->
createCourse 0, (err, course) ->
expect(err).toBeNull()
return done(err) if err
createCourseInstances user1, course.get('_id'), 2, null, (err, courseInstances) ->
expect(err).toBeNull()
return done(err) if err
expect(courseInstances.length).toEqual(1)
done()
it 'Create for paid course 1 seat', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
createCourse 7000, (err, course) ->
expect(err).toBeNull()
return done(err) if err
createCourseInstances user1, course.get('_id'), 1, token.id, (err, courseInstances) ->
expect(err).toBeNull()
return done(err) if err
expect(courseInstances.length).toEqual(1)
done()
it 'Create for paid course 50 seats', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
createCourse 7000, (err, course) ->
expect(err).toBeNull()
return done(err) if err
createCourseInstances user1, course.get('_id'), 50, token.id, (err, courseInstances) ->
expect(err).toBeNull()
return done(err) if err
expect(courseInstances.length).toEqual(1)
done()
it 'Create for paid course no token', (done) ->
loginNewUser (user1) ->
createCourse 7000, (err, course) ->
expect(err).toBeNull()
return done(err) if err
name = createName 'course instance '
requestBody =
courseID: course.get('_id')
name: createName('course instance ')
seats: 1
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(422)
done()
it 'Create for paid course -1 seats', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
createCourse 7000, (err, course) ->
expect(err).toBeNull()
return done(err) if err
name = createName 'course instance '
requestBody =
courseID: course.get('_id')
name: createName('course instance ')
seats: -1
request.post {uri: courseInstanceURL, json: requestBody }, (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(422)
done()
describe 'All Courses', ->
it 'Create for 50 seats', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
createCourse 7000, (err, course1) ->
expect(err).toBeNull()
return done(err) if err
createCourse 7000, (err, course2) ->
expect(err).toBeNull()
return done(err) if err
createCourseInstances user1, null, 50, token.id, (err, courseInstances) ->
expect(err).toBeNull()
return done(err) if err
Course.find {}, (err, courses) ->
expect(err).toBeNull()
return done(err) if err
expect(courseInstances.length).toEqual(courses.length)
done()