codecombat/spec/server/functional/course_instance.spec.coffee
phoenixeliot 91fa467251 Fix teacher sessionless play; uses default campaign levels
Fix typo

Fix race condition with loading next level

Return an empty object for next level instead of an error;
Supermodel has a race condition when some of the models fail to load,
when we override an error response as "success".

Fix next level spec

Remove comment per Scott's request
2016-04-28 12:46:09 -07:00

367 lines
14 KiB
CoffeeScript

async = require 'async'
config = require '../../../server_config'
require '../common'
stripe = require('stripe')(config.stripe.secretKey)
utils = require '../utils'
CourseInstance = require '../../../server/models/CourseInstance'
Course = require '../../../server/models/Course'
User = require '../../../server/models/User'
Classroom = require '../../../server/models/Classroom'
Campaign = require '../../../server/models/Campaign'
Level = require '../../../server/models/Level'
Prepaid = require '../../../server/models/Prepaid'
request = require '../request'
courseFixture = {
name: 'Unnamed course'
campaignID: ObjectId("55b29efd1cd6abe8ce07db0d")
concepts: ['basic_syntax', 'arguments', 'while_loops', 'strings', 'variables']
description: "Learn basic syntax, while loops, and the CodeCombat environment."
screenshot: "/images/pages/courses/101_info.png"
}
classroomFixture = {
name: 'Unnamed classroom'
members: []
}
describe 'POST /db/course_instance', ->
url = getURL('/db/course_instance')
beforeEach utils.wrap (done) ->
yield utils.clearModels([CourseInstance, Course, User, Classroom])
@teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(@teacher)
@course = yield new Course(courseFixture).save()
classroomData = _.extend({ownerID: @teacher._id}, classroomFixture)
@classroom = yield new Classroom(classroomData).save()
done()
it 'creates a CourseInstance', utils.wrap (done) ->
data = {
name: 'Some Name'
courseID: @course.id
classroomID: @classroom.id
}
[res, body] = yield request.postAsync {uri: url, json: data}
expect(res.statusCode).toBe(200)
expect(body.classroomID).toBeDefined()
done()
it 'returns the same CourseInstance if you POST twice', utils.wrap (done) ->
data = {
name: 'Some Name'
courseID: @course.id
classroomID: @classroom.id
}
[res, body] = yield request.postAsync {uri: url, json: data}
expect(res.statusCode).toBe(200)
expect(body.classroomID).toBeDefined()
firstID = body._id
[res, body] = yield request.postAsync {uri: url, json: data}
expect(res.statusCode).toBe(200)
expect(body.classroomID).toBeDefined()
secondID = body._id
expect(firstID).toBe(secondID)
done()
it 'returns 404 if the Course does not exist', utils.wrap (done) ->
data = {
name: 'Some Name'
courseID: '123456789012345678901234'
classroomID: @classroom.id
}
[res, body] = yield request.postAsync {uri: url, json: data}
expect(res.statusCode).toBe(404)
done()
it 'returns 404 if the Classroom does not exist', utils.wrap (done) ->
data = {
name: 'Some Name'
courseID: @course.id
classroomID: '123456789012345678901234'
}
[res, body] = yield request.postAsync {uri: url, json: data}
expect(res.statusCode).toBe(404)
done()
it 'return 403 if the logged in user does not own the Classroom', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
data = {
name: 'Some Name'
courseID: @course.id
classroomID: @classroom.id
}
[res, body] = yield request.postAsync {uri: url, json: data}
expect(res.statusCode).toBe(403)
done()
describe 'POST /db/course_instance/:id/members', ->
beforeEach utils.wrap (done) ->
utils.clearModels([CourseInstance, Course, User, Classroom, Prepaid])
@teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(@teacher)
courseData = _.extend({free: true}, courseFixture)
@course = yield new Course(courseData).save()
classroomData = _.extend({ownerID: @teacher._id}, classroomFixture)
@classroom = yield new Classroom(classroomData).save()
url = getURL('/db/course_instance')
data = {
name: 'Some Name'
courseID: @course.id
classroomID: @classroom.id
}
[res, body] = yield request.postAsync {uri: url, json: data}
@courseInstance = yield CourseInstance.findById res.body._id
@student = yield utils.initUser()
@prepaid = yield new Prepaid({
type: 'course'
maxRedeemers: 10
redeemers: []
}).save()
done()
it 'adds an array of members to the given CourseInstance', utils.wrap (done) ->
@classroom.set('members', [@student._id])
yield @classroom.save()
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userIDs: [@student.id]}}
expect(res.statusCode).toBe(200)
expect(body.members.length).toBe(1)
expect(body.members[0]).toBe(@student.id)
done()
it 'adds a member to the given CourseInstance', utils.wrap (done) ->
@classroom.set('members', [@student._id])
yield @classroom.save()
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userID: @student.id}}
expect(res.statusCode).toBe(200)
expect(res.body.members.length).toBe(1)
expect(res.body.members[0]).toBe(@student.id)
done()
it 'adds the CourseInstance id to the user', utils.wrap (done) ->
@classroom.set('members', [@student._id])
yield @classroom.save()
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userID: @student.id}}
user = yield User.findById(@student.id)
expect(_.size(user.get('courseInstances'))).toBe(1)
done()
it 'return 403 if the member is not in the classroom', utils.wrap (done) ->
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userID: @student.id}}
expect(res.statusCode).toBe(403)
done()
it 'returns 403 if the user does not own the course instance and is not adding self', utils.wrap (done) ->
@classroom.set('members', [@student._id])
yield @classroom.save()
otherUser = yield utils.initUser()
yield utils.loginUser(otherUser)
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userID: @student.id}}
expect(res.statusCode).toBe(403)
done()
it 'returns 200 if the user is a member of the classroom and is adding self', ->
it 'return 402 if the course is not free and the user is not in a prepaid', utils.wrap (done) ->
@classroom.set('members', [@student._id])
yield @classroom.save()
@course.set('free', false)
yield @course.save()
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userID: @student.id}}
expect(res.statusCode).toBe(402)
done()
it 'works if the course is not free and the user is in a prepaid', utils.wrap (done) ->
@classroom.set('members', [@student._id])
yield @classroom.save()
@course.set('free', false)
yield @course.save()
@student.set('coursePrepaidID', @prepaid._id)
yield @student.save()
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userID: @student.id}}
expect(res.statusCode).toBe(200)
done()
describe 'DELETE /db/course_instance/:id/members', ->
beforeEach utils.wrap (done) ->
utils.clearModels([CourseInstance, Course, User, Classroom, Prepaid])
# create, login user
@teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(@teacher)
# create student, course, classroom and course instance
@student = yield utils.initUser()
courseData = _.extend({free: true}, courseFixture)
@course = yield new Course(courseData).save()
classroomData = _.extend({}, classroomFixture, {ownerID: @teacher._id, members: [@student._id]})
@classroom = yield new Classroom(classroomData).save()
url = getURL('/db/course_instance')
data = {
name: 'Some Name'
courseID: @course.id
classroomID: @classroom.id
}
[res, body] = yield request.postAsync {uri: url, json: data}
@courseInstance = yield CourseInstance.findById res.body._id
# add user to course instance
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.postAsync {uri: url, json: {userID: @student.id}}
@prepaid = yield new Prepaid({
type: 'course'
maxRedeemers: 10
redeemers: []
}).save()
done()
it 'removes a member to the given CourseInstance', utils.wrap (done) ->
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
[res, body] = yield request.delAsync {uri: url, json: {userID: @student.id}}
expect(res.statusCode).toBe(200)
expect(res.body.members.length).toBe(0)
done()
it 'removes the CourseInstance from the User.courseInstances', utils.wrap (done) ->
url = getURL("/db/course_instance/#{@courseInstance.id}/members")
user = yield User.findById(@student.id)
expect(_.size(user.get('courseInstances'))).toBe(1)
[res, body] = yield request.delAsync {uri: url, json: {userID: @student.id}}
expect(res.statusCode).toBe(200)
expect(res.body.members.length).toBe(0)
user = yield User.findById(@student.id)
expect(_.size(user.get('courseInstances'))).toBe(0)
done()
describe 'GET /db/course_instance/:handle/levels/:levelOriginal/next', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels [User, Classroom, Course, Level, Campaign]
admin = yield utils.initAdmin()
yield utils.loginUser(admin)
levelJSON = { name: 'A', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
expect(res.statusCode).toBe(200)
@levelA = yield Level.findById(res.body._id)
paredLevelA = _.pick(res.body, 'name', 'original', 'type')
levelJSON = { name: 'B', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
expect(res.statusCode).toBe(200)
@levelB = yield Level.findById(res.body._id)
paredLevelB = _.pick(res.body, 'name', 'original', 'type')
levelJSON = { name: 'C', permissions: [{access: 'owner', target: admin.id}], type: 'course' }
[res, body] = yield request.postAsync({uri: getURL('/db/level'), json: levelJSON})
expect(res.statusCode).toBe(200)
@levelC = yield Level.findById(res.body._id)
paredLevelC = _.pick(res.body, 'name', 'original', 'type')
campaignJSONA = { name: 'Campaign A', levels: {} }
campaignJSONA.levels[paredLevelA.original] = paredLevelA
campaignJSONA.levels[paredLevelB.original] = paredLevelB
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONA})
@campaignA = yield Campaign.findById(res.body._id)
campaignJSONB = { name: 'Campaign B', levels: {} }
campaignJSONB.levels[paredLevelC.original] = paredLevelC
[res, body] = yield request.postAsync({uri: getURL('/db/campaign'), json: campaignJSONB})
@campaignB = yield Campaign.findById(res.body._id)
@courseA = Course({name: 'Course A', campaignID: @campaignA._id})
yield @courseA.save()
@courseB = Course({name: 'Course B', campaignID: @campaignB._id})
yield @courseB.save()
teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(teacher)
data = { name: 'Classroom 1' }
classroomsURL = getURL('/db/classroom')
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
expect(res.statusCode).toBe(201)
@classroom = yield Classroom.findById(res.body._id)
url = getURL('/db/course_instance')
dataA = { name: 'Some Name', courseID: @courseA.id, classroomID: @classroom.id }
[res, body] = yield request.postAsync {uri: url, json: dataA}
expect(res.statusCode).toBe(200)
@courseInstanceA = yield CourseInstance.findById(res.body._id)
dataB = { name: 'Some Other Name', courseID: @courseB.id, classroomID: @classroom.id }
[res, body] = yield request.postAsync {uri: url, json: dataB}
expect(res.statusCode).toBe(200)
@courseInstanceB = yield CourseInstance.findById(res.body._id)
done()
it 'returns the next level for the course in the linked classroom', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course_instance/#{@courseInstanceA.id}/levels/#{@levelA.id}/next"), json: true }
expect(res.statusCode).toBe(200)
expect(res.body.original).toBe(@levelB.original.toString())
done()
it 'returns empty object if the given level is the last level in its course', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course_instance/#{@courseInstanceA.id}/levels/#{@levelB.id}/next"), json: true }
expect(res.statusCode).toBe(200)
expect(res.body).toEqual({})
done()
it 'returns 404 if the given level is not in the course instance\'s course', utils.wrap (done) ->
[res, body] = yield request.getAsync { uri: utils.getURL("/db/course_instance/#{@courseInstanceB.id}/levels/#{@levelA.id}/next"), json: true }
expect(res.statusCode).toBe(404)
done()
describe 'GET /db/course_instance/:handle/classroom', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels [User, CourseInstance, Classroom]
@owner = yield utils.initUser()
yield @owner.save()
@member = yield utils.initUser()
yield @member.save()
@classroom = new Classroom({
ownerID: @owner._id
members: [@member._id]
})
yield @classroom.save()
@courseInstance = new CourseInstance({classroomID: @classroom._id})
yield @courseInstance.save()
@url = getURL("/db/course_instance/#{@courseInstance.id}/classroom")
done()
it 'returns the course instance\'s referenced classroom', utils.wrap (done) ->
yield utils.loginUser @owner
[res, body] = yield request.getAsync(@url, {json: true})
expect(res.statusCode).toBe(200)
expect(body.code).toBeDefined()
done()
it 'works if you are the owner or member', utils.wrap (done) ->
yield utils.loginUser @member
[res, body] = yield request.getAsync(@url, {json: true})
expect(res.statusCode).toBe(200)
expect(body.code).toBeUndefined()
done()
it 'does not work if you are not the owner or a member', utils.wrap (done) ->
@user = yield utils.initUser()
yield utils.loginUser @user
[res, body] = yield request.getAsync(@url, {json: true})
expect(res.statusCode).toBe(403)
done()