Implement listApps, listDatabases, and listTables.  Also start
refactoring the tests.

This CL depends on go/vcl/12071, otherwise the globbing is broken.

Change-Id: I7a1c48044a3fb77862f3f9c3df294a553f6973c1
diff --git a/src/app.js b/src/app.js
index 06b8b65..9452023 100644
--- a/src/app.js
+++ b/src/app.js
@@ -4,8 +4,8 @@
 
 var vanadium = require('vanadium');
 
-var Database = require('./nosql/database').Database;
-var utils = require('./utils');
+var Database = require('./nosql/database');
+var util = require('./util');
 var vdl = require('./gen-vdl/v.io/syncbase/v23/services/syncbase');
 
 var wireSignature = vdl.App.prototype._serviceDescription;
@@ -17,7 +17,7 @@
     return new App(parentFullName, relativeName);
   }
 
-  utils.addNameProperties(this, parentFullName, relativeName);
+  util.addNameProperties(this, parentFullName, relativeName);
 
   /**
    * Caches the database wire object.
@@ -37,7 +37,9 @@
 };
 
 // listDatabases returns of all database names.
-App.prototype.listDatabases = function(ctx) {};
+App.prototype.listDatabases = function(ctx, cb) {
+  util.getChildNames(ctx, this.fullName, cb);
+};
 
 // create creates this app.  If perms is empty, we inherit (copy) the Service
 // perms.
diff --git a/src/nosql/batch-database.js b/src/nosql/batch-database.js
new file mode 100644
index 0000000..5f8c130
--- /dev/null
+++ b/src/nosql/batch-database.js
@@ -0,0 +1,59 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+module.exports = BatchDatabase;
+
+/*
+ * A handle to a set of reads and writes to the database that should be
+ * considered an atomic unit. See beginBatch() for concurrency semantics.
+ * @constructor
+ * @param {module:vanadium.syncbase.database.Database} db Database.
+ */
+function BatchDatabase(db) {
+  if (!(this instanceof BatchDatabase)) {
+    return new BatchDatabase(db);
+  }
+
+  this._db = db;
+
+  throw new Error('not implemented');
+}
+
+/**
+ * Returns the Table with the given name.
+ * @param {string} relativeName Table name.  Must not contain slashes.
+ * @return {module:syncbase.table.Table} Table object.
+ */
+BatchDatabase.prototype.table = function(ctx, relativeName, cb) {
+  return this._db.table(ctx, relativeName, cb);
+};
+
+/**
+ * Returns a list of all Table names.
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+BatchDatabase.prototype.listTables = function(ctx, cb) {
+  return this._db.listTables(ctx, cb);
+};
+
+/**
+ * Persists the pending changes to the database.
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+BatchDatabase.prototype.commit = function(ctx, cb) {
+  cb(new Error('not implemented'));
+};
+
+/**
+ * Notifies the server that any pending changes can be discarded.  It is not
+ * strictly required, but it may allow the server to release locks or other
+ * resources sooner than if it was not called.
+ * @param {module:vanadium.context.Context} ctx Vanadium context.
+ * @param {function} cb Callback.
+ */
+BatchDatabase.prototype.abort = function(ctx, cb) {
+  cb(new Error('not implemented'));
+};
diff --git a/src/nosql/database.js b/src/nosql/database.js
index ba51d48..deaee2b 100644
--- a/src/nosql/database.js
+++ b/src/nosql/database.js
@@ -2,11 +2,13 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+module.exports = Database;
+
 var vanadium = require('vanadium');
 
 var nosqlVdl = require('../gen-vdl/v.io/syncbase/v23/services/syncbase/nosql');
 var Table = require('./table');
-var utils = require('../utils');
+var util = require('../util');
 
 /**
  * Database represents a collection of Tables. Batches, queries, sync, watch,
@@ -22,7 +24,7 @@
     return new Database(parentFullName, relativeName);
   }
 
-  utils.addNameProperties(this, parentFullName, relativeName);
+  util.addNameProperties(this, parentFullName, relativeName);
 
   /**
    * Caches the database wire object.
@@ -84,7 +86,7 @@
  * @param {function} cb Callback.
  */
 Database.prototype.listTables = function(ctx, cb) {
-  return cb(new Error('not implemented'));
+  util.getChildNames(ctx, this.fullName, cb);
 };
 
 /**
@@ -162,7 +164,7 @@
  * Configuration options for Batches.
  * @constructor
  */
