Bunch of server changes, mainly adding all the JSON schema validation and fixing tests.

This commit is contained in:
Scott Erickson 2014-04-08 14:10:50 -07:00
parent 17c1bb7344
commit c382889748
17 changed files with 45 additions and 22 deletions

View file

@ -46,7 +46,7 @@
"mongoose": "3.8.x", "mongoose": "3.8.x",
"mongoose-text-search": "~0.0.2", "mongoose-text-search": "~0.0.2",
"request": "2.12.x", "request": "2.12.x",
"tv4": "1.0.11", "tv4": "1.0.x",
"lodash": "~2.0.0", "lodash": "~2.0.0",
"underscore.string": "2.3.x", "underscore.string": "2.3.x",
"async": "0.2.x", "async": "0.2.x",

View file

@ -4,6 +4,7 @@ Handler = require('../commons/Handler')
ArticleHandler = class ArticleHandler extends Handler ArticleHandler = class ArticleHandler extends Handler
modelClass: Article modelClass: Article
editableProperties: ['body', 'name', 'i18n'] editableProperties: ['body', 'name', 'i18n']
jsonSchema: require './article_schema'
hasAccess: (req) -> hasAccess: (req) ->
req.method is 'GET' or req.user?.isAdmin() req.method is 'GET' or req.user?.isAdmin()

View file

@ -48,6 +48,7 @@ module.exports = class Handler
sendMethodNotAllowed: (res) -> errors.badMethod(res) sendMethodNotAllowed: (res) -> errors.badMethod(res)
sendBadInputError: (res, message) -> errors.badInput(res, message) sendBadInputError: (res, message) -> errors.badInput(res, message)
sendDatabaseError: (res, err) -> sendDatabaseError: (res, err) ->
return @sendError(res, err.code, err.response) if err.response and err.code
log.error "Database error, #{err}" log.error "Database error, #{err}"
errors.serverError(res, 'Database error, ' + err) errors.serverError(res, 'Database error, ' + err)
@ -203,10 +204,9 @@ module.exports = class Handler
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body) return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
return @sendBadInputError(res, 'id should not be included.') if req.body._id return @sendBadInputError(res, 'id should not be included.') if req.body._id
return @sendUnauthorizedError(res) unless @hasAccess(req) return @sendUnauthorizedError(res) unless @hasAccess(req)
validation = @validateDocumentInput(req.body)
return @sendBadInputError(res, validation.errors) unless validation.valid
document = @makeNewInstance(req) document = @makeNewInstance(req)
@saveChangesToDocument req, document, (err) => @saveChangesToDocument req, document, (err) =>
return @sendBadInputError(res, err.errors) if err?.valid is false
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, @formatEntity(req, document)) @sendSuccess(res, @formatEntity(req, document))
@ -220,13 +220,11 @@ module.exports = class Handler
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body) return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
return @sendBadInputError(res, 'id should not be included.') if req.body._id return @sendBadInputError(res, 'id should not be included.') if req.body._id
return @sendUnauthorizedError(res) unless @hasAccess(req) return @sendUnauthorizedError(res) unless @hasAccess(req)
validation = @validateDocumentInput(req.body)
return @sendBadInputError(res, validation.errors) unless validation.valid
document = @makeNewInstance(req) document = @makeNewInstance(req)
document.set('original', document._id) document.set('original', document._id)
document.set('creator', req.user._id) document.set('creator', req.user._id)
@saveChangesToDocument req, document, (err) => @saveChangesToDocument req, document, (err) =>
return @sendBadInputError(res, err.response) if err?.response return @sendBadInputError(res, err.errors) if err?.valid is false
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, @formatEntity(req, document)) @sendSuccess(res, @formatEntity(req, document))
@ -245,8 +243,6 @@ module.exports = class Handler
return @sendBadInputError(res, 'This entity is not versioned') unless @modelClass.schema.uses_coco_versions return @sendBadInputError(res, 'This entity is not versioned') unless @modelClass.schema.uses_coco_versions
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body) return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
return @sendUnauthorizedError(res) unless @hasAccess(req) return @sendUnauthorizedError(res) unless @hasAccess(req)
validation = @validateDocumentInput(req.body)
return @sendBadInputError(res, validation.errors) unless validation.valid
@getDocumentForIdOrSlug req.body._id, (err, parentDocument) => @getDocumentForIdOrSlug req.body._id, (err, parentDocument) =>
return @sendBadInputError(res, 'Bad id.') if err and err.name is 'CastError' return @sendBadInputError(res, 'Bad id.') if err and err.name is 'CastError'
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@ -261,6 +257,8 @@ module.exports = class Handler
delete updatedObject[prop] delete updatedObject[prop]
delete updatedObject._id delete updatedObject._id
major = req.body.version?.major major = req.body.version?.major
validation = @validateDocumentInput(updatedObject)
return @sendBadInputError(res, validation.errors) unless validation.valid
done = (err, newDocument) => done = (err, newDocument) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err

View file

