aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/node
diff options
context:
space:
mode:
Diffstat (limited to 'src/node')
-rw-r--r--src/node/ext/call.cc18
-rw-r--r--src/node/ext/call.h13
-rw-r--r--src/node/ext/call_credentials.cc13
-rw-r--r--src/node/ext/channel.cc118
-rw-r--r--src/node/ext/channel.h5
-rw-r--r--src/node/ext/channel_credentials.cc13
-rw-r--r--src/node/ext/server.cc49
-rw-r--r--src/node/ext/server_credentials.cc15
-rw-r--r--src/node/index.js8
-rw-r--r--src/node/interop/interop_client.js1
-rw-r--r--src/node/src/client.js1
-rw-r--r--src/node/src/common.js11
-rw-r--r--src/node/src/credentials.js3
-rw-r--r--src/node/test/call_test.js54
-rw-r--r--src/node/test/channel_test.js7
-rw-r--r--src/node/test/credentials_test.js62
-rw-r--r--src/node/test/health_test.js20
-rw-r--r--src/node/test/interop_sanity_test.js2
-rw-r--r--src/node/test/surface_test.js175
19 files changed, 440 insertions, 148 deletions
diff --git a/src/node/ext/call.cc b/src/node/ext/call.cc
index b63e294f9a..fe11905109 100644
--- a/src/node/ext/call.cc
+++ b/src/node/ext/call.cc
@@ -83,6 +83,18 @@ using v8::Value;
Callback *Call::constructor;
Persistent<FunctionTemplate> Call::fun_tpl;
+/**
+ * Helper function for throwing errors with a grpc_call_error value.
+ * Modified from the answer by Gus Goose to
+ * http://stackoverflow.com/questions/31794200.
+ */
+Local<Value> nanErrorWithCode(const char *msg, grpc_call_error code) {
+ EscapableHandleScope scope;
+ Local<Object> err = Nan::Error(msg).As<Object>();
+ Nan::Set(err, Nan::New("code").ToLocalChecked(), Nan::New<Uint32>(code));
+ return scope.Escape(err);
+}
+
bool EndsWith(const char *str, const char *substr) {
return strcmp(str+strlen(str)-strlen(substr), substr) == 0;
}
@@ -712,7 +724,11 @@ NAN_METHOD(Call::CancelWithStatus) {
Call *call = ObjectWrap::Unwrap<Call>(info.This());
grpc_status_code code = static_cast<grpc_status_code>(
Nan::To<uint32_t>(info[0]).FromJust());
- Utf8String details(info[0]);
+ if (code == GRPC_STATUS_OK) {
+ return Nan::ThrowRangeError(
+ "cancelWithStatus cannot be called with OK status");
+ }
+ Utf8String details(info[1]);
grpc_call_cancel_with_status(call->wrapped_call, code, *details, NULL);
}
diff --git a/src/node/ext/call.h b/src/node/ext/call.h
index dd6c38e4f8..1e3c3ba18d 100644
--- a/src/node/ext/call.h
+++ b/src/node/ext/call.h
@@ -53,18 +53,7 @@ using std::shared_ptr;
typedef Nan::Persistent<v8::Value, Nan::CopyablePersistentTraits<v8::Value>> PersistentValue;
-/**
- * Helper function for throwing errors with a grpc_call_error value.
- * Modified from the answer by Gus Goose to
- * http://stackoverflow.com/questions/31794200.
- */
-inline v8::Local<v8::Value> nanErrorWithCode(const char *msg,
- grpc_call_error code) {
- Nan::EscapableHandleScope scope;
- v8::Local<v8::Object> err = Nan::Error(msg).As<v8::Object>();
- Nan::Set(err, Nan::New("code").ToLocalChecked(), Nan::New<v8::Uint32>(code));
- return scope.Escape(err);
-}
+v8::Local<v8::Value> nanErrorWithCode(const char *msg, grpc_call_error code);
v8::Local<v8::Value> ParseMetadata(const grpc_metadata_array *metadata_array);
diff --git a/src/node/ext/call_credentials.cc b/src/node/ext/call_credentials.cc
index 839bb567e4..ff16a1f122 100644
--- a/src/node/ext/call_credentials.cc
+++ b/src/node/ext/call_credentials.cc
@@ -126,16 +126,9 @@ NAN_METHOD(CallCredentials::New) {
info.GetReturnValue().Set(info.This());
return;
} else {
- const int argc = 1;
- Local<Value> argv[argc] = {info[0]};
- MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
- argc, argv);
- if (maybe_instance.IsEmpty()) {
- // There's probably a pending exception
- return;
- } else {
- info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
- }
+ // This should never be called directly
+ return Nan::ThrowTypeError(
+ "CallCredentials can only be created with the provided functions");
}
}
diff --git a/src/node/ext/channel.cc b/src/node/ext/channel.cc
index a328c01713..584a0cf8ab 100644
--- a/src/node/ext/channel.cc
+++ b/src/node/ext/channel.cc
@@ -71,6 +71,72 @@ using v8::Value;
Callback *Channel::constructor;
Persistent<FunctionTemplate> Channel::fun_tpl;
+bool ParseChannelArgs(Local<Value> args_val,
+ grpc_channel_args **channel_args_ptr) {
+ if (args_val->IsUndefined() || args_val->IsNull()) {
+ *channel_args_ptr = NULL;
+ return true;
+ }
+ if (!args_val->IsObject()) {
+ *channel_args_ptr = NULL;
+ return false;
+ }
+ grpc_channel_args *channel_args = reinterpret_cast<grpc_channel_args*>(
+ malloc(sizeof(channel_args)));
+ *channel_args_ptr = channel_args;
+ Local<Object> args_hash = Nan::To<Object>(args_val).ToLocalChecked();
+ Local<Array> keys = Nan::GetOwnPropertyNames(args_hash).ToLocalChecked();
+ channel_args->num_args = keys->Length();
+ channel_args->args = reinterpret_cast<grpc_arg *>(
+ calloc(channel_args->num_args, sizeof(grpc_arg)));
+ for (unsigned int i = 0; i < channel_args->num_args; i++) {
+ Local<Value> key = Nan::Get(keys, i).ToLocalChecked();
+ Utf8String key_str(key);
+ if (*key_str == NULL) {
+ // Key string onversion failed
+ return false;
+ }
+ Local<Value> value = Nan::Get(args_hash, key).ToLocalChecked();
+ if (value->IsInt32()) {
+ channel_args->args[i].type = GRPC_ARG_INTEGER;
+ channel_args->args[i].value.integer = Nan::To<int32_t>(value).FromJust();
+ } else if (value->IsString()) {
+ Utf8String val_str(value);
+ channel_args->args[i].type = GRPC_ARG_STRING;
+ channel_args->args[i].value.string = reinterpret_cast<char*>(
+ calloc(val_str.length() + 1,sizeof(char)));
+ memcpy(channel_args->args[i].value.string,
+ *val_str, val_str.length() + 1);
+ } else {
+ // The value does not match either of the accepted types
+ return false;
+ }
+ channel_args->args[i].key = reinterpret_cast<char*>(
+ calloc(key_str.length() + 1, sizeof(char)));
+ memcpy(channel_args->args[i].key, *key_str, key_str.length() + 1);
+ }
+ return true;
+}
+
+void DeallocateChannelArgs(grpc_channel_args *channel_args) {
+ if (channel_args == NULL) {
+ return;
+ }
+ for (size_t i = 0; i < channel_args->num_args; i++) {
+ if (channel_args->args[i].key == NULL) {
+ /* NULL key implies that this argument and all subsequent arguments failed
+ * to parse */
+ break;
+ }
+ free(channel_args->args[i].key);
+ if (channel_args->args[i].type == GRPC_ARG_STRING) {
+ free(channel_args->args[i].value.string);
+ }
+ }
+ free(channel_args->args);
+ free(channel_args);
+}
+
Channel::Channel(grpc_channel *channel) : wrapped_channel(channel) {}
Channel::~Channel() {
@@ -119,49 +185,11 @@ NAN_METHOD(Channel::New) {
ChannelCredentials *creds_object = ObjectWrap::Unwrap<ChannelCredentials>(
Nan::To<Object>(info[1]).ToLocalChecked());
creds = creds_object->GetWrappedCredentials();
- grpc_channel_args *channel_args_ptr;
- if (info[2]->IsUndefined()) {
- channel_args_ptr = NULL;
- wrapped_channel = grpc_insecure_channel_create(*host, NULL, NULL);
- } else if (info[2]->IsObject()) {
- Local<Object> args_hash = Nan::To<Object>(info[2]).ToLocalChecked();
- Local<Array> keys(Nan::GetOwnPropertyNames(args_hash).ToLocalChecked());
- grpc_channel_args channel_args;
- channel_args.num_args = keys->Length();
- channel_args.args = reinterpret_cast<grpc_arg *>(
- calloc(channel_args.num_args, sizeof(grpc_arg)));
- /* These are used to keep all strings until then end of the block, then
- destroy them */
- std::vector<Nan::Utf8String *> key_strings(keys->Length());
- std::vector<Nan::Utf8String *> value_strings(keys->Length());
- for (unsigned int i = 0; i < channel_args.num_args; i++) {
- MaybeLocal<String> maybe_key = Nan::To<String>(
- Nan::Get(keys, i).ToLocalChecked());
- if (maybe_key.IsEmpty()) {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg keys must be strings");
- }
- Local<String> current_key = maybe_key.ToLocalChecked();
- Local<Value> current_value = Nan::Get(args_hash,
- current_key).ToLocalChecked();
- key_strings[i] = new Nan::Utf8String(current_key);
- channel_args.args[i].key = **key_strings[i];
- if (current_value->IsInt32()) {
- channel_args.args[i].type = GRPC_ARG_INTEGER;
- channel_args.args[i].value.integer = Nan::To<int32_t>(
- current_value).FromJust();
- } else if (current_value->IsString()) {
- channel_args.args[i].type = GRPC_ARG_STRING;
- value_strings[i] = new Nan::Utf8String(current_value);
- channel_args.args[i].value.string = **value_strings[i];
- } else {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg values must be strings");
- }
- }
- channel_args_ptr = &channel_args;
- } else {
- return Nan::ThrowTypeError("Channel expects a string and an object");
+ grpc_channel_args *channel_args_ptr = NULL;
+ if (!ParseChannelArgs(info[2], &channel_args_ptr)) {
+ DeallocateChannelArgs(channel_args_ptr);
+ return Nan::ThrowTypeError("Channel options must be an object with "
+ "string keys and integer or string values");
}
if (creds == NULL) {
wrapped_channel = grpc_insecure_channel_create(*host, channel_args_ptr,
@@ -170,9 +198,7 @@ NAN_METHOD(Channel::New) {
wrapped_channel =
grpc_secure_channel_create(creds, *host, channel_args_ptr, NULL);
}
- if (channel_args_ptr != NULL) {
- free(channel_args_ptr->args);
- }
+ DeallocateChannelArgs(channel_args_ptr);
Channel *channel = new Channel(wrapped_channel);
channel->Wrap(info.This());
info.GetReturnValue().Set(info.This());
diff --git a/src/node/ext/channel.h b/src/node/ext/channel.h
index 0062fd03f4..9ec28e15af 100644
--- a/src/node/ext/channel.h
+++ b/src/node/ext/channel.h
@@ -41,6 +41,11 @@
namespace grpc {
namespace node {
+bool ParseChannelArgs(v8::Local<v8::Value> args_val,
+ grpc_channel_args **channel_args_ptr);
+
+void DeallocateChannelArgs(grpc_channel_args *channel_args);
+
/* Wrapper class for grpc_channel structs */
class Channel : public Nan::ObjectWrap {
public:
diff --git a/src/node/ext/channel_credentials.cc b/src/node/ext/channel_credentials.cc
index 3d47ff293d..7ca3b9816c 100644
--- a/src/node/ext/channel_credentials.cc
+++ b/src/node/ext/channel_credentials.cc
@@ -127,16 +127,9 @@ NAN_METHOD(ChannelCredentials::New) {
info.GetReturnValue().Set(info.This());
return;
} else {
- const int argc = 1;
- Local<Value> argv[argc] = {info[0]};
- MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
- argc, argv);
- if (maybe_instance.IsEmpty()) {
- // There's probably a pending exception
- return;
- } else {
- info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
- }
+ // This should never be called directly
+ return Nan::ThrowTypeError(
+ "ChannelCredentials can only be created with the provided functions");
}
}
diff --git a/src/node/ext/server.cc b/src/node/ext/server.cc
index 87363fc446..b9e1fe9160 100644
--- a/src/node/ext/server.cc
+++ b/src/node/ext/server.cc
@@ -182,49 +182,14 @@ NAN_METHOD(Server::New) {
}
grpc_server *wrapped_server;
grpc_completion_queue *queue = CompletionQueueAsyncWorker::GetQueue();
- if (info[0]->IsUndefined()) {
- wrapped_server = grpc_server_create(NULL, NULL);
- } else if (info[0]->IsObject()) {
- Local<Object> args_hash = Nan::To<Object>(info[0]).ToLocalChecked();
- Local<Array> keys = Nan::GetOwnPropertyNames(args_hash).ToLocalChecked();
- grpc_channel_args channel_args;
- channel_args.num_args = keys->Length();
- channel_args.args = reinterpret_cast<grpc_arg *>(
- calloc(channel_args.num_args, sizeof(grpc_arg)));
- /* These are used to keep all strings until then end of the block, then
- destroy them */
- std::vector<Utf8String *> key_strings(keys->Length());
- std::vector<Utf8String *> value_strings(keys->Length());
- for (unsigned int i = 0; i < channel_args.num_args; i++) {
- MaybeLocal<String> maybe_key = Nan::To<String>(
- Nan::Get(keys, i).ToLocalChecked());
- if (maybe_key.IsEmpty()) {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg keys must be strings");
- }
- Local<String> current_key = maybe_key.ToLocalChecked();
- Local<Value> current_value = Nan::Get(args_hash,
- current_key).ToLocalChecked();
- key_strings[i] = new Utf8String(current_key);
- channel_args.args[i].key = **key_strings[i];
- if (current_value->IsInt32()) {
- channel_args.args[i].type = GRPC_ARG_INTEGER;
- channel_args.args[i].value.integer = Nan::To<int32_t>(
- current_value).FromJust();
- } else if (current_value->IsString()) {
- channel_args.args[i].type = GRPC_ARG_STRING;
- value_strings[i] = new Utf8String(current_value);
- channel_args.args[i].value.string = **value_strings[i];
- } else {
- free(channel_args.args);
- return Nan::ThrowTypeError("Arg values must be strings");
- }
- }
- wrapped_server = grpc_server_create(&channel_args, NULL);
- free(channel_args.args);
- } else {
- return Nan::ThrowTypeError("Server expects an object");
+ grpc_channel_args *channel_args;
+ if (!ParseChannelArgs(info[0], &channel_args)) {
+ DeallocateChannelArgs(channel_args);
+ return Nan::ThrowTypeError("Server options must be an object with "
+ "string keys and integer or string values");
}
+ wrapped_server = grpc_server_create(channel_args, NULL);
+ DeallocateChannelArgs(channel_args);
grpc_server_register_completion_queue(wrapped_server, queue, NULL);
Server *server = new Server(wrapped_server);
server->Wrap(info.This());
diff --git a/src/node/ext/server_credentials.cc b/src/node/ext/server_credentials.cc
index 5e922bd877..e6c55e263c 100644
--- a/src/node/ext/server_credentials.cc
+++ b/src/node/ext/server_credentials.cc
@@ -117,7 +117,7 @@ NAN_METHOD(ServerCredentials::New) {
if (info.IsConstructCall()) {
if (!info[0]->IsExternal()) {
return Nan::ThrowTypeError(
- "ServerCredentials can only be created with the provide functions");
+ "ServerCredentials can only be created with the provided functions");
}
Local<External> ext = info[0].As<External>();
grpc_server_credentials *creds_value =
@@ -126,16 +126,9 @@ NAN_METHOD(ServerCredentials::New) {
credentials->Wrap(info.This());
info.GetReturnValue().Set(info.This());
} else {
- const int argc = 1;
- Local<Value> argv[argc] = {info[0]};
- MaybeLocal<Object> maybe_instance = constructor->GetFunction()->NewInstance(
- argc, argv);
- if (maybe_instance.IsEmpty()) {
- // There's probably a pending exception
- return;
- } else {
- info.GetReturnValue().Set(maybe_instance.ToLocalChecked());
- }
+ // This should never be called directly
+ return Nan::ThrowTypeError(
+ "ServerCredentials can only be created with the provided functions");
}
}
diff --git a/src/node/index.js b/src/node/index.js
index 591d9dd915..0d1a7fd887 100644
--- a/src/node/index.js
+++ b/src/node/index.js
@@ -33,6 +33,14 @@
'use strict';
+var path = require('path');
+
+var SSL_ROOTS_PATH = path.resolve(__dirname, '..', '..', 'etc', 'roots.pem');
+
+if (!process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH) {
+ process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH = SSL_ROOTS_PATH;
+}
+
var _ = require('lodash');
var ProtoBuf = require('protobufjs');
diff --git a/src/node/interop/interop_client.js b/src/node/interop/interop_client.js
index cb55083d1a..b5061895cf 100644
--- a/src/node/interop/interop_client.js
+++ b/src/node/interop/interop_client.js
@@ -35,7 +35,6 @@
var fs = require('fs');
var path = require('path');
-var _ = require('lodash');
var grpc = require('..');
var testProto = grpc.load({
root: __dirname + '/../../..',
diff --git a/src/node/src/client.js b/src/node/src/client.js
index 909376e766..596ea5ebb0 100644
--- a/src/node/src/client.js
+++ b/src/node/src/client.js
@@ -661,6 +661,7 @@ exports.waitForClientReady = function(client, deadline, callback) {
var checkState = function(err) {
if (err) {
callback(new Error('Failed to connect before the deadline'));
+ return;
}
var new_state = client.$channel.getConnectivityState(true);
if (new_state === grpc.connectivityState.READY) {
diff --git a/src/node/src/common.js b/src/node/src/common.js
index 5551ebeec8..ebaaa13db0 100644
--- a/src/node/src/common.js
+++ b/src/node/src/common.js
@@ -87,14 +87,9 @@ exports.fullyQualifiedName = function fullyQualifiedName(value) {
return '';
}
var name = value.name;
- if (value.className === 'Service.RPCMethod') {
- name = _.capitalize(name);
- }
- if (value.hasOwnProperty('parent')) {
- var parent_name = fullyQualifiedName(value.parent);
- if (parent_name !== '') {
- name = parent_name + '.' + name;
- }
+ var parent_name = fullyQualifiedName(value.parent);
+ if (parent_name !== '') {
+ name = parent_name + '.' + name;
}
return name;
};
diff --git a/src/node/src/credentials.js b/src/node/src/credentials.js
index ddc094f896..ff10a22e6a 100644
--- a/src/node/src/credentials.js
+++ b/src/node/src/credentials.js
@@ -99,6 +99,9 @@ exports.createFromMetadataGenerator = function(metadata_generator) {
if (error.hasOwnProperty('code')) {
code = error.code;
}
+ if (!metadata) {
+ metadata = new Metadata();
+ }
}
callback(code, message, metadata._getCoreRepresentation());
});
diff --git a/src/node/test/call_test.js b/src/node/test/call_test.js
index c316fe7f10..f1f86b35db 100644
--- a/src/node/test/call_test.js
+++ b/src/node/test/call_test.js
@@ -107,6 +107,23 @@ describe('call', function() {
new grpc.Call(channel, 'method', 'now');
}, TypeError);
});
+ it('should succeed without the new keyword', function() {
+ assert.doesNotThrow(function() {
+ var call = grpc.Call(channel, 'method', new Date());
+ assert(call instanceof grpc.Call);
+ });
+ });
+ });
+ describe('deadline', function() {
+ it('should time out immediately with negative deadline', function(done) {
+ var call = new grpc.Call(channel, 'method', -Infinity);
+ var batch = {};
+ batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+ call.startBatch(batch, function(err, response) {
+ assert.strictEqual(response.status.code, grpc.status.DEADLINE_EXCEEDED);
+ done();
+ });
+ });
});
describe('startBatch', function() {
it('should fail without an object and a function', function() {
@@ -192,6 +209,43 @@ describe('call', function() {
});
});
});
+ describe('cancelWithStatus', function() {
+ it('should reject anything other than an integer and a string', function() {
+ assert.doesNotThrow(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus(1, 'details');
+ });
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus();
+ });
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus('');
+ });
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus(5, {});
+ });
+ });
+ it('should reject the OK status code', function() {
+ assert.throws(function() {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ call.cancelWithStatus(0, 'details');
+ });
+ });
+ it('should result in the call ending with a status', function(done) {
+ var call = new grpc.Call(channel, 'method', getDeadline(1));
+ var batch = {};
+ batch[grpc.opType.RECV_STATUS_ON_CLIENT] = true;
+ call.startBatch(batch, function(err, response) {
+ assert.strictEqual(response.status.code, 5);
+ assert.strictEqual(response.status.details, 'details');
+ done();
+ });
+ call.cancelWithStatus(5, 'details');
+ });
+ });
describe('getPeer', function() {
it('should return a string', function() {
var call = new grpc.Call(channel, 'method', getDeadline(1));
diff --git a/src/node/test/channel_test.js b/src/node/test/channel_test.js
index 05269f7b6e..7163a5fb5e 100644
--- a/src/node/test/channel_test.js
+++ b/src/node/test/channel_test.js
@@ -104,6 +104,12 @@ describe('channel', function() {
new grpc.Channel('hostname', insecureCreds, {'key' : new Date()});
});
});
+ it('should succeed without the new keyword', function() {
+ assert.doesNotThrow(function() {
+ var channel = grpc.Channel('hostname', insecureCreds);
+ assert(channel instanceof grpc.Channel);
+ });
+ });
});
describe('close', function() {
var channel;
@@ -155,7 +161,6 @@ describe('channel', function() {
deadline.setSeconds(deadline.getSeconds() + 1);
channel.watchConnectivityState(old_state, deadline, function(err, value) {
assert(err);
- console.log('Callback from watchConnectivityState');
done();
});
});
diff --git a/src/node/test/credentials_test.js b/src/node/test/credentials_test.js
index 7fc311a888..3d0b38fd52 100644
--- a/src/node/test/credentials_test.js
+++ b/src/node/test/credentials_test.js
@@ -60,6 +60,22 @@ function multiDone(done, count) {
};
}
+var fakeSuccessfulGoogleCredentials = {
+ getRequestMetadata: function(service_url, callback) {
+ setTimeout(function() {
+ callback(null, {Authorization: 'success'});
+ }, 0);
+ }
+};
+
+var fakeFailingGoogleCredentials = {
+ getRequestMetadata: function(service_url, callback) {
+ setTimeout(function() {
+ callback(new Error("Authorization failure"));
+ }, 0);
+ }
+};
+
describe('client credentials', function() {
var Client;
var server;
@@ -169,6 +185,52 @@ describe('client credentials', function() {
done();
});
});
+ it.skip('should propagate errors that the updater emits', function(done) {
+ var metadataUpdater = function(service_url, callback) {
+ var error = new Error('Authentication error');
+ error.code = grpc.status.UNAUTHENTICATED;
+ callback(error);
+ };
+ var creds = grpc.credentials.createFromMetadataGenerator(metadataUpdater);
+ var combined_creds = grpc.credentials.combineChannelCredentials(
+ client_ssl_creds, creds);
+ var client = new Client('localhost:' + port, combined_creds,
+ client_options);
+ client.unary({}, function(err, data) {
+ assert(err);
+ assert.strictEqual(err.message, 'Authentication error');
+ assert.strictEqual(err.code, grpc.status.UNAUTHENTICATED);
+ done();
+ });
+ });
+ it('should successfully wrap a Google credential', function(done) {
+ var creds = grpc.credentials.createFromGoogleCredential(
+ fakeSuccessfulGoogleCredentials);
+ var combined_creds = grpc.credentials.combineChannelCredentials(
+ client_ssl_creds, creds);
+ var client = new Client('localhost:' + port, combined_creds,
+ client_options);
+ var call = client.unary({}, function(err, data) {
+ assert.ifError(err);
+ });
+ call.on('metadata', function(metadata) {
+ assert.deepEqual(metadata.get('authorization'), ['success']);
+ done();
+ });
+ });
+ it.skip('should get an error from a Google credential', function(done) {
+ var creds = grpc.credentials.createFromGoogleCredential(
+ fakeFailingGoogleCredentials);
+ var combined_creds = grpc.credentials.combineChannelCredentials(
+ client_ssl_creds, creds);
+ var client = new Client('localhost:' + port, combined_creds,
+ client_options);
+ client.unary({}, function(err, data) {
+ assert(err);
+ assert.strictEqual(err.message, 'Authorization failure');
+ done();
+ });
+ });
describe('Per-rpc creds', function() {
var client;
var updater_creds;
diff --git a/src/node/test/health_test.js b/src/node/test/health_test.js
index a4dc24cf46..c93b528d42 100644
--- a/src/node/test/health_test.js
+++ b/src/node/test/health_test.js
@@ -45,11 +45,13 @@ describe('Health Checking', function() {
'grpc.test.TestServiceNotServing': 'NOT_SERVING',
'grpc.test.TestServiceServing': 'SERVING'
};
- var healthServer = new grpc.Server();
- healthServer.addProtoService(health.service,
- new health.Implementation(statusMap));
+ var healthServer;
+ var healthImpl;
var healthClient;
before(function() {
+ healthServer = new grpc.Server();
+ healthImpl = new health.Implementation(statusMap);
+ healthServer.addProtoService(health.service, healthImpl);
var port_num = healthServer.bind('0.0.0.0:0',
grpc.ServerCredentials.createInsecure());
healthServer.start();
@@ -89,4 +91,16 @@ describe('Health Checking', function() {
done();
});
});
+ it('should get a different response if the status changes', function(done) {
+ healthClient.check({service: 'transient'}, function(err, response) {
+ assert(err);
+ assert.strictEqual(err.code, grpc.status.NOT_FOUND);
+ healthImpl.setStatus('transient', 'SERVING');
+ healthClient.check({service: 'transient'}, function(err, response) {
+ assert.ifError(err);
+ assert.strictEqual(response.status, 'SERVING');
+ done();
+ });
+ });
+ });
});
diff --git a/src/node/test/interop_sanity_test.js b/src/node/test/interop_sanity_test.js
index f8c0b14137..f008a87585 100644
--- a/src/node/test/interop_sanity_test.js
+++ b/src/node/test/interop_sanity_test.js
@@ -71,7 +71,7 @@ describe('Interop tests', function() {
interop_client.runTest(port, name_override, 'server_streaming', true, true,
done);
});
- it.only('should pass ping_pong', function(done) {
+ it('should pass ping_pong', function(done) {
interop_client.runTest(port, name_override, 'ping_pong', true, true, done);
});
it('should pass empty_stream', function(done) {
diff --git a/src/node/test/surface_test.js b/src/node/test/surface_test.js
index 395ea887ec..39673e4e05 100644
--- a/src/node/test/surface_test.js
+++ b/src/node/test/surface_test.js
@@ -92,6 +92,31 @@ describe('File loader', function() {
});
});
});
+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 = {
@@ -202,6 +227,16 @@ describe('waitForClientReady', function() {
});
});
});
+ 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;
@@ -365,6 +400,123 @@ describe('Echo metadata', function() {
done();
});
});
+ it('properly handles duplicate values', function(done) {
+ var dup_metadata = metadata.clone();
+ dup_metadata.add('key', 'value2');
+ var call = client.unary({}, function(err, data) {assert.ifError(err); },
+ dup_metadata);
+ 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;
@@ -382,7 +534,8 @@ describe('Other conditions', function() {
unary: function(call, cb) {
var req = call.request;
if (req.error) {
- cb(new Error('Requested error'), null, trailer_metadata);
+ cb({code: grpc.status.UNKNOWN,
+ details: 'Requested error'}, null, trailer_metadata);
} else {
cb(null, {count: 1}, trailer_metadata);
}
@@ -407,7 +560,8 @@ describe('Other conditions', function() {
serverStream: function(stream) {
var req = stream.request;
if (req.error) {
- var err = new Error('Requested error');
+ var err = {code: grpc.status.UNKNOWN,
+ details: 'Requested error'};
err.metadata = trailer_metadata;
stream.emit('error', err);
} else {
@@ -447,6 +601,23 @@ describe('Other conditions', 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]);