-var BatchOptions = nosqlVdl.BatchOptions;
+Database.BatchOptions = nosqlVdl.BatchOptions;
 
 /**
  * Creates a new batch. Instead of calling this function directly, clients are
@@ -181,69 +183,9 @@
  *
  * Concurrency semantics can be configured using BatchOptions.
  * @param {module:vanadium.context.Context} ctx Vanadium context.
- * @param {module:vanadium.syncbase.database.BatchOptions} opts BatchOptions.
+ * @param {module:vanadium.syncbase.Database.BatchOptions} opts BatchOptions.
  * @param {function} cb Callback.
  */
 Database.prototype.beginBatch = function(ctx, opts, cb) {
   cb(new Error('not implemented'));
 };
-
-/*
- * A handle to a set of reads and writes to the database that should be
- * considered an atomic unit. See beginBatch() for concurrency semantics.
- * @constructor
- * @param {module:vanadium.syncbase.database.Database} db Database.
- */
-function BatchDatabase(db) {
-  if (!(this instanceof BatchDatabase)) {
-    return new BatchDatabase(db);
-  }
-
-  this._db = db;
-
-  throw new Error('not implemented');
-}
-
-/**
- * Returns the Table with the given name.
- * @param {string} relativeName Table name.  Must not contain slashes.
- * @return {module:syncbase.table.Table} Table object.
- */
-BatchDatabase.prototype.table = function(ctx, relativeName, cb) {
-  return this._db.table(ctx, relativeName, cb);
-};
-
-/**
- * Returns a list of all Table names.
- * @param {module:vanadium.context.Context} ctx Vanadium context.
- * @param {function} cb Callback.
- */
-BatchDatabase.prototype.listTables = function(ctx, cb) {
-  return this._db.listTables(ctx, cb);
-};
-
-/**
- * Persists the pending changes to the database.
- * @param {module:vanadium.context.Context} ctx Vanadium context.
- * @param {function} cb Callback.
- */
-BatchDatabase.prototype.commit = function(ctx, cb) {
-  cb(new Error('not implemented'));
-};
-
-/**
- * Notifies the server that any pending changes can be discarded.  It is not
- * strictly required, but it may allow the server to release locks or other
- * resources sooner than if it was not called.
- * @param {module:vanadium.context.Context} ctx Vanadium context.
- * @param {function} cb Callback.
- */
-BatchDatabase.prototype.abort = function(ctx, cb) {
-  cb(new Error('not implemented'));
-};
-
-module.exports = {
-  BatchDatabase: BatchDatabase,
-  BatchOptions: BatchOptions,
-  Database: Database
-};
diff --git a/src/nosql/index.js b/src/nosql/index.js
deleted file mode 100644
index 149478f..0000000
--- a/src/nosql/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-var Database = require('./database');
-var runInBatch = require('./batch');
-
-module.exports = {
-  newDatabase: newDatabase,
-  runInBatch: runInBatch
-};
-
-function newDatabase(name, relativeName) {
-  return new Database(name, relativeName);
-}
\ No newline at end of file
diff --git a/src/nosql/table.js b/src/nosql/table.js
index 47dc45d..cca247a 100644
--- a/src/nosql/table.js
+++ b/src/nosql/table.js
@@ -4,7 +4,7 @@
 
 module.exports = Table;
 
-var utils = require('../utils');
+var util = require('../util');
 
 // Table represents a collection of Rows.
 function Table(parentFullName, relativeName) {
@@ -12,7 +12,7 @@
     return new Table(parentFullName, relativeName);
   }
 
-  utils.addNameProperties(this, parentFullName, relativeName);
+  util.addNameProperties(this, parentFullName, relativeName);
 }
 
 // Row returns the Row with the given primary key.
diff --git a/src/service.js b/src/service.js
index 8874165..a8c61c1 100644
--- a/src/service.js
+++ b/src/service.js
@@ -5,6 +5,7 @@
 var vanadium = require('vanadium');
 
 var App = require('./app');
+var util = require('./util');
 var vdl = require('./gen-vdl/v.io/syncbase/v23/services/syncbase');
 
 // TODO(aghassemi): This looks clunky,
