Repository: basho/riak-nodejs-client (js)

Issue:
JSON parse fails when tombstone is returned [JIRA: CLIENTS-493]
I had a use case where a tombstone was returned and when setting convertToJs to true, the JSON parser fails on the buffer. This was a delete followed by a quick write. I worked around by making sure I set returnDeletedVClock to true when doing a read before the write. I will also submit a quick fix (return empty object) if you want to accept the pull request. 

Pull request description:
CLIENTS-493
Ensure that tombstones don't mess up JSON parsing.
Fix some issues with FetchPreflist command not being exported correctly.
Fixes #74.

Patch:
diff --git a/lib/client.js b/lib/client.js
index c64be87..ccbc72a 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -168,6 +168,17 @@ Client.prototype.fetchValue = function(options, callback) {
     this.cluster.execute(cmd);
 };
 
+/**
+ * See {{#crossLink "FetchPreflist"}}{{/crossLink}}
+ * @method fetchPreflist
+ * @param {Object} options The options for this operation.
+ * @param {Function} callback The callback to be executed when the operation completes.
+ */
+Client.prototype.fetchPreflist = function(options, callback) {
+    var cmd = new KV.FetchPreflist(options, callback);
+    this.cluster.execute(cmd);
+};
+
 /**
  * See {{#crossLink "ListBuckets"}}{{/crossLink}}
  * @method ListBuckets
diff --git a/lib/commands/kv.js b/lib/commands/kv.js
index 4195951..81cac6c 100644
--- a/lib/commands/kv.js
+++ b/lib/commands/kv.js
@@ -25,6 +25,7 @@ function KV() { }
 module.exports = KV;
 module.exports.DeleteValue = require('./kv/deletevalue');
 module.exports.FetchBucketProps = require('./kv/fetchbucketprops');
+module.exports.FetchPreflist = require('./kv/fetchpreflist');
 module.exports.FetchValue = require('./kv/fetchvalue');
 module.exports.ListBuckets = require('./kv/listbuckets');
 module.exports.ListKeys = require('./kv/listkeys');
diff --git a/lib/commands/kv/riakobject.js b/lib/commands/kv/riakobject.js
index 3666e28..0fc7be0 100644
--- a/lib/commands/kv/riakobject.js
+++ b/lib/commands/kv/riakobject.js
@@ -358,11 +358,17 @@ module.exports.createFromRpbContent = function(rpbContent, convertToJs) {
     
     var value = rpbContent.getValue();
     
-    // ReturnHead will only retun metadata
-    if (convertToJs && value) {
-        ro.value = JSON.parse(value.toString('utf8'));
-    } else if (value) {
-        ro.value = value.toBuffer();
+    ro.isTombstone = rpbContent.getDeleted() ? true : false;
+
+    if (ro.isTombstone) {
+        ro.value = {};
+    } else {
+        // ReturnHead will only retun metadata
+        if (convertToJs && value) {
+            ro.value = JSON.parse(value.toString('utf8')); 
+        } else if (value) {
+            ro.value = value.toBuffer();
+        }
     }
     
     if (rpbContent.getContentType()) {
@@ -379,8 +385,6 @@ module.exports.createFromRpbContent = function(rpbContent, convertToJs) {
         ro.lastModified = ((lm * 1000) + (lmu / 1000));
     }
     
-    ro.isTombstone = rpbContent.getDeleted() ? true : false;
-    
     // UserMeta
     var pbUsermeta = rpbContent.getUsermeta();
     var usermeta = new Array(pbUsermeta.length);


Test patch:
diff --git a/test/unit/client.js b/test/unit/client.js
index c3b9a0e..904d149 100644
--- a/test/unit/client.js
+++ b/test/unit/client.js
@@ -118,6 +118,11 @@ describe('Client', function() {
             assert(typeofKvStoreValue === 'function', "typeof(Riak.Commands.KV.StoreValue): " + typeofKvStoreValue);
             done();
         });
+        it('Riak.Commands.KV.FetchPreflist', function(done) {
+            var typeofKvFetchPreflist = typeof(Riak.Commands.KV.FetchPreflist);
+            assert(typeofKvFetchPreflist === 'function', "typeof(Riak.Commands.KV.FetchPreflist): " + typeofKvFetchPreflist);
+            done();
+        });
 
         it('Riak.Commands.CRDT.FetchSet', function(done) {
             var typeofCrdtFetchSet = typeof(Riak.Commands.CRDT.FetchSet);
diff --git a/test/unit/riakcluster.js b/test/unit/core/riakcluster.js
similarity index 94%
rename from test/unit/riakcluster.js
rename to test/unit/core/riakcluster.js
index 992ff9c..620e1f1 100644
--- a/test/unit/riakcluster.js
+++ b/test/unit/core/riakcluster.js
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-var RiakCluster = require('../../lib/core/riakcluster');
-var RiakNode = require('../../lib/core/riaknode');
+var RiakCluster = require('../../../lib/core/riakcluster');
+var RiakNode = require('../../../lib/core/riaknode');
 var assert = require('assert');
 
 describe('RiakCluster', function() {
diff --git a/test/unit/riakconnection.js b/test/unit/core/riakconnection.js
similarity index 92%
rename from test/unit/riakconnection.js
rename to test/unit/core/riakconnection.js
index eda4bcb..d9c742c 100644
--- a/test/unit/riakconnection.js
+++ b/test/unit/core/riakconnection.js
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-var RiakConnection = require('../../lib/core/riakconnection');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
-var responseCode = require('../../lib/protobuf/riakprotobuf').getCodeFor('RpbErrorResp');
-
-var RpbGetResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbGetResp');
-var RpbContent = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbContent');
-var rpbGetRespCode = require('../../lib/protobuf/riakprotobuf').getCodeFor('RpbGetResp');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var RiakConnection = require('../../../lib/core/riakconnection');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
+var responseCode = rpb.getCodeFor('RpbErrorResp');
+var RpbGetResp = rpb.getProtoFor('RpbGetResp');
+var RpbContent = rpb.getProtoFor('RpbContent');
+var rpbGetRespCode = rpb.getCodeFor('RpbGetResp');
 
 var assert = require('assert');
 var logger = require('winston');
diff --git a/test/unit/riaknode.js b/test/unit/core/riaknode.js
similarity index 99%
rename from test/unit/riaknode.js
rename to test/unit/core/riaknode.js
index 0067f6c..2b4ded3 100644
--- a/test/unit/riaknode.js
+++ b/test/unit/core/riaknode.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-var RiakNode = require('../../lib/core/riaknode');
+var RiakNode = require('../../../lib/core/riaknode');
 var assert = require('assert');
 var joi = require('joi');
 var fs = require('fs');
diff --git a/test/unit/deletevalue.js b/test/unit/kv/deletevalue.js
similarity index 93%
rename from test/unit/deletevalue.js
rename to test/unit/kv/deletevalue.js
index 4964471..eec2c8d 100644
--- a/test/unit/deletevalue.js
+++ b/test/unit/kv/deletevalue.js
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-var DeleteValue = require('../../lib/commands/kv/deletevalue');
-var RpbDelResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbDelResp');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var DeleteValue = require('../../../lib/commands/kv/deletevalue');
+var RpbDelResp = rpb.getProtoFor('RpbDelResp');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
 
 var assert = require('assert');
 
@@ -101,4 +102,4 @@ describe('DeleteValue', function() {
         });
         
     });
-});
\ No newline at end of file
+});
diff --git a/test/unit/fetchbucketprops.js b/test/unit/kv/fetchbucketprops.js
similarity index 91%
rename from test/unit/fetchbucketprops.js
rename to test/unit/kv/fetchbucketprops.js
index 96b9c22..920d07c 100644
--- a/test/unit/fetchbucketprops.js
+++ b/test/unit/kv/fetchbucketprops.js
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-var FetchBucketProps = require('../../lib/commands/kv/fetchbucketprops');
-var RpbGetBucketResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbGetBucketResp');
-var RpbBucketProps = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbBucketProps');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
-var RpbCommitHook = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbCommitHook');
-var RpbModFun = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbModFun');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var FetchBucketProps = require('../../../lib/commands/kv/fetchbucketprops');
+var RpbGetBucketResp = rpb.getProtoFor('RpbGetBucketResp');
+var RpbBucketProps = rpb.getProtoFor('RpbBucketProps');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
+var RpbCommitHook = rpb.getProtoFor('RpbCommitHook');
+var RpbModFun = rpb.getProtoFor('RpbModFun');
 var assert = require('assert');
 
 
@@ -157,4 +158,4 @@ describe('FetchBucketProps', function() {
        });
         
     });
-});
\ No newline at end of file
+});
diff --git a/test/unit/fetchpreflist.js b/test/unit/kv/fetchpreflist.js
similarity index 89%
rename from test/unit/fetchpreflist.js
rename to test/unit/kv/fetchpreflist.js
index 2bf5d69..71efdee 100644
--- a/test/unit/fetchpreflist.js
+++ b/test/unit/kv/fetchpreflist.js
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-var FetchPreflist = require('../../lib/commands/kv/fetchpreflist');
-var rpbuf = require('../../lib/protobuf/riakprotobuf');
-var RpbGetBucketKeyPreflistReq = rpbuf.getProtoFor('RpbGetBucketKeyPreflistReq');
-var RpbGetBucketKeyPreflistResp = rpbuf.getProtoFor('RpbGetBucketKeyPreflistResp');
-var RpbBucketKeyPreflistItem = rpbuf.getProtoFor('RpbBucketKeyPreflistItem');
-var RpbErrorResp = rpbuf.getProtoFor('RpbErrorResp');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var FetchPreflist = require('../../../lib/commands/kv/fetchpreflist');
+var RpbGetBucketKeyPreflistReq = rpb.getProtoFor('RpbGetBucketKeyPreflistReq');
+var RpbGetBucketKeyPreflistResp = rpb.getProtoFor('RpbGetBucketKeyPreflistResp');
+var RpbBucketKeyPreflistItem = rpb.getProtoFor('RpbBucketKeyPreflistItem');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
 
 var assert = require('assert');
 
diff --git a/test/unit/fetchvalue.js b/test/unit/kv/fetchvalue.js
similarity index 92%
rename from test/unit/fetchvalue.js
rename to test/unit/kv/fetchvalue.js
index 28e518d..a6b4432 100644
--- a/test/unit/fetchvalue.js
+++ b/test/unit/kv/fetchvalue.js
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-var FetchValue = require('../../lib/commands/kv/fetchvalue');
-var RpbGetResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbGetResp');
-var RpbContent = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbContent');
-var RpbPair = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbPair');
-var RpbLink = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbLink');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var FetchValue = require('../../../lib/commands/kv/fetchvalue');
+var RpbGetResp = rpb.getProtoFor('RpbGetResp');
+var RpbContent = rpb.getProtoFor('RpbContent');
+var RpbPair = rpb.getProtoFor('RpbPair');
+var RpbLink = rpb.getProtoFor('RpbLink');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
 
 var assert = require('assert');
 
diff --git a/test/unit/listbuckets.js b/test/unit/kv/listbuckets.js
similarity index 94%
rename from test/unit/listbuckets.js
rename to test/unit/kv/listbuckets.js
index b4c3a87..e85b25f 100644
--- a/test/unit/listbuckets.js
+++ b/test/unit/kv/listbuckets.js
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-var ListBuckets = require('../../lib/commands/kv/listbuckets');
-var RpbListBucketsResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbListBucketsResp');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var ListBuckets = require('../../../lib/commands/kv/listbuckets');
+var RpbListBucketsResp = rpb.getProtoFor('RpbListBucketsResp');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
 
 var assert = require('assert');
 
diff --git a/test/unit/listkeys.js b/test/unit/kv/listkeys.js
similarity index 94%
rename from test/unit/listkeys.js
rename to test/unit/kv/listkeys.js
index 8c4e7ea..f623951 100644
--- a/test/unit/listkeys.js
+++ b/test/unit/kv/listkeys.js
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-var ListKeys = require('../../lib/commands/kv/listkeys');
-var RpbListKeysResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbListKeysResp');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var ListKeys = require('../../../lib/commands/kv/listkeys');
+var RpbListKeysResp = rpb.getProtoFor('RpbListKeysResp');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
 
 var assert = require('assert');
 
@@ -127,4 +128,4 @@ describe('ListKeys', function() {
        });
         
     });
-});
\ No newline at end of file
+});
diff --git a/test/unit/kv/riakobject.js b/test/unit/kv/riakobject.js
new file mode 100644
index 0000000..8878498
--- /dev/null
+++ b/test/unit/kv/riakobject.js
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 Basho Technologies, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var RiakObject = require('../../../lib/commands/kv/riakobject');
+var RpbContent = rpb.getProtoFor('RpbContent');
+
+var assert = require('assert');
+
+describe('RiakObject', function() {
+    it('should build correctly when convertToJs is true and isTombstone is true', function(done) {
+        var value = 'this is a value';
+
+        var rpbContent = new RpbContent();
+        rpbContent.setValue(new Buffer(value));
+        rpbContent.setDeleted(true);
+
+        var ro = RiakObject.createFromRpbContent(rpbContent, true);
+        assert(ro.isTombstone);
+        assert(JSON.stringify(ro.value) === '{}');
+        done();
+    });
+});
diff --git a/test/unit/secondaryindexquery.js b/test/unit/kv/secondaryindexquery.js
similarity index 96%
rename from test/unit/secondaryindexquery.js
rename to test/unit/kv/secondaryindexquery.js
index 6bbf840..c8ac42c 100644
--- a/test/unit/secondaryindexquery.js
+++ b/test/unit/kv/secondaryindexquery.js
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-var SecondaryIndexQuery = require('../../lib/commands/kv/secondaryindexquery');
-var RpbIndexResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbIndexResp');
-var RpbIndexReq = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbIndexReq');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
-var RpbPair = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbPair');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var SecondaryIndexQuery = require('../../../lib/commands/kv/secondaryindexquery');
+var RpbIndexResp = rpb.getProtoFor('RpbIndexResp');
+var RpbIndexReq = rpb.getProtoFor('RpbIndexReq');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
+var RpbPair = rpb.getProtoFor('RpbPair');
 var assert = require('assert');
 
 describe('SecondaryIndexQuery', function() {
diff --git a/test/unit/storebucketprops.js b/test/unit/kv/storebucketprops.js
similarity index 93%
rename from test/unit/storebucketprops.js
rename to test/unit/kv/storebucketprops.js
index c33877e..4569086 100644
--- a/test/unit/storebucketprops.js
+++ b/test/unit/kv/storebucketprops.js
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-var StoreBucketProps = require('../../lib/commands/kv/storebucketprops');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
-var RpbCommitHook = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbCommitHook');
-var RpbModFun = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbModFun');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var StoreBucketProps = require('../../../lib/commands/kv/storebucketprops');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
+var RpbCommitHook = rpb.getProtoFor('RpbCommitHook');
+var RpbModFun = rpb.getProtoFor('RpbModFun');
 var assert = require('assert');
 
 describe('StoreBucketProps', function() {
@@ -134,4 +135,4 @@ describe('StoreBucketProps', function() {
         });
     });
 });
-        
\ No newline at end of file
+        
diff --git a/test/unit/storevalue.js b/test/unit/kv/storevalue.js
similarity index 94%
rename from test/unit/storevalue.js
rename to test/unit/kv/storevalue.js
index 76db32d..ef9e131 100644
--- a/test/unit/storevalue.js
+++ b/test/unit/kv/storevalue.js
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-var StoreValue = require('../../lib/commands/kv/storevalue');
-var RiakObject = require('../../lib/commands/kv/riakobject');
-var RpbPutResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbPutResp');
-var RpbContent = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbContent');
-var RpbPair = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbPair');
-var RpbErrorResp = require('../../lib/protobuf/riakprotobuf').getProtoFor('RpbErrorResp');
+var rpb = require('../../../lib/protobuf/riakprotobuf');
+var StoreValue = require('../../../lib/commands/kv/storevalue');
+var RiakObject = require('../../../lib/commands/kv/riakobject');
+var RpbPutResp = rpb.getProtoFor('RpbPutResp');
+var RpbContent = rpb.getProtoFor('RpbContent');
+var RpbPair = rpb.getProtoFor('RpbPair');
+var RpbErrorResp = rpb.getProtoFor('RpbErrorResp');
 
 var assert = require('assert');