Patching up batch JS deficiencies and adding tests

Change-Id: Iea1e49bc2b9986c147a3c3e69c92967b54555504
diff --git a/src/nosql/batch-database.js b/src/nosql/batch-database.js
index 2329dde..ad017a1 100644
--- a/src/nosql/batch-database.js
+++ b/src/nosql/batch-database.js
@@ -81,5 +81,14 @@
  * @returns {stream} Stream of rows.
  */
 BatchDatabase.prototype.exec = function(ctx, query, cb) {
-  this._db.exec(ctx, query, cb);
+  return this._db.exec(ctx, query, cb);
+};
+
+/**
+ * Gets the ResumeMarker that points to the current end of the event log.
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+BatchDatabase.prototype.getResumeMarker = function(ctx, cb) {
+  this._db.getResumeMarker(ctx, cb);
 };
diff --git a/test/integration/test-batch.js b/test/integration/test-batch.js
index c7e0b32..7093571 100644
--- a/test/integration/test-batch.js
+++ b/test/integration/test-batch.js
@@ -14,6 +14,7 @@
 
 var testUtil = require('./util');
 var assertScanRows = testUtil.assertScanRows;
+var assertSelectRows = testUtil.assertSelectRows;
 var setupDatabase = testUtil.setupDatabase;
 var setupTable = testUtil.setupTable;
 var uniqueName = testUtil.uniqueName;
@@ -213,7 +214,7 @@
   });
 });
 
-test('readonly batches', function(t) {
+test('read-only batches', function(t) {
   setupTable(t, function(err, o) {
     if (err) {
       return t.end(err);
@@ -267,10 +268,37 @@
     function attemptBatchDeleteRow() {
       batchTable.row(key).delete(ctx, function(err) {
         assertReadOnlyBatchError(err);
-        end();
+        batch.getResumeMarker(ctx, assertBatchResumeMarker);
       });
     }
 
+    function assertBatchResumeMarker(err, r) {
+      if (err) {
+        return end(err);
+      }
+      t.ok(r, 'should return a resume marker');
+
+      assertBatchScan();
+    }
+
+    var wantRows = [{
+      key: key,
+      value: value
+    }];
+
+    function assertBatchScan() {
+      assertScanRows(ctx, batchTable, range.prefix(''), wantRows,
+        assertBatchSelect);
+    }
+
+    function assertBatchSelect(err) {
+      if (err) {
+        return end(err);
+      }
+
+      assertSelectRows(ctx, batch, batchTable, '', wantRows, end);
+    }
+
     function end(err) {
       t.error(err);
       o.teardown(t.end);
diff --git a/test/integration/util.js b/test/integration/util.js
index fecaa4b..954a92b 100644
--- a/test/integration/util.js
+++ b/test/integration/util.js
@@ -10,6 +10,7 @@
   setupTable: setupTable,
 
   assertScanRows: assertScanRows,
+  assertSelectRows: assertSelectRows,
   testGetSetPermissions: testGetSetPermissions,
   uniqueName: uniqueName
 };
@@ -235,6 +236,25 @@
   return 0;
 }
 
+function assertRows(err, rows, wantRows, cb) {
+  if (err) {
+    return cb(err);
+  }
+
+  rows = rows || [];
+
+  rows.sort(compareRows);
+  wantRows.sort(compareRows);
+
+  if (!deepEqual(rows, wantRows)) {
+    var error = new Error('Expected rows to be ' + JSON.stringify(wantRows) +
+                      ' but got ' + JSON.stringify(rows));
+    return cb(error);
+  }
+
+  return cb(null);
+}
+
 function assertScanRows(ctx, table, range, wantRows, cb) {
   var stream = table.scan(ctx, range, function(err) {
     if (err) {
@@ -243,21 +263,27 @@
   });
 
   streamToArray(stream, function(err, rows) {
-    if (err) {
-      return cb(err);
+    assertRows(err, rows, wantRows, cb);
+  });
+}
+
+function assertSelectRows(ctx, db, table, prefix, wantRows, cb) {
+  var query = 'select k, v from ' + table.name;
+  if (prefix) {
+    query += ' where k like "' + prefix + '%"';
+  }
+  var isHeader = true;
+  var rows = [];
+  var streamErr;
+  db.exec(ctx, query, function(err) {
+    assertRows(streamErr || err, rows, wantRows, cb);
+  }).on('data', function(row) {
+    if (isHeader) {
+      isHeader = false;
+    } else {
+      rows.push({ key: row[0], value: row[1] });
     }
-
-    rows = rows || [];
-
-    rows.sort(compareRows);
-    wantRows.sort(compareRows);
-
-    if (!deepEqual(rows, wantRows)) {
-      var error = new Error('Expected rows to be ' + JSON.stringify(wantRows) +
-                        ' but got ' + JSON.stringify(rows));
-      return cb(error);
-    }
-
-    cb(null);
+  }).on('error', function(err) {
+    streamErr = streamErr || err;
   });
 }