Refactor POST /db/classroom and PUT /db/campaign/:handle and their tests for #3469

This commit is contained in:
Scott Erickson 2016-04-13 11:39:17 -07:00
parent 8a744db768
commit bd6a266f60
10 changed files with 129 additions and 67 deletions

View file

@ -7,24 +7,10 @@ mongoose = require 'mongoose'
CampaignHandler = class CampaignHandler extends Handler
modelClass: Campaign
editableProperties: [
'name'
'fullName'
'description'
'type'
'i18n'
'i18nCoverage'
'ambientSound'
'backgroundImage'
'backgroundColor'
'backgroundColorTransparent'
'adjacentCampaigns'
'levels'
]
jsonSchema: require '../../app/schemas/models/campaign.schema'
hasAccess: (req) ->
req.method in ['GET', 'PUT'] or req.user?.isAdmin()
req.method in ['GET'] or req.user?.isAdmin()
hasAccessToDocument: (req, document, method=null) ->
return true if req.user?.isAdmin()
@ -124,10 +110,6 @@ CampaignHandler = class CampaignHandler extends Handler
return @sendDatabaseError(res, err) if err
return @sendSuccess(res, (achievement.toObject() for achievement in achievements))
onPutSuccess: (req, doc) ->
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
@sendChangedSlackMessage creator: req.user, target: doc, docLink: docLink
getNamesByIDs: (req, res) -> @getNamesByOriginals req, res, true
module.exports = new CampaignHandler()

View file

@ -10,12 +10,11 @@ UserHandler = require './user_handler'
ClassroomHandler = class ClassroomHandler extends Handler
modelClass: Classroom
jsonSchema: require '../../app/schemas/models/classroom.schema'
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE']
allowedMethods: ['GET', 'PUT', 'DELETE']
hasAccess: (req) ->
return false unless req.user
return true if req.method is 'GET'
return false if req.method is 'POST' and not req.user?.isTeacher()
req.method in @allowedMethods or req.user?.isAdmin()
hasAccessToDocument: (req, document, method=null) ->
@ -27,12 +26,6 @@ ClassroomHandler = class ClassroomHandler extends Handler
return true if isGet and isMember
false
makeNewInstance: (req) ->
instance = super(req)
instance.set 'ownerID', req.user._id
instance.set 'members', []
instance
getByRelationship: (req, res, args...) ->
method = req.method.toLowerCase()
return @inviteStudents(req, res, args[0]) if args[1] is 'invite-members'

View file

@ -7,6 +7,7 @@ mongoose = require 'mongoose'
Campaign = require '../models/Campaign'
parse = require '../commons/parse'
LevelSession = require '../models/LevelSession'
slack = require '../slack'
module.exports =
fetchByType: wrap (req, res, next) ->
@ -19,3 +20,18 @@ module.exports =
campaigns = yield dbq.exec()
campaigns = (campaign.toObject({req: req}) for campaign in campaigns)
res.status(200).send(campaigns)
put: wrap (req, res) ->
campaign = yield database.getDocFromHandle(req, Campaign)
if not campaign
throw new errors.NotFound('Campaign not found.')
hasPermission = req.user.isAdmin()
unless hasPermission or database.isJustFillingTranslations(req, campaign)
throw new errors.Forbidden('Must be an admin or submitting translations to edit a campaign')
database.assignBody(req, campaign)
database.validateDoc(campaign)
campaign = yield campaign.save()
res.status(200).send(campaign.toObject())
docLink = "http://codecombat.com#{req.headers['x-current-path']}"
slack.sendChangedSlackMessage creator: req.user, target: campaign, docLink: docLink

View file

@ -62,3 +62,14 @@ module.exports =
memberObjects = (member.toObject({ req: req, includedPrivates: ["name", "email"] }) for member in members)
res.status(200).send(memberObjects)
post: wrap (req, res) ->
throw new errors.Unauthorized() unless req.user and not req.user.isAnonymous()
throw new errors.Forbidden() unless req.user?.isTeacher()
classroom = database.initDoc(req, Classroom)
classroom.set 'ownerID', req.user._id
classroom.set 'members', []
database.assignBody(req, classroom)
database.validateDoc(classroom)
classroom = yield classroom.save()
res.status(201).send(classroom.toObject({req: req}))

View file

