UNPKG

nodiak

Version:

Nodiak is a Node.js client for the Riak Distributed Database

583 lines (511 loc) 24.2 kB
// (The MIT License) // Copyright (c) 2012 Coradine Aviation Systems // Copyright (c) 2012 Nathan Aschbacher // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // 'Software'), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. describe("Nodiak Riak Client Test Suite", function() { var TIMEOUT = process.env.TIMEOUT || 20000; var NUM_OBJECTS = parseInt(process.env.NUM_OBJECTS, 10) || 1000; var backend = process.env.NODIAK_BACKEND || 'http'; var host = process.env.NODIAK_HOST || 'localhost'; var port = process.env.NODIAK_PORT || '8098'; var search_enabled = process.env.NODIAK_SEARCH && process.env.NODIAK_SEARCH != 'false' ? true : false; var twoi_enabled = process.env.NODIAK_2I && process.env.NODIAK_2I != 'false'? true : false; var riak = require('../index.js').getClient(backend, host, port); var async = require('async'); var should = require('should'); before(function(done){ // bootstrap settings and data for tests. this.timeout(TIMEOUT); riak.ping(function(err, response) { if(err) throw new Error(err.toString()); else { var search_hook = search_enabled ? {precommit:[{mod:"riak_search_kv_hook",fun:"precommit"}]} : {precommit:[]}; riak._bucket.save('nodiak_test', search_hook, function(err, result) { if(err) throw new Error(err.toString()); else { var data = { field1: "has been set" }; var metadata = { index: { strings: { bin: ['this', 'that', 'the', 'other'] }, numbers: { int: [1000,250,37,4234,5] } }, meta: { details: "you might want to know" } }; var created = []; for(var i = 1; i <= NUM_OBJECTS; i++) { riak._object.save('nodiak_test', i, data, metadata, function(err, obj) { if(err) throw new Error(err.toString()); else { created.push(obj); } if(created.length == NUM_OBJECTS) { done(); } }); } } }); } }); }); describe("Basic connection functionality", function() { it("should be able to ping the cluster via "+backend.toUpperCase(), function(done) { riak.ping(function(err, response) { should.not.exist(err); response.should.equal("OK"); done(); }); }); it("should be able to get stats via "+backend.toUpperCase(), function(done) { riak.stats(function(err, response) { should.not.exist(err); response.should.be.an.Object; done(); }); }); it("should be able to list resources via "+backend.toUpperCase(), function(done) { riak.resources(function(err, response) { should.not.exist(err); response.should.be.an.Object; done(); }); }); }); describe("Using the base client to interact with buckets", function() { it("should be able to read bucket properties", function(done) { riak._bucket.props('random_bucket', function(err, props) { should.not.exist(err); props.should.be.an.Object.and.have.property('name', 'random_bucket'); done(); }); }); it("should be able to save properties to a bucket", function(done) { riak._bucket.props('random_bucket', function(err, props) { should.not.exist(err); var toggled = props.allow_mult ? false : true; props.allow_mult = toggled; riak._bucket.save('random_bucket', props, function(err, response) { should.not.exist(err); riak._bucket.props('random_bucket', function(err, props) { should.not.exist(err); props.allow_mult.should.equal(toggled); done(); }); }); }); }); it("should be able to list all buckets", function(done) { riak._bucket.list(function(err, buckets) { should.not.exist(err); buckets.should.be.an.instanceOf(Array); done(); }); }); it("should be able to list all keys in a bucket", function(done) { riak._bucket.keys('nodiak_test').stream() .on('data', function(data) { data.should.be.an.instanceOf(Array); }) .on('end', function() { done(); }) .on('error', function(err) { should.not.exist(err); }); }); }); describe("Using the base client to interact with objects", function() { it("should be able to check existence of object", function(done) { riak._object.save('nodiak_test', 'this_ol_key', { "pointless": "data" }, null, function(err, obj) { should.not.exist(err); riak._object.exists('nodiak_test', 'this_ol_key', function(err, exists) { should.not.exist(err); exists.should.be.true; riak._object.exists('nodiak_test', 'no_key_here', function(err, exists) { should.not.exist(err); exists.should.be.false; done(); }); }); }); }); it("should be able to save an object", function(done) { riak._object.save('nodiak_test', 'this_ol_key', { "pointless": "data" }, null, function(err, obj) { should.not.exist(err); obj.should.be.an.Object.and.have.property('key', 'this_ol_key'); obj.should.be.an.Object.and.have.property('data'); obj.data.should.eql({ "pointless": "data" }); done(); }); }); it("should be able to get an object", function(done) { riak._object.get('nodiak_test', 'this_ol_key', function(err, obj) { should.not.exist(err); obj.should.be.an.Object.and.have.property('key', 'this_ol_key'); obj.should.be.an.Object.and.have.property('data'); obj.data.should.eql({ "pointless": "data" }); obj.metadata.should.be.an.Object.and.have.property('vclock'); done(); }); }); it("should be able to delete an object", function(done) { riak._object.delete('nodiak_test', 'this_ol_key', function(err, obj) { should.not.exist(err); obj.metadata.status_code.should.equal(204); done(); }); }); it("should be able to get sibling vtags when siblings exist", function(done) { riak._bucket.save('siblings_test', { allow_mult: true }, function(err, response) { should.not.exist(err); riak._object.save('siblings_test', 'this_ol_key', { "pointless": "data" }, { meta: { extra: "meta data goes here"} }, function(err, obj) { should.not.exist(err); obj.should.be.an.Object.and.have.property('key', 'this_ol_key'); obj.should.be.an.Object.and.have.property('data'); obj.data.should.eql({ "pointless": "data" }); riak._object.save('siblings_test', 'this_ol_key', { "pointless": "sibling" }, { meta: {extra: "meta data goes EVERYWHERE"} }, function(err, obj) { should.not.exist(err); obj.should.be.an.Object.and.have.property('key', 'this_ol_key'); obj.should.be.an.Object.and.have.property('data'); obj.data.should.eql({ "pointless": "sibling" }); riak._object.get('siblings_test', 'this_ol_key', function(err, obj) { should.not.exist(err); obj.should.be.an.Object.and.have.property('siblings'); obj.siblings.should.be.an.instanceof(Array); obj.metadata.should.be.an.Object.and.have.property('vclock'); done(); }); }); }); }); }); }); describe("Using the base client to perform 2i queries", function() { if(twoi_enabled) { it("should be able to perform ranged integer 2i searches", function(done) { riak._bucket.twoi('nodiak_test', [0,10000], 'numbers_int', {}, function(err, keys) { should.not.exist(err); keys.should.be.an.instanceOf(Array).with.lengthOf(NUM_OBJECTS * 5); done(); }); }); it("should be able to perform exact match integer 2i searches", function(done) { riak._bucket.twoi('nodiak_test', 1000, 'numbers_int', {}, function(err, keys) { should.not.exist(err); keys.should.be.an.instanceOf(Array).with.lengthOf(NUM_OBJECTS); done(); }); }); it("should be able to perform ranged binary 2i searches", function(done) { riak._bucket.twoi('nodiak_test', ['a','zzzzzzzzzzz'], 'strings_bin', {}, function(err, keys) { should.not.exist(err); keys.should.be.an.instanceOf(Array).with.lengthOf(NUM_OBJECTS * 4); done(); }); }); it("should be able to perform exact match binary 2i searches", function(done) { riak._bucket.twoi('nodiak_test', 'that', 'strings_bin', {}, function(err, keys) { should.not.exist(err); keys.should.be.an.instanceOf(Array).with.lengthOf(NUM_OBJECTS); done(); }); }); } else { return; } }); describe("Using the base client to perform Riak Search queries", function() { if(search_enabled) { it("should be able to perform solr search on indexed bucket", function(done) { riak._bucket.solr('nodiak_test', { q: 'field1:been' }, function(err, obj) { should.not.exist(null); obj.data.should.have.property('response'); obj.data.response.should.have.property('numFound', NUM_OBJECTS); obj.data.response.should.have.property('docs').with.lengthOf(10); done(); }); }); } else { return; } }); describe("Performing MapReduce queries", function() { it("should be able to handle streamed results", function(done) { riak.mapred.inputs('nodiak_test') .map({ language: 'erlang', module: 'riak_kv_mapreduce', function: 'map_object_value', arg: 'filter_notfound'}) .reduce({ language: 'erlang', module: 'riak_kv_mapreduce', function: 'reduce_count_inputs'}) .execute().stream() .on('data', function(result) { result.should.be.an.Object; result.data.should.be.an.instanceOf(Array); result.data[0].should.equal(NUM_OBJECTS + 1); }) .on('end', function() { done(); }) .on('error', function(err) { should.not.exist(err); }); }); it("should be able to handle non-streamed results", function(done) { riak.mapred.inputs('nodiak_test') .map({ language: 'erlang', module: 'riak_kv_mapreduce', function: 'map_object_value', arg: 'filter_notfound'}) .reduce({ language: 'erlang', module: 'riak_kv_mapreduce', function: 'reduce_count_inputs'}) .execute(function(err, result) { should.not.exist(err); result.should.be.an.Object; result.data.should.be.an.instanceOf(Array); result.data[0].should.equal(NUM_OBJECTS + 1); done(); } ); }); }); describe("Using the 'Bucket' class to interact with buckets and objects", function() { it("should be able to get a Bucket instance from the base client", function(done) { var bucket = riak.bucket('some_bucket'); bucket.should.have.property('constructor'); bucket.constructor.should.have.property('name', 'Bucket'); bucket.name.should.equal('some_bucket'); done(); }); it("should be able to fetch props from Riak", function(done) { var bucket = riak._bucket.get('nodiak_test'); bucket.getProps(function(err, props) { should.not.exist(err); props.should.have.property('name'); props.name.should.equal('nodiak_test'); bucket.props.name.should.eql(props.name); done(); }); }); it("should be able to save its props to Riak", function(done) { var bucket = riak._bucket.get('nodiak_test'); bucket.props.last_write_wins = true; bucket.saveProps(function(err, saved) { should.not.exist(err); bucket.props.should.have.property('last_write_wins', true); bucket.props.last_write_wins.should.equal(saved.props.last_write_wins); bucket.props.last_write_wins = false; bucket.saveProps(function(err, saved) { should.not.exist(err); bucket.props.should.have.property('last_write_wins', false); bucket.props.last_write_wins.should.equal(saved.props.last_write_wins); done(); }); }); }); it("should be able to get RObject w/ siblings fetched as async requests for vtags", function(done) { var bucket = riak.bucket('siblings_test'); bucket.objects.get('this_ol_key', function(err, obj) { should.not.exist(err); obj.constructor.name.should.eql('RObject'); obj.should.have.property('siblings'); obj.siblings.should.be.an.instanceOf(Array).with.lengthOf(2); obj.siblings[0].constructor.name.should.eql('RObject'); }); done(); }); it("should be able to get RObject w/ siblings as one request for multipart/mixed objects", function(done) { var bucket = riak.bucket('siblings_test'); bucket.getSiblingsSync = true; bucket.objects.get('this_ol_key', function(err, obj) { should.not.exist(err); obj.constructor.name.should.eql('RObject'); obj.should.have.property('siblings'); obj.siblings.should.be.an.instanceOf(Array).with.lengthOf(2); obj.siblings[0].constructor.name.should.eql('RObject'); }); done(); }); }); describe("Using the 'Bucket' class to perform 2i queries", function() { if(twoi_enabled) { it("should be able to perform ranged 2i searches, stream results as keys", function(done) { var all_results = []; riak.bucket('nodiak_test').search.twoi([0,10000], 'numbers').stream() .on('data', function(key) { key.constructor.name.should.eql('String'); all_results.push(key); }) .on('error', function(err) { should.not.exist(err); }) .on('end', function() { all_results.length.should.eql(NUM_OBJECTS); done(); }); }); it("should be able to perform ranged 2i searches, results as keys", function(done) { riak.bucket('nodiak_test').search.twoi([0,10000], 'numbers', function(err, response) { should.not.exist(err); response.should.be.an.instanceOf(Array).with.lengthOf(NUM_OBJECTS); response[0].constructor.name.should.eql('String'); done(); }); }); it("should be able to perform exact match 2i searches, stream results as keys", function(done) { var all_results = []; riak.bucket('nodiak_test').search.twoi('that', 'strings').stream() .on('data', function(key) { key.constructor.name.should.eql('String'); all_results.push(key); }) .on('error', function(err) { should.not.exist(err); }) .on('end', function() { all_results.length.should.eql(NUM_OBJECTS); done(); }); }); it("should be able to perform exact match 2i searches, results as keys", function(done) { riak.bucket('nodiak_test').search.twoi('that', 'strings', function(err, response) { should.not.exist(err); response.should.be.an.instanceOf(Array).with.lengthOf(NUM_OBJECTS); response[0].constructor.name.should.eql('String'); done(); }); }); } else { return; } }); describe("Using the 'Bucket' class to perform Riak Search queries", function() { if(search_enabled) { it("should be able to perform Solr search on indexed bucket, stream results as RObjects", function(done) { var all_results = []; riak.bucket('nodiak_test').search.solr({ q: 'field1:been' }).stream() .on('data', function(r_obj) { r_obj.constructor.name.should.eql('RObject'); all_results.push(r_obj); }) .on('error', function(err) { should.not.exist(err); }) .on('end', function() { all_results.length.should.eql(10); done(); }); }); it("should be able to perform Solr search on indexed bucket", function(done) { riak.bucket('nodiak_test').search.solr({ q: 'field1:been' }, function(err, results) { should.not.exist(err); results.should.have.property('response'); results.response.should.have.property('numFound'); results.response.numFound.should.eql(NUM_OBJECTS); results.response.should.have.property('docs'); results.response.docs.should.be.an.instanceOf(Array).with.lengthOf(10); results.response.docs[0].constructor.name.should.eql('Object'); done(); }); }); } else { return; } }); describe("Using the 'RObject' class to perform fetch/save/delete operations", function() { var obj = riak.bucket('nodiak_test').object.new('1'); it("should be able to hydrate an uninitialized RObject from Riak", function(done) { obj.fetch(function(err, result) { should.not.exist(err); done(); }); }); it("should be able to save a hydrated RObject to Riak", function(done) { obj.save(function(err, result) { should.not.exist(err); done(); }); }); it("should be able to delete a hydrate a RObject from Riak", function(done) { obj.delete(function(err, result) { should.not.exist(err); done(); }); }); }); describe("Using the 'Counter' class to perform CRDT Counter operations", function() { it("should be able to add to a counter", function(done) { riak.counter('siblings_test', 'the_count').add(4, function(err, response) { should.not.exist(err); done(); }); }); it("should be able to subtract from a counter", function(done) { riak.counter('siblings_test', 'the_count').subtract(2, function(err, response) { should.not.exist(err); done(); }); }); it("should be able to get the value of a counter", function(done) { riak.counter('siblings_test', 'the_count').value(function(err, response) { should.not.exist(err); var type = typeof(response); type.should.eql('number'); response.should.eql(2); done(); }); }); }); after(function(done) { // teardown pre-test setup. this.timeout(TIMEOUT); function delete_all(done) { async.parallel([ function(next) { var bucket = riak.bucket('nodiak_test'); bucket.objects.all(function(err, r_objs) { bucket.objects.delete(r_objs, function(err, result) { next(null, result); }); }); }, function(next) { var bucket = riak.bucket('siblings_test'); bucket.objects.all(function(err, r_objs) { bucket.objects.delete(r_objs, function(err, result) { next(null, result); }); }); } ], function(err, results){ if(err) throw err; done(); }); } delete_all(done); }); });