@@ -47,25 +48,7 @@
 
 // listApps returns a list of all app names.
 Service.prototype.listApps = function(ctx, cb) {
-  var rt = vanadium.runtimeForContext(ctx);
-  var namespace = rt.namespace();
-  var appNames = [];
-
-  //TODO(nlacasse): Refactor the glob->list into a common util
-  var stream = namespace.glob(ctx, vanadium.naming.join(this.fullName, '*'),
-    function(err) {
-      if (err) {
-        return cb(err);
-      }
-
-      cb(null, appNames);
-    }).stream;
-
-  stream.on('data', function(globResult) {
-    var fullName = globResult.name;
-    var name = vanadium.naming.basename(fullName);
-    appNames.push(name);
-  });
+  util.getChildNames(ctx, this.fullName, cb);
 };
 
 Service.prototype.getPermissions = function(ctx, cb) {
diff --git a/src/utils.js b/src/util.js
similarity index 60%
rename from src/utils.js
rename to src/util.js
index bee3858..3e1775c 100644
--- a/src/utils.js
+++ b/src/util.js
@@ -7,6 +7,7 @@
 
 module.exports = {
   addNameProperties: addNameProperties,
+  getChildNames: getChildNames,
   InvalidNameError: InvalidNameError
 };
 
@@ -49,3 +50,32 @@
 }
 inherits(InvalidNameError, Error);
 
+/**
+ * getChildNames returns all names that are children of the parentFullName.
+ * @private
+ */
+function getChildNames(ctx, parentFullName, cb) {
+  var rt = vanadium.runtimeForContext(ctx);
+  var namespace = rt.namespace();
+  var childNames = [];
+
+  var globPattern = vanadium.naming.join(parentFullName, '*');
+
+  var stream = namespace.glob(ctx, globPattern, function(err) {
+    if (err) {
+      return cb(err);
+    }
+
+    cb(null, childNames);
+  }).stream;
+
+  stream.on('data', function(globResult) {
+    var fullName = globResult.name;
+    var name = vanadium.naming.basename(fullName);
+    childNames.push(name);
+  });
+
+  stream.on('error', function(err) {
+    console.error('Stream error: ' + JSON.stringify(err));
+  });
+}
diff --git a/test/integration/test-app.js b/test/integration/test-app.js
index 3423de4..73838c2 100644
--- a/test/integration/test-app.js
+++ b/test/integration/test-app.js
@@ -8,30 +8,11 @@
 
 var syncbase = require('../..');
 