@ -6,6 +6,7 @@ module.exports.handlers =
'level_feedback': 'levels/feedbacks/level_feedback_handler' 'level_feedback': 'levels/feedbacks/level_feedback_handler'
'level_session': 'levels/sessions/level_session_handler' 'level_session': 'levels/sessions/level_session_handler'
'level_system': 'levels/systems/level_system_handler' 'level_system': 'levels/systems/level_system_handler'
'patch': 'patches/patch_handler'
'thang_type': 'levels/thangs/thang_type_handler' 'thang_type': 'levels/thangs/thang_type_handler'
'user': 'users/user_handler' 'user': 'users/user_handler'
@ -19,6 +20,7 @@ module.exports.schemas =
'level_session': 'levels/sessions/level_session_schema' 'level_session': 'levels/sessions/level_session_schema'
'level_system': 'levels/systems/level_system_schema' 'level_system': 'levels/systems/level_system_schema'
'metaschema': 'commons/metaschema' 'metaschema': 'commons/metaschema'
'patch': 'patches/patch_schema'
'thang_component': 'levels/thangs/thang_component_schema' 'thang_component': 'levels/thangs/thang_component_schema'
'thang_type': 'levels/thangs/thang_type_schema' 'thang_type': 'levels/thangs/thang_type_schema'
'user': 'users/user_schema' 'user': 'users/user_schema'

View file

@ -13,7 +13,7 @@ me.object = (ext, props) -> combine {type: 'object', additionalProperties: false
me.array = (ext, items) -> combine {type: 'array', items: items or {}}, ext me.array = (ext, items) -> combine {type: 'array', items: items or {}}, ext
me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext) me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext)
me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext) me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
me.date = (ext) -> combine({type: 'string', format: 'date-time'}, ext) me.date = (ext) -> combine({type: ['object', 'string'], format: 'date-time'}, ext)
# should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient # should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext) me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext)
@ -52,6 +52,17 @@ me.extendBasicProperties = (schema, linkFragment) ->
schema.properties = {} unless schema.properties? schema.properties = {} unless schema.properties?
_.extend(schema.properties, basicProps(linkFragment)) _.extend(schema.properties, basicProps(linkFragment))
# PATCHABLE
patchableProps = ->
patches: me.array({title:'Patches'}, {
_id: me.objectId(links: [{rel: "db", href: "/db/patch/{($)}"}], title: "Patch ID", description: "A reference to the patch.")
status: { enum: ['pending', 'accepted', 'rejected', 'cancelled']}
})
me.extendPatchableProperties = (schema) ->
schema.properties = {} unless schema.properties?
_.extend(schema.properties, patchableProps())
# NAMED # NAMED

View file

