/* * Copyright 2019 gRPC authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ const options = { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }; const assert = require('assert'); const _ = require('lodash'); const anyGrpc = require('../any_grpc'); const clientGrpc = anyGrpc.client; const serverGrpc = anyGrpc.server; const protoLoader = require('../../packages/proto-loader', options); const testServiceDef = protoLoader.loadSync(__dirname + '/../proto/test_service.proto'); const TestServiceClient = clientGrpc.loadPackageDefinition(testServiceDef).TestService; const clientInsecureCreds = clientGrpc.credentials.createInsecure(); const serverInsecureCreds = serverGrpc.ServerCredentials.createInsecure(); describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, function() { describe('Client malformed response handling', function() { var server; var client; var badArg = Buffer.from([0xFF]); before(function(done) { 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 serverGrpc.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(); }); } }); server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { assert.ifError(err); client = new TestServiceClient('localhost:' + port, clientInsecureCreds); server.start(); done(); }); }); 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, clientGrpc.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, clientGrpc.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, clientGrpc.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, clientGrpc.status.INTERNAL); done(); }); call.write({}); call.end(); }); }); describe('Server serialization failure handling', function() { function serializeFail(obj) { throw new Error('Serialization failed'); } var client; var server; before(function(done) { var malformed_test_service = { unary: { path: '/TestService/Unary', requestStream: false, responseStream: false, requestDeserialize: _.identity, responseSerialize: serializeFail }, clientStream: { path: '/TestService/ClientStream', requestStream: true, responseStream: false, requestDeserialize: _.identity, responseSerialize: serializeFail }, serverStream: { path: '/TestService/ServerStream', requestStream: false, responseStream: true, requestDeserialize: _.identity, responseSerialize: serializeFail }, bidiStream: { path: '/TestService/BidiStream', requestStream: true, responseStream: true, requestDeserialize: _.identity, responseSerialize: serializeFail } }; server = new serverGrpc.Server(); server.addService(malformed_test_service, { unary: function(call, cb) { cb(null, {}); }, clientStream: function(stream, cb) { stream.on('data', function() {/* Ignore requests */}); stream.on('end', function() { cb(null, {}); }); }, serverStream: function(stream) { stream.write({}); stream.end(); }, bidiStream: function(stream) { stream.on('data', function() { // Ignore requests stream.write({}); }); stream.on('end', function() { stream.end(); }); } }); server.bindAsync('localhost:0', serverInsecureCreds, (err, port) => { assert.ifError(err); client = new TestServiceClient('localhost:' + port, clientInsecureCreds); server.start(); done(); }); }); 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, clientGrpc.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, clientGrpc.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, clientGrpc.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, clientGrpc.status.INTERNAL); done(); }); call.write({}); call.end(); }); }); describe('Other conditions', function() { var Client; var client; var server; var port; before(function(done) { server = new serverGrpc.Server(); var trailer_metadata = new serverGrpc.Metadata(); trailer_metadata.add('trailer-present', 'yes'); server.addService(TestServiceClient.service, { unary: function(call, cb) { var req = call.request; if (req.error) { var message = 'Requested error'; if (req.message) { message = req.message; } cb({code: serverGrpc.status.UNKNOWN, details: message}, 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) { var message = 'Requested error'; if (data.message) { message = data.message; } errored = true; cb(new Error(message), 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 message = 'Requested error'; if (req.message) { message = req.message; } var err = {code: serverGrpc.status.UNKNOWN, details: message}; 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 message = 'Requested error'; if (data.message) { message = data.message; } var err = new Error(message); 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); }); } }); server.bindAsync('localhost:0', serverInsecureCreds, (err, _port) => { assert.ifError(err); port = _port; client = new TestServiceClient('localhost:' + port, clientInsecureCreds); server.start(); done(); }); }); after(function() { server.forceShutdown(); }); describe('Server recieving bad input', function() { var misbehavingClient; var badArg = Buffer.from([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 = clientGrpc.makeGenericClientConstructor(test_service_attrs, 'TestService'); misbehavingClient = new Client('localhost:' + port, clientInsecureCreds); }); it('should respond correctly to a unary call', function(done) { misbehavingClient.unary(badArg, function(err, data) { assert(err); assert.strictEqual(err.code, clientGrpc.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, clientGrpc.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, clientGrpc.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, clientGrpc.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, clientGrpc.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, clientGrpc.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, clientGrpc.status.UNKNOWN); assert.strictEqual(err.details, 'Requested error'); done(); }); }); it('for a client stream call', function(done) { var call = client.clientStream(function(err, data) { assert(err); assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); assert.strictEqual(err.details, '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, clientGrpc.status.UNKNOWN); assert.strictEqual(error.details, '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, clientGrpc.status.UNKNOWN); assert.strictEqual(error.details, 'Requested error'); done(); }); }); it('for a UTF-8 error message', function(done) { client.unary({error: true, message: '測試字符串'}, function(err, data) { assert(err); assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); assert.strictEqual(err.details, '測試字符串'); done(); }); }); it('for an error message with a comma', function(done) { client.unary({error: true, message: 'a message, with a comma'}, function(err, data) { assert(err); assert.strictEqual(err.code, clientGrpc.status.UNKNOWN); assert.strictEqual(err.details, 'a message, with a comma'); done(); }); }); }); }); describe('Other conditions', function() { var client; var server; var port; before(function(done) { server = new serverGrpc.Server(); var existing_metadata = new serverGrpc.Metadata(); existing_metadata.add('existing-metadata', 'yes'); server.addService(TestServiceClient.service, { unary: function(call, cb) { var req = call.request; if (req.error) { var message = 'Requested error'; if (req.message) { message = req.message; } cb({code: serverGrpc.status.UNKNOWN, message: message, metadata: existing_metadata}, null, null); } else { cb(null, {count: 1}); } }/*, clientStream: function(stream, cb){ var count = 0; var errored; stream.on('data', function(data) { if (data.error) { var message = 'Requested error'; if (data.message) { message = data.message; } errored = true; cb(new Error(message), 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 message = 'Requested error'; if (req.message) { message = req.message; } var err = {code: serverGrpc.status.UNKNOWN, details: message}; 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 message = 'Requested error'; if (data.message) { message = data.message; } var err = new Error(message); 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); }); } */}); server.bindAsync('localhost:0', serverInsecureCreds, (err, _port) => { assert.ifError(err); port = _port; client = new TestServiceClient('localhost:' + port, clientInsecureCreds); server.start(); done(); }); }); after(function() { server.forceShutdown(); }); describe('Existing metadata', function() { it('should not 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.deepStrictEqual(status.metadata.get('existing-metadata'), []); 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.deepStrictEqual(status.metadata.get('existing-metadata'), ['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, clientGrpc.status.UNKNOWN); assert.strictEqual(err.details, 'Requested error'); done(); }); }); }); }); });