-var testUtils = require('./utils');
-var setup = testUtils.setupService;
-var uniqueName = testUtils.uniqueName;
-
-var DEFAULT_PERMISSIONS = new Map([
-  ['Read', {
-    'in': ['...'],
-    'notIn': []
-  }],
-  ['Write', {
-    'in': ['...'],
-    'notIn': []
-  }],
-  ['Admin', {
-    'in': ['...'],
-    'notIn': []
-  }]
-]);
-
-/*
- * TODO(aghassemi) We should refactor some of the testing functionality,
- * specially around verifying children and setting/getting permissions, into a
- * common util as these types of test will be common across different layers.
- */
+var testUtil = require('./util');
+var appExists = testUtil.appExists;
+var setupApp = testUtil.setupApp;
+var setupService = testUtil.setupService;
+var uniqueName = testUtil.uniqueName;
 
 test('Creating a service and checking its full name', function(t) {
   var mockServiceName = 'foo/bar/baz';
@@ -42,7 +23,7 @@
 });
 
 test('Getting a handle to an app', function(t) {
-  setup(t, function(err, o) {
+  setupService(t, function(err, o) {
     if (err) {
       return t.end(err, 'Failed to setup');
     }
@@ -60,195 +41,67 @@
 });
 
 test('Creating and listing apps', function(t) {
-  setup(t, function(err, o) {
+  setupService(t, function(err, o) {
     if (err) {
       return t.end(err, 'Failed to setup');
     }
 
-    // Create multiple apps
-    var expectedAppNames = [
+    // Create multiple apps.
+    var appNames = [
       uniqueName('app'),
       uniqueName('app'),
       uniqueName('app')
     ];
-
-    createAppsAndVerifyExistance(t, o.service, o.ctx, expectedAppNames,
-      function() {
-        o.teardown(t.end);
+    async.forEach(appNames, function(appName, cb) {
+      o.service.app(appName).create(o.ctx, {}, cb);
+    }, function(err) {
+      if (err) {
+        t.error(err);
+        return o.teardown(t.end);
       }
-    );
+
+      // Verify each app exists.
+      async.map(appNames, function(appName, cb) {
+        appExists(o.ctx, o.service, appName, cb);
+      }, function(err, existsArray) {
+        t.error(err);
+        t.deepEqual(existsArray, [true, true, true], 'all apps exist');
+        o.teardown(t.end);
+      });
+    });
   });
 });
 
 test('Deleting an app', function(t) {
-  setup(t, function(err, o) {
+  setupApp(t, function(err, o) {
     if (err) {
       return t.end(err);
     }
 
-    var appName = uniqueName('app');
-
-    createAppsAndVerifyExistance(t, o.service, o.ctx, [appName], deleteApp);
-
-    function deleteApp() {
-      o.service.app(appName).delete(o.ctx, verifyItNoLongerExists);
-    }
-
-    function verifyItNoLongerExists(err) {
+    o.app.delete(o.ctx, function(err) {
       if (err) {
-        t.fail(err, 'Failed to delete app');
+        t.error(err);
         return o.teardown(t.end);
       }
 
-      o.service.listApps(o.ctx, function(err, apps) {
-        if (err) {
-          t.fail(err, 'Failed to list apps');
-          return o.teardown(t.end);
-        }
-
-        t.ok(apps.indexOf(appName) < 0, 'App is no longer listed');
-        return o.teardown(t.end);
+      appExists(o.ctx, o.service, o.app.name, function(err, exists) {
+        t.error(err);
+        t.notok(exists, 'app no longer exists');
+        o.teardown(t.end);
       });
-    }
-  });
-});
-
-test('Getting permissions of an app', function(t) {
-  setup(t, function(err, o) {
-    if (err) {
-      return t.end(err);
-    }
-
-    var appName = uniqueName('app');
-
-    createAppsAndVerifyExistance(t, o.service, o.ctx, [appName],
-                                 getPermissions);
-
-    function getPermissions() {
-      o.service.app(appName).getPermissions(o.ctx, verifyPermissions);
-    }
-
-    function verifyPermissions(err, perms, version) {
-      if (err) {
-        t.fail(err, 'Failed to get permissions for app');
-        return o.teardown(t.end);
-      }
-
-      t.equal(perms.size, DEFAULT_PERMISSIONS.size,
-        'Permissions size matches');
-      DEFAULT_PERMISSIONS.forEach(function(value, key) {
-        t.deepEqual(perms.get(key), value, 'Permission value matches');
-      });
-      t.equal(version, '0', 'Version matches');
-
-      return o.teardown(t.end);
-    }
-  });
-});
-
-test('Setting permissions of an app', function(t) {
-  setup(t, function(err, o) {
-    if (err) {
-      return t.end(err);
-    }
-
-    var appName = uniqueName('app');
-    var NEW_PERMS = new Map([
-      ['Read', {
-        'in': ['...', 'canRead'],
-        'notIn': ['cantRead']
-      }],
-      ['Write', {
-        'in': ['...', 'canWrite'],
-        'notIn': ['cantWrite']
-      }],
-      ['Admin', {
-        'in': ['...', 'canAdmin'],
-        'notIn': ['cantAdmin']
-      }]
-    ]);
-
-    createAppsAndVerifyExistance(t, o.service, o.ctx, [appName],
-                                 setPermissions);
-
-    function setPermissions() {
-      o.service.app(appName)
-        .setPermissions(o.ctx, NEW_PERMS, '0', getPermissions);
-    }
-
-    function getPermissions(err) {
-      if (err) {
-        t.fail(err, 'Failed to set permissions for app');
-        return o.teardown(t.end);
-      }
-      o.service.app(appName).getPermissions(o.ctx, verifyPermissions);
-    }
-
-    function verifyPermissions(err, perms, version) {
-      if (err) {
-        t.fail(err, 'Failed to get permissions for app');
-        return o.teardown(t.end);
-      }
-
-      t.equal(perms.size, NEW_PERMS.size,
-        'Permissions size matches');
-      NEW_PERMS.forEach(function(value, key) {
-        t.deepEqual(perms.get(key), value, 'Permission value matches');
-      });
-      // Version should have been incremented after setPermission call
-      t.equal(version, '1', 'Version matches');
-
-      return o.teardown(t.end);
-    }
-  });
-});
-
-// Helper function that creates bunch apps in parallel and calls the callback
-// when all are created.
-function createAppsAndVerifyExistance(t, service, ctx, appNames, cb) {
-  async.parallel(create(), verify);
-
-  // Returns an array of functions that create apps for the given appNames.
-  function create() {
-    return appNames.map(function(appName) {
-      return function(callback) {
-        service.app(appName).create(ctx, DEFAULT_PERMISSIONS, callback);
-      };
     });
-  }
+  });
+});
 
-  function verify(err) {
+test('Getting/Setting permissions of an app', function(t) {
+  setupApp(t, function(err, o) {
     if (err) {
-      t.fail('Failed to create apps');
-      return cb(err);
+      return t.end(err);
     }
 
-    service.listApps(ctx, verifyResults);
-
-    function verifyResults(err, apps) {
-      if (err) {
-        t.fail(err, 'Failed to list apps');
-        return cb(err);
-      }
-
-      var matchCounter = 0;
-      appNames.forEach(function(appName) {
-        if (apps.indexOf(appName) >= 0) {
-          matchCounter++;
-        }
-      });
-
-      var diff = appNames.length - matchCounter;
-      if (diff === 0) {
-        t.pass('All ' + matchCounter + ' expected app name(s) were listed');
-        return cb();
-      } else {
-        var failedErr = new Error(
-          'Some (' + diff + ') expected app name(s) were not listed'
-        );
-        t.fail(failedErr);
-        return cb(failedErr);
-      }
-    }
-  }
-}
+    testUtil.testGetSetPermissions(t, o.ctx, o.app, function(err) {
+      t.error(err);
+      return o.teardown(t.end);
+    });
+  });
+});
diff --git a/test/integration/test-database.js b/test/integration/test-database.js
index acf06ac..d24ab17 100644
--- a/test/integration/test-database.js
+++ b/test/integration/test-database.js
@@ -6,13 +6,15 @@
 var test = require('prova');
 var vanadium = require('vanadium');
 
-var Database = require('../../src/nosql/database').Database;
+var Database = require('../../src/nosql/database');
 var Table = require('../../src/nosql/table');
 
-var testUtils = require('./utils');
-var uniqueName = testUtils.uniqueName;
-var setupApp = testUtils.setupApp;
-var setupDatabase = testUtils.setupDatabase;
+var testUtil = require('./util');
+var databaseExists = testUtil.databaseExists;
+var tableExists = testUtil.tableExists;
+var setupApp = testUtil.setupApp;
+var setupDatabase = testUtil.setupDatabase;
+var uniqueName = testUtil.uniqueName;
 
 test('app.noSqlDatabase() returns a database', function(t) {
   setupApp(t, function(err, o) {
@@ -64,12 +66,16 @@
     var db = o.app.noSqlDatabase(uniqueName('db'));
 
     db.create(o.ctx, {}, function(err) {
-      t.error(err);
+      if (err) {
+        t.error(err);
+        return o.teardown(t.end);
+      }
 
-      // TODO(nlacasse): Verify that the database exists using
-      // app.listDatabases(), once that function has been implemented.
-
-      o.teardown(t.end);
+      databaseExists(o.ctx, o.app, db.name, function(err, exists) {
+        t.error(err);
+        t.ok(exists, 'database exists');
+        o.teardown(t.end);
+      });
     });
   });
 });
@@ -113,12 +119,16 @@
       }
 
       db.delete(o.ctx, function(err) {
-        t.error(err);
+        if (err) {
+          t.error(err);
+          return o.teardown(t.end);
+        }
 
-        // TODO(nlacasse): Verify that the database no longer exists using
-        // app.listDatabases(), once that function has been implemented.
-
-        o.teardown(t.end);
+        databaseExists(o.ctx, o.app, db.name, function(err, exists) {
+          t.error(err);
+          t.notok(exists, 'database does not exist');
+          o.teardown(t.end);
+        });
       });
     });
   });
@@ -169,12 +179,16 @@
 
     var tableName = uniqueName('table');
     db.createTable(o.ctx, tableName, {}, function(err) {
-      t.error(err);
+      if (err) {
+        t.error(err);
+        return o.teardown(t.end);
+      }
 
-      // TODO(nlacasse): Verify that the table exists using db.listTables(),
-      // once that function has been implemented.
-
-      o.teardown(t.end);
+      tableExists(o.ctx, db, tableName, function(err, exists) {
+        t.error(err);
+        t.ok(exists, 'table exists');
+        o.teardown(t.end);
+      });
     });
   });
 });
@@ -195,12 +209,16 @@
       }
 
       db.deleteTable(o.ctx, tableName, function(err) {
-        t.error(err);
+        if (err) {
+          t.error(err);
+          return o.teardown(t.end);
+        }
 
-        // TODO(nlacasse): Verify that the table no longer exists using
-        // db.listTables(), once that function has been implemented.
-
-        o.teardown(t.end);
+        tableExists(o.ctx, db, tableName, function(err, exists) {
+          t.error(err);
+          t.notok(exists, 'table does not exist');
+          o.teardown(t.end);
+        });
       });
     });
   });