@ -3,6 +3,7 @@ Handler = require('../../commons/Handler')
LevelComponentHandler = class LevelComponentHandler extends Handler LevelComponentHandler = class LevelComponentHandler extends Handler
modelClass: LevelComponent modelClass: LevelComponent
jsonSchema: require './level_component_schema'
editableProperties: [ editableProperties: [
'system' 'system'
'description' 'description'

View file

@ -4,6 +4,7 @@ Handler = require('../../commons/Handler')
class LevelFeedbackHandler extends Handler class LevelFeedbackHandler extends Handler
modelClass: LevelFeedback modelClass: LevelFeedback
editableProperties: ['rating', 'review', 'level', 'levelID', 'levelName'] editableProperties: ['rating', 'review', 'level', 'levelID', 'levelName']
jsonSchema: require './level_feedback_schema'
makeNewInstance: (req) -> makeNewInstance: (req) ->
feedback = super(req) feedback = super(req)

View file

@ -8,6 +8,7 @@ mongoose = require('mongoose')
LevelHandler = class LevelHandler extends Handler LevelHandler = class LevelHandler extends Handler
modelClass: Level modelClass: Level
jsonSchema: require './level_schema'
editableProperties: [ editableProperties: [
'description' 'description'
'documentation' 'documentation'

View file

@ -9,6 +9,7 @@ class LevelSessionHandler extends Handler
editableProperties: ['multiplayer', 'players', 'code', 'completed', 'state', editableProperties: ['multiplayer', 'players', 'code', 'completed', 'state',
'levelName', 'creatorName', 'levelID', 'screenshot', 'levelName', 'creatorName', 'levelID', 'screenshot',
'chat', 'teamSpells', 'submitted', 'unsubscribed'] 'chat', 'teamSpells', 'submitted', 'unsubscribed']
jsonSchema: require './level_session_schema'
getByRelationship: (req, res, args...) -> getByRelationship: (req, res, args...) ->
return @getActiveSessions req, res if args.length is 2 and args[1] is 'active' return @getActiveSessions req, res if args.length is 2 and args[1] is 'active'

View file

@ -13,6 +13,7 @@ LevelSystemHandler = class LevelSystemHandler extends Handler
'configSchema' 'configSchema'
] ]
postEditableProperties: ['name'] postEditableProperties: ['name']
jsonSchema: require './level_system_schema'
getEditableProperties: (req, document) -> getEditableProperties: (req, document) ->
props = super(req, document) props = super(req, document)

View file

@ -3,6 +3,7 @@ Handler = require('../../commons/Handler')
ThangTypeHandler = class ThangTypeHandler extends Handler ThangTypeHandler = class ThangTypeHandler extends Handler
modelClass: ThangType modelClass: ThangType
jsonSchema: require './thang_type_schema'
editableProperties: [ editableProperties: [
'name', 'name',
'raw', 'raw',

View file

@ -42,6 +42,7 @@ module.exports.setup = (app) ->
catch error catch error
log.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}") log.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}")
log.error(error) log.error(error)
log.error(error.stack)
errors.notFound(res, "Route #{req.path} not found.") errors.notFound(res, "Route #{req.path} not found.")
getSchema = (req, res, moduleName) -> getSchema = (req, res, moduleName) ->
@ -49,7 +50,7 @@ getSchema = (req, res, moduleName) ->
name = schemas[moduleName.replace '.', '_'] name = schemas[moduleName.replace '.', '_']
schema = require('../' + name) schema = require('../' + name)
res.send(schema) res.send(JSON.stringify(schema, null, '\t'))
res.end() res.end()
catch error catch error

View file

@ -78,12 +78,9 @@ unittest.getUser = (email, password, done, force) ->
req = request.post(getURL('/db/user'), (err, response, body) -> req = request.post(getURL('/db/user'), (err, response, body) ->
throw err if err throw err if err
User.findOne({email:email}).exec((err, user) -> User.findOne({email:email}).exec((err, user) ->
if password is '80yqxpb38j' user.set('permissions', if password is '80yqxpb38j' then [ 'admin' ] else [])
user.set('permissions', [ 'admin' ])
user.save (err) -> user.save (err) ->
wrapUpGetUser(email, user, done) wrapUpGetUser(email, user, done)
else
wrapUpGetUser(email, user, done)
) )
) )
form = req.form() form = req.form()

View file

@ -55,7 +55,7 @@ describe '/auth/login', ->
it 'rejects wrong passwords', (done) -> it 'rejects wrong passwords', (done) ->
req = request.post(urlLogin, (error, response) -> req = request.post(urlLogin, (error, response) ->
expect(response.statusCode).toBe(401) expect(response.statusCode).toBe(401)
expect(response.body.indexOf("wrong, wrong")).toBeGreaterThan(-1) expect(response.body.indexOf("wrong")).toBeGreaterThan(-1)
done() done()
) )
form = req.form() form = req.form()
@ -96,7 +96,6 @@ describe '/auth/reset', ->
it 'resets user password', (done) -> it 'resets user password', (done) ->
req = request.post(urlReset, (error, response) -> req = request.post(urlReset, (error, response) ->
expect(response).toBeDefined() expect(response).toBeDefined()
console.log 'status code is', response.statusCode
expect(response.statusCode).toBe(200) expect(response.statusCode).toBe(200)
expect(response.body).toBeDefined() expect(response.body).toBeDefined()
passwordReset = response.body passwordReset = response.body

View file

@ -6,6 +6,9 @@ describe 'Level', ->
name: "King's Peak 3" name: "King's Peak 3"
description: 'Climb a mountain.' description: 'Climb a mountain.'
permissions: simplePermissions permissions: simplePermissions
scripts: []
thangs: []
documentation: {specificArticles:[], generalArticles:[]}
urlLevel = '/db/level' urlLevel = '/db/level'

View file

@ -3,11 +3,14 @@ require '../common'
describe 'LevelComponent', -> describe 'LevelComponent', ->
component = component =
name:'Bashes Everything' name:'BashesEverything'
description:'Makes the unit uncontrollably bash anything bashable, using the bash system.' description:'Makes the unit uncontrollably bash anything bashable, using the bash system.'
code: 'bash();' code: 'bash();'
language: 'javascript' language: 'coffeescript'
permissions:simplePermissions permissions:simplePermissions
propertyDocumentation: []
system: 'ai'
dependencies: []
components = {} components = {}
@ -45,7 +48,7 @@ describe 'LevelComponent', ->
it 'have a unique name.', (done) -> it 'have a unique name.', (done) ->
loginAdmin -> loginAdmin ->
request.post {uri:url, json:component}, (err, res, body) -> request.post {uri:url, json:component}, (err, res, body) ->
expect(res.statusCode).toBe(422) expect(res.statusCode).toBe(409)
done() done()
it 'can be read by an admin.', (done) -> it 'can be read by an admin.', (done) ->

View file

@ -11,6 +11,8 @@ describe 'LevelSystem', ->
""" """
language: 'coffeescript' language: 'coffeescript'
permissions:simplePermissions permissions:simplePermissions
dependencies: []
propertyDocumentation: []
systems = {} systems = {}
@ -48,7 +50,7 @@ describe 'LevelSystem', ->
it 'have a unique name.', (done) -> it 'have a unique name.', (done) ->
loginAdmin -> loginAdmin ->
request.post {uri:url, json:system}, (err, res, body) -> request.post {uri:url, json:system}, (err, res, body) ->
expect(res.statusCode).toBe(422) expect(res.statusCode).toBe(409)
done() done()
it 'can be read by an admin.', (done) -> it 'can be read by an admin.', (done) ->