@ -37,5 +37,19 @@ CampaignSchema.statics.updateAdjacentCampaigns = (savedCampaign) ->
CampaignSchema.post 'save', -> @constructor.updateAdjacentCampaigns @
CampaignSchema.statics.jsonSchema = jsonSchema
CampaignSchema.statics.editableProperties = [
'name'
'fullName'
'description'
'type'
'i18n'
'i18nCoverage'
'ambientSound'
'backgroundImage'
'backgroundColor'
'backgroundColorTransparent'
'adjacentCampaigns'
'levels'
]
module.exports = mongoose.model('campaign', CampaignSchema)

View file

@ -54,7 +54,7 @@ ClassroomSchema.set('toObject', {
transform: (doc, ret, options) ->
return ret unless options.req
user = options.req.user
unless user?.isAdmin() or user?.get('_id').equals(doc.get('ownerID'))
unless user and (user.isAdmin() or user._id.equals(doc.get('ownerID')))
delete ret.code
delete ret.codeCamel
return ret

View file

@ -40,7 +40,11 @@ module.exports.setup = (app) ->
app.get('/db/article/:handle/patches', mw.patchable.patches(Article))
app.post('/db/article/:handle/watchers', mw.patchable.joinWatchers(Article))
app.delete('/db/article/:handle/watchers', mw.patchable.leaveWatchers(Article))
app.get('/db/campaign', mw.campaigns.fetchByType)
app.put('/db/campaign/:handle', mw.campaigns.put)
app.post('/db/classroom', mw.classrooms.post)
app.get('/db/classroom', mw.classrooms.getByOwner)
app.get('/db/classroom/:handle/member-sessions', mw.classrooms.fetchMemberSessions)
app.get('/db/classroom/:handle/members', mw.classrooms.fetchMembers) # TODO: Use mw.auth?
@ -50,8 +54,6 @@ module.exports.setup = (app) ->
app.get('/db/course', mw.rest.get(Course))
app.get('/db/course/:handle', mw.rest.getByHandle(Course))
app.get('/db/campaign', mw.campaigns.fetchByType) #TODO
app.post('/db/course_instance/:handle/members', mw.auth.checkLoggedIn(), mw.courseInstances.addMembers)
app.get('/db/user', mw.users.fetchByGPlusID, mw.users.fetchByFacebookID)

View file

@ -5,8 +5,13 @@ log = require 'winston'
roomChannelMap =
main: '#general'
artisans: '#artisan'
module.exports.sendChangedSlackMessage = (options) ->
message = "#{options.creator.get('name')} saved a change to #{options.target.get('name')}: #{options.target.get('commitMessage') or '(no commit message)'} #{options.docLink}"
rooms = if /Diplomat submission/.test(message) then ['dev-feed'] else ['dev-feed', 'artisans']
@sendSlackMessage message, rooms
module.exports.sendSlackMessage = sendSlackMessage = (message, rooms=['tower'], options={}) ->
module.exports.sendSlackMessage = (message, rooms=['tower'], options={}) ->
unless config.isProduction
log.info "Slack msg: #{message}"
return

View file

@ -22,6 +22,7 @@ achievement = {
campaign = {
name: 'Campaign'
levels: {}
i18n: {}
}
levelURL = getURL('/db/level')
@ -34,6 +35,46 @@ Campaign = require '../../../server/models/Campaign'
Level = require '../../../server/models/Level'
User = require '../../../server/models/User'
request = require '../request'
utils = require '../utils'
slack = require '../../../server/slack'
describe 'PUT /db/campaign', ->
beforeEach utils.wrap (done) ->
yield utils.clearModels [Achievement, Campaign, Level, User]
admin = yield utils.initAdmin()
yield utils.loginUser(admin)
[res, body] = yield request.postAsync { uri: campaignURL, json: campaign }
@campaign = yield Campaign.findById(body._id)
done()
it 'saves changes to campaigns', utils.wrap (done) ->
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: { name: 'A new name' } }
expect(body.name).toBe('A new name')
c = yield Campaign.findById(body._id)
expect(c.get('name')).toBe('A new name')
done()
it 'does not allow normal users to make changes', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: { name: 'A new name' } }
expect(res.statusCode).toBe(403)
done()
it 'allows normal users to put translation changes', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
json = _.clone @campaign.toObject()
json.i18n = { de: { name: 'A new name' } }
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: json }
expect(res.statusCode).toBe(200)
done()
it 'sends a slack message', utils.wrap (done) ->
spyOn(slack, 'sendSlackMessage')
[res, body] = yield request.putAsync { uri: campaignURL+'/'+@campaign.id, json: { name: 'A new name' } }
expect(slack.sendSlackMessage).toHaveBeenCalled()
done()
describe '/db/campaign', ->
it 'prepares the db first', (done) ->