@@ -221,3 +239,16 @@
     });
   });
 });
+
+test('Getting/Setting permissions of a database', function(t) {
+  setupDatabase(t, function(err, o) {
+    if (err) {
+      return t.end(err);
+    }
+
+    testUtil.testGetSetPermissions(t, o.ctx, o.database, function(err) {
+      t.error(err);
+      return o.teardown(t.end);
+    });
+  });
+});
diff --git a/test/integration/util.js b/test/integration/util.js
new file mode 100644
index 0000000..c5730e8
--- /dev/null
+++ b/test/integration/util.js
@@ -0,0 +1,185 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+module.exports = {
+  appExists: appExists,
+  databaseExists: databaseExists,
+  tableExists: tableExists,
+
+  setupApp: setupApp,
+  setupDatabase: setupDatabase,
+  setupService: setupService,
+
+  uniqueName: uniqueName,
+
+  testGetSetPermissions: testGetSetPermissions
+};
+
+var vanadium = require('vanadium');
+var extend = require('xtend');
+
+var syncbase = require('../..');
+
+var SERVICE_NAME = require('./service-name');
+
+// Helper function to generate unique names.
+var nameCounter = 0;
+function uniqueName(prefix) {
+  prefix = prefix || 'name';
+  return prefix + '_' + nameCounter++;
+}
+
+// Initializes Vanadium runtime.
+function setupService(t, cb) {
+  vanadium.init(function(err, rt) {
+    if (err) {
+      return cb(err);
+    }
+
+    function teardown(cb) {
+      rt.close(function(err) {
+        t.error(err, 'rt.close should not error.');
+        cb(null);
+      });
+    }
+
+    var service = syncbase.newService(SERVICE_NAME);
+
+    return cb(null, {
+      ctx: rt.getContext(),
+      rt: rt,
+      service: service,
+      teardown: teardown
+    });
+  });
+}
+
+// Initializes Vanadium runtime and creates an App.
+function setupApp(t, cb) {
+  setupService(t, function(err, o) {
+    if (err) {
+      return cb(err);
+    }
+
+    var app = o.service.app(uniqueName('app'));
+
+    app.create(o.ctx, {}, function(err) {
+      if (err) {
+        o.rt.close(t.error);
+        return cb(err);
+      }
+
+      return cb(null, extend(o, {
+        app:app
+      }));
+    });
+  });
+}
+
+// Initializes Vanadium runtime and creates an App and a Database.
+function setupDatabase(t, cb) {
+  setupApp(t, function(err, o) {
+
+    var db = o.app.noSqlDatabase(uniqueName('db'));
+
+    db.create(o.ctx, {}, function(err) {
+      if (err) {
+        o.rt.close(t.error);
+        return cb(err);
+      }
+
+      return cb(null, extend(o, {
+        database: db
+      }));
+    });
+  });
+}
+
+// Assert that two permissions objects are equal.
+function assertPermissionsEqual(t, got, want) {
+  t.equal(got.size, want.size, 'Permissions size matches');
+  want.forEach(function(value, key) {
+    t.deepEqual(got.get(key), value, 'Permission value matches');
+  });
+}
+
+// For any object that implements get/setPermissions, test that getting and
+// setting permissions behaves as it should.
+function testGetSetPermissions(t, ctx, obj, cb){
+  obj.getPermissions(ctx, function(err, perms, version) {
+    if (err) {
+      t.error('error getting permissions ' + err);
+      return cb(err);
+    }
+
+    t.ok(perms, 'Has permissions');
+    t.ok(version, 'Has a version');
+
+    var newPerms = new Map([
+      ['Read', {
+        'in': ['...', 'canRead'],
+        'notIn': ['cantRead']
+      }],
+      ['Write', {
+        'in': ['...', 'canWrite'],
+        'notIn': ['cantWrite']
+      }],
+      ['Admin', {
+        'in': ['...', 'canAdmin'],
+        'notIn': ['cantAdmin']
+      }]
+    ]);
+
+    obj.setPermissions(ctx, newPerms, version, function(err) {
+      if (err) {
+        t.error('error setting permissions ' + err);
+        return cb(err);
+      }
+
+      obj.getPermissions(ctx, function(err, gotPerms, gotVersion) {
+        if (err) {
+          t.error('error getting permissions ' + err);
+          return cb(err);
+        }
+
+        t.ok(perms, 'Has permissions');
+        t.ok(version, 'Has a version');
+
+        t.notEqual(version, gotVersion, 'should have a new version');
+        assertPermissionsEqual(t, gotPerms, newPerms);
+        return cb(null);
+      });
+    });
+  });
+}
+
+function appExists(ctx, service, name, cb) {
+  service.listApps(ctx, function(err, names) {
+    if (err) {
+      return cb(err);
+    }
+
+    cb(null, names.indexOf(name) >= 0);
+  });
+}
+
+function databaseExists(ctx, app, name, cb) {
+  app.listDatabases(ctx, function(err, names) {
+    if (err) {
+      return cb(err);
+    }
+
+    cb(null, names.indexOf(name) >= 0);
+  });
+}
+
+function tableExists(ctx, db, name, cb) {
+  db.listTables(ctx, function(err, names) {
+    if (err) {
+      return cb(err);
+    }
+
+    cb(null, names.indexOf(name) >= 0);
+  });
+}
diff --git a/test/integration/utils.js b/test/integration/utils.js
deleted file mode 100644
index 89cf8c5..0000000
--- a/test/integration/utils.js
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-var vanadium = require('vanadium');
-var extend = require('xtend');
-
-var syncbase = require('../..');
-
-var SERVICE_NAME = require('./service-name');
-
-// Helper function to generate unique names.
-var uniqueName = (function() {
-  var i = 0;
-  return function(prefix) {
-    prefix = prefix || 'name';
-    i++;
-    return prefix + '_' + i;
-  };
-})();
-
-// Initializes Vanadium runtime.
-function setupService(t, cb) {
-  vanadium.init(function(err, rt) {
-    if (err) {
-      return cb(err);
-    }
-
-    function teardown(cb) {
-      rt.close(function(err) {
-        t.error(err, 'rt.close should not error.');
-        cb(null);
-      });
-    }
-
-    var service = syncbase.newService(SERVICE_NAME);
-
-    return cb(null, {
-      ctx: rt.getContext(),
-      rt: rt,
-      service: service,
-      teardown: teardown
-    });
-  });
-}
-
-// Initializes Vanadium runtime and creates an App.
-function setupApp(t, cb) {
-  setupService(t, function(err, o) {
-    if (err) {
-      return cb(err);
-    }
-
-    var app = o.service.app(uniqueName('app'));
-
-    app.create(o.ctx, {}, function(err) {
-      if (err) {
-        o.rt.close(t.error);
-        return cb(err);
-      }
-
-      return cb(null, extend(o, {
-        app:app
-      }));
-    });
-  });
-}
-
-// Initializes Vanadium runtime and creates an App and a Database.
-function setupDatabase(t, cb) {
-  setupApp(t, function(err, o) {
-
-    var db = o.app.noSqlDatabase(uniqueName('db'));
-
-    db.create(o.ctx, {}, function(err) {
-      if (err) {
-        o.rt.close(t.error);
-        return cb(err);
-      }
-
-      return cb(null, extend(o, {
-        database: db
-      }));
-    });
-  });
-}
-
-module.exports = {
-  setupApp: setupApp,
-  setupDatabase: setupDatabase,
-  setupService: setupService,
-  uniqueName: uniqueName
-};