Skip to content

Commit 769d755

Browse files
test(): add missing unit tests, resolve conflicts
2 parents 497e5fe + c26bbbb commit 769d755

8 files changed

Lines changed: 533 additions & 18 deletions

File tree

integration/microservices/e2e/sum-grpc.spec.ts

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
import * as ProtoLoader from '@grpc/proto-loader';
12
import { INestApplication } from '@nestjs/common';
23
import { Transport } from '@nestjs/microservices';
34
import { Test } from '@nestjs/testing';
5+
import { fail } from 'assert';
6+
import { expect } from 'chai';
7+
import * as GRPC from 'grpc';
48
import { join } from 'path';
59
import * as request from 'supertest';
610
import { GrpcController } from '../src/grpc/grpc.controller';
711

812
describe('GRPC transport', () => {
913
let server;
1014
let app: INestApplication;
15+
let client: any;
1116

12-
beforeEach(async () => {
17+
before(async () => {
1318
const module = await Test.createTestingModule({
1419
controllers: [GrpcController],
1520
}).compile();
@@ -24,18 +29,83 @@ describe('GRPC transport', () => {
2429
protoPath: join(__dirname, '../src/grpc/math.proto'),
2530
},
2631
});
32+
// Start gRPC microservice
2733
await app.startAllMicroservicesAsync();
2834
await app.init();
35+
// Load proto-buffers for test gRPC dispatch
36+
const proto = ProtoLoader.loadSync(
37+
join(__dirname, '../src/grpc/math.proto'),
38+
) as any;
39+
// Create Raw gRPC client object
40+
const protoGRPC = GRPC.loadPackageDefinition(proto) as any;
41+
// Create client connected to started services at standard 5000 port
42+
client = new protoGRPC.math.Math(
43+
'localhost:5000',
44+
GRPC.credentials.createInsecure(),
45+
);
2946
});
3047

31-
it(`/POST`, () => {
48+
it(`GRPC Sending and Receiving HTTP POST`, () => {
3249
return request(server)
3350
.post('/')
3451
.send([1, 2, 3, 4, 5])
3552
.expect(200, { result: 15 });
3653
});
3754

38-
afterEach(async () => {
55+
it('GRPC Sending and receiving Stream from RX handler', async () => {
56+
const callHandler = client.SumStream();
57+
58+
callHandler.on('data', (msg: number) => {
59+
expect(msg).to.eql({ result: 15 });
60+
callHandler.cancel();
61+
});
62+
63+
callHandler.on('error', (err: any) => {
64+
// We want to fail only on real errors while Cancellation error
65+
// is expected
66+
if (
67+
String(err)
68+
.toLowerCase()
69+
.indexOf('cancelled') === -1
70+
) {
71+
fail('gRPC Stream error happened, error: ' + err);
72+
}
73+
});
74+
75+
return new Promise((resolve, reject) => {
76+
callHandler.write({ data: [1, 2, 3, 4, 5] });
77+
setTimeout(() => resolve(), 1000);
78+
});
79+
});
80+
81+
it('GRPC Sending and receiving Stream from Call Passthrough handler', async () => {
82+
const callHandler = client.SumStreamPass();
83+
84+
callHandler.on('data', (msg: number) => {
85+
expect(msg).to.eql({ result: 15 });
86+
callHandler.cancel();
87+
});
88+
89+
callHandler.on('error', (err: any) => {
90+
// We want to fail only on real errors while Cancellation error
91+
// is expected
92+
if (
93+
String(err)
94+
.toLowerCase()
95+
.indexOf('cancelled') === -1
96+
) {
97+
fail('gRPC Stream error happened, error: ' + err);
98+
}
99+
});
100+
101+
return new Promise((resolve, reject) => {
102+
callHandler.write({ data: [1, 2, 3, 4, 5] });
103+
setTimeout(() => resolve(), 1000);
104+
});
105+
});
106+
107+
after(async () => {
39108
await app.close();
109+
client.close();
40110
});
41111
});

integration/microservices/src/grpc/grpc.controller.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { Body, Controller, HttpCode, Post } from '@nestjs/common';
2-
import { Client, ClientGrpc, GrpcMethod, Transport } from '@nestjs/microservices';
2+
import {
3+
Client,
4+
ClientGrpc,
5+
GrpcMethod,
6+
GrpcStreamCall,
7+
GrpcStreamMethod,
8+
Transport,
9+
} from '@nestjs/microservices';
310
import { join } from 'path';
411
import { Observable, of } from 'rxjs';
512

@@ -27,4 +34,27 @@ export class GrpcController {
2734
result: data.reduce((a, b) => a + b),
2835
});
2936
}
37+
38+
@GrpcStreamMethod('Math')
39+
async sumStream(messages: Observable<any>): Promise<any> {
40+
return new Promise<any>((resolve, reject) => {
41+
messages.subscribe(
42+
msg => {
43+
resolve({
44+
result: msg.data.reduce((a, b) => a + b),
45+
});
46+
},
47+
err => {
48+
reject(err);
49+
},
50+
);
51+
});
52+
}
53+
54+
@GrpcStreamCall('Math')
55+
async sumStreamPass(stream: any) {
56+
stream.on('data', (msg: any) => {
57+
stream.write({ result: msg.data.reduce((a, b) => a + b) });
58+
});
59+
}
3060
}

integration/microservices/src/grpc/math.proto

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ syntax = "proto3";
33
package math;
44

55
service Math {
6-
rpc Sum (RequestSum) returns (SumResult) {}
6+
rpc Sum (RequestSum) returns (SumResult);
7+
rpc SumStream(stream RequestSum) returns(stream SumResult);
8+
rpc SumStreamPass(stream RequestSum) returns(stream SumResult);
79
}
810

911
message SumResult {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
],
142142
"exclude": [
143143
"node_modules/",
144+
"packages/**/test/**",
144145
"packages/**/*.spec.ts",
145146
"packages/**/adapters/*.ts",
146147
"packages/**/nest-*.ts",

packages/microservices/decorators/message-pattern.decorator.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import { PATTERN_HANDLER_METADATA, PATTERN_METADATA } from '../constants';
22
import { PatternHandler } from '../enums/pattern-handler.enum';
33
import { PatternMetadata } from '../interfaces/pattern-metadata.interface';
44

5+
export enum GrpcMethodStreamingType {
6+
NO_STREAMING = 'no_stream',
7+
RX_STREAMING = 'rx_stream',
8+
PT_STREAMING = 'pt_stream',
9+
}
10+
511
/**
612
* Subscribes to incoming messages which fulfils chosen pattern.
713
*/
@@ -39,21 +45,82 @@ export function GrpcMethod(service: string, method?: string): MethodDecorator {
3945
};
4046
}
4147

48+
/**
49+
* Registers gRPC call through RX handler for service and method
50+
*
51+
* @param service String parameter reflecting the name of service definition from proto file
52+
*/
53+
export function GrpcStreamMethod(service?: string);
54+
/**
55+
* @param service String parameter reflecting the name of service definition from proto file
56+
* @param method Optional string parameter reflecting the name of method inside of a service definition coming after rpc keyword
57+
*/
58+
export function GrpcStreamMethod(service: string, method?: string);
59+
export function GrpcStreamMethod(service: string, method?: string) {
60+
return (
61+
target: any,
62+
key: string | symbol,
63+
descriptor: PropertyDescriptor,
64+
) => {
65+
const metadata = createMethodMetadata(
66+
target,
67+
key,
68+
service,
69+
method,
70+
GrpcMethodStreamingType.RX_STREAMING,
71+
);
72+
return MessagePattern(metadata)(target, key, descriptor);
73+
};
74+
}
75+
76+
/**
77+
* Registers gRPC call pass through handler for service and method
78+
*
79+
* @param service String parameter reflecting the name of service definition from proto file
80+
*/
81+
export function GrpcStreamCall(service?: string);
82+
/**
83+
* @param service String parameter reflecting the name of service definition from proto file
84+
* @param method Optional string parameter reflecting the name of method inside of a service definition coming after rpc keyword
85+
*/
86+
export function GrpcStreamCall(service: string, method?: string);
87+
export function GrpcStreamCall(service: string, method?: string) {
88+
return (
89+
target: any,
90+
key: string | symbol,
91+
descriptor: PropertyDescriptor,
92+
) => {
93+
const metadata = createMethodMetadata(
94+
target,
95+
key,
96+
service,
97+
method,
98+
GrpcMethodStreamingType.PT_STREAMING,
99+
);
100+
return MessagePattern(metadata)(target, key, descriptor);
101+
};
102+
}
103+
42104
export function createMethodMetadata(
43105
target: any,
44106
key: string | symbol,
45107
service: string | undefined,
46108
method: string | undefined,
109+
streaming = GrpcMethodStreamingType.NO_STREAMING,
47110
) {
48111
const capitalizeFirstLetter = (str: string) =>
49112
str.charAt(0).toUpperCase() + str.slice(1);
50113

51114
if (!service) {
52115
const { name } = target.constructor;
53-
return { service: name, rpc: capitalizeFirstLetter(key as string) };
116+
return {
117+
service: name,
118+
rpc: capitalizeFirstLetter(key as string),
119+
streaming,
120+
};
54121
}
55122
if (service && !method) {
56-
return { service, rpc: capitalizeFirstLetter(key as string) };
123+
return { service, rpc: capitalizeFirstLetter(key as string), streaming };
57124
}
58-
return { service, rpc: method };
125+
return { service, rpc: method, streaming };
59126
}

0 commit comments

Comments
 (0)