View file

@ -50,7 +50,7 @@ describe 'GET /db/classroom/:id', ->
user1.save (err) ->
data = { name: 'Classroom 1' }
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
classroomID = body._id
request.get {uri: classroomsURL + '/' + body._id }, (err, res, body) ->
expect(res.statusCode).toBe(200)
@ -59,36 +59,34 @@ describe 'GET /db/classroom/:id', ->
describe 'POST /db/classroom', ->
it 'clears database users and classrooms', (done) ->
clearModels [User, Classroom], (err) ->
throw err if err
done()
it 'creates a new classroom for the given user', (done) ->
loginNewUser (user1) ->
user1.set('role', 'teacher')
user1.save (err) ->
data = { name: 'Classroom 1' }
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
expect(res.statusCode).toBe(200)
expect(body.name).toBe('Classroom 1')
expect(body.members.length).toBe(0)
expect(body.ownerID).toBe(user1.id)
done()
beforeEach utils.wrap (done) ->
yield utils.clearModels [User, Classroom]
done()
it 'creates a new classroom for the given user with teacher role', utils.wrap (done) ->
teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(teacher)
data = { name: 'Classroom 1' }
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
expect(res.statusCode).toBe(201)
expect(res.body.name).toBe('Classroom 1')
expect(res.body.members.length).toBe(0)
expect(res.body.ownerID).toBe(teacher.id)
done()
it 'does not work for anonymous users', (done) ->
logoutUser ->
data = { name: 'Classroom 2' }
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
expect(res.statusCode).toBe(401)
done()
it 'returns 401 for anonymous users', utils.wrap (done) ->
data = { name: 'Classroom 2' }
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
expect(res.statusCode).toBe(401)
done()
it 'does not work for non-teacher users', (done) ->
loginNewUser (user1) ->
data = { name: 'Classroom 1' }
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
expect(res.statusCode).toBe(403)
done()
it 'does not work for non-teacher users', utils.wrap (done) ->
user = yield utils.initUser()
yield utils.loginUser(user)
data = { name: 'Classroom 1' }
[res, body] = yield request.postAsync {uri: classroomsURL, json: data }
expect(res.statusCode).toBe(403)
done()
describe 'PUT /db/classroom', ->
@ -104,7 +102,7 @@ describe 'PUT /db/classroom', ->
user1.save (err) ->
data = { name: 'Classroom 2' }
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
data = { name: 'Classroom 3', description: 'New Description' }
url = classroomsURL + '/' + body._id
request.put { uri: url, json: data }, (err, res, body) ->
@ -118,7 +116,7 @@ describe 'PUT /db/classroom', ->
user1.save (err) ->
data = { name: 'Classroom 4' }
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
classroomCode = body.code
loginNewUser (user2) ->
url = getURL("/db/classroom/~/members")
@ -145,7 +143,7 @@ describe 'POST /db/classroom/~/members', ->
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
classroomCode = body.code
classroomID = body._id
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
loginNewUser (user2) ->
url = getURL("/db/classroom/~/members")
data = { code: classroomCode }
@ -166,7 +164,7 @@ describe 'POST /db/classroom/~/members', ->
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
classroomCode = body.code
classroomID = body._id
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
loginNewUser (user2) ->
user2.set('role', 'teacher')
user2.save (err, user2) ->
@ -183,7 +181,7 @@ describe 'POST /db/classroom/~/members', ->
teacher = yield utils.initUser({role: 'teacher'})
yield utils.loginUser(teacher)
[res, body] = yield request.postAsync {uri: classroomsURL, json: { name: 'Classroom' } }
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
classroomCode = body.code
yield utils.becomeAnonymous()
[res, body] = yield request.postAsync { uri: getURL("/db/classroom/~/members"), json: { code: classroomCode } }
@ -206,7 +204,7 @@ describe 'DELETE /db/classroom/:id/members', ->
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
classroomCode = body.code
classroomID = body._id
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
loginNewUser (user2) ->
url = getURL("/db/classroom/~/members")
data = { code: classroomCode }
@ -231,7 +229,7 @@ describe 'POST /db/classroom/:id/invite-members', ->
user1.save (err) ->
data = { name: 'Classroom 6' }
request.post {uri: classroomsURL, json: data }, (err, res, body) ->
expect(res.statusCode).toBe(200)
expect(res.statusCode).toBe(201)
url = classroomsURL + '/' + body._id + '/invite-members'
data = { emails: ['test@test.com'] }
request.post { uri: url, json: data }, (err, res, body) ->