Merge "js/core: Make all the test-canonicalize.js tests create a test per testcase"
diff --git a/extension/manifest.json b/extension/manifest.json
index 26cbcc6..39ff54a 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -1,6 +1,6 @@
 {
   "name": "Vanadium Extension",
-  "version": "0.1.11",
+  "version": "0.1.13",
   "description": "Access and create Vanadium services from JavaScript.",
   "manifest_version": 2,
   "icons": {
diff --git a/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js b/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js
index 072765b..0d99bb0 100644
--- a/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js
+++ b/src/gen-vdl/v.io/x/ref/services/wspr/internal/app/index.js
@@ -22,7 +22,6 @@
 
 // Types:
 var _type1 = new vdl.Type();
-var _type10 = new vdl.Type();
 var _type2 = new vdl.Type();
 var _type3 = new vdl.Type();
 var _type4 = new vdl.Type();
@@ -41,9 +40,6 @@
 _type1.kind = vdl.kind.LIST;
 _type1.name = "";
 _type1.elem = _typeRpcCallOption;
-_type10.kind = vdl.kind.LIST;
-_type10.name = "";
-_type10.elem = new security.WireBlessings()._type;
 _type2.kind = vdl.kind.LIST;
 _type2.name = "";
 _type2.elem = new security.BlessingPattern()._type;
@@ -90,7 +86,6 @@
 _typeRpcServerOption.name = "v.io/x/ref/services/wspr/internal/app.RpcServerOption";
 _typeRpcServerOption.fields = [{name: "IsLeaf", type: vdl.types.BOOL}, {name: "ServesMountTable", type: vdl.types.BOOL}];
 _type1.freeze();
-_type10.freeze();
 _type2.freeze();
 _type3.freeze();
 _type4.freeze();
@@ -214,11 +209,6 @@
 Controller.prototype.signature = function(ctx, serverCall, name) {
   throw new Error('Method Signature not implemented');
 };
-    
-      
-Controller.prototype.unionOfBlessings = function(ctx, serverCall, toJoin) {
-  throw new Error('Method UnionOfBlessings not implemented');
-};
      
 
     
@@ -564,27 +554,6 @@
     outStream: null,
     tags: []
   },
-    
-      
-    {
-    name: 'UnionOfBlessings',
-    doc: "// UnionOfBlessings returns a Blessings object that carries the union of the provided blessings.",
-    inArgs: [{
-      name: 'toJoin',
-      doc: "",
-      type: _type10
-    },
-    ],
-    outArgs: [{
-      name: '',
-      doc: "",
-      type: new principal.BlessingsId()._type
-    },
-    ],
-    inStream: null,
-    outStream: null,
-    tags: []
-  },
      
   ]
 };
