From c38288974823218ff94ad39b1038895e6ed55e53 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 8 Apr 2014 14:10:50 -0700 Subject: [PATCH] Bunch of server changes, mainly adding all the JSON schema validation and fixing tests. --- package.json | 4 ++-- server/articles/article_handler.coffee | 1 + server/commons/Handler.coffee | 12 +++++------- server/commons/mapping.coffee | 2 ++ server/commons/schemas.coffee | 13 ++++++++++++- .../components/level_component_handler.coffee | 1 + .../levels/feedbacks/level_feedback_handler.coffee | 1 + server/levels/level_handler.coffee | 1 + server/levels/sessions/level_session_handler.coffee | 1 + server/levels/systems/level_system_handler.coffee | 1 + server/levels/thangs/thang_type_handler.coffee | 1 + server/routes/db.coffee | 3 ++- test/server/common.coffee | 7 ++----- test/server/functional/auth.spec.coffee | 3 +-- test/server/functional/level.spec.coffee | 3 +++ test/server/functional/level_component.spec.coffee | 9 ++++++--- test/server/functional/level_system.spec.coffee | 4 +++- 17 files changed, 45 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 665ac0131..3e2552f1c 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "mongoose": "3.8.x", "mongoose-text-search": "~0.0.2", "request": "2.12.x", - "tv4": "1.0.11", + "tv4": "1.0.x", "lodash": "~2.0.0", "underscore.string": "2.3.x", "async": "0.2.x", @@ -56,7 +56,7 @@ "graceful-fs": "~2.0.1", "node-force-domain": "~0.1.0", "mailchimp-api": "2.0.x", - "express-useragent": "~0.0.9", + "express-useragent": "~0.0.9", "gridfs-stream": "0.4.x", "stream-buffers": "0.2.x", "sendwithus": "2.0.x", diff --git a/server/articles/article_handler.coffee b/server/articles/article_handler.coffee index b519b8b9f..0e632539f 100644 --- a/server/articles/article_handler.coffee +++ b/server/articles/article_handler.coffee @@ -4,6 +4,7 @@ Handler = require('../commons/Handler') ArticleHandler = class ArticleHandler extends Handler modelClass: Article editableProperties: ['body', 'name', 'i18n'] + jsonSchema: require './article_schema' hasAccess: (req) -> req.method is 'GET' or req.user?.isAdmin() diff --git a/server/commons/Handler.coffee b/server/commons/Handler.coffee index f38885fd9..b7b8aad0f 100644 --- a/server/commons/Handler.coffee +++ b/server/commons/Handler.coffee @@ -48,6 +48,7 @@ module.exports = class Handler sendMethodNotAllowed: (res) -> errors.badMethod(res) sendBadInputError: (res, message) -> errors.badInput(res, message) sendDatabaseError: (res, err) -> + return @sendError(res, err.code, err.response) if err.response and err.code log.error "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, 'id should not be included.') if req.body._id return @sendUnauthorizedError(res) unless @hasAccess(req) - validation = @validateDocumentInput(req.body) - return @sendBadInputError(res, validation.errors) unless validation.valid document = @makeNewInstance(req) @saveChangesToDocument req, document, (err) => + return @sendBadInputError(res, err.errors) if err?.valid is false return @sendDatabaseError(res, err) if err @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, 'id should not be included.') if req.body._id return @sendUnauthorizedError(res) unless @hasAccess(req) - validation = @validateDocumentInput(req.body) - return @sendBadInputError(res, validation.errors) unless validation.valid document = @makeNewInstance(req) document.set('original', document._id) document.set('creator', req.user._id) @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 @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, 'No input.') if _.isEmpty(req.body) return @sendUnauthorizedError(res) unless @hasAccess(req) - validation = @validateDocumentInput(req.body) - return @sendBadInputError(res, validation.errors) unless validation.valid @getDocumentForIdOrSlug req.body._id, (err, parentDocument) => return @sendBadInputError(res, 'Bad id.') if err and err.name is 'CastError' return @sendDatabaseError(res, err) if err @@ -261,6 +257,8 @@ module.exports = class Handler delete updatedObject[prop] delete updatedObject._id major = req.body.version?.major + validation = @validateDocumentInput(updatedObject) + return @sendBadInputError(res, validation.errors) unless validation.valid done = (err, newDocument) => return @sendDatabaseError(res, err) if err diff --git a/server/commons/mapping.coffee b/server/commons/mapping.coffee index 2f659811b..d7400c951 100644 --- a/server/commons/mapping.coffee +++ b/server/commons/mapping.coffee @@ -6,6 +6,7 @@ module.exports.handlers = 'level_feedback': 'levels/feedbacks/level_feedback_handler' 'level_session': 'levels/sessions/level_session_handler' 'level_system': 'levels/systems/level_system_handler' + 'patch': 'patches/patch_handler' 'thang_type': 'levels/thangs/thang_type_handler' 'user': 'users/user_handler' @@ -19,6 +20,7 @@ module.exports.schemas = 'level_session': 'levels/sessions/level_session_schema' 'level_system': 'levels/systems/level_system_schema' 'metaschema': 'commons/metaschema' + 'patch': 'patches/patch_schema' 'thang_component': 'levels/thangs/thang_component_schema' 'thang_type': 'levels/thangs/thang_type_schema' 'user': 'users/user_schema' diff --git a/server/commons/schemas.coffee b/server/commons/schemas.coffee index 060ff8348..7d57a8c66 100644 --- a/server/commons/schemas.coffee +++ b/server/commons/schemas.coffee @@ -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.shortString = (ext) -> combine({type: 'string', maxLength: 100}, 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 me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext) @@ -51,7 +51,18 @@ basicProps = (linkFragment) -> me.extendBasicProperties = (schema, linkFragment) -> schema.properties = {} unless schema.properties? _.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 diff --git a/server/levels/components/level_component_handler.coffee b/server/levels/components/level_component_handler.coffee index 89a3ea21c..576bad3c8 100644 --- a/server/levels/components/level_component_handler.coffee +++ b/server/levels/components/level_component_handler.coffee @@ -3,6 +3,7 @@ Handler = require('../../commons/Handler') LevelComponentHandler = class LevelComponentHandler extends Handler modelClass: LevelComponent + jsonSchema: require './level_component_schema' editableProperties: [ 'system' 'description' diff --git a/server/levels/feedbacks/level_feedback_handler.coffee b/server/levels/feedbacks/level_feedback_handler.coffee index 5cb8be50b..21f581ea7 100644 --- a/server/levels/feedbacks/level_feedback_handler.coffee +++ b/server/levels/feedbacks/level_feedback_handler.coffee @@ -4,6 +4,7 @@ Handler = require('../../commons/Handler') class LevelFeedbackHandler extends Handler modelClass: LevelFeedback editableProperties: ['rating', 'review', 'level', 'levelID', 'levelName'] + jsonSchema: require './level_feedback_schema' makeNewInstance: (req) -> feedback = super(req) diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index ad26fe0e1..ccd76700a 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -8,6 +8,7 @@ mongoose = require('mongoose') LevelHandler = class LevelHandler extends Handler modelClass: Level + jsonSchema: require './level_schema' editableProperties: [ 'description' 'documentation' diff --git a/server/levels/sessions/level_session_handler.coffee b/server/levels/sessions/level_session_handler.coffee index ca8680a17..9017f99c4 100644 --- a/server/levels/sessions/level_session_handler.coffee +++ b/server/levels/sessions/level_session_handler.coffee @@ -9,6 +9,7 @@ class LevelSessionHandler extends Handler editableProperties: ['multiplayer', 'players', 'code', 'completed', 'state', 'levelName', 'creatorName', 'levelID', 'screenshot', 'chat', 'teamSpells', 'submitted', 'unsubscribed'] + jsonSchema: require './level_session_schema' getByRelationship: (req, res, args...) -> return @getActiveSessions req, res if args.length is 2 and args[1] is 'active' diff --git a/server/levels/systems/level_system_handler.coffee b/server/levels/systems/level_system_handler.coffee index 1b1e511c1..a76fed659 100644 --- a/server/levels/systems/level_system_handler.coffee +++ b/server/levels/systems/level_system_handler.coffee @@ -13,6 +13,7 @@ LevelSystemHandler = class LevelSystemHandler extends Handler 'configSchema' ] postEditableProperties: ['name'] + jsonSchema: require './level_system_schema' getEditableProperties: (req, document) -> props = super(req, document) diff --git a/server/levels/thangs/thang_type_handler.coffee b/server/levels/thangs/thang_type_handler.coffee index a446b56be..0627fc5f7 100644 --- a/server/levels/thangs/thang_type_handler.coffee +++ b/server/levels/thangs/thang_type_handler.coffee @@ -3,6 +3,7 @@ Handler = require('../../commons/Handler') ThangTypeHandler = class ThangTypeHandler extends Handler modelClass: ThangType + jsonSchema: require './thang_type_schema' editableProperties: [ 'name', 'raw', diff --git a/server/routes/db.coffee b/server/routes/db.coffee index 723e15b90..2cbbc7df9 100644 --- a/server/routes/db.coffee +++ b/server/routes/db.coffee @@ -42,6 +42,7 @@ module.exports.setup = (app) -> catch error log.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}") log.error(error) + log.error(error.stack) errors.notFound(res, "Route #{req.path} not found.") getSchema = (req, res, moduleName) -> @@ -49,7 +50,7 @@ getSchema = (req, res, moduleName) -> name = schemas[moduleName.replace '.', '_'] schema = require('../' + name) - res.send(schema) + res.send(JSON.stringify(schema, null, '\t')) res.end() catch error diff --git a/test/server/common.coffee b/test/server/common.coffee index d88fa21e8..1af742db6 100644 --- a/test/server/common.coffee +++ b/test/server/common.coffee @@ -78,11 +78,8 @@ unittest.getUser = (email, password, done, force) -> req = request.post(getURL('/db/user'), (err, response, body) -> throw err if err User.findOne({email:email}).exec((err, user) -> - if password is '80yqxpb38j' - user.set('permissions', [ 'admin' ]) - user.save (err) -> - wrapUpGetUser(email, user, done) - else + user.set('permissions', if password is '80yqxpb38j' then [ 'admin' ] else []) + user.save (err) -> wrapUpGetUser(email, user, done) ) ) diff --git a/test/server/functional/auth.spec.coffee b/test/server/functional/auth.spec.coffee index 18c3c7fc8..750f4997e 100644 --- a/test/server/functional/auth.spec.coffee +++ b/test/server/functional/auth.spec.coffee @@ -55,7 +55,7 @@ describe '/auth/login', -> it 'rejects wrong passwords', (done) -> req = request.post(urlLogin, (error, response) -> expect(response.statusCode).toBe(401) - expect(response.body.indexOf("wrong, wrong")).toBeGreaterThan(-1) + expect(response.body.indexOf("wrong")).toBeGreaterThan(-1) done() ) form = req.form() @@ -96,7 +96,6 @@ describe '/auth/reset', -> it 'resets user password', (done) -> req = request.post(urlReset, (error, response) -> expect(response).toBeDefined() - console.log 'status code is', response.statusCode expect(response.statusCode).toBe(200) expect(response.body).toBeDefined() passwordReset = response.body diff --git a/test/server/functional/level.spec.coffee b/test/server/functional/level.spec.coffee index 13dc6425a..edd163d0d 100644 --- a/test/server/functional/level.spec.coffee +++ b/test/server/functional/level.spec.coffee @@ -6,6 +6,9 @@ describe 'Level', -> name: "King's Peak 3" description: 'Climb a mountain.' permissions: simplePermissions + scripts: [] + thangs: [] + documentation: {specificArticles:[], generalArticles:[]} urlLevel = '/db/level' diff --git a/test/server/functional/level_component.spec.coffee b/test/server/functional/level_component.spec.coffee index 4850d834c..9127ccefd 100644 --- a/test/server/functional/level_component.spec.coffee +++ b/test/server/functional/level_component.spec.coffee @@ -3,11 +3,14 @@ require '../common' describe 'LevelComponent', -> component = - name:'Bashes Everything' + name:'BashesEverything' description:'Makes the unit uncontrollably bash anything bashable, using the bash system.' code: 'bash();' - language: 'javascript' + language: 'coffeescript' permissions:simplePermissions + propertyDocumentation: [] + system: 'ai' + dependencies: [] components = {} @@ -45,7 +48,7 @@ describe 'LevelComponent', -> it 'have a unique name.', (done) -> loginAdmin -> request.post {uri:url, json:component}, (err, res, body) -> - expect(res.statusCode).toBe(422) + expect(res.statusCode).toBe(409) done() it 'can be read by an admin.', (done) -> diff --git a/test/server/functional/level_system.spec.coffee b/test/server/functional/level_system.spec.coffee index 32ca61df1..229c3a39d 100644 --- a/test/server/functional/level_system.spec.coffee +++ b/test/server/functional/level_system.spec.coffee @@ -11,6 +11,8 @@ describe 'LevelSystem', -> """ language: 'coffeescript' permissions:simplePermissions + dependencies: [] + propertyDocumentation: [] systems = {} @@ -48,7 +50,7 @@ describe 'LevelSystem', -> it 'have a unique name.', (done) -> loginAdmin -> request.post {uri:url, json:system}, (err, res, body) -> - expect(res.statusCode).toBe(422) + expect(res.statusCode).toBe(409) done() it 'can be read by an admin.', (done) ->