text: /*
   *
   * Copyright 2015, Google Inc.
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   */
  
  'use strict';
  
  var assert = require('assert');
  
  var surface_client = require('../src/client.js');
  
  var ProtoBuf = require('protobufjs');
  
  var grpc = require('..');
  
  var math_proto = ProtoBuf.loadProtoFile(__dirname +
      '/../../proto/math/math.proto');
  
  var mathService = math_proto.lookup('math.Math');
  
  var _ = require('lodash');
  
  /**
   * This is used for testing functions with multiple asynchronous calls that
   * can happen in different orders. This should be passed the number of async
   * function invocations that can occur last, and each of those should call this
   * function's return value
   * @param {function()} done The function that should be called when a test is
   *     complete.
   * @param {number} count The number of calls to the resulting function if the
   *     test passes.
   * @return {function()} The function that should be called at the end of each
   *     sequence of asynchronous functions.
   */
  function multiDone(done, count) {
    return function() {
      count -= 1;
      if (count <= 0) {
        done();
      }
    };
  }
  
  var server_insecure_creds = grpc.ServerCredentials.createInsecure();
  
  describe('File loader', function() {
    it('Should load a proto file by default', function() {
      assert.doesNotThrow(function() {
        grpc.load(__dirname + '/test_service.proto');
      });
    });
    it('Should load a proto file with the proto format', function() {
      assert.doesNotThrow(function() {
        grpc.load(__dirname + '/test_service.proto', 'proto');
      });
    });
    it('Should load a json file with the json format', function() {
      assert.doesNotThrow(function() {
        grpc.load(__dirname + '/test_service.json', 'json');
      });
    });
    it('Should fail to load a file with an unknown format', function() {
      assert.throws(function() {
        grpc.load(__dirname + '/test_service.proto', 'fake_format');
      });
    });
  });
  describe('surface Server', function() {
    var server;
    beforeEach(function() {
      server = new grpc.Server();
    });
    afterEach(function() {
      server.forceShutdown();
    });
    it('should error if started twice', function() {
      server.start();
      assert.throws(function() {
        server.start();
      });
    });
    it('should error if a port is bound after the server starts', function() {
      server.start();
      assert.throws(function() {
        server.bind('localhost:0', grpc.ServerCredentials.createInsecure());
      });
    });
    it('should successfully shutdown if tryShutdown is called', function(done) {
      server.start();
      server.tryShutdown(done);
    });
  });
  describe('Server.prototype.addProtoService', function() {
    var server;
    var dummyImpls = {
      'div': function() {},
      'divMany': function() {},
      'fib': function() {},
      'sum': function() {}
    };
    beforeEach(function() {
      server = new grpc.Server();
    });
    afterEach(function() {
      server.forceShutdown();
    });
    it('Should succeed with a single service', function() {
      assert.doesNotThrow(function() {
        server.addProtoService(mathService, dummyImpls);
      });
    });
    it('Should fail with conflicting method names', function() {
      server.addProtoService(mathService, dummyImpls);
      assert.throws(function() {
        server.addProtoService(mathService, dummyImpls);
      });
    });
    it('Should fail if the server has been started', function() {
      server.start();
      assert.throws(function() {
        server.addProtoService(mathService, dummyImpls);
      });
    });
    describe('Default handlers', function() {
      var client;
      beforeEach(function() {
        server.addProtoService(mathService, {});
        var port = server.bind('localhost:0', server_insecure_creds);
        var Client = surface_client.makeProtobufClientConstructor(mathService);
        client = new Client('localhost:' + port,
                            grpc.credentials.createInsecure());
        server.start();
      });
      it('should respond to a unary call with UNIMPLEMENTED', function(done) {
        client.div({divisor: 4, dividend: 3}, function(error, response) {
          assert(error);
          assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED);
          done();
        });
      });
      it('should respond to a client stream with UNIMPLEMENTED', function(done) {
        var call = client.sum(function(error, respones) {
          assert(error);
          assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED);
          done();
        });
        call.end();
      });
      it('should respond to a server stream with UNIMPLEMENTED', function(done) {
        var call = client.fib({limit: 5});
        call.on('data', function(value) {
          assert.fail('No messages expected');
        });
        call.on('error', function(err) {
          assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
          done();
        });
      });
      it('should respond to a bidi call with UNIMPLEMENTED', function(done) {
        var call = client.divMany();
        call.on('data', function(value) {
          assert.fail('No messages expected');
        });
        call.on('error', function(err) {
          assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
          done();
        });
        call.end();
      });
    });
  });
  describe('Client constructor building', function() {
    var illegal_service_attrs = {
      $method : {
        path: '/illegal/$method',
        requestStream: false,
        responseStream: false,
        requestSerialize: _.identity,
        requestDeserialize: _.identity,
        responseSerialize: _.identity,
        responseDeserialize: _.identity
      }
    };
    it('Should reject method names starting with $', function() {
      assert.throws(function() {
        grpc.makeGenericClientConstructor(illegal_service_attrs);
      }, /\$/);
    });
  });
  describe('waitForClientReady', function() {
    var server;
    var port;
    var Client;
    var client;
    before(function() {
      server = new grpc.Server();
      port = server.bind('localhost:0', grpc.ServerCredentials.createInsecure());
      server.start();
      Client = surface_client.makeProtobufClientConstructor(mathService);
    });
    beforeEach(function() {
      client = new Client('localhost:' + port, grpc.credentials.createInsecure());
    });
    after(function() {
      server.forceShutdown();
    });
    it('should complete when called alone', function(done) {
      grpc.waitForClientReady(client, Infinity, function(error) {
        assert.ifError(error);
        done();
      });
    });
    it('should complete when a call is initiated', function(done) {
      grpc.waitForClientReady(client, Infinity, function(error) {
        assert.ifError(error);
        done();
      });
      var call = client.div({}, function(err, response) {});
      call.cancel();
    });
    it('should complete if called more than once', function(done) {
      done = multiDone(done, 2);
      grpc.waitForClientReady(client, Infinity, function(error) {
        assert.ifError(error);
        done();
      });
      grpc.waitForClientReady(client, Infinity, function(error) {
        assert.ifError(error);
        done();
      });
    });
    it('should complete if called when already ready', function(done) {
      grpc.waitForClientReady(client, Infinity, function(error) {
        assert.ifError(error);
        grpc.waitForClientReady(client, Infinity, function(error) {
          assert.ifError(error);
          done();
        });
      });
    });
    it('should time out if the server does not exist', function(done) {
      var bad_client = new Client('nonexistent_hostname',
                                  grpc.credentials.createInsecure());
      var deadline = new Date();
      deadline.setSeconds(deadline.getSeconds() + 1);
      grpc.waitForClientReady(bad_client, deadline, function(error) {
        assert(error);
        done();
      });
    });
  });
  describe('Echo service', function() {
    var server;
    var client;
    before(function() {
      var test_proto = ProtoBuf.loadProtoFile(__dirname + '/echo_service.proto');
      var echo_service = test_proto.lookup('EchoService');
      server = new grpc.Server();
      server.addProtoService(echo_service, {
        echo: function(call, callback) {
          callback(null, call.request);
        }
      });
      var port = server.bind('localhost:0', server_insecure_creds);
      var Client = surface_client.makeProtobufClientConstructor(echo_service);
      client = new Client('localhost:' + port, grpc.credentials.createInsecure());
      server.start();
    });
    after(function() {
      server.forceShutdown();
    });
    it('should echo the recieved message directly', function(done) {
      client.echo({value: 'test value', value2: 3}, function(error, response) {
        assert.ifError(error);
        assert.deepEqual(response, {value: 'test value', value2: 3});
        done();
      });
    });
  });
  describe('Generic client and server', function() {
    function toString(val) {
      return val.toString();
    }
    function toBuffer(str) {
      return new Buffer(str);
    }
    var string_service_attrs = {
      'capitalize' : {
        path: '/string/capitalize',
        requestStream: false,
        responseStream: false,
        requestSerialize: toBuffer,
        requestDeserialize: toString,
        responseSerialize: toBuffer,
        responseDeserialize: toString
      }
    };
    describe('String client and server', function() {
      var client;
      var server;
      before(function() {
        server = new grpc.Server();
        server.addService(string_service_attrs, {
          capitalize: function(call, callback) {
            callback(null, _.capitalize(call.request));
          }
        });
        var port = server.bind('localhost:0', server_insecure_creds);
        server.start();
        var Client = grpc.makeGenericClientConstructor(string_service_attrs);
        client = new Client('localhost:' + port,
                            grpc.credentials.createInsecure());
      });
      after(function() {
        server.forceShutdown();
      });
      it('Should respond with a capitalized string', function(done) {
        client.capitalize('abc', function(err, response) {
          assert.ifError(err);
          assert.strictEqual(response, 'Abc');
          done();
        });
      });
    });
  });
  describe('Server-side getPeer', function() {
    function toString(val) {
      return val.toString();
    }
    function toBuffer(str) {
      return new Buffer(str);
    }
    var string_service_attrs = {
      'getPeer' : {
        path: '/string/getPeer',
        requestStream: false,
        responseStream: false,
        requestSerialize: toBuffer,
        requestDeserialize: toString,
        responseSerialize: toBuffer,
        responseDeserialize: toString
      }
    };
    var client;
    var server;
    before(function() {
      server = new grpc.Server();
      server.addService(string_service_attrs, {
        getPeer: function(call, callback) {
          try {
            callback(null, call.getPeer());
          } catch (e) {
            call.emit('error', e);
          }
        }
      });
      var port = server.bind('localhost:0', server_insecure_creds);
      server.start();
      var Client = grpc.makeGenericClientConstructor(string_service_attrs);
      client = new Client('localhost:' + port,
                          grpc.credentials.createInsecure());
    });
    after(function() {
      server.forceShutdown();
    });
    it('should respond with a string representing the client', function(done) {
      client.getPeer('', function(err, response) {
        assert.ifError(err);
        // We don't expect a specific value, just that it worked without error
        done();
      });
    });
  });
  describe('Echo metadata', function() {
    var client;
    var server;
    var metadata;
    before(function() {
      var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
      var test_service = test_proto.lookup('TestService');
      server = new grpc.Server();
      server.addProtoService(test_service, {
        unary: function(call, cb) {
          call.sendMetadata(call.metadata);
          cb(null, {});
        },
        clientStream: function(stream, cb){
          stream.on('data', function(data) {});
          stream.on('end', function() {
            stream.sendMetadata(stream.metadata);
            cb(null, {});
          });
        },
        serverStream: function(stream) {
          stream.sendMetadata(stream.metadata);
          stream.end();
        },
        bidiStream: function(stream) {
          stream.on('data', function(data) {});
          stream.on('end', function() {
            stream.sendMetadata(stream.metadata);
            stream.end();
          });
        }
      });
      var port = server.bind('localhost:0', server_insecure_creds);
      var Client = surface_client.makeProtobufClientConstructor(test_service);
      client = new Client('localhost:' + port, grpc.credentials.createInsecure());
      server.start();
      metadata = new grpc.Metadata();
      metadata.set('key', 'value');
    });
    after(function() {
      server.forceShutdown();
    });
    it('with unary call', function(done) {
      var call = client.unary({}, metadata, function(err, data) {
        assert.ifError(err);
      });
      call.on('metadata', function(metadata) {
        assert.deepEqual(metadata.get('key'), ['value']);
        done();
      });
    });
    it('with client stream call', function(done) {
      var call = client.clientStream(metadata, function(err, data) {
        assert.ifError(err);
      });
      call.on('metadata', function(metadata) {
        assert.deepEqual(metadata.get('key'), ['value']);
        done();
      });
      call.end();
    });
    it('with server stream call', function(done) {
      var call = client.serverStream({}, metadata);
      call.on('data', function() {});
      call.on('metadata', function(metadata) {
        assert.deepEqual(metadata.get('key'), ['value']);
        done();
      });
    });
    it('with bidi stream call', function(done) {
      var call = client.bidiStream(metadata);
      call.on('data', function() {});
      call.on('metadata', function(metadata) {
        assert.deepEqual(metadata.get('key'), ['value']);
        done();
      });
      call.end();
    });
    it('shows the correct user-agent string', function(done) {
      var version = require('../../../package.json').version;
      var call = client.unary({}, metadata,
                              function(err, data) { assert.ifError(err); });
      call.on('metadata', function(metadata) {
        assert(_.startsWith(metadata.get('user-agent')[0],
                            'grpc-node/' + version));
        done();
      });
    });
    it('properly handles duplicate values', function(done) {
      var dup_metadata = metadata.clone();
      dup_metadata.add('key', 'value2');
      var call = client.unary({}, dup_metadata,
                              function(err, data) {assert.ifError(err); });
      call.on('metadata', function(resp_metadata) {
        // Two arrays are equal iff their symmetric difference is empty
        assert.deepEqual(_.xor(dup_metadata.get('key'), resp_metadata.get('key')),
                         []);
        done();
      });
    });
  });
  describe('Client malformed response handling', function() {
    var server;
    var client;
    var badArg = new Buffer([0xFF]);
    before(function() {
      var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
      var test_service = test_proto.lookup('TestService');
      var malformed_test_service = {
        unary: {
          path: '/TestService/Unary',
          requestStream: false,
          responseStream: false,
          requestDeserialize: _.identity,
          responseSerialize: _.identity
        },
        clientStream: {
          path: '/TestService/ClientStream',
          requestStream: true,
          responseStream: false,
          requestDeserialize: _.identity,
          responseSerialize: _.identity
        },
        serverStream: {
          path: '/TestService/ServerStream',
          requestStream: false,
          responseStream: true,
          requestDeserialize: _.identity,
          responseSerialize: _.identity
        },
        bidiStream: {
          path: '/TestService/BidiStream',
          requestStream: true,
          responseStream: true,
          requestDeserialize: _.identity,
          responseSerialize: _.identity
        }
      };
      server = new grpc.Server();
      server.addService(malformed_test_service, {
        unary: function(call, cb) {
          cb(null, badArg);
        },
        clientStream: function(stream, cb) {
          stream.on('data', function() {/* Ignore requests */});
          stream.on('end', function() {
            cb(null, badArg);
          });
        },
        serverStream: function(stream) {
          stream.write(badArg);
          stream.end();
        },
        bidiStream: function(stream) {
          stream.on('data', function() {
            // Ignore requests
            stream.write(badArg);
          });
          stream.on('end', function() {
            stream.end();
          });
        }
      });
      var port = server.bind('localhost:0', server_insecure_creds);
      var Client = surface_client.makeProtobufClientConstructor(test_service);
      client = new Client('localhost:' + port, grpc.credentials.createInsecure());
      server.start();
    });
    after(function() {
      server.forceShutdown();
    });
    it('should get an INTERNAL status with a unary call', function(done) {
      client.unary({}, function(err, data) {
        assert(err);
        assert.strictEqual(err.code, grpc.status.INTERNAL);
        done();
      });
    });
    it('should get an INTERNAL status with a client stream call', function(done) {
      var call = client.clientStream(function(err, data) {
        assert(err);
        assert.strictEqual(err.code, grpc.status.INTERNAL);
        done();
      });
      call.write({});
      call.end();
    });
    it('should get an INTERNAL status with a server stream call', function(done) {
      var call = client.serverStream({});
      call.on('data', function(){});
      call.on('error', function(err) {
        assert.strictEqual(err.code, grpc.status.INTERNAL);
        done();
      });
    });
    it('should get an INTERNAL status with a bidi stream call', function(done) {
      var call = client.bidiStream();
      call.on('data', function(){});
      call.on('error', function(err) {
        assert.strictEqual(err.code, grpc.status.INTERNAL);
        done();
      });
      call.write({});
      call.end();
    });
  });
  describe('Other conditions', function() {
    var test_service;
    var Client;
    var client;
    var server;
    var port;
    before(function() {
      var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
      test_service = test_proto.lookup('TestService');
      server = new grpc.Server();
      var trailer_metadata = new grpc.Metadata();
      trailer_metadata.add('trailer-present', 'yes');
      server.addProtoService(test_service, {
        unary: function(call, cb) {
          var req = call.request;
          if (req.error) {
            cb({code: grpc.status.UNKNOWN,
                details: 'Requested error'}, null, trailer_metadata);
          } else {
            cb(null, {count: 1}, trailer_metadata);
          }
        },
        clientStream: function(stream, cb){
          var count = 0;
          var errored;
          stream.on('data', function(data) {
            if (data.error) {
              errored = true;
              cb(new Error('Requested error'), null, trailer_metadata);
            } else {
              count += 1;
            }
          });
          stream.on('end', function() {
            if (!errored) {
              cb(null, {count: count}, trailer_metadata);
            }
          });
        },
        serverStream: function(stream) {
          var req = stream.request;
          if (req.error) {
            var err = {code: grpc.status.UNKNOWN,
                       details: 'Requested error'};
            err.metadata = trailer_metadata;
            stream.emit('error', err);
          } else {
            for (var i = 0; i < 5; i++) {
              stream.write({count: i});
            }
            stream.end(trailer_metadata);
          }
        },
        bidiStream: function(stream) {
          var count = 0;
          stream.on('data', function(data) {
            if (data.error) {
              var err = new Error('Requested error');
              err.metadata = trailer_metadata.clone();
              err.metadata.add('count', '' + count);
              stream.emit('error', err);
            } else {
              stream.write({count: count});
              count += 1;
            }
          });
          stream.on('end', function() {
            stream.end(trailer_metadata);
          });
        }
      });
      port = server.bind('localhost:0', server_insecure_creds);
      Client = surface_client.makeProtobufClientConstructor(test_service);
      client = new Client('localhost:' + port, grpc.credentials.createInsecure());
      server.start();
    });
    after(function() {
      server.forceShutdown();
    });
    it('channel.getTarget should be available', function() {
      assert.strictEqual(typeof grpc.getClientChannel(client).getTarget(),
                         'string');
    });
    it('client should be able to pause and resume a stream', function(done) {
      var call = client.bidiStream();
      call.on('data', function(data) {
        assert(data.count < 3);
        call.pause();
        setTimeout(function() {
          call.resume();
        }, 10);
      });
      call.on('end', function() {
        done();
      });
      call.write({});
      call.write({});
      call.write({});
      call.end();
    });
    describe('Server recieving bad input', function() {
      var misbehavingClient;
      var badArg = new Buffer([0xFF]);
      before(function() {
        var test_service_attrs = {
          unary: {
            path: '/TestService/Unary',
            requestStream: false,
            responseStream: false,
            requestSerialize: _.identity,
            responseDeserialize: _.identity
          },
          clientStream: {
            path: '/TestService/ClientStream',
            requestStream: true,
            responseStream: false,
            requestSerialize: _.identity,
            responseDeserialize: _.identity
          },
          serverStream: {
            path: '/TestService/ServerStream',
            requestStream: false,
            responseStream: true,
            requestSerialize: _.identity,
            responseDeserialize: _.identity
          },
          bidiStream: {
            path: '/TestService/BidiStream',
            requestStream: true,
            responseStream: true,
            requestSerialize: _.identity,
            responseDeserialize: _.identity
          }
        };
        var Client = surface_client.makeClientConstructor(test_service_attrs,
                                                          'TestService');
        misbehavingClient = new Client('localhost:' + port,
                                       grpc.credentials.createInsecure());
      });
      it('should respond correctly to a unary call', function(done) {
        misbehavingClient.unary(badArg, function(err, data) {
          assert(err);
          assert.strictEqual(err.code, grpc.status.INTERNAL);
          done();
        });
      });
      it('should respond correctly to a client stream', function(done) {
        var call = misbehavingClient.clientStream(function(err, data) {
          assert(err);
          assert.strictEqual(err.code, grpc.status.INTERNAL);
          done();
        });
        call.write(badArg);
        // TODO(mlumish): Remove call.end()
        call.end();
      });
      it('should respond correctly to a server stream', function(done) {
        var call = misbehavingClient.serverStream(badArg);
        call.on('data', function(data) {
          assert.fail(data, null, 'Unexpected data', '===');
        });
        call.on('error', function(err) {
          assert.strictEqual(err.code, grpc.status.INTERNAL);
          done();
        });
      });
      it('should respond correctly to a bidi stream', function(done) {
        var call = misbehavingClient.bidiStream();
        call.on('data', function(data) {
          assert.fail(data, null, 'Unexpected data', '===');
        });
        call.on('error', function(err) {
          assert.strictEqual(err.code, grpc.status.INTERNAL);
          done();
        });
        call.write(badArg);
        // TODO(mlumish): Remove call.end()
        call.end();
      });
    });
    describe('Trailing metadata', function() {
      it('should be present when a unary call succeeds', function(done) {
        var call = client.unary({error: false}, function(err, data) {
          assert.ifError(err);
        });
        call.on('status', function(status) {
          assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
      it('should be present when a unary call fails', function(done) {
        var call = client.unary({error: true}, function(err, data) {
          assert(err);
        });
        call.on('status', function(status) {
          assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
      it('should be present when a client stream call succeeds', function(done) {
        var call = client.clientStream(function(err, data) {
          assert.ifError(err);
        });
        call.write({error: false});
        call.write({error: false});
        call.end();
        call.on('status', function(status) {
          assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
      it('should be present when a client stream call fails', function(done) {
        var call = client.clientStream(function(err, data) {
          assert(err);
        });
        call.write({error: false});
        call.write({error: true});
        call.end();
        call.on('status', function(status) {
          assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
      it('should be present when a server stream call succeeds', function(done) {
        var call = client.serverStream({error: false});
        call.on('data', function(){});
        call.on('status', function(status) {
          assert.strictEqual(status.code, grpc.status.OK);
          assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
      it('should be present when a server stream call fails', function(done) {
        var call = client.serverStream({error: true});
        call.on('data', function(){});
        call.on('error', function(error) {
          assert.deepEqual(error.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
      it('should be present when a bidi stream succeeds', function(done) {
        var call = client.bidiStream();
        call.write({error: false});
        call.write({error: false});
        call.end();
        call.on('data', function(){});
        call.on('status', function(status) {
          assert.strictEqual(status.code, grpc.status.OK);
          assert.deepEqual(status.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
      it('should be present when a bidi stream fails', function(done) {
        var call = client.bidiStream();
        call.write({error: false});
        call.write({error: true});
        call.end();
        call.on('data', function(){});
        call.on('error', function(error) {
          assert.deepEqual(error.metadata.get('trailer-present'), ['yes']);
          done();
        });
      });
    });
    describe('Error object should contain the status', function() {
      it('for a unary call', function(done) {
        client.unary({error: true}, function(err, data) {
          assert(err);
          assert.strictEqual(err.code, grpc.status.UNKNOWN);
          assert.strictEqual(err.message, 'Requested error');
          done();
        });
      });
      it('for a client stream call', function(done) {
        var call = client.clientStream(function(err, data) {
          assert(err);
          assert.strictEqual(err.code, grpc.status.UNKNOWN);
          assert.strictEqual(err.message, 'Requested error');
          done();
        });
        call.write({error: false});
        call.write({error: true});
        call.end();
      });
      it('for a server stream call', function(done) {
        var call = client.serverStream({error: true});
        call.on('data', function(){});
        call.on('error', function(error) {
          assert.strictEqual(error.code, grpc.status.UNKNOWN);
          assert.strictEqual(error.message, 'Requested error');
          done();
        });
      });
      it('for a bidi stream call', function(done) {
        var call = client.bidiStream();
        call.write({error: false});
        call.write({error: true});
        call.end();
        call.on('data', function(){});
        call.on('error', function(error) {
          assert.strictEqual(error.code, grpc.status.UNKNOWN);
          assert.strictEqual(error.message, 'Requested error');
          done();
        });
      });
    });
    describe('call.getPeer should return the peer', function() {
      it('for a unary call', function(done) {
        var call = client.unary({error: false}, function(err, data) {
          assert.ifError(err);
          done();
        });
        assert.strictEqual(typeof call.getPeer(), 'string');
      });
      it('for a client stream call', function(done) {
        var call = client.clientStream(function(err, data) {
          assert.ifError(err);
          done();
        });
        assert.strictEqual(typeof call.getPeer(), 'string');
        call.write({error: false});
        call.end();
      });
      it('for a server stream call', function(done) {
        var call = client.serverStream({error: false});
        assert.strictEqual(typeof call.getPeer(), 'string');
        call.on('data', function(){});
        call.on('status', function(status) {
          assert.strictEqual(status.code, grpc.status.OK);
          done();
        });
      });
      it('for a bidi stream call', function(done) {
        var call = client.bidiStream();
        assert.strictEqual(typeof call.getPeer(), 'string');
        call.write({error: false});
        call.end();
        call.on('data', function(){});
        call.on('status', function(status) {
          done();
        });
      });
    });
  });
  describe('Call propagation', function() {
    var proxy;
    var proxy_impl;
  
    var test_service;
    var Client;
    var client;
    var server;
    before(function() {
      var test_proto = ProtoBuf.loadProtoFile(__dirname + '/test_service.proto');
      test_service = test_proto.lookup('TestService');
      server = new grpc.Server();
      server.addProtoService(test_service, {
        unary: function(call) {},
        clientStream: function(stream) {},
        serverStream: function(stream) {},
        bidiStream: function(stream) {}
      });
      var port = server.bind('localhost:0', server_insecure_creds);
      Client = surface_client.makeProtobufClientConstructor(test_service);
      client = new Client('localhost:' + port, grpc.credentials.createInsecure());
      server.start();
    });
    after(function() {
      server.forceShutdown();
    });
    beforeEach(function() {
      proxy = new grpc.Server();
      proxy_impl = {
        unary: function(call) {},
        clientStream: function(stream) {},
        serverStream: function(stream) {},
        bidiStream: function(stream) {}
      };
    });
    afterEach(function() {
      proxy.forceShutdown();
    });
    describe('Cancellation', function() {
      it('With a unary call', function(done) {
        done = multiDone(done, 2);
        var call;
        proxy_impl.unary = function(parent, callback) {
          client.unary(parent.request, {parent: parent}, function(err, value) {
            try {
              assert(err);
              assert.strictEqual(err.code, grpc.status.CANCELLED);
            } finally {
              callback(err, value);
              done();
            }
          });
          call.cancel();
        };
        proxy.addProtoService(test_service, proxy_impl);
        var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
        proxy.start();
        var proxy_client = new Client('localhost:' + proxy_port,
                                      grpc.credentials.createInsecure());
        call = proxy_client.unary({}, function(err, value) { done(); });
      });
      it('With a client stream call', function(done) {
        done = multiDone(done, 2);
        var call;
        proxy_impl.clientStream = function(parent, callback) {
          client.clientStream({parent: parent}, function(err, value) {
            try {
              assert(err);
              assert.strictEqual(err.code, grpc.status.CANCELLED);
            } finally {
              callback(err, value);
              done();
            }
          });
          call.cancel();
        };
        proxy.addProtoService(test_service, proxy_impl);
        var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
        proxy.start();
        var proxy_client = new Client('localhost:' + proxy_port,
                                      grpc.credentials.createInsecure());
        call = proxy_client.clientStream(function(err, value) { done(); });
      });
      it('With a server stream call', function(done) {
        done = multiDone(done, 2);
        var call;
        proxy_impl.serverStream = function(parent) {
          var child = client.serverStream(parent.request, {parent: parent});
          child.on('data', function() {});
          child.on('error', function(err) {
            assert(err);
            assert.strictEqual(err.code, grpc.status.CANCELLED);
            done();
          });
          call.cancel();
        };
        proxy.addProtoService(test_service, proxy_impl);
        var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
        proxy.start();
        var proxy_client = new Client('localhost:' + proxy_port,
                                      grpc.credentials.createInsecure());
        call = proxy_client.serverStream({});
        call.on('data', function() {});
        call.on('error', function(err) {
          done();
        });
      });
      it('With a bidi stream call', function(done) {
        done = multiDone(done, 2);
        var call;
        proxy_impl.bidiStream = function(parent) {
          var child = client.bidiStream({parent: parent});
          child.on('data', function() {});
          child.on('error', function(err) {
            assert(err);
            assert.strictEqual(err.code, grpc.status.CANCELLED);
            done();
          });
          call.cancel();
        };
        proxy.addProtoService(test_service, proxy_impl);
        var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
        proxy.start();
        var proxy_client = new Client('localhost:' + proxy_port,
                                      grpc.credentials.createInsecure());
        call = proxy_client.bidiStream();
        call.on('data', function() {});
        call.on('error', function(err) {
          done();
        });
      });
    });
    describe('Deadline', function() {
      /* jshint bitwise:false */
      var deadline_flags = (grpc.propagate.DEFAULTS &
          ~grpc.propagate.CANCELLATION);
      it('With a client stream call', function(done) {
        done = multiDone(done, 2);
        proxy_impl.clientStream = function(parent, callback) {
          var options = {parent: parent, propagate_flags: deadline_flags};
          client.clientStream(options, function(err, value) {
            try {
              assert(err);
              assert(err.code === grpc.status.DEADLINE_EXCEEDED ||
                  err.code === grpc.status.INTERNAL);
            } finally {
              callback(err, value);
              done();
            }
          });
        };
        proxy.addProtoService(test_service, proxy_impl);
        var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
        proxy.start();
        var proxy_client = new Client('localhost:' + proxy_port,
                                      grpc.credentials.createInsecure());
        var deadline = new Date();
        deadline.setSeconds(deadline.getSeconds() + 1);
        proxy_client.clientStream({deadline: deadline}, function(err, value) {
          done();
        });
      });
      it('With a bidi stream call', function(done) {
        done = multiDone(done, 2);
        proxy_impl.bidiStream = function(parent) {
          var child = client.bidiStream(
              {parent: parent, propagate_flags: deadline_flags});
          child.on('data', function() {});
          child.on('error', function(err) {
            assert(err);
            assert(err.code === grpc.status.DEADLINE_EXCEEDED ||
                err.code === grpc.status.INTERNAL);
            done();
          });
        };
        proxy.addProtoService(test_service, proxy_impl);
        var proxy_port = proxy.bind('localhost:0', server_insecure_creds);
        proxy.start();
        var proxy_client = new Client('localhost:' + proxy_port,
                                      grpc.credentials.createInsecure());
        var deadline = new Date();
        deadline.setSeconds(deadline.getSeconds() + 1);
        var call = proxy_client.bidiStream({deadline: deadline});
        call.on('data', function() {});
        call.on('error', function(err) {
          done();
        });
      });
    });
  });
  describe('Cancelling surface client', function() {
    var client;
    var server;
    before(function() {
      server = new grpc.Server();
      server.addProtoService(mathService, {
        'div': function(stream) {},
        'divMany': function(stream) {},
        'fib': function(stream) {},
        'sum': function(stream) {}
      });
      var port = server.bind('localhost:0', server_insecure_creds);
      var Client = surface_client.makeProtobufClientConstructor(mathService);
      client = new Client('localhost:' + port, grpc.credentials.createInsecure());
      server.start();
    });
    after(function() {
      server.forceShutdown();
    });
    it('Should correctly cancel a unary call', function(done) {
      var call = client.div({'divisor': 0, 'dividend': 0}, function(err, resp) {
        assert.strictEqual(err.code, surface_client.status.CANCELLED);
        done();
      });
      call.cancel();
    });
    it('Should correctly cancel a client stream call', function(done) {
      var call = client.sum(function(err, resp) {
        assert.strictEqual(err.code, surface_client.status.CANCELLED);
        done();
      });
      call.cancel();
    });
    it('Should correctly cancel a server stream call', function(done) {
      var call = client.fib({'limit': 5});
      call.on('data', function() {});
      call.on('error', function(error) {
        assert.strictEqual(error.code, surface_client.status.CANCELLED);
        done();
      });
      call.cancel();
    });
    it('Should correctly cancel a bidi stream call', function(done) {
      var call = client.divMany();
      call.on('data', function() {});
      call.on('error', function(error) {
        assert.strictEqual(error.code, surface_client.status.CANCELLED);
        done();
      });
      call.cancel();
    });
  });
  