diff --git a/src/rpc/server-router.js b/src/rpc/server-router.js
index 4c649ae..5c327fe 100644
--- a/src/rpc/server-router.js
+++ b/src/rpc/server-router.js
@@ -148,11 +148,11 @@
                                             router._typeEncoder),
                               Outgoing.AUTHORIZATION_RESPONSE, null, messageId);
   }).catch(function(e) {
-    var errMsg = {
+    var authReply = new AuthReply({
       err: ErrorConversion.fromNativeValue(e, router._appName,
                                            decodedRequest.call.method)
-    };
-    router._proxy.sendRequest(hexVom.encode(errMsg, undefined,
+    });
+    router._proxy.sendRequest(hexVom.encode(authReply, undefined,
                                             router._typeEncoder),
                               Outgoing.AUTHORIZATION_RESPONSE, null,
                               messageId);
diff --git a/src/security/blessings-util.js b/src/security/blessings-util.js
index 9f6ce4d..d876ac1 100644
--- a/src/security/blessings-util.js
+++ b/src/security/blessings-util.js
@@ -2,7 +2,16 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-var runtimeFromContext = require('../../src/runtime/runtime-from-context');
+var Blessings = require('../../src/security/blessings');
+var makeError = require('../../src/verror/make-errors');
+var actions = require('../../src/verror/actions');
+
+var InvalidUnionError = makeError('v.io/v23/security.errInvalidUnion',
+  actions.NO_RETRY, {
+    'en':
+     '{1:}{2:} cannot create union of blessings bound to different public keys',
+  }, []);
+
 
 /**
  * @fileoverview Blessings related utilities that don't belong on the
@@ -27,21 +36,58 @@
 * provided blessings.
 * @param {module:vanadium.context.Context} ctx The context.
 * @param {...string} blessingsList The blessings to join
-* @param {module:vanadium.security~blessingsCb} cb An optional
-* callback that will return the blessing.
-* @return {Promise<module:vanadium.security~Blessings>} A promise that will
-* be resolved with the blessing.
+* @return {module:vanadium.security~Blessings} A blessing object consisting
+* of the union of the input.
 * @memberof module:vanadium.security
 */
-function unionOfBlessings(ctx /*, blessingsA, blessingsB, ..., cb*/) {
-   var args = Array.prototype.slice.call(arguments);
-   args.shift(); // remove ctx
-   var cb;
-   if (args.length > 0 && typeof args[args.length - 1] === 'function') {
-     cb = args.pop();
-   }
-   var blessingsList = args;
+function unionOfBlessings(ctx /*, blessingsA, blessingsB, ...*/) {
+  var blessingsList = Array.prototype.slice.call(arguments, 1);
 
-   var runtime = runtimeFromContext(ctx);
-   return runtime._controller.unionOfBlessings(ctx, blessingsList, cb);
+  blessingsList = blessingsList.filter(function(blessings) {
+    return !!blessings;
+  });
+
+  switch(blessingsList.length) {
+    case 0:
+      return null;
+    case 1:
+      return blessingsList[0];
+  }
+
+  var firstKey = blessingsList[0].publicKey;
+  var chains = [];
+  for (var i = 0; i < blessingsList.length; i++) {
+    var blessings = blessingsList[i];
+    if (JSON.stringify(blessings.publicKey) !== JSON.stringify(firstKey)) {
+      throw new InvalidUnionError();
+    }
+    chains = chains.concat(blessings.chains);
+  }
+
+  // Sort for prettier and more consistent output.
+  chains = chains.sort(chainSorter);
+
+  return new Blessings({
+    publicKey: firstKey,
+    certificateChains: chains
+  });
+}
+
+// Provide some stability by sorting the chain list.
+// The chains are first ordered by length, followed by the the result
+// of comparing the first differing extension.
+function chainSorter(a, b) {
+  if (a.length !== b.length) {
+    return a.length > b.length;
+  }
+
+  for (var i = 0; i < a.length; i++) {
+    var aext = a[i].extension;
+    var bext = b[i].extension;
+    if (aext !== bext) {
+      return aext > bext;
+    }
+  }
+
+  return false;
 }
diff --git a/test/integration/test-bless.js b/test/integration/test-bless.js
index a3f147e..7fc6de9 100644
--- a/test/integration/test-bless.js
+++ b/test/integration/test-bless.js
@@ -28,6 +28,11 @@
     runtime.principal.blessSelf(runtime.getContext(), 'blessedname')
     .then(function(blessings) {
       validateBlessings(t, blessings);
+      t.equal(blessings.chains.length, 1, 'Has exactly one chain');
+      t.equal(blessings.chains[0].length, 1, 'Chain has exactly one blessing');
+      t.equal(blessings.chains[0][0].extension, 'blessedname',
+        'Has correct extension');
+      t.equal(blessings.chains[0][0].caveats.length, 0, 'Has no caveats');
       rt.close(t.end);
     }).catch(function(err) {
       t.error(err);
@@ -45,11 +50,17 @@
 
     rt = runtime;
 
-    runtime.principal.blessSelf(runtime.getContext(), 'blessedname',
-      security.createExpiryCaveat(new Date()),
+    var cav = security.createExpiryCaveat(new Date());
+    runtime.principal.blessSelf(runtime.getContext(), 'blessedname', cav,
       function(err, blessings) {
       t.error(err);
       validateBlessings(t, blessings);
+      t.equal(blessings.chains.length, 1, 'Has exactly one chain');
+      t.equal(blessings.chains[0].length, 1, 'Chain has exactly one blessing');
+      t.equal(blessings.chains[0][0].extension, 'blessedname',
+        'Has correct extension');
+      t.equal(blessings.chains[0][0].caveats.length, 1, 'Has one caveat');
+      t.deepEqual(blessings.chains[0][0].caveats[0], cav, 'Has correct caveat');
       rt.close(t.end);
     });
   });
@@ -89,11 +100,20 @@
       var rt = vanadium.runtimeForContext(ctx);
       var secCall = serverCall.securityCall;
       var remoteKey = secCall.remoteBlessings.publicKey;
+      var expiryCav = security.createExpiryCaveat(new Date(Date.now() - 1000));
+      var constCav = security.createConstCaveat(true);
       rt.principal.bless(ctx, remoteKey, secCall.localBlessings,
-       'ext', security.createExpiryCaveat(new Date(Date.now() - 1000)),
-       security.createConstCaveat(true), function(err, blessings) {
+       'ext', expiryCav, constCav, function(err, blessings) {
          t.notOk(err, 'No error expected during bless');
          validateBlessings(t, blessings);
+         for (var i = 0; i < blessings.chains.length; i++) {
+           var chain = blessings.chains[i];
+           t.equal(chain[chain.length - 1].extension, 'ext',
+            'Expected final extension to match');
+           t.deepEqual(chain[chain.length - 1].caveats.sort(objectSorter),
+             [expiryCav, constCav].sort(objectSorter),
+             'Has correct caveats');
+         }
          cb(null, null);
        });
     }
@@ -159,47 +179,88 @@
     });
 });
 
-// TODO(bprosnitz) This test is weak. Improve it.
+// Tests add roots by trying to invoke a method with a blessing not in the
+// roots and then adding it to the roots.
 test('Test add roots', function(t) {
-  var rt;
-  vanadium.init(config, function(err, runtime) {
+  var service = {
+    method: function(ctx, serverCall) {
+      return 'aResponse';
+    }
+  };
+
+  var authorizer = function(ctx, securityCall) {
+    var hasBlessedName = securityCall.remoteBlessingStrings.some(function(str) {
+      return str === 'blessedname';
+    });
+    if (hasBlessedName) {
+      return Promise.resolve();
+    }
+    return Promise.reject(new Error('Expected blessedname in blessings'));
+  };
+
+  serve('testing/addroots', leafDispatcher(service, authorizer),
+    function(err, res) {
     if (err) {
       t.end(err);
+      return;
     }
 
-    rt = runtime;
-
-    runtime.principal.blessSelf(runtime.getContext(), 'blessedname')
-    .then(function(blessings) {
+    var runtime = res.runtime;
+    var ctx = runtime.getContext();
+    var blessings;
+    var origDefault;
+    var origTripDot;
+    runtime.principal.blessSelf(ctx, 'blessedname')
+    .then(function(selfBlessings) {
+      blessings = selfBlessings;
       validateBlessings(t, blessings);
-
-      return runtime.principal.addToRoots(runtime.getContext(), blessings);
+      // Get the original blessings used for '...' and save them for later.
+      return runtime.principal.blessingStore.forPeer(ctx, '...');
+    }).then(function(oldBlessings) {
+      origTripDot = oldBlessings;
+      // Replace it with the union of the old and the new self-blessing
+      return vanadium.security.unionOfBlessings(ctx, oldBlessings, blessings);
+    }).then(function(unionedBlessings) {
+      return runtime.principal.blessingStore.set(ctx, unionedBlessings, '...');
     }).then(function() {
-      rt.close(t.end);
+      // Get the original default blessings and save them for later.
+      return runtime.principal.blessingStore.getDefault(ctx);
+    }).then(function(oldDefault) {
+      origDefault = oldDefault;
+      // Replace it with the union of the old and the new self-blessing
+      return vanadium.security.unionOfBlessings(ctx, oldDefault, blessings);
+    }).then(function(unionedBlessings) {
+      return runtime.principal.blessingStore.setDefault(ctx, unionedBlessings);
     }).catch(function(err) {
-      t.error(err, 'either blessSelf or addToRoots errored ' + err);
-      rt.close(t.end);
+      t.error('Failed to configure blessings: ' + err);
+    }).then(function() {
+      // Attempt to call the method; it should fail.
+      return res.service.method(ctx);
+    }).then(function() {
+      t.error('Method call unexpectedly succeeded without valid roots');
+    }).catch(function() {
+      return runtime.principal.addToRoots(ctx, blessings);
+    }).then(function() {
+      // Call the method after addToRoots; it should succeed.
+      return res.service.method(ctx);
+    }).then(function(result) {
+      t.equal(result, 'aResponse', 'Got correct result');
+    }).catch(function(err) {
+      t.error('either blessSelf or addToRoots errored ' + err);
+    }).then(function() {
+      // Reset the blessingStore for default blessings.
+      return runtime.principal.blessingStore.setDefault(ctx, origDefault);
+    }).then(function() {
+      // Reset the blessingStore for peer '...'
+      return runtime.principal.blessingStore.set(ctx, origTripDot, '...');
+    }).catch(function(err) {
+      t.error('Unexpected error resetting blessing store');
+    }).then(function(){
+      res.end(t);
     });
   });
 });
 
-test('Test fetching blessing debug string', function(t) {
-  var rt;
-  vanadium.init(config, function(err, runtime) {
-    if (err) {
-      t.end(err);
-    }
-
-    rt = runtime;
-
-    runtime.principal.blessSelf(runtime.getContext(), 'blessedname')
-    .then(function(blessings) {
-      t.ok(blessings.chains[0][0].extension === 'blessedname',
-        'Blessing had correct extension');
-      rt.close(t.end);
-    }).catch(function(err) {
-      t.error(err);
-      rt.close(t.end);
-    });
-  });
-});
+function objectSorter(a, b) {
+  return JSON.stringify(a) < JSON.stringify(b);
+}
diff --git a/test/integration/test-blessings-util.js b/test/integration/test-blessings-util.js
index 9a98e0c..48d1a5a 100644
--- a/test/integration/test-blessings-util.js
+++ b/test/integration/test-blessings-util.js
@@ -7,7 +7,21 @@
 var config = require('./default-config');
 var security = vanadium.security;
 
-test('Test union of blessings (promise case)', function(t) {
+function validateUnionedBlessings(t, blessings) {
+  t.equal(blessings.chains.length, 2, 'Should have 2 chains');
+  t.equal(blessings.chains[0].length, 1, 'First chain has 1 cert');
+  t.equal(blessings.chains[1].length, 1, 'Second chain has 1 cert');
+  t.equal(blessings.chains[0][0].extension, 'blessedname1',
+    'Get first extension on first chain');
+  t.equal(blessings.chains[1][0].extension, 'blessedname2',
+    'Get second extension on second chain');
+  t.deepEqual(blessings.chains[0][0].publicKey, blessings.publicKey,
+    'First public key matches blessing key');
+  t.deepEqual(blessings.chains[1][0].publicKey, blessings.publicKey,
+    'Second public key matches blessing key');
+}
+
+test('Test union of blessings', function(t) {
   vanadium.init(config, function(err, runtime) {
     if (err) {
       t.end(err);
@@ -22,10 +36,7 @@
       });
     })
     .then(function(unionedBlessings) {
-      t.ok(unionedBlessings.toString().indexOf('blessedname1') >= 0,
-          'first blessing in union');
-      t.ok(unionedBlessings.toString().indexOf('blessedname2') >= 0,
-          'second blessing in union');
+      validateUnionedBlessings(t, unionedBlessings);
       runtime.close(t.end);
     }).catch(function(err) {
       runtime.close();
@@ -34,40 +45,31 @@
   });
 });
 
-test('Test union of blessings (callback case)', function(t) {
+test('Test union of blessings with differing public keys', function(t) {
   vanadium.init(config, function(err, runtime) {
     if (err) {
       t.end(err);
     }
 
-    runtime.principal.blessSelf(runtime.getContext(), 'blessedname1',
-      function(err, blessings1) {
-      if (err) {
-        t.error(err);
-        runtime.close(t.end);
-        return;
-      }
-      runtime.principal.blessSelf(runtime.getContext(), 'blessedname2',
-        function(err, blessings2) {
-        if (err) {
-          t.error(err);
-          runtime.close(t.end);
-          return;
-        }
-        security.unionOfBlessings(runtime.getContext(), blessings1,
-          blessings2, function(err, unionedBlessings) {
-          if (err) {
-            t.error(err);
-            runtime.close(t.end);
-            return;
-          }
-          t.ok(unionedBlessings.toString().indexOf('blessedname1') >= 0,
-              'first blessing in union');
-          t.ok(unionedBlessings.toString().indexOf('blessedname2') >= 0,
-              'second blessing in union');
-            runtime.close(t.end);
-        });
+    runtime.principal.blessSelf(runtime.getContext(), 'blessedname1')
+    .then(function(blessings1) {
+      return runtime.principal.blessSelf(runtime.getContext(), 'blessedname2')
+      .then(function(blessings2) {
+        // modify the public key so it doesn't match
+        blessings2.chains[0][0].publicKey[0] -= 1;
+        return security.unionOfBlessings(
+          runtime.getContext(), blessings1, blessings2);
       });
+    })
+    .then(function(unionedBlessings) {
+      runtime.close();
+      t.end('Should have failed due to public keys not matching');
+    }).catch(function(err) {
+      t.ok(err instanceof Error, 'Got error');
+      t.ok(err.toString().indexOf('cannot create union of blessings ' +
+        'bound to different public keys') !== -1,
+        'Should get message about keys differing');
+      runtime.close(t.end);
     });
   });
 });
diff --git a/test/integration/test-remote-blessings.js b/test/integration/test-remote-blessings.js
index 84d0316..4c3548b 100644
--- a/test/integration/test-remote-blessings.js
+++ b/test/integration/test-remote-blessings.js
@@ -14,7 +14,8 @@
   t.ok(Array.isArray(blessings), 'blessings is an array');
   t.ok(blessings.length > 0, 'blessings has at least one blessing');
   t.ok(defaultBlessingRegex.test(blessings[0]),
-      'blessings[0] matches the default blessing regex.');
+      'blessings[0]: ' + blessings[0] +
+      ' should match the default blessings regex ' + defaultBlessingRegex);
 }
 
 var serverName = 'foo';