diff options
Diffstat (limited to 'src/node')
-rw-r--r-- | src/node/ext/call.cc | 18 | ||||
-rw-r--r-- | src/node/ext/call.h | 13 | ||||
-rw-r--r-- | src/node/ext/call_credentials.cc | 13 | ||||
-rw-r--r-- | src/node/ext/channel.cc | 118 | ||||
-rw-r--r-- | src/node/ext/channel.h | 5 | ||||
-rw-r--r-- | src/node/ext/channel_credentials.cc | 13 | ||||
-rw-r--r-- | src/node/ext/server.cc | 49 | ||||
-rw-r--r-- | src/node/ext/server_credentials.cc | 15 | ||||
-rw-r--r-- | src/node/index.js | 8 | ||||
-rw-r--r-- | src/node/interop/interop_client.js | 1 | ||||
-rw-r--r-- | src/node/src/client.js | 1 | ||||
-rw-r--r-- | src/node/src/common.js | 11 | ||||
-rw-r--r-- | src/node/src/credentials.js | 3 | ||||
-rw-r--r-- | src/node/test/call_test.js | 54 | ||||
-rw-r--r-- | src/node/test/channel_test.js | 7 | ||||
-rw-r--r-- | src/node/test/credentials_test.js | 62 | ||||
-rw-r--r-- | src/node/test/health_test.js | 20 | ||||
-rw-r--r-- | src/node/test/interop_sanity_test.js | 2 | ||||
-rw-r--r-- | src/node/test/surface_test.js | 175 |
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]); |