From 1bf77533b5f53492d617035b96e16388ec956dcd Mon Sep 17 00:00:00 2001 From: Aiko Mastboom Date: Fri, 3 Jul 2015 15:54:25 +0200 Subject: [PATCH] initial rethink incorporation --- lib/importer.js | 8 +- lib/preview.js | 4 +- lib/rethinkData.js | 475 +++++++++++++++++++++++++++++++++++++++ lib/routes.js | 6 +- package.json | 15 +- server.js | 141 +++++++----- test/test.rethinkData.js | 209 +++++++++++++++++ 7 files changed, 785 insertions(+), 73 deletions(-) create mode 100644 lib/rethinkData.js create mode 100644 test/test.rethinkData.js diff --git a/lib/importer.js b/lib/importer.js index c549689..659d7ad 100644 --- a/lib/importer.js +++ b/lib/importer.js @@ -57,7 +57,7 @@ module.exports = function (config, mongoInstance, helpers, markers) { config.error && config.error('ERR importer.importer ensureContent', err); return cb && cb(err); } - leftover.replacement.query = {_id: parent_result._id}; + leftover.replacement.query = {id: parent_result.id}; remainder = remainder.replace(leftover.regExp, ''); return mongoInstance.setMongoAttribute(remainder, leftover.replacement, function savedAttribute(err) { @@ -103,8 +103,8 @@ module.exports = function (config, mongoInstance, helpers, markers) { config.error && config.error('ERR importer.replaceMarkers JSON.parse(remainder)', remainder, error); return callback && callback(error); } - if (data._id) { - delete data._id; + if (data.id) { + delete data.id; } _.extend(parent_result, data); context.update = true; @@ -120,7 +120,7 @@ module.exports = function (config, mongoInstance, helpers, markers) { }); }); } - context.query = {_id: parent_result._id}; + context.query = {id: parent_result.id}; return mongoInstance.setMongoAttribute(remainder, context, function savedAttribute(err) { if (err) { config.error && config.error('ERR2 importer.importer setMongoAttribute', err); diff --git a/lib/preview.js b/lib/preview.js index fef68bd..0bb627f 100644 --- a/lib/preview.js +++ b/lib/preview.js @@ -157,7 +157,7 @@ module.exports = function (config, mongoDataInstance, helpers, markers) { var attribute_context = { collection: context.collection, attribute: key, - query: {_id: context_result._id} + query: {id: context_result.id} }; return mongoDataInstance.getMongoAttribute(attribute_context, function cacheTemplateKey(err, template_key_result) { if (err) { @@ -180,7 +180,7 @@ module.exports = function (config, mongoDataInstance, helpers, markers) { return callback && callback(err); } config.debug && config.debug('// handle markers on rendered template'); - context.query = {_id: context_result._id}; + context.query = {id: context_result.id}; return getPreviewHTML(rendered, context, function handlePreviewResult(err, preview_html) { if (err) { config.error && config.error('ERR template_tag getPreviewHTML', err); diff --git a/lib/rethinkData.js b/lib/rethinkData.js new file mode 100644 index 0000000..6111bc0 --- /dev/null +++ b/lib/rethinkData.js @@ -0,0 +1,475 @@ +'use strict'; +var _ = require('underscore'); + +module.exports = function (config, r, connection, shareModel) { + /* + * options: + * collection (mandatory) + * query (mandatory) + */ + function getRethinkContent(options, callback) { + config.debug && config.debug('getRethinkContent options', options); + if (!options.collection) { + return callback && callback(new Error('Data not found / missing collection')); + } + if (!options.query) { + return callback && callback(new Error('Data not found ' + options.collection + ' / missing query')); + } + return r.tableList() + .contains(options.collection) + .do( + function table(containsTable) { + return r.branch( + containsTable, + {exists: true}, + {exists: false} + ); + }) + .run(connection, function table_exists(err, contains) { + if (contains && contains.exists) { + return r.table(options.collection) + .filter(options.query) + .run(connection, function filter_table(err, cursor) { + if (err) { + config.error && config.error('ERR4 getRethinkContent', err); + return callback && callback(err); + } + return cursor.next(function first_item(err, result) { + if (err) { + if ((err.name === 'RqlDriverError') && (err.message === 'No more rows in the cursor.')) { + return callback && callback(new Error('Data not found ' + options.collection + '/' + JSON.stringify(options.query)), null); + } + config.error && config.error('ERR4 getRethinkContent', err); + return callback && callback(err); + } + if (!result) { + return callback && callback(new Error('Data not found ' + options.collection + '/' + JSON.stringify(options.query)), null); + } + return callback && callback(null, result, options.collection); + }); + }); + } else { + return callback && callback(new Error('Data not found ' + options.collection + '/' + JSON.stringify(options.query)), null); + } + }); + } + + + /* + * options: + * collection (mandatory) + * query (mandatory) + * attribute (mandatory) + */ + function getRethinkAttribute(options, callback) { + config.debug && config.debug('getRethinkAttribute options', options); + return getRethinkContent(options, function document(err, result) { + if (err) { + config.error && config.error('ERR1 getRethinkAttribute', err); + return callback && callback(err); + } + if (!options.attribute) { + return callback && callback(new Error('Data not found / ' + options.collection + '/' + JSON.stringify(options.query) + ' missing attribute')); + } + var attribute_options = null; + config.debug && config.debug('getRethinkAttribute result', result); + if (result && + result.hasOwnProperty(options.attribute) && + result[options.attribute].guid) { + attribute_options = { + collection: options.collection, + query: {id: result[options.attribute].guid} + }; + config.debug && config.debug('getRethinkAttribute attribute_options', attribute_options); + + getRethinkContent(attribute_options, function attribute(err, attribute_result, coll) { + if (err) { + config.error && config.error('ERR2 getRethinkAttribute', err); + return callback && callback(err); + } + config.debug && config.debug('getRethinkAttribute attribute_result', attribute_result); + return callback && callback(err, attribute_result, coll); + }); + } else { + config.debug && config.debug('getRethinkAttribute try direct lookup'); + attribute_options = { + collection: options.collection, + query: { + parent: result.id, + name: result.name + '.' + options.attribute + } + }; + config.debug && config.debug('getRethinkAttribute attribute_options', attribute_options); + + return getRethinkContent(attribute_options, function attribute(err, attribute_result, coll) { + if (err) { + config.error && config.error('ERR getRethinkAttribute', err); + return callback && callback(err); + } + config.debug && config.debug('getRethinkAttribute direct attribute_result', attribute_result); + return callback && callback(err, attribute_result, coll); + }); + } + }); + } + + function saveData(collection, data, callback) { + config.debug && config.debug('saveData saving', data.id, data.guid, collection); + r.table(collection).insert(data, {conflict: 'replace'}) + .run(connection, function inserted(err, status) { + if (err) { + config.error && config.error('ERR saveData', err); + return callback && callback(err); + + } + if (!data.id && status.generated_keys) { + data.id = status.generated_keys[0]; + } + config.debug && config.debug('saveData saved', data.id, status); + return callback && callback(null, data, collection); + }); + } + + var updating = false; + + function updateData(collection, data, callback) { + if (updating) { + //noinspection JSUnresolvedFunction + setImmediate(function rescheduling() { + config.debug && config.debug('Updating, rescheduling'); + return updateData(collection, data, callback); + }); + } else { + updating = true; + var stopUpdating = function (err, result, col) { + config.debug && config.debug('Stop updating'); + updating = false; + if (err) { + return callback && callback(err); + } + return callback && callback(null, result, col); + }; + + return r.table(collection).get(data.id).run(connection, function foundOne(err, result) { + if (err) { + config.error && config.error('ERR updateData', err); + return callback && callback(err); + } + _.extend(result, data); + return saveData(collection, result, stopUpdating); + }); + } + } + + + /* + * options: + * collection (mandatory) + * query (mandatory) + * operation (optional) : version info + * update (optional) : extends existing content + */ + function setRethinkContent(data, options, callback) { + config.debug && config.debug('setRethinkContent options', options); + //Create the table if needed. + return r.tableList() + .contains(options.collection) + .do( + function table(containsTable) { + return r.branch( + containsTable, + {created: 0}, + r.tableCreate(options.collection) + ); + }) + .run(connection, function table_exists(err) { + if (err) { + config.error && config.error('ERR2 setRethinkContent', err); + return callback && callback(err); + } + if (options.operation && options.operation.v) { + data.version = options.operation.v; + } + var dumpData = saveData; + if (options.update) { + dumpData = updateData; + } + if (!data.id) { + config.debug && config.debug('setRethinkContent lookup by query', options.query, 'updating:', options.update); + return r.table(options.collection) + .filter(options.query) + .run(connection, function filter_table(err, cursor) { + + if (err) { + config.error && config.error('ERR3 setRethinkContent', err); + return callback && callback(err); + } + return cursor.next(function first_item(err, result) { + if (err) { + if (!((err.name === 'RqlDriverError') && (err.message === 'No more rows in the cursor.'))) { + config.error && config.error('ERR4 getRethinkContent', err); + return callback && callback(err); + } + // ignore when item does not exist yet, that's why we are here to begin with. + } + + if (result && result.id) { + data.id = result.id; + } + return dumpData(options.collection, data, callback); + }); + }); + } else { + return dumpData(options.collection, data, callback); + } + }); + } + + function updateShareDocumentPath(documentId, data, path, callback) { + shareModel.getSnapshot(documentId, function (err, doc) { + if (err) { + config.warn && config.warn('WARN 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) { + config.error && config.error('ERR updateShareDocumentPath shareModel.applyOp', documentId, err); + return callback && callback(); + } + config.debug && config.debug('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) { + config.warn && config.warn('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) { + config.warn && config.warn('WARN updateShareDocument shareModel.applyOp', documentId, err); + return callback && callback(); + } + config.debug && config.debug('updateShareDocument shareModel.applyOp', documentId, ops, err, result); + return callback && callback(); + }); + }); + } + + + var ensuring = false; + + function ensureContent(options, callback) { + if (ensuring) { + //noinspection JSUnresolvedFunction + setImmediate(function rescheduling() { + config.debug && config.debug('Ensuring, rescheduling', options); + return ensureContent(options, callback); + }); + } else { + ensuring = true; + if (!options.query) { + options.query = { + name: options.name + }; + } + var stopEnsuring = function (err, result, col) { + config.debug && config.debug('Stop ensuring', options); + ensuring = false; + if (err) { + return callback && callback(err); + } + return callback && callback(null, result, col); + }; + + return getRethinkContent(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}; + return setRethinkContent(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 from updating itself. + */ + function setRethinkAttribute(data, options, callback) { + config.debug && config.debug('setMongoAttribute options', options); + return ensureContent(options, function document(err, result, col) { + if (err) { + config.error && config.error('ERR1 setMongoAttribute', err); + return callback && 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 + }; + + } + + config.debug && config.debug('getMongoAttribute parent found, get child and save', result, attribute_options); + return ensureContent(attribute_options, function attribute(err, attribute_result) { + if (err) { + config.error && config.error('ERR2 setMongoAttribute ensureContent', err); + return callback && callback(err); + } + var updateContent = true; + if (result[options.attribute]) { + if (attribute_result.id === 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) { + config.error && config.error('ERR3 setMongoAttribute', err); + return callback && 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) { + config.error && config.error('ERR3 setMongoAttribute', err); + return callback && callback(err); + } + var path = [options.attribute, 'guid']; // reset just guid attribute; + return updateShareDocumentPath(documentId, result, path, function updatedShareContent() { + return callback && callback(null, attribute_result, col); + }); + }); + } else { + return callback && callback(null, attribute_result, col); + } + }); + }); + }); + }); + } + + return { + getMongoAttribute: getRethinkAttribute, + getMongoContent: getRethinkContent, + setMongoAttribute: setRethinkAttribute, + setMongoContent: setRethinkContent, + ensureContent: ensureContent, + updateShareDocument: updateShareDocument + }; +}; diff --git a/lib/routes.js b/lib/routes.js index 27c110c..ebe81e0 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -14,7 +14,7 @@ module.exports = function (app, handlers, markers, config) { collection: req.params.collection, attribute: req.params.attribute, ext: req.params.ext, - query: {_id: req.params.guid} + query: {id: req.params.guid} }; handlers.getAttribute(options, responder(config, options, res, next) @@ -29,7 +29,7 @@ module.exports = function (app, handlers, markers, config) { var options = { collection: req.params.collection, ext: req.params.ext, - query: {_id: req.params.guid} + query: {id: req.params.guid} }; handlers.getContent(options, responder(config, options, res, next) @@ -103,7 +103,7 @@ module.exports = function (app, handlers, markers, config) { collection: options.collection, name: options.name, attribute: attribute, - query: {_id: result._id}, + query: {id: result.id}, req: options.req }; diff --git a/package.json b/package.json index 1bb58f2..8b7043c 100644 --- a/package.json +++ b/package.json @@ -8,17 +8,18 @@ "engine": "node 0.10.38", "private": "true", "dependencies": { - "share": "~0.6.0", + "async": "~0.2.8", + "bson": "~0.1.8", "connect": "~2.7.9", "express": "~3.2.4", - "bson": "~0.1.8", - "mongodb": "~1.2.14", "handlebars": "~1.0.10", - "underscore": "~1.4.4", "less": "~1.3.3", - "when": "~2.1.0", - "markdown": "~0.4.0", - "async": "~0.2.8" + "markdown": "~0.5.0", + "mongodb": "~1.2.14", + "rethinkdb": "^2.0.2", + "share": "~0.6.0", + "underscore": "~1.4.4", + "when": "~2.1.0" }, "devDependencies": { "sockjs": "~0.3.7", diff --git a/server.js b/server.js index 1886ff4..532007e 100644 --- a/server.js +++ b/server.js @@ -3,11 +3,11 @@ process.title = 'Prototyper'; var connect = require('connect'); var express = require('express'); -var MongoClient = require('mongodb').MongoClient; +var rethink = require('rethinkdb'); var addRoutes = require('./lib/routes.js'); var shareServer = require('./lib/share.js'); var shareHandlers = require('./lib/shareHandlers.js'); -var mongoData = require('./lib/mongoData.js'); +var rethinkData = require('./lib/rethinkData.js'); var preview = require('./lib/preview.js'); var importer = require('./lib/importer.js'); var handlers = require('./lib/handlers.js'); @@ -84,6 +84,13 @@ var config = { }, savedelay: 200 }, + rethink: { + server: { + host: 'rethinkdb.40n8.me', + port: 28015, + db: 'Prototyper' + } + }, share: { sockjs: { prefix: '', @@ -170,74 +177,94 @@ config.debug && 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) { +rethink.connect(config.rethink.server, function connection_result(err, connection) { + if (err) { config.error && config.error('ERR connection to database', err); return process.exit(3); } - config.debug && config.debug('database connected'); - - var share = shareServer(config, app, db); - var model = share.model; - var server = share.server; - - config.debug && config.debug('share attached'); - - var mongoDataInstance = mongoData(config, db, model); - - config.debug && config.debug('mongodata initialized'); - - shareHandlers(config, model, mongoDataInstance); - - config.debug && config.debug('shareHandlers attached'); - - var previewInstance = preview(config, mongoDataInstance, helperInstance, markerInstance); - - config.debug && config.debug('previews initialized'); - - var importerInstance = importer(config, mongoDataInstance, helperInstance, markerInstance); - - config.debug && config.debug('importer initialized'); - - var handlerInstance = handlers(mongoDataInstance, previewInstance, importerInstance); - - config.debug && config.debug('handlers initialized'); - - app = addRoutes(app, handlerInstance, markers, config); - - config.debug && config.debug('routes added'); function exit(code) { - db.close(); - // Probably there are also some other listeners for uncaughtException, - // so we postpone process.exit - process.nextTick(function () { - process.exit(code); + connection.close(function () { + // Probably there are also some other listeners for uncaughtException, + // so we postpone process.exit + process.nextTick(function () { + process.exit(code); + }); }); } - process.on('uncaughtException', function (err) { - config.error && config.error('HALTING ON UNCAUGHT EXCEPTION:' + err.message, err); - config.error && config.error(err.stack); - config.error && config.error('EXIT 1'); - return exit(1); - }); + config.debug && config.debug('database connected'); - server.on('error', function (err) { - config.error && config.error('Server error', err); - if (err.code && err.code === 'EADDRINUSE') { - return exit(2); - } - }); - - return server.listen(config.port, function handleServerResult(err) { + //Create the database if needed. + rethink.dbList().contains(config.rethink.server.db).do(function (containsDb) { + return rethink.branch( + containsDb, + {created: 0}, + rethink.dbCreate(config.rethink.server.db) + ); + }).run(connection, function (err) { + connection.use(config.rethink.server.db); if (err) { - console.error('Server error', err); - return exit(1); + config.error && config.error('ERR creating table in database', err); + return exit(4); } - config.debug && config.debug('routes', app.routes); - return config.info && config.info('Server running at http://127.0.0.1:' + config.port); + + var share = shareServer(config, app); + var model = share.model; + var server = share.server; + + config.debug && config.debug('share attached'); + + var dataInstance = rethinkData(config, rethink, connection, model); + + config.debug && config.debug('dataInstance initialized'); + + shareHandlers(config, model, dataInstance); + + config.debug && config.debug('shareHandlers attached'); + + var previewInstance = preview(config, dataInstance, helperInstance, markerInstance); + + config.debug && config.debug('previews initialized'); + + var importerInstance = importer(config, dataInstance, helperInstance, markerInstance); + + config.debug && config.debug('importer initialized'); + + var handlerInstance = handlers(dataInstance, previewInstance, importerInstance); + + config.debug && config.debug('handlers initialized'); + + app = addRoutes(app, handlerInstance, markerInstance, config); + + config.debug && config.debug('routes added'); + + + process.on('uncaughtException', function (err) { + config.error && config.error('HALTING ON UNCAUGHT EXCEPTION:' + err.message, err); + config.error && config.error(err.stack); + config.error && config.error('EXIT 1'); + return exit(1); + }); + + server.on('error', function (err) { + config.error && config.error('Server error', err); + if (err.code && err.code === 'EADDRINUSE') { + return exit(2); + } + }); + + return server.listen(config.port, function handleServerResult(err) { + if (err) { + console.error('Server error', err); + return exit(1); + } + config.debug && config.debug('routes', app.routes); + return config.info && config.info('Server running at http://127.0.0.1:' + config.port); + }); }); + }); diff --git a/test/test.rethinkData.js b/test/test.rethinkData.js new file mode 100644 index 0000000..1ff74ff --- /dev/null +++ b/test/test.rethinkData.js @@ -0,0 +1,209 @@ +'use strict'; +//noinspection JSUnresolvedVariable +var libPath = process.env.PROTOTYPER_COV ? '../lib-cov' : '../lib'; +var chai = require('chai'); +chai.config.includeStack = true; // defaults to false +chai.config.showDiff = false; // defaults to false +var expect = chai.expect; + +var rethinkData = require(libPath + '/rethinkData.js'); +var config = { + debug: function () { + //console.log(arguments); + }, + info: function () { + //console.log(arguments); + }, + warn: function () { + //console.error(arguments); + }, + error: function () { + //console.error(arguments); + } +}; + +describe('rethinkData', function () { + describe('getContent', function () { + + var db = { + tableList: function(){ + return this; + }, + contains: function(){ + return this; + }, + do: function(){ + return this; + }, + run: function(c,callback){ + return callback(new Error()); + } + }; + 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 dataInstance = rethinkData(config, db, 'ok', shareModel); + var i; + + function testArguments(options) { + return function (done) { + dataInstance.getMongoContent(options, function (err, result) { + if (result === 'ok') { + expect(result).to.equal('ok'); + expect(err).to.not.be.ok; + done(); + } else { + expect(err).to.be.instanceOf(Error); + expect(result).to.not.be.ok; + done(); + } + }); + }; + } + + for (i = 0; i < option_list.length; i += 1) { + it('should handle arguments correctly ' + JSON.stringify(option_list[i]), + testArguments(option_list[i]) + ); + } + }); + + describe('getContentAttribute', function () { + var ok = { + id: '345678901234567890123456', + parent: '456789012345678901234567', + name: 'test.content_attribute' + }; + function findOne(q, cb) { + if (q._id && q._id.toString() === '123456789012345678901234') { + return cb(null, { + id: '123456789012345678901234', + name: 'test', + error_attribute: { + guid: '234567890123456789012345' + }, + content_attribute: { + guid: '345678901234567890123456' + } + }); + } + if (q.id && q.id === '234567890123456789012345') { + return cb(new Error(q)); + } + if (q.id && q.id === '345678901234567890123456') { + return cb(null, ok); + } + if (q.id && q.id === '456789012345678901234567') { + return cb(null, { + id: '456789012345678901234567', + name: 'test' + }); + } + if (q.parent && q.parent.toString() === '456789012345678901234567') { + if (q.name === 'test.content_attribute'){ + return cb(null, ok); + } else { + return cb(new Error(q)); + } + } + throw new Error('fail:' + JSON.stringify(arguments)); + } + + var col = {findOne: findOne}; + var db = { + tableList: function(){ + return this; + }, + contains: function(){ + return this; + }, + do: function(){ + return this; + }, + run: function(c,callback){ + return callback(new Error()); + } + }; + var shareModel = {}; + var option_list = [ + {}, // no collection + {query: 'q'}, // no collection + {collection: 'col'}, // no query + { + collection: 'no_attr', + query: {id: '123456789012345678901234'} // no attribute + }, + { + collection: 'col', // trigger null result + query: {id: '123456789012345678901234'}, + attribute: 'error_attribute' + }, + { + collection: 'ok', // trigger 'ok' result + query: {id: '123456789012345678901234'}, + attribute: 'content_attribute' + + }, + { + collection: 'col', // trigger error result + query: {id: '456789012345678901234567'}, + attribute: 'error_attribute' + }, + { + collection: 'ok', // trigger 'ok' result + query: {id: '456789012345678901234567'}, + attribute: 'content_attribute' + } + ]; + var mongoDataInstance = rethinkData(config, db, shareModel); + var i; + + function testArguments(options) { + return function (done) { + mongoDataInstance.getMongoAttribute(options, function (err, result) { + if (options.collection === 'ok') { + //console.log('result',result,'err',err,'coll',coll); + expect(result).to.equal(ok); + expect(err).to.not.be.ok; + done(); + } else { + //console.log('err',err); + expect(err).to.be.instanceOf(Error); + expect(result).to.not.be.ok; + done(); + } + }); + }; + } + + for (i = 0; i < option_list.length; i += 1) { + it('should handle arguments correctly ' + JSON.stringify(option_list[i]), + testArguments(option_list[i]) + ); + } + }); +});