diff --git a/API.md b/API.md index a627c55..98d3ffd 100644 --- a/API.md +++ b/API.md @@ -109,7 +109,7 @@ Represents a gRPC service * [.env](#Mali+env) : String * [.silent](#Mali+silent) : Boolean * [.init(proto, name, options)](#Mali+init) - * [.use(name, ...fns)](#Mali+use) + * [.use(service, name, ...fns)](#Mali+use) * [.onerror(err)](#Mali+onerror) * [.start(port, creds)](#Mali+start) ⇒ Object * [.close()](#Mali+close) @@ -125,7 +125,7 @@ Create a gRPC service | Param | Type | Description | | --- | --- | --- | | proto | String | Object | Path to the protocol buffer definition file - Object specifying root directory and file to load - The static service proto object itself | -| name | Object | Name of the service. In case of proto path the name of the service as defined in the proto definition. In case of proto object the name of the constructor. | +| name | Object | Optional name of the service or an array of names. Otherwise all services are used. In case of proto path the name of the service as defined in the proto definition. In case of proto object the name of the constructor. | | options | Object | Options to be passed to grpc.load | **Example** *(Create service dynamically)* @@ -183,19 +183,31 @@ app construction time for some reason. | Param | Type | Description | | --- | --- | --- | | proto | String | Object | Path to the protocol buffer definition file - Object specifying root directory and file to load - The static service proto object itself | -| name | Object | Name of the service. In case of proto path the name of the service as defined in the proto definition. In case of proto object the name of the constructor. | +| name | Object | Optional name of the service or an array of names. Otherwise all services are used. In case of proto path the name of the service as defined in the proto definition. In case of proto object the name of the constructor. | | options | Object | Options to be passed to grpc.load | -#### mali.use(name, ...fns) -Define midelware and handlers +#### mali.use(service, name, ...fns) +Define middleware and handlers. +If service and name are given applies fns for that call under that service. +If service name is provided and matches one of the services defined in proto, +but no name is provided applies the fns as middleware as service level middleware +for all handlers in that service. +If service is provided and no name is provided, and service does not +match any of the service names in the proto, assumes service is actually rpc call +name. Uses 0th property in internal services object. Useful for protos with only +one service. +If an object is provided, you can set middleware and handlers for all services. +If object provided but 0th key does not match any of the services in +proto, assumes 0th service. Useful for protos with only one service. **Kind**: instance method of [Mali](#Mali) | Param | Type | Description | | --- | --- | --- | -| name | String | Object | Name of the function as specified in the protocol buffer definition. or an object of name and handlers | +| service | String | Object | Service name | +| name | String | function | RPC name | | ...fns | function | Array | Middleware and/or handler | **Example** *(Define handler for rpc function 'fn1')* @@ -210,17 +222,45 @@ app.use('fn1', fn1) app.use('fn2', mw1, mw2, fn2) ``` +**Example** *(Define handler with middleware for rpc function 'fn2' in service 'Service2')* + +```js +app.use('Service2', 'fn2', mw1, mw2, fn2) +``` + **Example** *(Using destructuring define handlers for rpc functions 'fn1' and 'fn2')* ```js app.use({ fn1, fn2 }) ``` +**Example** *(Apply middleware to all handers for a service)* + +```js +app.use('Service1', mw1) +``` + **Example** *(Using destructuring define handlers for rpc functions 'fn1' and 'fn2')* ```js // fn2 has middleware mw1 and mw2 -app.use({ fn1, fn2: [mw1, mw2, fn2] }) +app.use({ MyService: { fn1, fn2: [mw1, mw2, fn2] } }) +``` + +**Example** *(Multiple services using object notation)* + +```js +app.use(mw1) // global for all services +app.use('Service1', mw2) // applies to all Service1 handers +app.use({ + Service1: { + sayGoodbye: handler1, // has mw1, mw2 + sayHello: [ mw3, handler2 ] // has mw1, mw2, mw3 + }, + Service2: { + saySomething: handler3 // only has mw1 + } +}) ``` diff --git a/lib/index.js b/lib/index.js index 6f5ca24..20deed9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,7 +11,7 @@ const Context = require('./context') const run = require('./run') const mu = require('./utils') -const REMOVE_PROPS = ['grpc', 'middleware', 'handlers', 'servers', 'load', 'proto', 'service', 'methods'] +const REMOVE_PROPS = ['grpc', 'middleware', 'handlers', 'servers', 'load', 'proto', 'services', 'methods'] const EE_PROPS = Object.getOwnPropertyNames(new Emitter()) /** @@ -32,7 +32,7 @@ class Mali extends Emitter { * @param {String|Object} proto - Path to the protocol buffer definition file * - Object specifying root directory and file to load * - The static service proto object itself - * @param {Object} name - Name of the service. + * @param {Object} name - Optional name of the service or an array of names. Otherwise all services are used. * In case of proto path the name of the service as defined in the proto definition. * In case of proto object the name of the constructor. * @param {Object} options - Options to be passed to grpc.load @@ -41,7 +41,7 @@ class Mali extends Emitter { super() this.grpc = grpc - this.middleware = [] // just the global middleware + this.middleware = {} // just the global middleware this.handlers = {} // specific middleware and handlers this.servers = [] @@ -49,7 +49,7 @@ class Mali extends Emitter { this.context = new Context() this.env = process.env.NODE_ENV || 'development' - if (path && name) { + if (path) { this.init(path, name, options) } } @@ -60,7 +60,7 @@ class Mali extends Emitter { * @param {String|Object} proto - Path to the protocol buffer definition file * - Object specifying root directory and file to load * - The static service proto object itself - * @param {Object} name - Name of the service. + * @param {Object} name - Optional name of the service or an array of names. Otherwise all services are used. * In case of proto path the name of the service as defined in the proto definition. * In case of proto object the name of the constructor. * @param {Object} options - Options to be passed to grpc.load @@ -70,39 +70,65 @@ class Mali extends Emitter { this.load = _.isString(path) || (_.isObject(path) && path.root && path.file) this.proto = this.load ? this.grpc.load(path, options) : path - let service - let descriptor + this.services = {} + this.methods = {} if (this.load) { - descriptor = gi(this.proto) + let descriptor = gi(this.proto) if (!descriptor) { throw new Error(String.raw `Error parsing protocol buffer`) } - const client = descriptor.client(name) - service = client ? client.service : null + let names = descriptor.serviceNames() + if (_.isString(name)) { + names = [name] + } else if (_.isArray(name)) { + names = _.intersection(name, names) + } + names.forEach(n => { + const client = descriptor.client(n) + const service = client ? client.service : null + if (service) { + this.services[n] = service + this.methods[n] = {} + this.middleware[n] = [] + const methods = descriptor.methods(n) + methods.forEach(m => { + this.methods[n][_.camelCase(m.name)] = m + }) + } + }) } else if (_.isObject(this.proto)) { - service = this.proto[name] - } - - if (!_.isObject(service)) { - throw new Error(String.raw `${name} is not a service definition within the protocol buffer definition`) - } + let names = _.keys(this.proto) + if (_.isString(name)) { + names = [name] + } else if (_.isArray(name)) { + names = _.intersection(name, names) + } - this.service = service - if (this.load) { - const methods = descriptor.methods(name) - this.methods = {} - methods.forEach(m => { - this.methods[_.camelCase(m.name)] = m + _.forOwn(this.proto, (v, n) => { + if (_.isObject(v) && !_.isFunction(v) && names.indexOf(n) >= 0) { + this.services[n] = v + this.middleware[n] = [] + this.methods[n] = mu.getMethodDescriptors(v) + } }) - } else { - this.methods = mu.getMethodDescriptors(service) } } /** - * Define midelware and handlers - * @param {String|Object} name Name of the function as specified in the protocol buffer definition. - * or an object of name and handlers + * Define middleware and handlers. + * If service and name are given applies fns for that call under that service. + * If service name is provided and matches one of the services defined in proto, + * but no name is provided applies the fns as middleware as service level middleware + * for all handlers in that service. + * If service is provided and no name is provided, and service does not + * match any of the service names in the proto, assumes service is actually rpc call + * name. Uses 0th property in internal services object. Useful for protos with only + * one service. + * If an object is provided, you can set middleware and handlers for all services. + * If object provided but 0th key does not match any of the services in + * proto, assumes 0th service. Useful for protos with only one service. + * @param {String|Object} service Service name + * @param {String|Function} name RPC name * @param {Function|Array} fns - Middleware and/or handler * * @example Define handler for rpc function 'fn1' @@ -110,38 +136,92 @@ class Mali extends Emitter { * * @example Define handler with middleware for rpc function 'fn2' * app.use('fn2', mw1, mw2, fn2) + * + * @example Define handler with middleware for rpc function 'fn2' in service 'Service2' + * app.use('Service2', 'fn2', mw1, mw2, fn2) * * @example Using destructuring define handlers for rpc functions 'fn1' and 'fn2' * app.use({ fn1, fn2 }) * + * @example Apply middleware to all handers for a service + * app.use('Service1', mw1) + * * @example Using destructuring define handlers for rpc functions 'fn1' and 'fn2' * // fn2 has middleware mw1 and mw2 - * app.use({ fn1, fn2: [mw1, mw2, fn2] }) + * app.use({ MyService: { fn1, fn2: [mw1, mw2, fn2] } }) + * + * @example Multiple services using object notation + * app.use(mw1) // global for all services + * app.use('Service1', mw2) // applies to all Service1 handers + * app.use({ + * Service1: { + * sayGoodbye: handler1, // has mw1, mw2 + * sayHello: [ mw3, handler2 ] // has mw1, mw2, mw3 + * }, + * Service2: { + * saySomething: handler3 // only has mw1 + * } + * }) */ - use (name, ...fns) { - if (_.isPlainObject(name)) { - for (var prop in name) { - if (name.hasOwnProperty(prop)) { - const mw = name[prop] + use (service, name, ...fns) { + if (_.isFunction(service)) { + _.forOwn(this.middleware, (v, sn) => { + this.middleware[sn] = _.concat(v, service, fns) + }) + } else if (_.isPlainObject(service)) { + const testKey = _.keys(service)[0] + if (_.isFunction(service[testKey]) || _.isArray(service[testKey])) { + const serviceName = _.keys(this.services)[0] + _.forOwn(service, (mw, mwName) => { if (_.isFunction(mw)) { - this.use(prop, mw) + this.use(serviceName, mwName, mw) } else if (_.isArray(mw)) { - this.use(prop, ...mw) + this.use(serviceName, mwName, ...mw) } else { - throw new TypeError(`Handler for ${prop} is not a function or array`) + throw new TypeError(`Handler for ${mwName} is not a function or array`) } - } + }) + } else if (_.isPlainObject(service[testKey])) { + _.forOwn(service, (def, serviceName) => { + _.forOwn(def, (mw, mwName) => { + if (_.isFunction(mw)) { + this.use(serviceName, mwName, mw) + } else if (_.isArray(mw)) { + this.use(serviceName, mwName, ...mw) + } else { + throw new TypeError(`Handler for ${mwName} is not a function or array`) + } + }) + }) + } else { + throw new TypeError(`Invalid type for handler for ${testKey}`) } - } else if (_.isFunction(name)) { - this.middleware = _.concat(this.middleware, name, fns) } else { - if (this.handlers[name]) { - throw new Error(String.raw `Handler for ${name} already defined`) + let serviceName = service + if (!_.isString(name)) { + fns.unshift(name) + const sNames = _.keys(this.services) + if (sNames.indexOf(service) >= 0) { + this.middleware[service] = _.concat(this.middleware[service], fns) + return + } else { + name = serviceName + serviceName = _.keys(this.services)[0] + } } - if (!this.methods[name]) { - throw new Error(String.raw `Unknown method: ${name}`) + if (!this.services[serviceName]) { + throw new Error(String.raw `Unknown service ${serviceName}`) } - this.handlers[name] = _.concat(this.middleware, fns) + if (!this.handlers[serviceName]) { + this.handlers[serviceName] = {} + } + if (this.handlers[serviceName][name]) { + throw new Error(String.raw `Handler for ${name} already defined for service ${serviceName}`) + } + if (!this.methods[serviceName][name]) { + throw new Error(String.raw `Unknown method: ${name} for service ${serviceName}`) + } + this.handlers[serviceName][name] = _.concat(this.middleware[serviceName], fns) } } @@ -188,12 +268,16 @@ class Mali extends Emitter { const method = this.load ? 'addProtoService' : 'addService' - const composed = {} - _.forOwn(this.handlers, (v, k) => { - composed[k] = this.callback(this.methods[k], v) + _.forOwn(this.services, (s, sn) => { + const composed = {} + const methods = this.methods[sn] + const handlers = this.handlers[sn] + _.forOwn(handlers, (v, k) => { + composed[k] = this.callback(methods[k], v) + }) + server[method](s, composed) }) - server[method](this.service, composed) server.bind(port, creds) server.start() diff --git a/package.json b/package.json index c741ca0..3d807ae 100644 --- a/package.json +++ b/package.json @@ -42,11 +42,11 @@ "pify": "^2.3.0" }, "devDependencies": { - "ava": "^0.17.0", + "ava": "^0.18.1", "babel-eslint": "^7.1.1", "google-protobuf": "^3.1.1", "highland": "^3.0.0-beta.3", - "jsdoc-to-markdown": "^2.0.1", + "jsdoc-to-markdown": "^3.0.0", "md-wrap-code": "^0.1.1", "standard": "^8.5.0", "test-console": "^1.0.0" diff --git a/test/create.test.js b/test/create.test.js index aa42c1c..e13d14c 100644 --- a/test/create.test.js +++ b/test/create.test.js @@ -1,18 +1,16 @@ import test from 'ava' import path from 'path' -import grpc from 'grpc' +import _ from 'lodash' import pMap from 'p-map' import Mali from '../lib' import * as tu from './util' const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') +const PROTO_PATH_MULTI = path.resolve(__dirname, './protos/multi.proto') const apps = [] -const STATIC_HOST = tu.getHost() -const DYNAMIC_HOST = tu.getHost() - test.serial('should dynamically create service', t => { function sayHello (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } @@ -23,7 +21,21 @@ test.serial('should dynamically create service', t => { apps.push(app) app.use({ sayHello }) - const server = app.start(DYNAMIC_HOST) + const server = app.start(tu.getHost()) + t.truthy(server) +}) + +test.serial('should dynamically create service without a service name', t => { + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH, 'Greeter') + t.truthy(app) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) t.truthy(server) }) @@ -42,42 +54,27 @@ test.serial('should statically create service', t => { apps.push(app) app.use({ sayHello }) - const server = app.start(STATIC_HOST) + const server = app.start(tu.getHost()) t.truthy(server) }) -test.cb('call dynamic service', t => { - t.plan(4) - const helloproto = grpc.load(PROTO_PATH).helloworld - const client = new helloproto.Greeter(DYNAMIC_HOST, grpc.credentials.createInsecure()) - client.sayHello({ name: 'Bob' }, (err, response) => { - t.ifError(err) - t.truthy(response) - t.truthy(response.message) - t.is(response.message, 'Hello Bob') - t.end() - }) -}) - -test.cb('call static service', t => { - t.plan(5) - +test.serial('should statically create service without a service name', t => { const messages = require('./static/helloworld_pb') const services = require('./static/helloworld_grpc_pb') - const client = new services.GreeterClient(STATIC_HOST, grpc.credentials.createInsecure()) - - const request = new messages.HelloRequest() - request.setName('Jane') - client.sayHello(request, (err, response) => { - t.ifError(err) - t.truthy(response) - t.truthy(response.getMessage) - const msg = response.getMessage() - t.truthy(msg) - t.is(msg, 'Hello Jane') - t.end() - }) + function sayHello (ctx) { + const reply = new messages.HelloReply() + reply.setMessage('Hello ' + ctx.req.getName()) + ctx.res = reply + } + + const app = new Mali(services) + t.truthy(app) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) + t.truthy(server) }) test.serial('should dynamically create service using object specifying root and file', async t => { @@ -120,6 +117,113 @@ test.serial('should dynamically create service using object specifying root and await app.close() }) +test.serial('should dynamically create a named service from defition with multiple services', t => { + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH_MULTI, 'Greeter') + t.truthy(app) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) + t.truthy(server) +}) + +test.serial('should dynamically create all named services from defition with multiple services', t => { + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH_MULTI, [ 'Greeter', 'Greeter2' ]) + t.truthy(app) + t.truthy(app.services) + t.is(_.keys(app.services).length, 2) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) + t.truthy(server) +}) + +test.serial('should dynamically create all services from defition with multiple services', t => { + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH_MULTI) + t.truthy(app) + t.truthy(app.services) + t.is(_.keys(app.services).length, 4) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) + t.truthy(server) +}) + +test.serial('should statically create a named service from defition with multiple services', t => { + const messages = require('./static/multi_pb') + const services = require('./static/multi_grpc_pb') + + function sayHello (ctx) { + const reply = new messages.HelloReply() + reply.setMessage('Hello ' + ctx.req.getName()) + ctx.res = reply + } + + const app = new Mali(services, 'GreeterService') + t.truthy(app) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) + t.truthy(server) +}) + +test.serial('should statically create all named services from defition with multiple services', t => { + const messages = require('./static/multi_pb') + const services = require('./static/multi_grpc_pb') + + function sayHello (ctx) { + const reply = new messages.HelloReply() + reply.setMessage('Hello ' + ctx.req.getName()) + ctx.res = reply + } + + const app = new Mali(services, [ 'GreeterService', 'Greeter2Service' ]) + t.truthy(app) + t.truthy(app.services) + t.is(_.keys(app.services).length, 2) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) + t.truthy(server) +}) + +test.serial('should statically create all services from defition with multiple services', t => { + const messages = require('./static/multi_pb') + const services = require('./static/multi_grpc_pb') + + function sayHello (ctx) { + const reply = new messages.HelloReply() + reply.setMessage('Hello ' + ctx.req.getName()) + ctx.res = reply + } + + const app = new Mali(services) + t.truthy(app) + t.truthy(app.services) + t.is(_.keys(app.services).length, 4) + apps.push(app) + + app.use({ sayHello }) + const server = app.start(tu.getHost()) + t.truthy(server) +}) + test.after.always('cleanup', async t => { await pMap(apps, app => app.close()) }) diff --git a/test/middleware.test.js b/test/middleware.test.js index b6fc7c0..b4610a5 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -320,6 +320,61 @@ test.cb('should not call middleware downstream of one that does not call next', }) }) +test('multi: call multiple services with middleware', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function hello (ctx) { + ctx.res = { message: ctx.req.message || '' + ':Hello ' + ctx.req.name } + } + + function goodbye (ctx) { + ctx.res = { message: ctx.req.message || '' + ':Goodbye ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.message = ':mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter2', mw1) + app.use({ + Greeter4: { + sayGoodbye: goodbye, + sayHello: [ mw1, hello ] + }, + Greeter2: { sayHello: hello } + }) + + const host = tu.getHost() + const server = app.start(host) + t.truthy(server) + + const proto = grpc.load(PROTO_PATH).helloworld + const client2 = new proto.Greeter2(host, grpc.credentials.createInsecure()) + const client4 = new proto.Greeter4(host, grpc.credentials.createInsecure()) + client2.sayHello({ name: 'Bob' }, (err, response) => { + t.ifError(err) + t.truthy(response) + t.is(response.message, ':mw1:Hello Bob') + + client4.sayHello({ name: 'Jane' }, (err, response) => { + t.ifError(err) + t.truthy(response) + t.is(response.message, ':mw1:Hello Jane') + + client4.sayGoodbye({ name: 'Bill' }, (err, response) => { + t.ifError(err) + t.truthy(response) + t.is(response.message, ':Goodbye Bill') + app.close().then(() => t.end()) + }) + }) + }) +}) + test.after.always('cleanup', async t => { await pMap(apps, app => app.close()) }) diff --git a/test/protos/multi.proto b/test/protos/multi.proto new file mode 100644 index 0000000..63abecb --- /dev/null +++ b/test/protos/multi.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package helloworld; + +service Greeter { + rpc SayHello (Request) returns (Reply) {} +} + +service Greeter2 { + rpc SayHello (Request) returns (Reply) {} +} + +service Greeter3 { + rpc SayGoodbye (Request) returns (Reply) {} +} + +service Greeter4 { + rpc SayHello (Request) returns (Reply) {} + rpc SayGoodbye (Request) returns (Reply) {} +} + +message Request { + string name = 1; +} + +message Reply { + string message = 1; +} diff --git a/test/static/multi_grpc_pb.js b/test/static/multi_grpc_pb.js new file mode 100644 index 0000000..27e16c4 --- /dev/null +++ b/test/static/multi_grpc_pb.js @@ -0,0 +1,100 @@ +// GENERATED CODE -- DO NOT EDIT! + +'use strict'; +var grpc = require('grpc'); +var test_protos_multi_pb = require('./multi_pb.js'); + +function serialize_helloworld_Reply(arg) { + if (!(arg instanceof test_protos_multi_pb.Reply)) { + throw new Error('Expected argument of type helloworld.Reply'); + } + return new Buffer(arg.serializeBinary()); +} + +function deserialize_helloworld_Reply(buffer_arg) { + return test_protos_multi_pb.Reply.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_helloworld_Request(arg) { + if (!(arg instanceof test_protos_multi_pb.Request)) { + throw new Error('Expected argument of type helloworld.Request'); + } + return new Buffer(arg.serializeBinary()); +} + +function deserialize_helloworld_Request(buffer_arg) { + return test_protos_multi_pb.Request.deserializeBinary(new Uint8Array(buffer_arg)); +} + + +var GreeterService = exports.GreeterService = { + sayHello: { + path: '/helloworld.Greeter/SayHello', + requestStream: false, + responseStream: false, + requestType: test_protos_multi_pb.Request, + responseType: test_protos_multi_pb.Reply, + requestSerialize: serialize_helloworld_Request, + requestDeserialize: deserialize_helloworld_Request, + responseSerialize: serialize_helloworld_Reply, + responseDeserialize: deserialize_helloworld_Reply, + }, +}; + +exports.GreeterClient = grpc.makeGenericClientConstructor(GreeterService); +var Greeter2Service = exports.Greeter2Service = { + sayHello: { + path: '/helloworld.Greeter2/SayHello', + requestStream: false, + responseStream: false, + requestType: test_protos_multi_pb.Request, + responseType: test_protos_multi_pb.Reply, + requestSerialize: serialize_helloworld_Request, + requestDeserialize: deserialize_helloworld_Request, + responseSerialize: serialize_helloworld_Reply, + responseDeserialize: deserialize_helloworld_Reply, + }, +}; + +exports.Greeter2Client = grpc.makeGenericClientConstructor(Greeter2Service); +var Greeter3Service = exports.Greeter3Service = { + sayGoodbye: { + path: '/helloworld.Greeter3/SayGoodbye', + requestStream: false, + responseStream: false, + requestType: test_protos_multi_pb.Request, + responseType: test_protos_multi_pb.Reply, + requestSerialize: serialize_helloworld_Request, + requestDeserialize: deserialize_helloworld_Request, + responseSerialize: serialize_helloworld_Reply, + responseDeserialize: deserialize_helloworld_Reply, + }, +}; + +exports.Greeter3Client = grpc.makeGenericClientConstructor(Greeter3Service); +var Greeter4Service = exports.Greeter4Service = { + sayHello: { + path: '/helloworld.Greeter4/SayHello', + requestStream: false, + responseStream: false, + requestType: test_protos_multi_pb.Request, + responseType: test_protos_multi_pb.Reply, + requestSerialize: serialize_helloworld_Request, + requestDeserialize: deserialize_helloworld_Request, + responseSerialize: serialize_helloworld_Reply, + responseDeserialize: deserialize_helloworld_Reply, + }, + sayGoodbye: { + path: '/helloworld.Greeter4/SayGoodbye', + requestStream: false, + responseStream: false, + requestType: test_protos_multi_pb.Request, + responseType: test_protos_multi_pb.Reply, + requestSerialize: serialize_helloworld_Request, + requestDeserialize: deserialize_helloworld_Request, + responseSerialize: serialize_helloworld_Reply, + responseDeserialize: deserialize_helloworld_Reply, + }, +}; + +exports.Greeter4Client = grpc.makeGenericClientConstructor(Greeter4Service); diff --git a/test/static/multi_pb.js b/test/static/multi_pb.js new file mode 100644 index 0000000..439c3d5 --- /dev/null +++ b/test/static/multi_pb.js @@ -0,0 +1,294 @@ +/** + * @fileoverview + * @enhanceable + * @public + */ +// GENERATED CODE -- DO NOT EDIT! + +var jspb = require('google-protobuf'); +var goog = jspb; +var global = Function('return this')(); + +goog.exportSymbol('proto.helloworld.Reply', null, global); +goog.exportSymbol('proto.helloworld.Request', null, global); + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.helloworld.Request = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.helloworld.Request, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.helloworld.Request.displayName = 'proto.helloworld.Request'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.helloworld.Request.prototype.toObject = function(opt_includeInstance) { + return proto.helloworld.Request.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.helloworld.Request} msg The msg instance to transform. + * @return {!Object} + */ +proto.helloworld.Request.toObject = function(includeInstance, msg) { + var f, obj = { + name: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.helloworld.Request} + */ +proto.helloworld.Request.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.helloworld.Request; + return proto.helloworld.Request.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.helloworld.Request} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.helloworld.Request} + */ +proto.helloworld.Request.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setName(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.helloworld.Request.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.helloworld.Request.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.helloworld.Request} message + * @param {!jspb.BinaryWriter} writer + */ +proto.helloworld.Request.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getName(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string name = 1; + * @return {string} + */ +proto.helloworld.Request.prototype.getName = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** @param {string} value */ +proto.helloworld.Request.prototype.setName = function(value) { + jspb.Message.setField(this, 1, value); +}; + + + +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.helloworld.Reply = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.helloworld.Reply, jspb.Message); +if (goog.DEBUG && !COMPILED) { + proto.helloworld.Reply.displayName = 'proto.helloworld.Reply'; +} + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.helloworld.Reply.prototype.toObject = function(opt_includeInstance) { + return proto.helloworld.Reply.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.helloworld.Reply} msg The msg instance to transform. + * @return {!Object} + */ +proto.helloworld.Reply.toObject = function(includeInstance, msg) { + var f, obj = { + message: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.helloworld.Reply} + */ +proto.helloworld.Reply.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.helloworld.Reply; + return proto.helloworld.Reply.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.helloworld.Reply} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.helloworld.Reply} + */ +proto.helloworld.Reply.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setMessage(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.helloworld.Reply.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.helloworld.Reply.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.helloworld.Reply} message + * @param {!jspb.BinaryWriter} writer + */ +proto.helloworld.Reply.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getMessage(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string message = 1; + * @return {string} + */ +proto.helloworld.Reply.prototype.getMessage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** @param {string} value */ +proto.helloworld.Reply.prototype.setMessage = function(value) { + jspb.Message.setField(this, 1, value); +}; + + +goog.object.extend(exports, proto.helloworld); diff --git a/test/use.test.js b/test/use.test.js index e666a03..5374cac 100644 --- a/test/use.test.js +++ b/test/use.test.js @@ -1,9 +1,11 @@ import path from 'path' import test from 'ava' +import _ from 'lodash' import Mali from '../lib' -test('shoud throw on unknown function', t => { +test('should throw on unknown function', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function saySomething (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -15,10 +17,10 @@ test('shoud throw on unknown function', t => { app.use({ saySomething }) }, Error) - t.is(error.message, 'Unknown method: saySomething') + t.is(error.message, 'Unknown method: saySomething for service Greeter') }) -test('shoud throw on invalid parameter', t => { +test('should throw on invalid parameter', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') const sayHello = 'sayHello' @@ -29,11 +31,12 @@ test('shoud throw on invalid parameter', t => { app.use({ sayHello }) }, Error) - t.is(error.message, 'Handler for sayHello is not a function or array') + t.is(error.message, 'Invalid type for handler for sayHello') }) -test('shoud add handler using object notation', t => { +test('should add handler using object notation', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function sayHello (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -45,8 +48,9 @@ test('shoud add handler using object notation', t => { t.pass() }) -test('shoud add handler using name and function', t => { +test('should add handler using name and function', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function handler (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -58,8 +62,9 @@ test('shoud add handler using name and function', t => { t.pass() }) -test('shoud throw on duplicate handlers', t => { +test('should throw on duplicate handlers', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function sayHello (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -76,11 +81,12 @@ test('shoud throw on duplicate handlers', t => { app.use('sayHello', sayHello2) }, Error) - t.is(error.message, 'Handler for sayHello already defined') + t.is(error.message, 'Handler for sayHello already defined for service Greeter') }) -test('shoud add handler and middleware when set using name', t => { +test('should add handler and middleware when set using name', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function handler (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -95,15 +101,40 @@ test('shoud add handler and middleware when set using name', t => { app.use('sayHello', mw1, handler) - t.truthy(app.handlers.sayHello) - t.true(Array.isArray(app.handlers.sayHello)) - t.is(app.handlers.sayHello.length, 2) - t.is(app.handlers.sayHello[0], mw1) - t.is(app.handlers.sayHello[1], handler) + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 2) + t.is(app.handlers.Greeter.sayHello[0], mw1) + t.is(app.handlers.Greeter.sayHello[1], handler) }) -test('shoud add handler and middleware when set as array using object', t => { +test('should add handler and middleware when set using service and call name', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH, 'Greeter') + t.truthy(app) + + app.use('Greeter', 'sayHello', mw1, handler) + + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 2) + t.is(app.handlers.Greeter.sayHello[0], mw1) + t.is(app.handlers.Greeter.sayHello[1], handler) +}) + +test('should add handler and middleware when set as array using object', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function handler (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -118,15 +149,17 @@ test('shoud add handler and middleware when set as array using object', t => { app.use({ sayHello: [mw1, handler] }) - t.truthy(app.handlers.sayHello) - t.true(Array.isArray(app.handlers.sayHello)) - t.is(app.handlers.sayHello.length, 2) - t.is(app.handlers.sayHello[0], mw1) - t.is(app.handlers.sayHello[1], handler) + t.truthy(app.handlers.Greeter) + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 2) + t.is(app.handlers.Greeter.sayHello[0], mw1) + t.is(app.handlers.Greeter.sayHello[1], handler) }) -test('shoud add global middleware when set before app.use for that handler', t => { +test('should add global middleware when set before app.use for that handler', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function handler (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -142,15 +175,17 @@ test('shoud add global middleware when set before app.use for that handler', t = app.use(mw1) app.use({ sayHello: handler }) - t.truthy(app.handlers.sayHello) - t.true(Array.isArray(app.handlers.sayHello)) - t.is(app.handlers.sayHello.length, 2) - t.is(app.handlers.sayHello[0], mw1) - t.is(app.handlers.sayHello[1], handler) + t.truthy(app.handlers.Greeter) + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 2) + t.is(app.handlers.Greeter.sayHello[0], mw1) + t.is(app.handlers.Greeter.sayHello[1], handler) }) -test('shoud not add global middleware when set after app.use for that handler', t => { +test('should not add global middleware when set after app.use for that handler', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function handler (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -166,14 +201,16 @@ test('shoud not add global middleware when set after app.use for that handler', app.use({ sayHello: handler }) app.use(mw1) - t.truthy(app.handlers.sayHello) - t.true(Array.isArray(app.handlers.sayHello)) - t.is(app.handlers.sayHello.length, 1) - t.is(app.handlers.sayHello[0], handler) + t.truthy(app.handlers.Greeter) + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 1) + t.is(app.handlers.Greeter.sayHello[0], handler) }) -test('shoud add global middleware and handler and middleware', t => { +test('should add global middleware and handler and middleware', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function handler (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -194,16 +231,18 @@ test('shoud add global middleware and handler and middleware', t => { app.use(mw1) app.use('sayHello', mw2, handler) - t.truthy(app.handlers.sayHello) - t.true(Array.isArray(app.handlers.sayHello)) - t.is(app.handlers.sayHello.length, 3) - t.is(app.handlers.sayHello[0], mw1) - t.is(app.handlers.sayHello[1], mw2) - t.is(app.handlers.sayHello[2], handler) + t.truthy(app.handlers.Greeter) + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 3) + t.is(app.handlers.Greeter.sayHello[0], mw1) + t.is(app.handlers.Greeter.sayHello[1], mw2) + t.is(app.handlers.Greeter.sayHello[2], handler) }) -test('shoud not add global middleware if added after handler and middleware', t => { +test('should not add global middleware if added after handler and middleware', t => { const PROTO_PATH = path.resolve(__dirname, './protos/helloworld.proto') + function handler (ctx) { ctx.res = { message: 'Hello ' + ctx.req.name } } @@ -224,9 +263,477 @@ test('shoud not add global middleware if added after handler and middleware', t app.use('sayHello', mw2, handler) app.use(mw1) - t.truthy(app.handlers.sayHello) - t.true(Array.isArray(app.handlers.sayHello)) - t.is(app.handlers.sayHello.length, 2) - t.is(app.handlers.sayHello[0], mw2) - t.is(app.handlers.sayHello[1], handler) + t.truthy(app.handlers.Greeter) + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 2) + t.is(app.handlers.Greeter.sayHello[0], mw2) + t.is(app.handlers.Greeter.sayHello[1], handler) +}) + +test('multi: should add handler using object notation', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use({ sayHello }) + t.is(_.keys(app.handlers).length, 1) + t.truthy(app.handlers.Greeter.sayHello) + + t.pass() +}) + +test('multi: should add handler using object notation specifying service', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use({ Greeter2: { sayHello } }) + t.is(_.keys(app.handlers).length, 1) + t.truthy(app.handlers.Greeter2.sayHello) + + t.pass() +}) + +test('multi: should add handler using object notation specifying services', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use({ + Greeter: { sayHello }, + Greeter2: { sayHello } + }) + t.is(_.keys(app.handlers).length, 2) + t.truthy(app.handlers.Greeter.sayHello) + t.truthy(app.handlers.Greeter2.sayHello) + + t.pass() +}) + +test('multi: should add handler using name and function', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('sayHello', handler) + t.is(_.keys(app.handlers).length, 1) + t.truthy(app.handlers.Greeter.sayHello) + t.pass() +}) + +test('multi: should add handler using name, service name and function', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter2', 'sayHello', handler) + t.is(_.keys(app.handlers).length, 1) + t.truthy(app.handlers.Greeter2.sayHello) + t.pass() +}) + +test('multi: should add handler using name, and service names and function', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter', 'sayHello', handler) + app.use('Greeter2', 'sayHello', handler) + t.is(_.keys(app.handlers).length, 2) + t.truthy(app.handlers.Greeter.sayHello) + t.truthy(app.handlers.Greeter2.sayHello) + t.pass() +}) + +test('multi: should throw on duplicate handlers using multimple services', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function sayHello (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + function sayHello2 (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use({ sayHello }) + app.use('Greeter2', 'sayHello', sayHello2) + t.is(_.keys(app.handlers).length, 2) + t.truthy(app.handlers.Greeter.sayHello) + t.truthy(app.handlers.Greeter2.sayHello) + + const error = t.throws(() => { + app.use('Greeter2', 'sayHello', sayHello2) + }, Error) + + t.is(error.message, 'Handler for sayHello already defined for service Greeter2') +}) + +test('multi: should add handler and middleware when set using name', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('sayHello', mw1, handler) + + t.truthy(app.handlers.Greeter.sayHello) + t.true(Array.isArray(app.handlers.Greeter.sayHello)) + t.is(app.handlers.Greeter.sayHello.length, 2) + t.is(app.handlers.Greeter.sayHello[0], mw1) + t.is(app.handlers.Greeter.sayHello[1], handler) +}) + +test('multi: should add handler and middleware when set using service name and function name', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter2', 'sayHello', mw1, handler) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 2) + t.is(app.handlers.Greeter2.sayHello[0], mw1) + t.is(app.handlers.Greeter2.sayHello[1], handler) +}) + +test('multi: should add handler and middleware when set using service name and function name 2', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter2', 'sayHello', mw1, handler) + app.use('Greeter4', 'sayGoodbye', handler) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 2) + t.is(app.handlers.Greeter2.sayHello[0], mw1) + t.is(app.handlers.Greeter2.sayHello[1], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 1) + t.is(app.handlers.Greeter4.sayGoodbye[0], handler) +}) + +test('multi: should add service level global middleware', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter2', mw1) + app.use('Greeter2', 'sayHello', handler) + app.use('Greeter4', 'sayGoodbye', handler) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 2) + t.is(app.handlers.Greeter2.sayHello[0], mw1) + t.is(app.handlers.Greeter2.sayHello[1], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 1) + t.is(app.handlers.Greeter4.sayGoodbye[0], handler) +}) + +test('multi: should add service level global middleware', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter4', mw1) + app.use('Greeter2', 'sayHello', handler) + app.use('Greeter4', 'sayGoodbye', handler) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 1) + t.is(app.handlers.Greeter2.sayHello[0], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 2) + t.is(app.handlers.Greeter4.sayGoodbye[0], mw1) + t.is(app.handlers.Greeter4.sayGoodbye[1], handler) +}) + +test('multi: should add global middleware to all services', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use(mw1) + app.use('Greeter2', 'sayHello', handler) + app.use('Greeter4', 'sayGoodbye', handler) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 2) + t.is(app.handlers.Greeter2.sayHello[0], mw1) + t.is(app.handlers.Greeter2.sayHello[1], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 2) + t.is(app.handlers.Greeter4.sayGoodbye[0], mw1) + t.is(app.handlers.Greeter4.sayGoodbye[1], handler) +}) + +test('multi: should add global middleware to all services after the global use', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter4', 'sayGoodbye', handler) + app.use(mw1) + app.use('Greeter2', 'sayHello', handler) + app.use('Greeter4', 'sayHello', handler) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 2) + t.is(app.handlers.Greeter2.sayHello[0], mw1) + t.is(app.handlers.Greeter2.sayHello[1], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 1) + t.is(app.handlers.Greeter4.sayGoodbye[0], handler) + + t.truthy(app.handlers.Greeter4.sayHello) + t.true(Array.isArray(app.handlers.Greeter4.sayHello)) + t.is(app.handlers.Greeter4.sayHello.length, 2) + t.is(app.handlers.Greeter4.sayHello[0], mw1) + t.is(app.handlers.Greeter4.sayHello[1], handler) +}) + +test('multi: should add handlers using object notation for all services', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use({ + Greeter4: { + sayGoodbye: handler, + sayHello: [ mw1, handler ] + }, + Greeter2: { sayHello: handler } + }) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 1) + t.is(app.handlers.Greeter2.sayHello[0], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 1) + t.is(app.handlers.Greeter4.sayGoodbye[0], handler) + + t.truthy(app.handlers.Greeter4.sayHello) + t.true(Array.isArray(app.handlers.Greeter4.sayHello)) + t.is(app.handlers.Greeter4.sayHello.length, 2) + t.is(app.handlers.Greeter4.sayHello[0], mw1) + t.is(app.handlers.Greeter4.sayHello[1], handler) +}) + +test('multi: should add handlers using object notation for all services with global middleware', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use(mw1) + app.use({ + Greeter4: { + sayGoodbye: handler, + sayHello: handler + }, + Greeter2: { sayHello: handler } + }) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 2) + t.is(app.handlers.Greeter2.sayHello[0], mw1) + t.is(app.handlers.Greeter2.sayHello[1], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 2) + t.is(app.handlers.Greeter4.sayGoodbye[0], mw1) + t.is(app.handlers.Greeter4.sayGoodbye[1], handler) + + t.truthy(app.handlers.Greeter4.sayHello) + t.true(Array.isArray(app.handlers.Greeter4.sayHello)) + t.is(app.handlers.Greeter4.sayHello.length, 2) + t.is(app.handlers.Greeter4.sayHello[0], mw1) + t.is(app.handlers.Greeter4.sayHello[1], handler) +}) + +test('multi: should add handlers using object notation for all services with service level middleware', t => { + const PROTO_PATH = path.resolve(__dirname, './protos/multi.proto') + + function handler (ctx) { + ctx.res = { message: 'Hello ' + ctx.req.name } + } + + async function mw1 (ctx, next) { + ctx.mw1 = 'mw1' + await next() + } + + const app = new Mali(PROTO_PATH) + t.truthy(app) + + app.use('Greeter2', mw1) + app.use({ + Greeter4: { + sayGoodbye: handler, + sayHello: [ mw1, handler ] + }, + Greeter2: { sayHello: handler } + }) + + t.truthy(app.handlers.Greeter2.sayHello) + t.true(Array.isArray(app.handlers.Greeter2.sayHello)) + t.is(app.handlers.Greeter2.sayHello.length, 2) + t.is(app.handlers.Greeter2.sayHello[0], mw1) + t.is(app.handlers.Greeter2.sayHello[1], handler) + + t.truthy(app.handlers.Greeter4.sayGoodbye) + t.true(Array.isArray(app.handlers.Greeter4.sayGoodbye)) + t.is(app.handlers.Greeter4.sayGoodbye.length, 1) + t.is(app.handlers.Greeter4.sayGoodbye[0], handler) + + t.truthy(app.handlers.Greeter4.sayHello) + t.true(Array.isArray(app.handlers.Greeter4.sayHello)) + t.is(app.handlers.Greeter4.sayHello.length, 2) + t.is(app.handlers.Greeter4.sayHello[0], mw1) + t.is(app.handlers.Greeter4.sayHello[1], handler) })