From d74f9b15db4bedc32d61021fce4a8b6c7f5038ab Mon Sep 17 00:00:00 2001 From: Aiko Mastboom Date: Mon, 13 May 2013 23:38:08 +0200 Subject: [PATCH] added more tests, changed debug handling --- lib/markers.js | 2 +- lib/mongoData.js | 17 +- lib/mongodata.js | 503 ----------------------------------------- server.js | 58 +++-- test/test.helpers.js | 9 +- test/test.mongoData.js | 96 ++++++++ 6 files changed, 152 insertions(+), 533 deletions(-) delete mode 100644 lib/mongodata.js create mode 100644 test/test.mongoData.js diff --git a/lib/markers.js b/lib/markers.js index 5554262..fcb9dea 100644 --- a/lib/markers.js +++ b/lib/markers.js @@ -41,7 +41,7 @@ module.exports = function markers(config) { function createTag(type, collection, name, attribute) { var tag = ''; if (config.debug) { - console.log('markers.createTag created:', tag); + config.debug('markers.createTag created:', tag); } return tag; } diff --git a/lib/mongoData.js b/lib/mongoData.js index e991f22..a03f7f8 100644 --- a/lib/mongoData.js +++ b/lib/mongoData.js @@ -11,12 +11,15 @@ module.exports = function (config, db, shareModel) { */ function getMongoContent(options, callback) { if (config.debug) { - console.log('getMongoContent options', options); + config.debug('getMongoContent options', options); + } + if ( !options.collection) { + return callback(new Error('Data not found / missing collection')); } return db.collection(options.collection, function collection(err, col) { if (err) { - if (config.errors) { - console.error('ERR2 getMongoContent', err); + if (config.error) { + config.error('ERR2 getMongoContent', err); } return callback(err); } @@ -27,16 +30,16 @@ module.exports = function (config, db, shareModel) { try { options.query._id = new ObjectID.createFromHexString(options.query._id); } catch (error) { - if (config.errors) { - console.error('ERR3 getMongoContent', error); + if (config.error) { + config.error('ERR3 getMongoContent', error); } return callback(error); } } return col.findOne(options.query, function foundOne(err, result) { if (err) { - if (config.errors) { - console.error('ERR4 getMongoContent', err); + if (config.error) { + config.error('ERR4 getMongoContent', err); } return callback(err); } diff --git a/lib/mongodata.js b/lib/mongodata.js deleted file mode 100644 index e991f22..0000000 --- a/lib/mongodata.js +++ /dev/null @@ -1,503 +0,0 @@ -var ObjectID = require('mongodb').ObjectID; -var _ = require('underscore'); - - -module.exports = function (config, db, shareModel) { - "use strict"; - /* - options: - * collection (mandatory) - * query (mandatory) - */ - function getMongoContent(options, callback) { - if (config.debug) { - console.log('getMongoContent options', options); - } - return db.collection(options.collection, function collection(err, col) { - if (err) { - if (config.errors) { - console.error('ERR2 getMongoContent', err); - } - return callback(err); - } - if (!options.query) { - return callback(new Error('Data not found ' + options.collection + '/ missing query'), null, col); - } - if (options.query._id && !(options.query._id instanceof Object)) { - try { - options.query._id = new ObjectID.createFromHexString(options.query._id); - } catch (error) { - if (config.errors) { - console.error('ERR3 getMongoContent', error); - } - return callback(error); - } - } - return col.findOne(options.query, function foundOne(err, result) { - if (err) { - if (config.errors) { - console.error('ERR4 getMongoContent', err); - } - return callback(err); - } - if (!result) { - return callback(new Error('Data not found ' + options.collection + '/' + JSON.stringify(options.query)), null, col); - } - return callback(null, result, col); - }); - }); - } - - - /* - options: - * collection (mandatory) - * query (mandatory) - * attribute (mandatory) - */ - function getMongoAttribute(options, callback) { - if (config.debug) { - console.log('getMongoAttribute options', options); - } - return getMongoContent(options, function document(err, result) { - if (err) { - if (config.errors) { - console.error('ERR1 getMongoAttribute', err); - } - return callback(err); - } - var attribute_options = null; - if (config.debug) { - console.log('getMongoAttribute result', result); - } - if (result && - result.hasOwnProperty(options.attribute) && - result[options.attribute].guid) { - attribute_options = { - collection: options.collection, - query: {_id: result[options.attribute].guid} - }; - if (config.debug) { - console.log('getMongoAttribute attribute_options', attribute_options); - } - - getMongoContent(attribute_options, function attribute(err, attribute_result) { - if (err) { - if (config.errors) { - console.error('ERR2 getMongoAttribute', err); - } - return callback(err); - } - if (config.debug) { - console.log('getMongoAttribute attribute_result', attribute_result); - } - return callback(err, attribute_result); - }); - } else { - if (config.debug) { - console.log('getMongoAttribute try direct lookup'); - } - attribute_options = { - collection: options.collection, - query: { - parent: result._id, - name: result.name + '.' + options.attribute - } - }; - if (config.debug) { - console.log('getMongoAttribute attribute_options', attribute_options); - } - - return getMongoContent(attribute_options, function attribute(err, attribute_result) { - if (err) { - if (config.errors) { - console.error('ERR getMongoAttribute', err); - } - return callback(err); - } - if (config.debug) { - console.log('getMongoAttribute direct attribute_result', attribute_result); - } - return callback(err, attribute_result); - }); - } - }); - } - - function saveData(collection, data, callback) { - if (data._id && !(data._id instanceof Object)) { - data._id = new ObjectID.createFromHexString(data._id); - } - if (config.debug) { - console.log('saveData saving', data._id); - } - collection.save(data, {safe: true}, function (err, result2, result3) { - if (err) { - if (config.errors) { - console.error('ERR saveData', err); - } - return callback(err); - - } - if (config.debug) { - console.log('saveData saved', data._id, result2, result3); - } - return callback(null, data, collection); - }); - } - - var updating = false; - - function updateData(collection, data, callback) { - if (updating) { - //noinspection JSUnresolvedFunction - setImmediate(function rescheduling() { - if (config.debug) { - console.log('Updating, rescheduling'); - } - return updateData(collection, data, callback); - }); - } else { - updating = true; - var stopUpdating = function (err, result, col) { - if (config.debug) { - console.log('Stop updating'); - } - updating = false; - if (err) { - return callback(err); - } - return callback(null, result, col); - }; - - return collection.findOne({ _id: data._id}, function foundOne(err, result) { - if (err) { - if (config.errors) { - console.error('ERR updateData', err); - } - return callback(err); - } - _.extend(result, data); - return saveData(collection, result, stopUpdating); - }); - } - } - - - /* - options: - * collection (mandatory) - * query (mandatory) - * update (optional) : extends existing content - */ - function setMongoContent(data, options, callback) { - if (config.debug) { - console.log('setMongoContent options', options); - } - return db.collection(options.collection, function handleCollection(err, collection) { - if (err) { - if (config.errors) { - console.error('ERR2 setMongoContent', err); - } - return callback(err); - } - if (options.operation) { - data.version = options.operation.v; - } - var dumpData = saveData; - if (options.update) { - dumpData = updateData; - } - if (!data._id) { - if (config.debug) { - console.log('setMongoContent lookup by query', options.query, 'updating:', options.update); - } - collection.findOne(options.query, function foundOne(err, result) { - if (err) { - if (config.errors) { - console.error('ERR3 setMongoContent', err); - } - return callback(err); - } - if (result) { - data._id = result._id; - } - return dumpData(collection, data, callback); - }); - } else { - return dumpData(collection, data, callback); - } - }); - } - - function updateShareDocumentPath(documentId, data, path, callback) { - shareModel.getSnapshot(documentId, function (err, doc) { - if (err) { - if (config.errors) { - console.error('WARN setMongoAttribute updateShareDocumentPath shareModel.getSnapshot', documentId, err); - } - return callback && callback(); - } - var sub_data = data; - var sub_snapshot_data = doc.snapshot; - var equal_path = []; - var found = false; - var x; - for (x = 0; !found && x < path.length; x += 1) { - var key = path[x]; - if (sub_data && sub_data.hasOwnProperty(key) - && sub_snapshot_data && sub_snapshot_data.hasOwnProperty(key)) { - sub_data = sub_data[key]; - sub_snapshot_data = sub_snapshot_data[key]; - equal_path.push(key); - } else if (!sub_snapshot_data || !sub_snapshot_data.hasOwnProperty(key)) { - found = true; - } - } - if (found) { - path = equal_path; - } - var op = { - p: path - }; - if (sub_data) { - op.oi = sub_data; - } - if (sub_snapshot_data) { - op.od = sub_snapshot_data; - } - return shareModel.applyOp(documentId, { op: [op], v: doc.v }, function (err, result) { - if (err) { - if (config.errors) { - console.error('ERR updateShareDocumentPath shareModel.applyOp', documentId, err); - } - return callback && callback(); - } - if (config.debug) { - console.log('updateShareDocumentPath shareModel.applyOp', documentId, op, err, result); - } - return callback && callback(); - - }); - }); - } - - function updateShareDocument(documentId, data, keys, callback) { - if (!keys) { - // no keys no update - return callback && callback(); - } - var ops = []; - return shareModel.getSnapshot(documentId, function (err, doc) { - if (err) { - if (config.errors) { - console.error('WARN updateShareDocument shareModel.getSnapshot', documentId, err); - } - return callback && callback(); - } - if (doc.type.name === 'text') { - ops.push({ - d: doc.snapshot, p: 0 - }); - ops.push({ - i: data, p: 0 - }); - } else if (doc.type.name === 'json') { - _.forEach(keys, function (key) { - if (key !== '_id') { - var op = { - p: [key] - }; - if (doc.snapshot[key]) { - op.od = doc.snapshot[key]; - } - if (data[key]) { - op.oi = data[key]; - } - ops.push(op); - } - }); - } - return shareModel.applyOp(documentId, { op: ops, v: doc.v }, function (err, result) { - if (err) { - if (config.errors) { - console.error('WARN updateShareDocument shareModel.applyOp', documentId, err); - } - return callback && callback(); - } - if (config.debug) { - console.log('updateShareDocument shareModel.applyOp', documentId, ops, err, result); - } - return callback && callback(); - }); - }); - } - - - var ensuring = false; - - function ensureContent(options, callback) { - if (ensuring) { - //noinspection JSUnresolvedFunction - setImmediate(function rescheduling() { - if (config.debug) { - console.log('Ensuring, rescheduling', options); - } - return ensureContent(options, callback); - }); - } else { - ensuring = true; - if (!options.query) { - options.query = { - name: options.name - }; - } - var stopEnsuring = function (err, result, col) { - if (config.debug) { - console.log('Stop ensuring', options); - } - ensuring = false; - if (err) { - return callback(err); - } - return callback(null, result, col); - }; - - getMongoContent(options, function document(err, result, col) { - if (err) { - if (/Data not found*/.test(err.message)) { - var documentId = 'json:' + options.collection + ':' + options.name; - var data = {name: options.name}; - setMongoContent(data, options, function (err, content_result, col) { - var keys = _.keys(data); // reset all attributes; - return updateShareDocument(documentId, data, keys, function updatedShareDocument() { - stopEnsuring(err, content_result, col); - }); - }); - } else { - return stopEnsuring(err); - } - } else { - return stopEnsuring(null, result, col); - } - }); - } - } - - - /* options: - * no_share (optional): prevent share to update itself. - */ - function setMongoAttribute(data, options, callback) { - if (config.debug) { - console.log('setMongoAttribute options', options); - } - ensureContent(options, function document(err, result, col) { - if (err) { - if (config.errors) { - console.error('ERR1 setMongoAttribute', err); - } - return callback(err); - } - var attribute_options = { - collection: options.collection, - name: result.name + '.' + options.attribute - }; - if (result.hasOwnProperty(options.attribute) - && result[options.attribute].guid) { - attribute_options.query = { - _id: result[options.attribute].guid - }; - } else { - attribute_options.query = { - parent: result._id, - name: result.name + '.' + options.attribute - }; - - } - - if (config.debug) { - console.log('getMongoAttribute parent found, get child and save', result, attribute_options); - } - return ensureContent(attribute_options, function attribute(err, attribute_result) { - if (err) { - if (config.errors) { - console.error('ERR2 setMongoAttribute ensureContent', err); - } - return callback(err); - } - var updateContent = true; - if (result[options.attribute]) { - if (attribute_result._id.toString() === String(result[options.attribute].guid)) { - updateContent = false; - } else { - result[options.attribute].guid = attribute_result._id; - } - } else { - result[options.attribute] = { guid: attribute_result._id }; - } - - attribute_result.parent = result._id; - attribute_result.name = result.name + '.' + options.attribute; - attribute_result[options.attribute] = data; - if (options.operation) { - attribute_result.version = options.operation.v; - } - return saveData(col, attribute_result, function saved(err) { - if (err) { - if (config.errors) { - console.error('ERR3 setMongoAttribute', err); - } - return callback(err); - } - var documentId = 'json:' + options.collection + ':' + result.name; - var type = options.type || 'text'; - var attributeDocumentId = type + ':' + options.collection + ':' + result.name + ':' + options.attribute; - var keys = null; - var share_data = null; - if (type === 'json') { - keys = _.keys(attribute_result); // reset all attributes; - share_data = attribute_result; - } else { - keys = [attribute_result.name]; - share_data = data; - } - if (options.no_share) { - keys = null; - } - return updateShareDocument(attributeDocumentId, share_data, keys, function updatedShareAttribute() { - if (updateContent) { - updateData(col, result, function saved(err) { - if (err) { - if (config.errors) { - console.error('ERR3 setMongoAttribute', err); - } - return callback(err); - } - var path = [options.attribute, 'guid']; // reset just guid attribute; - return updateShareDocumentPath(documentId, result, path, function updatedShareContent() { - return callback(null, attribute_result, col); - }); - }); - } else { - callback(null, attribute_result, col); - } - }); - }); - }); - }); - } - - return { - getMongoAttribute: getMongoAttribute, - getMongoContent: getMongoContent, - setMongoAttribute: setMongoAttribute, - setMongoContent: setMongoContent, - ensureContent: ensureContent, - updateShareDocument: updateShareDocument - }; -}; - - - - diff --git a/server.js b/server.js index 4f58643..bc6269f 100644 --- a/server.js +++ b/server.js @@ -1,3 +1,4 @@ +"use strict"; process.title = "Prototyper"; var mime = require('mime'); @@ -19,8 +20,15 @@ mime.define({ }); var config = { + debug: function () { + if (process.env.DEBUG) { + console.log(JSON.stringify(arguments)); + } + }, + error: function () { + console.error(JSON.stringify(arguments)); + }, errors: true, - debug: process.env.DEBUG || false, port: process.env.npm_package_config_port || 8000, mongo: { server: "mongodb://localhost:27017/Prototyper", @@ -65,7 +73,7 @@ var config = { }; if (config.debug) { - console.log('config loaded'); + config.debug('config loaded'); } var app = express(); @@ -77,7 +85,6 @@ app.use(express.compress()); if (!process.env.NODE_ENV) { app.get('/favicon.ico', function (req, res) { - "use strict"; res.sendfile(config.statics.dev_favicon_path, null, null); }); } @@ -93,22 +100,21 @@ app.use('/lib/ace', express.static(config.statics.ace_client)); app.use('/lib/async', express.static(config.statics.async_client)); if (config.debug) { - console.log('static routes set'); + config.debug('static routes set'); } var markerInstance = markers(config); var helperInstance = helpers(markerInstance); MongoClient.connect(config.mongo.server, config.mongo.options, function connection(err, db) { - "use strict"; if (err) { - if (config.errors) { - console.error('ERR connection to database', err); + if (config.error) { + config.error('ERR connection to database', err); } - return process.exit(1); + return process.exit(3); } if (config.debug) { - console.log('database connected'); + config.debug('database connected'); } var share = shareServer(config, app, db); @@ -116,58 +122,68 @@ MongoClient.connect(config.mongo.server, config.mongo.options, function connecti var server = share.server; if (config.debug) { - console.log('share attached'); + config.debug('share attached'); } var mongoDataInstance = mongoData(config, db, model); if (config.debug) { - console.log('mongodata initialized'); + config.debug('mongodata initialized'); } shareHandlers(config, model, mongoDataInstance); if (config.debug) { - console.log('shareHandlers attached'); + config.debug('shareHandlers attached'); } var previewInstance = preview(config, mongoDataInstance, helperInstance, markerInstance); if (config.debug) { - console.log('previews initialized'); + config.debug('previews initialized'); } var importerInstance = importer(config, mongoDataInstance, helperInstance, markerInstance); if (config.debug) { - console.log('importer initialized'); + config.debug('importer initialized'); } var handlerInstance = handlers(mongoDataInstance, previewInstance, importerInstance); if (config.debug) { - console.log('handlers initialized'); + config.debug('handlers initialized'); } app = addRoutes(app, handlerInstance, markers, config); if (config.debug) { - console.log('routes added'); + config.debug('routes added'); + } + + function exit(code) { + db.close(); + process.exit(code); } server.on('error', function (err) { if (config.error) { - console.error('server error', err); + config.error('Server error', err); + } + if (err.code && err.code === 'EADDRINUSE') { + exit(2); } }); + return server.listen(config.port, function handleServerResult(err) { if (err) { - app.stop(); - console.error('Server error', err); - return process.exit(1); + if (config.error) { + console.error('Server error', err); + } + return exit(1); } if (config.debug) { - console.log('routes', app.routes); + config.debug('routes', app.routes); } return console.log('Server running at http://127.0.0.1:', config.port); }); diff --git a/test/test.helpers.js b/test/test.helpers.js index 9bc4e2e..4f1d954 100644 --- a/test/test.helpers.js +++ b/test/test.helpers.js @@ -10,7 +10,14 @@ var when = require('when'); describe('Helpers', function () { "use strict"; - var config = { debug: false }; + var config = { + debug: function () { + //console.log(arguments); + }, + error: function () { + //console.error(arguments); + } + }; var markersInstance = markers(config); var marker_prefix = ''; diff --git a/test/test.mongoData.js b/test/test.mongoData.js new file mode 100644 index 0000000..af2a958 --- /dev/null +++ b/test/test.mongoData.js @@ -0,0 +1,96 @@ +"use strict"; +var libPath = process.env.PROTOTYPER_COV ? '../lib-cov' : '../lib'; +var chai = require('chai'); +chai.Assertion.includeStack = true; // defaults to false +var expect = chai.expect; + +var mongoData = require(libPath + '/mongoData.js'); +var config = { + debug: function () { + //console.log(arguments); + }, + error: function () { + //console.error(arguments); + } +}; + +describe('mongoData', function () { + describe('getMongoContent', function () { + + function findOne(q, cb) { + //console.log('findOne', arguments); + if (q._id.toString() === '123456789012345678901234') { + return cb(new Error(q)); + } + if (q._id.toString() === '234567890123456789012345') { + return cb(null, null); + } + if (q._id.toString() === '345678901234567890123456') { + return cb(null, ''); + } + if (q._id.toString() === '456789012345678901234567') { + return cb(null, 'ok'); + } + throw new Error('fail:' + JSON.stringify(arguments)); + } + + var col = {findOne: findOne}; + var db = { collection: function (c, cb) { + //console.log('collection arguments', arguments); + if (c === 'test_error') { + return cb(new Error(c)); + } + return cb(null, col); + }}; + var shareModel = {}; + var option_list = [ + {}, // no collection + {query: 'q'}, // no collection + {collection: 'test_error'}, // bad collection + {collection: 'col'}, // no query + {collection: 'no_hex', + query: {_id: 'id'} // not a hexString + }, + {collection: 'no_col', // trigger findOne error + query: {_id: '123456789012345678901234'} + }, + {collection: 'col', // trigger null result + query: {_id: '234567890123456789012345'} + }, + {collection: 'col', // trigger '' result + query: {_id: '345678901234567890123456'} + }, + {collection: 'ok', // trigger 'ok' result + query: {_id: '456789012345678901234567'} + } + ]; + var mongoDataInstance = mongoData(config, db, shareModel); + var i; + + function testArguments(options) { + return function (done) { + mongoDataInstance.getMongoContent(options, function (err, result, coll) { + if (options.collection === 'ok') { + expect(result).to.equal('ok'); + expect(err).to.not.be.ok; + expect(coll).to.equal(col); + done(); + } else { + expect(err).to.be.instanceOf(Error); + expect(result).to.not.be.ok; + if (options.collection === 'col') { + expect(coll).to.equal(col); + } else { + expect(coll, JSON.stringify(options)).to.not.be.ok; + } + done(); + } + }); + }; + } + + for (i = 0; i < option_list.length; i += 1) { + it('should handle arguments correctly', testArguments(option_list[i])); + } + }); +});