mirror of https://github.com/grpc/grpc-node.git
487 lines
13 KiB
TypeScript
487 lines
13 KiB
TypeScript
/*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
// Allow `any` data type for testing runtime type checking.
|
|
// tslint:disable no-any
|
|
import * as assert from 'assert';
|
|
import * as fs from 'fs';
|
|
import * as http2 from 'http2';
|
|
import * as path from 'path';
|
|
|
|
import * as grpc from '../src';
|
|
import { Server, ServerCredentials } from '../src';
|
|
import { ServiceError } from '../src/call';
|
|
import { ServiceClient, ServiceClientConstructor } from '../src/make-client';
|
|
import { sendUnaryData, ServerUnaryCall } from '../src/server-call';
|
|
|
|
import { loadProtoFile } from './common';
|
|
|
|
const ca = fs.readFileSync(path.join(__dirname, 'fixtures', 'ca.pem'));
|
|
const key = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.key'));
|
|
const cert = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.pem'));
|
|
function noop(): void {}
|
|
|
|
describe('Server', () => {
|
|
describe('constructor', () => {
|
|
it('should work with no arguments', () => {
|
|
assert.doesNotThrow(() => {
|
|
new Server(); // tslint:disable-line:no-unused-expression
|
|
});
|
|
});
|
|
|
|
it('should work with an empty object argument', () => {
|
|
assert.doesNotThrow(() => {
|
|
new Server({}); // tslint:disable-line:no-unused-expression
|
|
});
|
|
});
|
|
|
|
it('should be an instance of Server', () => {
|
|
const server = new Server();
|
|
|
|
assert(server instanceof Server);
|
|
});
|
|
});
|
|
|
|
describe('bindAsync', () => {
|
|
it('binds with insecure credentials', done => {
|
|
const server = new Server();
|
|
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
(err, port) => {
|
|
assert.ifError(err);
|
|
assert(typeof port === 'number' && port > 0);
|
|
server.tryShutdown(done);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('binds with secure credentials', done => {
|
|
const server = new Server();
|
|
const creds = ServerCredentials.createSsl(
|
|
ca,
|
|
[{ private_key: key, cert_chain: cert }],
|
|
true
|
|
);
|
|
|
|
server.bindAsync('localhost:0', creds, (err, port) => {
|
|
assert.ifError(err);
|
|
assert(typeof port === 'number' && port > 0);
|
|
server.tryShutdown(done);
|
|
});
|
|
});
|
|
|
|
it('throws if bind is called after the server is started', done => {
|
|
const server = new Server();
|
|
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
(err, port) => {
|
|
assert.ifError(err);
|
|
server.start();
|
|
assert.throws(() => {
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
noop
|
|
);
|
|
}, /server is already started/);
|
|
server.tryShutdown(done);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('throws on invalid inputs', () => {
|
|
const server = new Server();
|
|
|
|
assert.throws(() => {
|
|
server.bindAsync(null as any, ServerCredentials.createInsecure(), noop);
|
|
}, /port must be a string/);
|
|
|
|
assert.throws(() => {
|
|
server.bindAsync('localhost:0', null as any, noop);
|
|
}, /creds must be an object/);
|
|
|
|
assert.throws(() => {
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
null as any
|
|
);
|
|
}, /callback must be a function/);
|
|
});
|
|
});
|
|
|
|
describe('start', () => {
|
|
let server: Server;
|
|
|
|
beforeEach(done => {
|
|
server = new Server();
|
|
server.bindAsync('localhost:0', ServerCredentials.createInsecure(), done);
|
|
});
|
|
|
|
afterEach(done => {
|
|
server.tryShutdown(done);
|
|
});
|
|
|
|
it('starts without error', () => {
|
|
assert.doesNotThrow(() => {
|
|
server.start();
|
|
});
|
|
});
|
|
|
|
it('throws if started twice', () => {
|
|
server.start();
|
|
assert.throws(() => {
|
|
server.start();
|
|
}, /server is already started/);
|
|
});
|
|
|
|
it('throws if the server is not bound', () => {
|
|
const server = new Server();
|
|
|
|
assert.throws(() => {
|
|
server.start();
|
|
}, /server must be bound in order to start/);
|
|
});
|
|
});
|
|
|
|
describe('addService', () => {
|
|
const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto');
|
|
const mathClient = (loadProtoFile(mathProtoFile).math as any).Math;
|
|
const mathServiceAttrs = mathClient.service;
|
|
const dummyImpls = { div() {}, divMany() {}, fib() {}, sum() {} };
|
|
const altDummyImpls = { Div() {}, DivMany() {}, Fib() {}, Sum() {} };
|
|
|
|
it('succeeds with a single service', () => {
|
|
const server = new Server();
|
|
|
|
assert.doesNotThrow(() => {
|
|
server.addService(mathServiceAttrs, dummyImpls);
|
|
});
|
|
});
|
|
|
|
it('fails to add an empty service', () => {
|
|
const server = new Server();
|
|
|
|
assert.throws(() => {
|
|
server.addService({}, dummyImpls);
|
|
}, /Cannot add an empty service to a server/);
|
|
});
|
|
|
|
it('fails with conflicting method names', () => {
|
|
const server = new Server();
|
|
|
|
server.addService(mathServiceAttrs, dummyImpls);
|
|
assert.throws(() => {
|
|
server.addService(mathServiceAttrs, dummyImpls);
|
|
}, /Method handler for .+ already provided/);
|
|
});
|
|
|
|
it('supports method names as originally written', () => {
|
|
const server = new Server();
|
|
|
|
assert.doesNotThrow(() => {
|
|
server.addService(mathServiceAttrs, altDummyImpls);
|
|
});
|
|
});
|
|
|
|
it('fails if the server has been started', done => {
|
|
const server = new Server();
|
|
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
(err, port) => {
|
|
assert.ifError(err);
|
|
server.start();
|
|
assert.throws(() => {
|
|
server.addService(mathServiceAttrs, dummyImpls);
|
|
}, /Can't add a service to a started server\./);
|
|
server.tryShutdown(done);
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
it('throws when unimplemented methods are called', () => {
|
|
const server = new Server();
|
|
|
|
assert.throws(() => {
|
|
server.addProtoService();
|
|
}, /Not implemented. Use addService\(\) instead/);
|
|
|
|
assert.throws(() => {
|
|
server.addHttp2Port();
|
|
}, /Not yet implemented/);
|
|
|
|
assert.throws(() => {
|
|
server.bind('localhost:0', ServerCredentials.createInsecure());
|
|
}, /Not implemented. Use bindAsync\(\) instead/);
|
|
});
|
|
|
|
describe('Default handlers', () => {
|
|
let server: Server;
|
|
let client: ServiceClient;
|
|
|
|
const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto');
|
|
const mathClient = (loadProtoFile(mathProtoFile).math as any).Math;
|
|
const mathServiceAttrs = mathClient.service;
|
|
|
|
before(done => {
|
|
server = new Server();
|
|
server.addService(mathServiceAttrs, {});
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
(err, port) => {
|
|
assert.ifError(err);
|
|
client = new mathClient(
|
|
`localhost:${port}`,
|
|
grpc.credentials.createInsecure()
|
|
);
|
|
server.start();
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
after(done => {
|
|
client.close();
|
|
server.tryShutdown(done);
|
|
});
|
|
|
|
it('should respond to a unary call with UNIMPLEMENTED', done => {
|
|
client.div(
|
|
{ divisor: 4, dividend: 3 },
|
|
(error: ServiceError, response: any) => {
|
|
assert(error);
|
|
assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED);
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should respond to a client stream with UNIMPLEMENTED', done => {
|
|
const call = client.sum((error: ServiceError, response: any) => {
|
|
assert(error);
|
|
assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED);
|
|
done();
|
|
});
|
|
|
|
call.end();
|
|
});
|
|
|
|
it('should respond to a server stream with UNIMPLEMENTED', done => {
|
|
const call = client.fib({ limit: 5 });
|
|
|
|
call.on('data', (value: any) => {
|
|
assert.fail('No messages expected');
|
|
});
|
|
|
|
call.on('error', (err: ServiceError) => {
|
|
assert(err);
|
|
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should respond to a bidi call with UNIMPLEMENTED', done => {
|
|
const call = client.divMany();
|
|
|
|
call.on('data', (value: any) => {
|
|
assert.fail('No messages expected');
|
|
});
|
|
|
|
call.on('error', (err: ServiceError) => {
|
|
assert(err);
|
|
assert.strictEqual(err.code, grpc.status.UNIMPLEMENTED);
|
|
done();
|
|
});
|
|
|
|
call.end();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Echo service', () => {
|
|
let server: Server;
|
|
let client: ServiceClient;
|
|
|
|
before(done => {
|
|
const protoFile = path.join(__dirname, 'fixtures', 'echo_service.proto');
|
|
const echoService = loadProtoFile(protoFile)
|
|
.EchoService as ServiceClientConstructor;
|
|
|
|
server = new Server();
|
|
server.addService(echoService.service, {
|
|
echo(call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>) {
|
|
callback(null, call.request);
|
|
},
|
|
});
|
|
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
(err, port) => {
|
|
assert.ifError(err);
|
|
client = new echoService(
|
|
`localhost:${port}`,
|
|
grpc.credentials.createInsecure()
|
|
);
|
|
server.start();
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
after(done => {
|
|
client.close();
|
|
server.tryShutdown(done);
|
|
});
|
|
|
|
it('should echo the recieved message directly', done => {
|
|
client.echo(
|
|
{ value: 'test value', value2: 3 },
|
|
(error: ServiceError, response: any) => {
|
|
assert.ifError(error);
|
|
assert.deepStrictEqual(response, { value: 'test value', value2: 3 });
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Generic client and server', () => {
|
|
function toString(val: any) {
|
|
return val.toString();
|
|
}
|
|
|
|
function toBuffer(str: string) {
|
|
return Buffer.from(str);
|
|
}
|
|
|
|
function capitalize(str: string) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
|
|
const stringServiceAttrs = {
|
|
capitalize: {
|
|
path: '/string/capitalize',
|
|
requestStream: false,
|
|
responseStream: false,
|
|
requestSerialize: toBuffer,
|
|
requestDeserialize: toString,
|
|
responseSerialize: toBuffer,
|
|
responseDeserialize: toString,
|
|
},
|
|
};
|
|
|
|
describe('String client and server', () => {
|
|
let client: ServiceClient;
|
|
let server: Server;
|
|
|
|
before(done => {
|
|
server = new Server();
|
|
|
|
server.addService(stringServiceAttrs as any, {
|
|
capitalize(
|
|
call: ServerUnaryCall<any, any>,
|
|
callback: sendUnaryData<any>
|
|
) {
|
|
callback(null, capitalize(call.request));
|
|
},
|
|
});
|
|
|
|
server.bindAsync(
|
|
'localhost:0',
|
|
ServerCredentials.createInsecure(),
|
|
(err, port) => {
|
|
assert.ifError(err);
|
|
server.start();
|
|
const clientConstr = grpc.makeGenericClientConstructor(
|
|
stringServiceAttrs as any,
|
|
'unused_but_lets_appease_typescript_anyway'
|
|
);
|
|
client = new clientConstr(
|
|
`localhost:${port}`,
|
|
grpc.credentials.createInsecure()
|
|
);
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
after(done => {
|
|
client.close();
|
|
server.tryShutdown(done);
|
|
});
|
|
|
|
it('Should respond with a capitalized string', done => {
|
|
client.capitalize('abc', (err: ServiceError, response: string) => {
|
|
assert.ifError(err);
|
|
assert.strictEqual(response, 'Abc');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
it('responds with HTTP status of 415 on invalid content-type', done => {
|
|
const server = new Server();
|
|
const creds = ServerCredentials.createInsecure();
|
|
|
|
server.bindAsync('localhost:0', creds, (err, port) => {
|
|
assert.ifError(err);
|
|
const client = http2.connect(`http://localhost:${port}`);
|
|
let count = 0;
|
|
|
|
function makeRequest(headers: http2.IncomingHttpHeaders) {
|
|
const req = client.request(headers);
|
|
let statusCode: string;
|
|
|
|
req.on('response', headers => {
|
|
statusCode = headers[http2.constants.HTTP2_HEADER_STATUS] as string;
|
|
assert.strictEqual(
|
|
statusCode,
|
|
http2.constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE
|
|
);
|
|
});
|
|
|
|
req.on('end', () => {
|
|
assert(statusCode);
|
|
count++;
|
|
if (count === 2) {
|
|
client.close();
|
|
server.tryShutdown(done);
|
|
}
|
|
});
|
|
|
|
req.end();
|
|
}
|
|
|
|
server.start();
|
|
|
|
// Missing Content-Type header.
|
|
makeRequest({ ':path': '/' });
|
|
// Invalid Content-Type header.
|
|
makeRequest({ ':path': '/', 'content-type': 'application/not-grpc' });
|
|
});
|
|
});
|
|
});
|