diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..9bf7d78 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,3 @@ +{ + "es5": true +} diff --git a/.travis.yml b/.travis.yml index c9d3322..cb9e2e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,13 @@ language: node_js +env: + - RIAK_VERSION=default + - RIAK_VERSION=2.0.0pre5 RIAK_RELEASE=2.0 +before_install: + - ./install_riak.sh node_js: - - "0.8" - "0.10" services: - riak script: - make test -branches: - only: - - master + diff --git a/Makefile b/Makefile index 8b4a40f..6040d9f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,12 @@ -MOCHA_OPTS=-t 20000 test/*-test.js +MOCHA_OPTS = -t 20000 REPORTER = spec +ifeq ($(RIAK_VERSION),default) +TESTS = test/*-test.js test/1.4-specific/*-test.js +else +TESTS = test/*-test.js test/2.0-specific/*-test.js +endif + check: test test: test-unit @@ -8,6 +14,7 @@ test: test-unit test-unit: @NODE_ENV=test ./node_modules/.bin/mocha \ --reporter $(REPORTER) \ - $(MOCHA_OPTS) + $(MOCHA_OPTS) \ + $(TESTS) -.PHONY: test \ No newline at end of file +.PHONY: test diff --git a/install_riak.sh b/install_riak.sh new file mode 100755 index 0000000..6520eb3 --- /dev/null +++ b/install_riak.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +if [ $RIAK_VERSION != "default" ] +then + echo "Stopping Riak service" + sudo service riak stop + sudo apt-get purge riak + riak_file="riak_$RIAK_VERSION-1_amd64.deb" + cd /tmp + echo "Downloading Riak $RIAK_VERSION" + wget http://s3.amazonaws.com/downloads.basho.com/riak/$RIAK_RELEASE/$RIAK_VERSION/ubuntu/precise/$riak_file + echo "Installing Riak $RIAK_VERSION" + sudo dpkg -i $riak_file + echo "Enabling Yokozuna" + sudo sed -i 's/yokozuna = off/yokozuna = on/' /etc/riak/riak.conf + echo "Use leveldb as the backend" + sudo sed -i 's/storage_backend = bitcask/storage_backend = leveldb/' /etc/riak/riak.conf + echo "Set ulimit" + sudo sh -c 'echo "ulimit -n 65536" > /etc/default/riak' + echo "Starting Riak..." + sudo service riak start +fi diff --git a/lib/http-client.js b/lib/http-client.js index a7c6fa5..99a7d32 100644 --- a/lib/http-client.js +++ b/lib/http-client.js @@ -10,6 +10,7 @@ var http = require('http'), HttpMeta = require('./http-meta'), HttpRequest = require('./http-request'), HttpSearchClient = require('./http-search-client'), + HttpYokozunaClient = require('./http-yokozuna-client'), Mapper = require('./mapper'), HttpMapReduceClient = require('./http-mapreduce-client'), Pool = require('poolee'); @@ -29,6 +30,7 @@ var HttpClient = function HttpClient(options) { this._execute(meta) }.bind(this); this.search = new HttpSearchClient(this._defaults, execute); + this.yokozuna = new HttpYokozunaClient(this._defaults, execute); this.mapreduce = new HttpMapReduceClient(this._defaults, execute); var pool = this._defaults.pool; if (pool) { @@ -357,14 +359,18 @@ HttpClient.prototype.query = function(bucket, query, options, callback) { end = value[1]; value = value[0]; } - - var type = typeof value == 'number' ? 'int' : 'bin', - key = "index/" + field + "_" + type + "/" + encodeURIComponent(value); + + + var typedIndex = field != "$bucket" && field != "$key"; + var key = "index/" + field + ( typedIndex? ( "_" + (typeof value == 'number' ? 'int' : 'bin') ): "" ) + "/" + encodeURIComponent(value); if (end) key += "/" + encodeURIComponent(end); this.get(bucket, key, meta, function(err, data, _meta) { + var continuation = data && data.continuation; data = data ? data.keys : undefined; + if ( continuation && data ) + data.continuation = continuation; meta.callback(err, data, _meta); }); diff --git a/lib/http-meta.js b/lib/http-meta.js index e80d10c..7c12abb 100644 --- a/lib/http-meta.js +++ b/lib/http-meta.js @@ -38,9 +38,15 @@ HttpMeta.prototype.loadResponse = function(response) { this.links = linkUtils.stringToLinks(response.client._httpMessage._headers.link); } + // 2ii + var indices = indexUtils.extract(headers); + if (indices) { + this.index = indices; + } + // etag -- replace any quotes in the string if (headers.etag) this.etag = headers.etag.replace(/"/g, ''); - + // location if (headers.location) { var match = headers.location.match(/^\/([^\/]+)(?:\/([^\/]+))?\/([^\/]+)$/); @@ -222,7 +228,10 @@ HttpMeta.queryProperties = [ 'vtag', 'returnbody', 'chunked', - 'buckets' + 'buckets', + 'max_results', + 'return_terms', + 'continuation', ] module.exports = HttpMeta; @@ -247,6 +256,49 @@ var _encodeUri = function(component, encodeUri) { return encodeURIComponent(component.replace(/\+/g, "%20")); } +var indexUtils = { + Matcher: /x-riak-index-(.*)_(.*)/, + + /** + * Parses indices from a response + * @param {Object} headers + * @return {Object} with indices in `Meta` representation. + * @api private + */ + extract: function(headers) { + var rawIndices; + if (rawIndices = indexUtils.propsAndMatchesByRegex(indexUtils.Matcher, headers)) { + var indices = {}; + rawIndices.forEach(function(index) { + var name = index.match[1]; + var type = index.match[2]; + var value = index.value; + if (type == "int") value = parseInt(value); + indices[name] = value; + }) + return indices; + } else { + return false; + } + }, + + propsAndMatchesByRegex: function(r, obj) { + var m; + var key; + var res = []; + for (key in obj) { + if (m = r.exec(key)) { + res.push({ match: m, value: obj[key] }); + } + } + if (res.length > 0) { + return res; + } else { + return false; + } + } +} + var linkUtils = { /** diff --git a/lib/http-request.js b/lib/http-request.js index e2e05a1..2375e90 100644 --- a/lib/http-request.js +++ b/lib/http-request.js @@ -60,6 +60,8 @@ HttpRequest.prototype.execute = function() { this.client.emit('riak.request.start', this.event) + + var request = httpClient.request(meta, function(err, response) { var normalstatus = [200,201,204,300,304]; if(self.event.method == 'delete') normalstatus.push(404); @@ -236,7 +238,13 @@ HttpRequest.prototype.handleRequestEnd = function(buffer) { // deal with errors if (this.meta.statusCode >= 400) { var err = new Error(); - err.message = buffer && buffer.toString().trim(); + if ( buffer ){ + if ( Array.isArray( buffer ) || buffer.length && Object.prototype.toString.call(buffer[0]) == '[object Number]' ) + for ( var i = 0; i< buffer.length; i++ ) + err.message += String.fromCharCode(buffer[i]) + else + err.message = ( typeof buffer == "string" ? buffer : JSON.stringify( buffer ) ).trim(); + } err.statusCode = this.meta.statusCode; buffer = err; } diff --git a/lib/http-search-client.js b/lib/http-search-client.js index 1c4ca9b..a0b54fe 100644 --- a/lib/http-search-client.js +++ b/lib/http-search-client.js @@ -12,7 +12,7 @@ var XML = require('xml'), */ var HttpSearchClient = function HttpSearchClient(options, callback) { this._execute = callback; - this._defaults = {resource: 'solr', method: 'get'}; + this._defaults = {resource: 'search/query', method: 'get'}; // need to add the options to the defaults object so as to not overwrite them // for the calling http-client @@ -78,7 +78,7 @@ HttpSearchClient.prototype.remove = function(index, documents, callback) { * @param {Function} callback(err, data, meta */ HttpSearchClient.prototype.find = function(index, query, options, callback) { - var meta = new HttpSearchMeta(this._defaults, options, {callback: callback, index: index, operation: 'select', q: query, wt: 'json', }); + var meta = new HttpSearchMeta(this._defaults, options, {callback: callback, index: index, operation: '', q: query, wt: 'json' }); this._run(meta); } diff --git a/lib/http-search-meta.js b/lib/http-search-meta.js index 1538566..236f80e 100644 --- a/lib/http-search-meta.js +++ b/lib/http-search-meta.js @@ -53,10 +53,16 @@ Object.defineProperty(HttpSearchMeta.prototype, 'path', { }.bind(this)); var queryString = querystring.stringify(queryParameters), - operation = "/" + this.operation, index = "/" + this.index, + operation, qs = queryString ? "?" + queryString : ''; + if(this.operation) { + operation = "/" + this.operation; + } else { + operation = ""; + } + return "/" + this.resource + index + operation + qs; }, enumerable: true @@ -84,13 +90,26 @@ var responseMappings = { HttpSearchMeta.queryProperties = [ 'q', + 'fq', 'start', 'rows', 'wt', 'sort', 'presort', 'filter', - 'fl' + 'fl', + 'df', + 'facet', //TODO: SOLR have huge ammount of options. nightmare to specify it all + 'facet.field', + 'facet.query', + 'stats', + 'stats.field', + 'group', + 'group.field', + 'group.main', + 'group.sort', + 'group.limit' + ]; module.exports = HttpSearchMeta; diff --git a/lib/http-yokozuna-client.js b/lib/http-yokozuna-client.js new file mode 100644 index 0000000..905d6fe --- /dev/null +++ b/lib/http-yokozuna-client.js @@ -0,0 +1,109 @@ +var HttpSearchMeta = require('./http-search-meta'); + +/** + * Initialize a Riak Yokozuna Search client. + * Expects only a callback that's call to execute a search query, e.g. + * in the HttpClient, therefore doesn't have any coupling to the + * underlying transport. + * + * @param {Object} options + * @param {Function} callback(meta) + */ +var HttpYokozunaClient = function HttpYokozunaClient(options, callback) { + this._execute = callback; + this._defaults = {resource: 'search/query', method: 'get'}; + + // need to add the options to the defaults object so as to not overwrite them + // for the calling http-client + + if (options.host) { + this._defaults.host = options.host; + } + + if (options.port) { + this._defaults.port = options.port; + } + + if (options.client) { + this._defaults.client = options.client; + } +}; + +/** + * Add a search index to yokozuna + * + * @param {String} index + * @param {String} schema + * @param {Function} callback(err) + * + * @api public + */ +HttpYokozunaClient.prototype.createIndex = function(index, schema, callback) { + if (typeof schema === 'function') { + callback = schema; + schema = null; + schema = null; + } + + var meta = new HttpSearchMeta(this._defaults, {resource: 'yz'}, {method: 'put', callback: callback, index: 'index', operation: index}); + + this._execute(meta); +}; + +/** + * Remove a search index from yokozuna + * + * @param {String} index + * @param {String} schema + * @param {Function} callback(err) + * + * @api public + */ +HttpYokozunaClient.prototype.getIndex = function(index, callback) { + var meta = new HttpSearchMeta(this._defaults, {resource: 'yz'}, {callback: callback, index: 'index', operation: index}); + + this._execute(meta); +}; + +/** + * Find a set of documents in Riak Yokozuna in the specified index. + * The query must be a valid query string as described in the Riak Yokozuna + * documentation. + * + * The options can include all query options supported by Riak Yokozuna. + * + * @param {String} index + * @param {String} query + * @param {Object} options + * @param {Function} callback(err, data, meta + */ +HttpYokozunaClient.prototype.find = function(index, query, options, callback) { + var meta = new HttpSearchMeta(this._defaults, options, {callback: callback, index: index, q: query, wt: 'json' }); + this._run(meta); +}; + + +HttpYokozunaClient.prototype._run = function(meta) { + var callback = meta.callback; + meta.callback = function(err, data, meta) { + if (err) { + return callback(err, data, meta); + } + + try { + data = JSON.parse(data.toString()); + } catch (e) { + return callback(e, e, meta); + } + if ( !data.response) data.response = {}; + + data.response.facet_counts = data.facet_counts; + data.response.stats = data.stats; + data.response.grouped = data.grouped; + + callback(err, data.response, meta); + }; + this._execute(meta); +}; + +module.exports = HttpYokozunaClient; diff --git a/lib/meta.js b/lib/meta.js index d8ef892..70eb4d1 100644 --- a/lib/meta.js +++ b/lib/meta.js @@ -108,8 +108,9 @@ Meta.prototype.serialize = function(data) { * * @api public */ + Meta.prototype.loadData = function(data) { - if (data) data = this.serialize(data); + if (data!=null) data = this.serialize(data); // cannot check like if(data), Riak 2.0 accept numeric body for counters, and its failed with counter value 0 this.data = data; } @@ -204,6 +205,7 @@ Meta.keywords = [ 'callback', 'method', 'q', + 'fq', 'start', 'rows', 'wt', @@ -211,12 +213,25 @@ Meta.keywords = [ 'presort', 'filter', 'fl', - 'noError404', + 'df', + 'facet', //TODO: SOLR have huge ammount of options. nightmare to specify it all + 'facet.field', + 'facet.query', + 'group', + 'group.field', + 'group.main', + 'group.sort', + 'group.limit', + 'stats', + 'stats.field', 'date', 'operation', 'pool', '_pool', - 'agent' + 'agent', + 'continuation', + 'max_results', + 'return_terms' ] module.exports = Meta; diff --git a/test/http-client-search-test.js b/test/1.4-specific/http-client-search-test.js similarity index 74% rename from test/http-client-search-test.js rename to test/1.4-specific/http-client-search-test.js index 006e257..b42d5bb 100644 --- a/test/http-client-search-test.js +++ b/test/1.4-specific/http-client-search-test.js @@ -1,5 +1,6 @@ -var HttpClient = require('../lib/http-client'), - should = require('should'); +var HttpClient = require('../../lib/http-client'), +should = require('should'), +helpers = require('./../test_helper'); var db, events = [], listener, bucket; @@ -9,8 +10,16 @@ describe('http-client-search-tests', function() { // Ensure unit tests don't collide with pre-existing buckets bucket = 'users-riak-js-tests'; + var obj = { email: 'test-search@gmail.com', name: 'Testy Test for Riak Search' }; + db.saveBucket(bucket, {search: true}, function(error) { + db.save(bucket, 'test-search@gmail.com', obj, function(err, data, meta) { + done(); + }); + }); + }); - done(); + after(function (done) { + helpers.cleanupBucket(bucket, done); }); it('Save the properties of a bucket', function(done) { @@ -32,16 +41,16 @@ describe('http-client-search-tests', function() { }); it('Save a document to a search enabled bucket', function(done) { - db.save(bucket, 'test-search@gmail.com', + db.save(bucket, 'test-search2@gmail.com', { - email: 'test-search@gmail.com', - name: 'Testy Test for Riak Search' + email: 'test-search2@gmail.com', + name: 'Testy Test 2 for Riak Search' }, function(err, data, meta) { should.not.exist(err); should.not.exist(data); should.exist(meta); - meta.key.should.equal('test-search@gmail.com'); + meta.key.should.equal('test-search2@gmail.com'); meta.statusCode.should.equal(204); done(); @@ -50,15 +59,15 @@ describe('http-client-search-tests', function() { it('Map/Reduce with search', function(done) { db.mapreduce.search(bucket, 'email:test-search@gmail.com') - .map('Riak.mapValuesJson') - .run(function(err, data) { - should.not.exist(err); - should.exist(data); - data.length.should.equal(1); - data[0].email.should.equal('test-search@gmail.com'); + .map('Riak.mapValuesJson') + .run(function(err, data) { + should.not.exist(err); + should.exist(data); + data.length.should.equal(1); + data[0].email.should.equal('test-search@gmail.com'); - done(); - }); + done(); + }); }); it('Searching via Solr interface', function(done) { @@ -79,7 +88,7 @@ describe('http-client-search-tests', function() { should.not.exist(err); done(); - }); + }); }); it('Find added document', function(done) { @@ -108,11 +117,4 @@ describe('http-client-search-tests', function() { }); }); }); - - after(function(done) { - db.remove(bucket, 'test-search@gmail.com'); - db.remove(bucket, 'test-add-search@gmail.com'); - - done(); - }); }); diff --git a/test/1.4-specific/protocol-buffers-search-test.js b/test/1.4-specific/protocol-buffers-search-test.js new file mode 100644 index 0000000..8adf24d --- /dev/null +++ b/test/1.4-specific/protocol-buffers-search-test.js @@ -0,0 +1,35 @@ +var ProtocolBuffersClient = require('../../lib/protocol-buffers-client'), + HttpClient = require('../../lib/http-client'), + should = require('should'), + helpers = require('./../test_helper'); + +var db, http, bucket; + +describe('protocol-buffers-search-client', function() { + before(function(done) { + db = new ProtocolBuffersClient(); + http = new HttpClient(); + bucket = 'pb-search'; + http.saveBucket(bucket, {search: true}, function(error) { + db.save(bucket, 'roidrage', {name: "Mathias Meyer"}, {content_type: "application/json"}, function(error, data) { + done(); + }); + }); + }); + + after(function(done) { + helpers.cleanupBucket(bucket, function() { + db.end(); + done(); + }); + }); + + + it('Finds documents via search', function(done) { + db.search.find('pb-search', 'name:Mathias*', function(error, data) { + data.docs[0].fields.should.include({name: 'Mathias Meyer'}); + data.docs[0].fields.should.include({id: 'roidrage'}); + done(); + }); + }); +}); diff --git a/test/2.0-specific/http-yokozuna-client-test.js b/test/2.0-specific/http-yokozuna-client-test.js new file mode 100644 index 0000000..52d375d --- /dev/null +++ b/test/2.0-specific/http-yokozuna-client-test.js @@ -0,0 +1,70 @@ +var HttpClient = require('../../lib/http-client'), + should = require('should'), + helpers = require('./../test_helper'); + +var db, bucket, yzIndex; + +describe('http-client-solr-tests', function() { + before(function(done) { + db = new HttpClient({ port: 8098 }); + + // Ensure unit tests don't collide with pre-existing buckets + bucket = 'users-riak-js-tests-solr'; + yzIndex = 'riak-js-yz-index-test'; + var obj = { + email_s: 'test-search@gmail.com', + name: 'Testy Test for Riak Search' + }; + db.yokozuna.createIndex(yzIndex, function(err) { + db.saveBucket(bucket, { yz_index: yzIndex }, function (err) { + setTimeout(function () { + db.save(bucket, 'test-search@gmail.com', obj, function(err, data, meta) { + setTimeout(function () { + done(); + }, 4000); + }); + }, 4000); + }); + }); + }); + + after(function (done) { + helpers.cleanupBucket(bucket, done); + }); + + it('creates an index', function (done) { + db.yokozuna.getIndex(yzIndex, function (err, data) { + should.not.exist(err); + data.name.should.equal(yzIndex); + data.schema.should.equal('_yz_default'); + done(); + }); + }); + + it('saves the index bucket property', function (done) { + db.saveBucket(bucket, { yz_index: yzIndex }, function (err) { + should.not.exist(err); + + done(); + }); + }); + + it('gets the index bucket property', function (done) { + db.getBucket(bucket, function (err, props) { + props.yz_index.should.equal(yzIndex); + done(); + }); + }); + + + it('Searching via Solr interface', function (done) { + db.yokozuna.find(yzIndex, 'email_s:test-search@gmail.com', function(err, data) { + should.not.exist(err); + should.exist(data); + + data.docs[0].email_s.should.equal('test-search@gmail.com'); + + done(); + }); + }); +}); diff --git a/test/http-client-instrumentation-test.js b/test/http-client-instrumentation-test.js index f1dee82..9f44a4f 100644 --- a/test/http-client-instrumentation-test.js +++ b/test/http-client-instrumentation-test.js @@ -1,7 +1,8 @@ var HttpClient = require('../lib/http-client'), HttpMeta = require('../lib/http-meta'), should = require('should'), - util = require('util'); + util = require('util'), + helpers = require('./test_helper.js'); var db, events = [], listener, bucket; @@ -11,16 +12,16 @@ describe('http-client-instrumentation-tests', function() { listener = { "riak.request.start": function(event) { - events.push(event) + events.push(event); }, "riak.request.response": function(event) { - events.push(event) + events.push(event); }, "riak.request.finish": function(event) { - events.push(event) + events.push(event); }, "riak.request.end": function(event) { - events.push(event) + events.push(event); } }; @@ -32,6 +33,10 @@ describe('http-client-instrumentation-tests', function() { done(); }); + after(function (done) { + helpers.cleanupBucket(bucket, done); + }); + it('Create an object', function(done) { db.save(bucket, 'someone@gmail.com', {name: 'Someone Else'}, function(err, doc, meta) { diff --git a/test/http-client-luwak-test.js b/test/http-client-luwak-test.js deleted file mode 100644 index 6668e83..0000000 --- a/test/http-client-luwak-test.js +++ /dev/null @@ -1,106 +0,0 @@ -var HttpClient = require('../lib/http-client'), - fs = require('fs'), - should = require('should'); - -var db, events = [], listener, - filename = __dirname + '/fixtures/cat.jpg', - filename2 = __dirname + '/fixtures/cat2.jpg', - filename3 = __dirname + '/fixtures/cat3.jpg', - image; - -//describe('http-client-luwak-tests', function() { -// before(function(done) { -// db = new HttpClient({ port: 8098 }); -// -// image = fs.readFileSync(filename); -// -// // Ensure unit tests don't collide with pre-existing buckets -// bucketName = 'users-riak-js-tests'; -// -// done(); -// }); -// -// it('Save a file from a buffer', function(done) { -// db.saveFile('cat2', image, -// { contentType: 'image/jpeg' }, -// function(err, data, meta) { -// should.not.exist(err); -// should.not.exist(data); -// should.exist(meta); -// meta.key.should.equal('cat2'); -// meta.statusCode.should.equal(204); -// -// // race condition - wait for riak -// setTimeout(function() { -// db.getFile('cat2', function(data) { -// data.should.be.an.instanceof(Buffer); -// -// // TODO compare data to image -// fs.writeFileSync(filename2, data); -// -// done(); -// }); -// }, 500); -// }); -// }); -// -// it('Remove a file', function(done) { -// db.removeFile('cat2', function(data) { -// // TODO what are we supposed to be checking here? -// done(); -// }); -// }); -// -// it('Save a file from a stream', function(done) { -// db.saveFile('cat3', -// fs.createReadStream(filename), -// { contentType: 'image/jpeg' }, -// function(err, data, meta) { -// should.not.exist(err); -// should.not.exist(data); -// should.exist(meta); -// meta.statusCode.should.equal(204); -// -// // race condition - wait for riak -// setTimeout(function() { -// db.getFile('cat3', { stream: true }, function(stream) { -// -// should.exist(stream); -// var out = fs.createWriteStream(filename3); -// stream.pipe(out); -// -// out.on('close', function() { -// done(); -// }); -// }); -// }, 500); -// }); -// }); -// -// // TODO test luwak with returnbody=true -// -// it('Remove the file stream', function(done) { -// db.removeFile('cat3', function(data) { -// // TODO what are we supposed to be checking here? -// done(); -// }); -// }); -// -// it('Buffers are equal', function(done) { -// var buf2 = fs.readFileSync(filename2), -// buf3 = fs.readFileSync(filename3); -// -// should.exist(buf2); -// should.exist(buf3); -// -// buf2.length.should.equal(buf3.length); -// -// // TODO should there be a better comparison here? -// -// // cleanup -// fs.unlinkSync(filename2); -// fs.unlinkSync(filename3); -// -// done(); -// }); -//}); diff --git a/test/http-client-pool-test.js b/test/http-client-pool-test.js index d41aef1..851d634 100644 --- a/test/http-client-pool-test.js +++ b/test/http-client-pool-test.js @@ -1,11 +1,13 @@ var HttpClient = require('../lib/http-client'), HttpMeta = require('../lib/http-meta'), - should = require('should'); + should = require('should'), + helpers = require('./test_helper.js'); -var db; +var db, bucket; describe('http-client-pool', function () { before(function(done) { + bucket = 'languages'; db = new HttpClient({ pool: { servers: [ 'localhost:8098', @@ -16,9 +18,13 @@ describe('http-client-pool', function () { done(); }); + after(function (done) { + helpers.cleanupBucket(bucket, done); + }); + it('Creates an object', function(done) { - db.save('languages', 'erlang', {type: 'functional'}, function(err) { - db.get('languages', 'erlang', function(err, data) { + db.save(bucket, 'erlang', {type: 'functional'}, function(err) { + db.get(bucket, 'erlang', function(err, data) { should.not.exist(err); data.type.should.equal('functional'); done(); @@ -27,7 +33,7 @@ describe('http-client-pool', function () { }); it('Fetches the streamed object', function(done) { - db.get('languages', 'erlang', {stream: true}, function(err, response, meta) { + db.get(bucket, 'erlang', {stream: true}, function(err, response, meta) { should.not.exist(err); response.on('data', function(data) { data = JSON.parse(String(data)); @@ -35,6 +41,6 @@ describe('http-client-pool', function () { done(); }); }); - }) -}) + }); +}); diff --git a/test/http-client-test.js b/test/http-client-test.js index 080dea7..87bb861 100644 --- a/test/http-client-test.js +++ b/test/http-client-test.js @@ -2,6 +2,7 @@ var HttpClient = require('../lib/http-client'), HttpMeta = require('../lib/http-meta'), async = require('async'), util = require('util'), + helpers = require('./test_helper'), should = require('should'); var db, db2, many = [], bucket; @@ -21,6 +22,12 @@ describe('http-client-tests', function() { done(); }); + after(function (done) { + helpers.cleanupBucket(bucket + '-keys', function () { + helpers.cleanupBucket(bucket, done); + }); + }); + it('Save with returnbody', function(done) { db.save(bucket, 'test-returnbody@gmail.com', { email: 'test@gmail.com', @@ -112,6 +119,20 @@ describe('http-client-tests', function() { }); }); + it('Fetching a document with 2ii', function(done) { + var index = { type: 'dude', number: 1 }; + db.save(bucket, 'indexed_dude', + { name: 'Indexed Dude' }, + { index: index }, + function(_err, _data, _meta) { + db.get(bucket, 'indexed_dude', function(err, data, meta) { + should.exist(meta.index); + meta.index.should.eql(index); + done(); + }) + }) + }) + it('Fetching a document with links', function(done) { db.get(bucket, 'other@gmail.com', function(err, data, meta) { should.not.exist(err); @@ -131,7 +152,7 @@ describe('http-client-tests', function() { { bucket: bucket, key: 'test@gmail.com' } ]}, function(err, data, meta) { should.exist(meta.links); - meta.links.should.have.length(1) + meta.links.should.have.length(1); done(); }); @@ -265,7 +286,7 @@ describe('http-client-tests', function() { }, function() { var buf = [], keys = function(keys) { - buf = buf.concat(keys) + buf = buf.concat(keys); }, end = function() { // keys come in random order, need to sort @@ -324,6 +345,45 @@ describe('http-client-tests', function() { }); }); + it('Special Secondary Indices', function (done) { + db.save(bucket, 1500, + { email: 'fran@gmail.com', age: 28 }, + function (err, data, meta) { + should.not.exist(err); + db.save(bucket, 1501, + { email: 'bob@gmail.com', age: 29 }, + function (err, data, meta) { + should.not.exist(err); + db.save(bucket, 1502, + { email: 'joe@gmail.com', age: 30 }, + function (err, data, meta) { + should.not.exist(err); + async.parallel([ + function (callback) { + db.query(bucket, { "$key": [1500, 1501] }, function (err, results) { + should.not.exist(err); + should.exist(results); + results.length.should.equal(2); + results[0].should.equal('1500'); + results[1].should.equal('1501'); + callback(); + }); + }, function (callback) { + db.query(bucket, { "$bucket": '_' }, {max_results: 2}, function (err, results) { + should.not.exist(err); + should.exist(results); + results.length.should.equal(2); + should.exist(results.continuation); + callback(); + }); + }], function () { + done(); + }); + }); + }); + }); + }); + it('Buckets is an Array', function(done) { db.buckets(function(err, buckets) { should.not.exist(err); @@ -398,7 +458,7 @@ describe('http-client-tests', function() { var CustomMeta = function() { var args = Array.prototype.slice.call(arguments); HttpMeta.apply(this, args); -} +}; util.inherits(CustomMeta, HttpMeta); @@ -406,4 +466,4 @@ CustomMeta.prototype.parse = function(data) { var result = HttpMeta.prototype.parse.call(this, data); if (result instanceof Object) result.intercepted = true; return result; -} +}; diff --git a/test/http-mapreduce-client-test.js b/test/http-mapreduce-client-test.js index 9439a84..2fa9dcb 100644 --- a/test/http-mapreduce-client-test.js +++ b/test/http-mapreduce-client-test.js @@ -1,7 +1,8 @@ var HttpClient = require('../lib/http-client'), HttpMeta = require('../lib/http-meta'), util = require('util'), - should = require('should'); + should = require('should'), + helpers = require('./test_helper'); var db, bucket; @@ -23,6 +24,10 @@ describe('http-mapreduce-client-tests', function() { }); }); + after(function (done) { + helpers.cleanupBucket(bucket, done); + }); + it('Map to an array of JSON objects', function(done) { db.mapreduce.add(bucket).map('Riak.mapValuesJson').run(function(err, data) { should.not.exist(err); diff --git a/test/protocol-buffers-client-test.js b/test/protocol-buffers-client-test.js index 7a4533b..13b7448 100644 --- a/test/protocol-buffers-client-test.js +++ b/test/protocol-buffers-client-test.js @@ -1,18 +1,24 @@ var ProtocolBuffersClient = require('../lib/protocol-buffers-client'), util = require('util'), + helpers = require('./test_helper'), should = require('should'); + var db; describe('protocol-buffers-client-tests', function() { - beforeEach(function(done) { + before(function(done) { db = new ProtocolBuffersClient(); done(); }); - afterEach(function(done) { - db.end(); - done(); + after(function(done) { + helpers.cleanupBucket('pb-users', function () { + helpers.cleanupBucket('users', function () { + db.end(); + done(); + }); + }); }); it("Saves an object", function(done) { @@ -22,10 +28,12 @@ describe('protocol-buffers-client-tests', function() { }); it('Gets an object', function(done) { - db.get('pb-users', 'user@gmail.com', function(err, data, meta) { - should.not.exist(err); - data.name.should.equal('Joe Example'); - done(); + db.save('pb-users', 'user2@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { + db.get('pb-users', 'user2@gmail.com', function(err, data, meta) { + should.not.exist(err); + data.name.should.equal('Joe Example'); + done(); + }); }); }); @@ -63,7 +71,7 @@ describe('protocol-buffers-client-tests', function() { db.getBucket('users', function(err, properties, meta) { should.exist(properties); properties.n_val.should.equal(3); - should.exist(properties.allow_mult) + should.exist(properties.allow_mult); done(); }); }); @@ -76,11 +84,11 @@ describe('protocol-buffers-client-tests', function() { done(); }); }); - }) + }); it("Pings", function(done) { db.ping(function(err, pong) { - should.exist(pong) + should.exist(pong); done(); }); }); @@ -89,7 +97,7 @@ describe('protocol-buffers-client-tests', function() { db.ping(function(err, pong) { should.not.exist(err); done(); - }) + }); }); it("Doesn't set notFound on save", function(done) { @@ -100,17 +108,19 @@ describe('protocol-buffers-client-tests', function() { }); it("Fetches keys", function(done) { - db.save('pb-users', 'user1@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { - db.save('pb-users', 'user2@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { - db.save('pb-users', 'user3@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { - db.save('pb-users', 'user4@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { - var keys = db.keys('pb-users', {keys: 'stream'}); - var result = [] - keys.on('keys', function(keys) { - result = result.concat(keys); - }).on('end', function(data) { - result.should.have.length(5); - done(); + db.save('pb-users', 'user@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { + db.save('pb-users', 'user1@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { + db.save('pb-users', 'user2@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { + db.save('pb-users', 'user3@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { + db.save('pb-users', 'user4@gmail.com', {name: 'Joe Example'}, {content_type: "application/json"}, function(data) { + var keys = db.keys('pb-users', {keys: 'stream'}); + var result = []; + keys.on('keys', function(keys) { + result = result.concat(keys); + }).on('end', function(data) { + result.should.have.length(5); + done(); + }); }); }); }); diff --git a/test/protocol-buffers-mapreduce-test.js b/test/protocol-buffers-mapreduce-test.js index 567b644..daf9493 100644 --- a/test/protocol-buffers-mapreduce-test.js +++ b/test/protocol-buffers-mapreduce-test.js @@ -1,11 +1,12 @@ var ProtocolBuffersClient = require('../lib/protocol-buffers-client'), - should = require('should'); + should = require('should'), + helpers = require('./test_helper'); var db, bucket; describe('protocol-buffers-mapreduce-client', function() { - beforeEach(function(done) { - db = new ProtocolBuffersClient(); + before(function(done) { + db = new ProtocolBuffersClient(); bucket = 'map-pb-users-riak-js-tests'; db.save(bucket, 'test@gmail.com', {name: "Sean Cribbs"}, function(err, data, meta) { @@ -15,9 +16,11 @@ describe('protocol-buffers-mapreduce-client', function() { }); }); - afterEach(function(done) { - db.end(); - done(); + after(function(done) { + helpers.cleanupBucket(bucket, function () { + db.end(); + done(); + }); }); it('Map to an array of JSON objects', function(done) { @@ -67,7 +70,7 @@ describe('protocol-buffers-mapreduce-client', function() { }).reduce('Riak.reduceLimit', 2) .run(function(err, data) { should.exist(data); - data[1].should.have.length(1) + data[1].should.have.length(1); done(); }); }); @@ -89,17 +92,17 @@ describe('protocol-buffers-mapreduce-client', function() { should.exist(err.message); should.exist(err.statusCode); done(); - }) + }); }); it('Supports chunked map/reduce', function(done) { var job = db.mapreduce.add(bucket).map('Riak.mapValuesJson').run({chunked: true}); var result = []; job.on('data', function(data) { - result.push(data) + result.push(data); }).on('end', function(data) { result.should.have.length(2); done(); }); - }) + }); }); diff --git a/test/protocol-buffers-search-test.js b/test/protocol-buffers-search-test.js deleted file mode 100644 index 9062983..0000000 --- a/test/protocol-buffers-search-test.js +++ /dev/null @@ -1,30 +0,0 @@ -var ProtocolBuffersClient = require('../lib/protocol-buffers-client'), - HttpClient = require('../lib/http-client'), - should = require('should'); - -var db, http; - -describe('protocol-buffers-search-client', function() { - beforeEach(function(done) { - db = new ProtocolBuffersClient(); - http = new HttpClient(); - http.saveBucket('pb-search', {search: true}, function(error) { - db.save('pb-search', 'roidrage', {name: "Mathias Meyer"}, {content_type: "application/json"}, function(error, data) { - done(); - }); - }) - }); - - afterEach(function(done) { - db.end(); - done(); - }); - - it('Finds documents via search', function(done) { - db.search.find('pb-search', 'name:Mathias*', function(error, data) { - data.docs[0].fields.should.include({name: 'Mathias Meyer'}) - data.docs[0].fields.should.include({id: 'roidrage'}) - done(); - }); - }); -}); diff --git a/test/test_helper.js b/test/test_helper.js new file mode 100644 index 0000000..ef195ce --- /dev/null +++ b/test/test_helper.js @@ -0,0 +1,30 @@ +var async = require('async'); +var HttpClient = require('../lib/http-client'); + +var helpers = module.exports = { + cleanupBucket: function (bucket, done) { + var db = new HttpClient({ port: 8098 }); + var allKeys = []; + var onKeys = function (keys) { + allKeys = allKeys.concat(keys); + }; + var onEnd = function () { + async.each( + allKeys, + function (key, cb) { + db.remove(bucket, key, function (err) { + cb(); + }); + }, + function (err) { + if (err) throw err; + done(); + } + ); + }; + db.keys(bucket) + .on('keys', onKeys) + .on('end', onEnd) + .start(); + } +};