@hazeljs/grpc 0.2.0-beta.38 → 0.2.0-beta.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,8 @@
5
5
  RPC server support with decorator-based handlers. Built on [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js) and [@grpc/proto-loader](https://www.npmjs.com/package/@grpc/proto-loader).
6
6
 
7
7
  [![npm version](https://img.shields.io/npm/v/@hazeljs/grpc.svg)](https://www.npmjs.com/package/@hazeljs/grpc)
8
- [![License: Apache-2.0](https://img.shields.io/npm/l/@hazeljs/grpc.svg)](https://www.apache.org/licenses/LICENSE-2.0)
8
+ [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/grpc)](https://www.npmjs.com/package/@hazeljs/grpc)
9
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
9
10
 
10
11
  ## Features
11
12
 
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=grpc-method.decorator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc-method.decorator.test.d.ts","sourceRoot":"","sources":["../../src/decorators/grpc-method.decorator.test.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC"}
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ require("reflect-metadata");
13
+ const grpc_method_decorator_1 = require("./grpc-method.decorator");
14
+ describe('GrpcMethod decorator', () => {
15
+ it('should define GRPC_METHOD_METADATA_KEY', () => {
16
+ expect(typeof grpc_method_decorator_1.GRPC_METHOD_METADATA_KEY).toBe('symbol');
17
+ });
18
+ it('should add metadata for service and method', () => {
19
+ class TestController {
20
+ findOne(_data) {
21
+ return { id: 1, name: 'Hero' };
22
+ }
23
+ }
24
+ __decorate([
25
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
26
+ __metadata("design:type", Function),
27
+ __metadata("design:paramtypes", [Object]),
28
+ __metadata("design:returntype", void 0)
29
+ ], TestController.prototype, "findOne", null);
30
+ const metadata = (0, grpc_method_decorator_1.getGrpcMethodMetadata)(new TestController());
31
+ expect(metadata).toHaveLength(1);
32
+ expect(metadata[0].service).toBe('HeroService');
33
+ expect(metadata[0].method).toBe('FindOne');
34
+ expect(metadata[0].methodName).toBe('findOne');
35
+ });
36
+ it('should default method name to property key when not provided', () => {
37
+ class TestController {
38
+ findOne(_data) {
39
+ return { id: 1, name: 'Hero' };
40
+ }
41
+ }
42
+ __decorate([
43
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService'),
44
+ __metadata("design:type", Function),
45
+ __metadata("design:paramtypes", [Object]),
46
+ __metadata("design:returntype", void 0)
47
+ ], TestController.prototype, "findOne", null);
48
+ const metadata = (0, grpc_method_decorator_1.getGrpcMethodMetadata)(new TestController());
49
+ expect(metadata[0].method).toBe('findOne');
50
+ expect(metadata[0].methodName).toBe('findOne');
51
+ });
52
+ it('should add metadata for multiple methods on same class', () => {
53
+ class TestController {
54
+ findOne(_data) {
55
+ return { id: 1, name: 'Hero' };
56
+ }
57
+ findAll(_data) {
58
+ return [];
59
+ }
60
+ }
61
+ __decorate([
62
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
63
+ __metadata("design:type", Function),
64
+ __metadata("design:paramtypes", [Object]),
65
+ __metadata("design:returntype", void 0)
66
+ ], TestController.prototype, "findOne", null);
67
+ __decorate([
68
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindAll'),
69
+ __metadata("design:type", Function),
70
+ __metadata("design:paramtypes", [Object]),
71
+ __metadata("design:returntype", void 0)
72
+ ], TestController.prototype, "findAll", null);
73
+ const metadata = (0, grpc_method_decorator_1.getGrpcMethodMetadata)(new TestController());
74
+ expect(metadata).toHaveLength(2);
75
+ expect(metadata[0].service).toBe('HeroService');
76
+ expect(metadata[0].method).toBe('FindOne');
77
+ expect(metadata[1].service).toBe('HeroService');
78
+ expect(metadata[1].method).toBe('FindAll');
79
+ });
80
+ it('should support different services on same class', () => {
81
+ class TestController {
82
+ findHero(_data) {
83
+ return { id: 1, name: 'Hero' };
84
+ }
85
+ getUser(_data) {
86
+ return { id: '1', name: 'User' };
87
+ }
88
+ }
89
+ __decorate([
90
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
91
+ __metadata("design:type", Function),
92
+ __metadata("design:paramtypes", [Object]),
93
+ __metadata("design:returntype", void 0)
94
+ ], TestController.prototype, "findHero", null);
95
+ __decorate([
96
+ (0, grpc_method_decorator_1.GrpcMethod)('UserService', 'GetUser'),
97
+ __metadata("design:type", Function),
98
+ __metadata("design:paramtypes", [Object]),
99
+ __metadata("design:returntype", void 0)
100
+ ], TestController.prototype, "getUser", null);
101
+ const metadata = (0, grpc_method_decorator_1.getGrpcMethodMetadata)(new TestController());
102
+ expect(metadata).toHaveLength(2);
103
+ expect(metadata[0].service).toBe('HeroService');
104
+ expect(metadata[1].service).toBe('UserService');
105
+ });
106
+ });
107
+ describe('getGrpcMethodMetadata', () => {
108
+ it('should return empty array for class without @GrpcMethod decorators', () => {
109
+ class PlainClass {
110
+ }
111
+ const metadata = (0, grpc_method_decorator_1.getGrpcMethodMetadata)(new PlainClass());
112
+ expect(metadata).toEqual([]);
113
+ });
114
+ it('should return metadata from class constructor', () => {
115
+ class TestController {
116
+ testMethod() { }
117
+ }
118
+ __decorate([
119
+ (0, grpc_method_decorator_1.GrpcMethod)('TestService', 'TestMethod'),
120
+ __metadata("design:type", Function),
121
+ __metadata("design:paramtypes", []),
122
+ __metadata("design:returntype", void 0)
123
+ ], TestController.prototype, "testMethod", null);
124
+ const instance = new TestController();
125
+ const metadata = (0, grpc_method_decorator_1.getGrpcMethodMetadata)(instance);
126
+ expect(metadata).toHaveLength(1);
127
+ expect(metadata[0].service).toBe('TestService');
128
+ expect(metadata[0].method).toBe('TestMethod');
129
+ });
130
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=grpc.client.module.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc.client.module.test.d.ts","sourceRoot":"","sources":["../src/grpc.client.module.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const grpc_client_module_1 = require("./grpc.client.module");
8
+ const grpc_client_1 = require("./grpc.client");
9
+ describe('GrpcClientModule', () => {
10
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
11
+ describe('forRoot', () => {
12
+ it('should return module config with providers and exports', () => {
13
+ const config = grpc_client_module_1.GrpcClientModule.forRoot({
14
+ protoPath,
15
+ package: 'hero',
16
+ defaultUrl: 'localhost:50051',
17
+ });
18
+ expect(config.module).toBe(grpc_client_module_1.GrpcClientModule);
19
+ expect(config.providers).toHaveLength(1);
20
+ expect(config.providers[0].provide).toBe(grpc_client_1.GrpcClientService);
21
+ expect(config.providers[0].useFactory).toBeDefined();
22
+ expect(config.exports).toContain(grpc_client_1.GrpcClientService);
23
+ expect(config.global).toBe(true);
24
+ });
25
+ it('should use isGlobal option', () => {
26
+ const config = grpc_client_module_1.GrpcClientModule.forRoot({
27
+ protoPath,
28
+ package: 'hero',
29
+ defaultUrl: 'localhost:50051',
30
+ isGlobal: false,
31
+ });
32
+ expect(config.global).toBe(false);
33
+ });
34
+ it('should default isGlobal to true when not provided', () => {
35
+ const config = grpc_client_module_1.GrpcClientModule.forRoot({
36
+ protoPath,
37
+ package: 'hero',
38
+ defaultUrl: 'localhost:50051',
39
+ });
40
+ expect(config.global).toBe(true);
41
+ });
42
+ it('should create configured GrpcClientService via factory', () => {
43
+ const config = grpc_client_module_1.GrpcClientModule.forRoot({
44
+ protoPath,
45
+ package: 'hero',
46
+ defaultUrl: 'localhost:50051',
47
+ });
48
+ const factory = config.providers[0].useFactory;
49
+ const client = factory();
50
+ expect(client).toBeInstanceOf(grpc_client_1.GrpcClientService);
51
+ });
52
+ });
53
+ describe('forRootAsync', () => {
54
+ it('should return module config with async providers', () => {
55
+ const config = grpc_client_module_1.GrpcClientModule.forRootAsync({
56
+ useFactory: () => ({ protoPath, package: 'hero', defaultUrl: 'localhost:50051' }),
57
+ });
58
+ expect(config.module).toBe(grpc_client_module_1.GrpcClientModule);
59
+ expect(config.providers).toHaveLength(2);
60
+ expect(config.providers[0].provide).toBe('GRPC_CLIENT_OPTIONS');
61
+ expect(config.providers[1].provide).toBe(grpc_client_1.GrpcClientService);
62
+ expect(config.global).toBe(true);
63
+ });
64
+ it('should support inject option', () => {
65
+ const config = grpc_client_module_1.GrpcClientModule.forRootAsync({
66
+ useFactory: () => ({
67
+ protoPath: 'test.proto',
68
+ package: 'test',
69
+ defaultUrl: 'localhost:50051',
70
+ }),
71
+ inject: ['ConfigService'],
72
+ });
73
+ expect(config.providers[0]).toHaveProperty('inject', ['ConfigService']);
74
+ });
75
+ it('should create GrpcClientService with async options', async () => {
76
+ const config = grpc_client_module_1.GrpcClientModule.forRootAsync({
77
+ useFactory: async () => ({ protoPath, package: 'hero', defaultUrl: 'localhost:50051' }),
78
+ });
79
+ const optionsFactory = config.providers[0].useFactory;
80
+ const options = await optionsFactory();
81
+ const clientFactory = config.providers[1].useFactory;
82
+ const client = clientFactory(options);
83
+ expect(client).toBeInstanceOf(grpc_client_1.GrpcClientService);
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=grpc.client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc.client.test.d.ts","sourceRoot":"","sources":["../src/grpc.client.test.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,CAAC"}
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ /// <reference types="jest" />
40
+ require("reflect-metadata");
41
+ const path_1 = __importDefault(require("path"));
42
+ const grpc = __importStar(require("@grpc/grpc-js"));
43
+ const grpc_client_1 = require("./grpc.client");
44
+ const mockStub = {
45
+ FindOne: jest.fn(),
46
+ };
47
+ jest.mock('@grpc/grpc-js', () => {
48
+ const actual = jest.requireActual('@grpc/grpc-js');
49
+ return {
50
+ ...actual,
51
+ loadPackageDefinition: jest.fn((_pkgDef) => {
52
+ return {
53
+ hero: {
54
+ HeroService: jest.fn().mockImplementation(() => mockStub),
55
+ },
56
+ };
57
+ }),
58
+ };
59
+ });
60
+ describe('GrpcClientService', () => {
61
+ let client;
62
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
63
+ beforeEach(() => {
64
+ client = new grpc_client_1.GrpcClientService();
65
+ jest.clearAllMocks();
66
+ });
67
+ describe('configure', () => {
68
+ it('should load proto and set options', () => {
69
+ client.configure({
70
+ protoPath,
71
+ package: 'hero',
72
+ defaultUrl: 'localhost:50051',
73
+ });
74
+ expect(grpc.loadPackageDefinition).toHaveBeenCalled();
75
+ });
76
+ it('should accept array of proto paths', () => {
77
+ client.configure({
78
+ protoPath: [protoPath],
79
+ package: 'hero',
80
+ defaultUrl: 'localhost:50051',
81
+ });
82
+ expect(grpc.loadPackageDefinition).toHaveBeenCalled();
83
+ });
84
+ });
85
+ describe('getClient', () => {
86
+ it('should throw when not configured', () => {
87
+ expect(() => client.getClient('HeroService')).toThrow('GrpcClientService not configured. Use GrpcClientModule.forRoot() first.');
88
+ });
89
+ it('should return stub for configured service', () => {
90
+ client.configure({
91
+ protoPath,
92
+ package: 'hero',
93
+ defaultUrl: 'localhost:50051',
94
+ });
95
+ const stub = client.getClient('HeroService');
96
+ expect(stub).toBeDefined();
97
+ expect(stub.FindOne).toBeDefined();
98
+ });
99
+ it('should use defaultUrl when url not passed', () => {
100
+ client.configure({
101
+ protoPath,
102
+ package: 'hero',
103
+ defaultUrl: 'localhost:50052',
104
+ });
105
+ const stub = client.getClient('HeroService');
106
+ expect(stub).toBe(mockStub);
107
+ });
108
+ it('should use passed url when provided', () => {
109
+ client.configure({
110
+ protoPath,
111
+ package: 'hero',
112
+ defaultUrl: 'localhost:50051',
113
+ });
114
+ const stub = client.getClient('HeroService', 'localhost:50053');
115
+ expect(stub).toBe(mockStub);
116
+ });
117
+ it('should throw when package not found in proto', () => {
118
+ grpc.loadPackageDefinition.mockReturnValueOnce({});
119
+ client.configure({
120
+ protoPath,
121
+ package: 'nonexistent',
122
+ defaultUrl: 'localhost:50051',
123
+ });
124
+ expect(() => client.getClient('HeroService')).toThrow('Package "nonexistent" not found in proto descriptor');
125
+ });
126
+ it('should throw when service not found in package', () => {
127
+ grpc.loadPackageDefinition.mockReturnValueOnce({
128
+ hero: {},
129
+ });
130
+ client.configure({
131
+ protoPath,
132
+ package: 'hero',
133
+ defaultUrl: 'localhost:50051',
134
+ });
135
+ expect(() => client.getClient('NonExistentService')).toThrow('Service "NonExistentService" not found in package "hero"');
136
+ });
137
+ it('should throw when no defaultUrl and url not passed', () => {
138
+ client.configure({
139
+ protoPath,
140
+ package: 'hero',
141
+ });
142
+ expect(() => client.getClient('HeroService')).toThrow('No URL provided. Pass url to getClient() or set defaultUrl in GrpcClientModule.forRoot()');
143
+ });
144
+ it('should cache stubs per service and url', () => {
145
+ client.configure({
146
+ protoPath,
147
+ package: 'hero',
148
+ defaultUrl: 'localhost:50051',
149
+ });
150
+ const stub1 = client.getClient('HeroService');
151
+ const stub2 = client.getClient('HeroService');
152
+ expect(stub1).toBe(stub2);
153
+ });
154
+ });
155
+ describe('getClientAsync', () => {
156
+ it('should resolve with stub when using defaultUrl', async () => {
157
+ client.configure({
158
+ protoPath,
159
+ package: 'hero',
160
+ defaultUrl: 'localhost:50051',
161
+ });
162
+ const stub = await client.getClientAsync('HeroService');
163
+ expect(stub).toBeDefined();
164
+ expect(stub.FindOne).toBeDefined();
165
+ });
166
+ it('should resolve with stub when url passed', async () => {
167
+ client.configure({
168
+ protoPath,
169
+ package: 'hero',
170
+ defaultUrl: 'localhost:50051',
171
+ });
172
+ const stub = await client.getClientAsync('HeroService', 'localhost:50052');
173
+ expect(stub).toBe(mockStub);
174
+ });
175
+ it('should use Discovery when configured', async () => {
176
+ const mockInstance = { host: '127.0.0.1', port: 50051 };
177
+ client.configure({
178
+ protoPath,
179
+ package: 'hero',
180
+ discovery: {
181
+ client: {
182
+ getInstance: jest.fn().mockResolvedValue(mockInstance),
183
+ },
184
+ serviceName: 'hero-service',
185
+ },
186
+ });
187
+ const stub = await client.getClientAsync('HeroService');
188
+ expect(stub).toBe(mockStub);
189
+ });
190
+ it('should throw when Discovery returns null', async () => {
191
+ client.configure({
192
+ protoPath,
193
+ package: 'hero',
194
+ discovery: {
195
+ client: {
196
+ getInstance: jest.fn().mockResolvedValue(null),
197
+ },
198
+ serviceName: 'hero-service',
199
+ },
200
+ });
201
+ await expect(client.getClientAsync('HeroService')).rejects.toThrow('Discovery: no gRPC instance found for service "hero-service"');
202
+ });
203
+ });
204
+ describe('close', () => {
205
+ it('should clear state', () => {
206
+ client.configure({
207
+ protoPath,
208
+ package: 'hero',
209
+ defaultUrl: 'localhost:50051',
210
+ });
211
+ client.getClient('HeroService');
212
+ client.close();
213
+ expect(() => client.getClient('HeroService')).toThrow('GrpcClientService not configured');
214
+ });
215
+ });
216
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=grpc.module.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc.module.test.d.ts","sourceRoot":"","sources":["../src/grpc.module.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const core_1 = require("@hazeljs/core");
16
+ const grpc_module_1 = require("./grpc.module");
17
+ const grpc_server_1 = require("./grpc.server");
18
+ const grpc_method_decorator_1 = require("./decorators/grpc-method.decorator");
19
+ const path_1 = __importDefault(require("path"));
20
+ describe('GrpcModule', () => {
21
+ let container;
22
+ const originalGetInstance = core_1.Container.getInstance;
23
+ beforeEach(() => {
24
+ container = core_1.Container.createTestInstance();
25
+ core_1.Container.getInstance = jest.fn(() => container);
26
+ });
27
+ afterEach(() => {
28
+ core_1.Container.getInstance = originalGetInstance;
29
+ });
30
+ describe('forRoot', () => {
31
+ it('should return module config with providers and exports', () => {
32
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
33
+ const config = grpc_module_1.GrpcModule.forRoot({
34
+ protoPath,
35
+ package: 'hero',
36
+ });
37
+ expect(config.module).toBe(grpc_module_1.GrpcModule);
38
+ expect(config.providers).toHaveLength(1);
39
+ expect(config.providers[0].provide).toBe(grpc_server_1.GrpcServer);
40
+ expect(config.providers[0].useFactory).toBeDefined();
41
+ expect(config.exports).toContain(grpc_server_1.GrpcServer);
42
+ expect(config.global).toBe(true);
43
+ });
44
+ it('should use isGlobal option', () => {
45
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
46
+ const config = grpc_module_1.GrpcModule.forRoot({
47
+ protoPath,
48
+ package: 'hero',
49
+ isGlobal: false,
50
+ });
51
+ expect(config.global).toBe(false);
52
+ });
53
+ it('should default isGlobal to true when not provided', () => {
54
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
55
+ const config = grpc_module_1.GrpcModule.forRoot({
56
+ protoPath,
57
+ package: 'hero',
58
+ });
59
+ expect(config.global).toBe(true);
60
+ });
61
+ it('should create configured GrpcServer via factory', () => {
62
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
63
+ const config = grpc_module_1.GrpcModule.forRoot({
64
+ protoPath,
65
+ package: 'hero',
66
+ url: '0.0.0.0:50052',
67
+ });
68
+ const factory = config.providers[0].useFactory;
69
+ const server = factory();
70
+ expect(server).toBeInstanceOf(grpc_server_1.GrpcServer);
71
+ expect(server.getServer()).toBeNull();
72
+ });
73
+ });
74
+ describe('forRootAsync', () => {
75
+ it('should return module config with async providers', () => {
76
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
77
+ const config = grpc_module_1.GrpcModule.forRootAsync({
78
+ useFactory: () => ({ protoPath, package: 'hero' }),
79
+ });
80
+ expect(config.module).toBe(grpc_module_1.GrpcModule);
81
+ expect(config.providers).toHaveLength(2);
82
+ expect(config.providers[0].provide).toBe('GRPC_OPTIONS');
83
+ expect(config.providers[1].provide).toBe(grpc_server_1.GrpcServer);
84
+ expect(config.global).toBe(true);
85
+ });
86
+ it('should support inject option', () => {
87
+ const config = grpc_module_1.GrpcModule.forRootAsync({
88
+ useFactory: () => ({ protoPath: 'test.proto', package: 'test' }),
89
+ inject: ['ConfigService'],
90
+ });
91
+ expect(config.providers[0]).toHaveProperty('inject', ['ConfigService']);
92
+ });
93
+ it('should create GrpcServer with async options', async () => {
94
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
95
+ const config = grpc_module_1.GrpcModule.forRootAsync({
96
+ useFactory: async () => ({ protoPath, package: 'hero' }),
97
+ });
98
+ const optionsFactory = config.providers[0].useFactory;
99
+ const options = await optionsFactory();
100
+ const serverFactory = config.providers[1].useFactory;
101
+ const server = serverFactory(options);
102
+ expect(server).toBeInstanceOf(grpc_server_1.GrpcServer);
103
+ });
104
+ });
105
+ describe('registerHandlersFromProvider', () => {
106
+ it('should register gRPC handlers from provider with @GrpcMethod', () => {
107
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
108
+ const grpcServer = new grpc_server_1.GrpcServer();
109
+ grpcServer.configure({ protoPath, package: 'hero' });
110
+ container.register(grpc_server_1.GrpcServer, grpcServer);
111
+ class TestController {
112
+ findOne(data) {
113
+ return { id: data.id, name: 'Hero' };
114
+ }
115
+ }
116
+ __decorate([
117
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
118
+ __metadata("design:type", Function),
119
+ __metadata("design:paramtypes", [Object]),
120
+ __metadata("design:returntype", void 0)
121
+ ], TestController.prototype, "findOne", null);
122
+ const controller = new TestController();
123
+ grpc_module_1.GrpcModule.registerHandlersFromProvider(controller);
124
+ expect(grpcServer.getServer()).toBeNull();
125
+ });
126
+ it('should handle GrpcServer not in container', () => {
127
+ const emptyContainer = core_1.Container.createTestInstance();
128
+ emptyContainer.register(grpc_server_1.GrpcServer, undefined);
129
+ core_1.Container.getInstance = jest.fn(() => emptyContainer);
130
+ class TestController {
131
+ findOne() {
132
+ return { id: 1, name: 'Hero' };
133
+ }
134
+ }
135
+ __decorate([
136
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
137
+ __metadata("design:type", Function),
138
+ __metadata("design:paramtypes", []),
139
+ __metadata("design:returntype", void 0)
140
+ ], TestController.prototype, "findOne", null);
141
+ expect(() => {
142
+ grpc_module_1.GrpcModule.registerHandlersFromProvider(new TestController());
143
+ }).not.toThrow();
144
+ });
145
+ it('should handle errors during registration gracefully', () => {
146
+ core_1.Container.getInstance = jest.fn(() => {
147
+ throw new Error('Container error');
148
+ });
149
+ class TestController {
150
+ findOne() {
151
+ return { id: 1, name: 'Hero' };
152
+ }
153
+ }
154
+ __decorate([
155
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
156
+ __metadata("design:type", Function),
157
+ __metadata("design:paramtypes", []),
158
+ __metadata("design:returntype", void 0)
159
+ ], TestController.prototype, "findOne", null);
160
+ expect(() => {
161
+ grpc_module_1.GrpcModule.registerHandlersFromProvider(new TestController());
162
+ }).not.toThrow();
163
+ });
164
+ });
165
+ describe('registerHandlersFromProviders', () => {
166
+ it('should register handlers from multiple provider classes', () => {
167
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
168
+ const grpcServer = new grpc_server_1.GrpcServer();
169
+ grpcServer.configure({ protoPath, package: 'hero' });
170
+ container.register(grpc_server_1.GrpcServer, grpcServer);
171
+ class HeroController {
172
+ findOne(data) {
173
+ return { id: data.id, name: 'Hero' };
174
+ }
175
+ }
176
+ __decorate([
177
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
178
+ __metadata("design:type", Function),
179
+ __metadata("design:paramtypes", [Object]),
180
+ __metadata("design:returntype", void 0)
181
+ ], HeroController.prototype, "findOne", null);
182
+ const heroInstance = new HeroController();
183
+ container.register(HeroController, heroInstance);
184
+ grpc_module_1.GrpcModule.registerHandlersFromProviders([HeroController]);
185
+ expect(grpcServer.getServer()).toBeNull();
186
+ });
187
+ it('should handle GrpcServer not in container', () => {
188
+ const emptyContainer = core_1.Container.createTestInstance();
189
+ emptyContainer.register(grpc_server_1.GrpcServer, undefined);
190
+ core_1.Container.getInstance = jest.fn(() => emptyContainer);
191
+ class TestController {
192
+ findOne() {
193
+ return { id: 1, name: 'Hero' };
194
+ }
195
+ }
196
+ __decorate([
197
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
198
+ __metadata("design:type", Function),
199
+ __metadata("design:paramtypes", []),
200
+ __metadata("design:returntype", void 0)
201
+ ], TestController.prototype, "findOne", null);
202
+ container.register(TestController, new TestController());
203
+ expect(() => {
204
+ grpc_module_1.GrpcModule.registerHandlersFromProviders([TestController]);
205
+ }).not.toThrow();
206
+ });
207
+ });
208
+ });
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=grpc.server.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc.server.test.d.ts","sourceRoot":"","sources":["../src/grpc.server.test.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC"}
@@ -0,0 +1,441 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const core_1 = require("@hazeljs/core");
50
+ const grpc_server_1 = require("./grpc.server");
51
+ const grpc_method_decorator_1 = require("./decorators/grpc-method.decorator");
52
+ const grpc = __importStar(require("@grpc/grpc-js"));
53
+ const path_1 = __importDefault(require("path"));
54
+ jest.mock('@grpc/grpc-js', () => {
55
+ const actual = jest.requireActual('@grpc/grpc-js');
56
+ return {
57
+ ...actual,
58
+ Server: jest.fn().mockImplementation(() => {
59
+ const mockServer = {
60
+ addService: jest.fn(),
61
+ bindAsync: jest.fn((_url, _creds, callback) => {
62
+ setImmediate(() => callback(null));
63
+ }),
64
+ start: jest.fn(),
65
+ tryShutdown: jest.fn((callback) => {
66
+ setImmediate(() => callback(null));
67
+ }),
68
+ forceShutdown: jest.fn(),
69
+ };
70
+ return mockServer;
71
+ }),
72
+ };
73
+ });
74
+ describe('GrpcServer', () => {
75
+ let container;
76
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
77
+ beforeEach(() => {
78
+ container = core_1.Container.createTestInstance();
79
+ core_1.Container.getInstance = jest.fn(() => container);
80
+ });
81
+ describe('configure', () => {
82
+ it('should load proto and set options', () => {
83
+ const server = new grpc_server_1.GrpcServer();
84
+ server.configure({
85
+ protoPath,
86
+ package: 'hero',
87
+ url: '0.0.0.0:50052',
88
+ });
89
+ expect(server.getServer()).toBeNull();
90
+ });
91
+ it('should accept array of proto paths', () => {
92
+ const server = new grpc_server_1.GrpcServer();
93
+ server.configure({
94
+ protoPath: [protoPath],
95
+ package: 'hero',
96
+ });
97
+ expect(server.getServer()).toBeNull();
98
+ });
99
+ it('should accept loader options', () => {
100
+ const server = new grpc_server_1.GrpcServer();
101
+ server.configure({
102
+ protoPath,
103
+ package: 'hero',
104
+ loader: { keepCase: false },
105
+ });
106
+ expect(server.getServer()).toBeNull();
107
+ });
108
+ });
109
+ describe('registerHandlersFromProvider', () => {
110
+ it('should register handler from provider with @GrpcMethod', () => {
111
+ const server = new grpc_server_1.GrpcServer();
112
+ server.configure({ protoPath, package: 'hero' });
113
+ class TestController {
114
+ findOne(data) {
115
+ return { id: data.id, name: 'Hero' };
116
+ }
117
+ }
118
+ __decorate([
119
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
120
+ __metadata("design:type", Function),
121
+ __metadata("design:paramtypes", [Object]),
122
+ __metadata("design:returntype", void 0)
123
+ ], TestController.prototype, "findOne", null);
124
+ server.registerHandlersFromProvider(new TestController());
125
+ expect(server.getServer()).toBeNull();
126
+ });
127
+ it('should skip when method is not a function', () => {
128
+ const server = new grpc_server_1.GrpcServer();
129
+ server.configure({ protoPath, package: 'hero' });
130
+ class TestController {
131
+ findOne(_data) {
132
+ return { id: 1, name: 'Hero' };
133
+ }
134
+ }
135
+ __decorate([
136
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
137
+ __metadata("design:type", Function),
138
+ __metadata("design:paramtypes", [Object]),
139
+ __metadata("design:returntype", void 0)
140
+ ], TestController.prototype, "findOne", null);
141
+ const controller = new TestController();
142
+ controller.findOne = 'not a function';
143
+ expect(() => server.registerHandlersFromProvider(controller)).not.toThrow();
144
+ });
145
+ it('should handle provider with no @GrpcMethod decorators', () => {
146
+ const server = new grpc_server_1.GrpcServer();
147
+ server.configure({ protoPath, package: 'hero' });
148
+ class PlainController {
149
+ }
150
+ expect(() => server.registerHandlersFromProvider(new PlainController())).not.toThrow();
151
+ });
152
+ it('should support default method name from property key', () => {
153
+ const server = new grpc_server_1.GrpcServer();
154
+ server.configure({ protoPath, package: 'hero' });
155
+ class TestController {
156
+ findOne(data) {
157
+ return { id: data.id, name: 'Hero' };
158
+ }
159
+ }
160
+ __decorate([
161
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService'),
162
+ __metadata("design:type", Function),
163
+ __metadata("design:paramtypes", [Object]),
164
+ __metadata("design:returntype", void 0)
165
+ ], TestController.prototype, "findOne", null);
166
+ expect(() => server.registerHandlersFromProvider(new TestController())).not.toThrow();
167
+ });
168
+ });
169
+ describe('registerHandlersFromProviders', () => {
170
+ it('should register handlers from container-resolved providers', () => {
171
+ const server = new grpc_server_1.GrpcServer();
172
+ server.configure({ protoPath, package: 'hero' });
173
+ container.register(grpc_server_1.GrpcServer, server);
174
+ class HeroController {
175
+ findOne(data) {
176
+ return { id: data.id, name: 'Hero' };
177
+ }
178
+ }
179
+ __decorate([
180
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
181
+ __metadata("design:type", Function),
182
+ __metadata("design:paramtypes", [Object]),
183
+ __metadata("design:returntype", void 0)
184
+ ], HeroController.prototype, "findOne", null);
185
+ container.register(HeroController, new HeroController());
186
+ server.registerHandlersFromProviders([HeroController]);
187
+ expect(server.getServer()).toBeNull();
188
+ });
189
+ it('should skip provider when resolve returns undefined', () => {
190
+ const server = new grpc_server_1.GrpcServer();
191
+ server.configure({ protoPath, package: 'hero' });
192
+ container.register(grpc_server_1.GrpcServer, server);
193
+ class HeroController {
194
+ findOne() {
195
+ return { id: 1, name: 'Hero' };
196
+ }
197
+ }
198
+ __decorate([
199
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
200
+ __metadata("design:type", Function),
201
+ __metadata("design:paramtypes", []),
202
+ __metadata("design:returntype", void 0)
203
+ ], HeroController.prototype, "findOne", null);
204
+ const originalResolve = container.resolve.bind(container);
205
+ jest.spyOn(container, 'resolve').mockImplementation((token) => {
206
+ if (token === HeroController)
207
+ return undefined;
208
+ return originalResolve(token);
209
+ });
210
+ expect(() => server.registerHandlersFromProviders([HeroController])).not.toThrow();
211
+ });
212
+ });
213
+ describe('start', () => {
214
+ it('should start server and bind to url', async () => {
215
+ const server = new grpc_server_1.GrpcServer();
216
+ server.configure({ protoPath, package: 'hero', url: '0.0.0.0:50051' });
217
+ class TestController {
218
+ findOne(data) {
219
+ return { id: data.id, name: 'Hero' };
220
+ }
221
+ }
222
+ __decorate([
223
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
224
+ __metadata("design:type", Function),
225
+ __metadata("design:paramtypes", [Object]),
226
+ __metadata("design:returntype", void 0)
227
+ ], TestController.prototype, "findOne", null);
228
+ server.registerHandlersFromProvider(new TestController());
229
+ await server.start();
230
+ expect(server.getServer()).not.toBeNull();
231
+ expect(grpc.Server).toHaveBeenCalled();
232
+ const mockServer = grpc.Server.mock.results[0]?.value;
233
+ expect(mockServer.addService).toHaveBeenCalled();
234
+ expect(mockServer.bindAsync).toHaveBeenCalledWith('0.0.0.0:50051', expect.anything(), expect.any(Function));
235
+ await server.close();
236
+ });
237
+ it('should throw when not configured', async () => {
238
+ const server = new grpc_server_1.GrpcServer();
239
+ await expect(server.start()).rejects.toThrow('GrpcServer not configured. Call configure() or use GrpcModule.forRoot() first.');
240
+ });
241
+ it('should throw when package not found in proto', async () => {
242
+ const server = new grpc_server_1.GrpcServer();
243
+ server.configure({ protoPath, package: 'nonexistent_package' });
244
+ await expect(server.start()).rejects.toThrow('Package "nonexistent_package" not found in proto descriptor');
245
+ });
246
+ it('should reject when bindAsync fails', async () => {
247
+ const ServerMock = grpc.Server;
248
+ const originalImpl = ServerMock.getMockImplementation();
249
+ ServerMock.mockImplementationOnce(() => ({
250
+ addService: jest.fn(),
251
+ bindAsync: jest.fn((_url, _creds, callback) => {
252
+ setImmediate(() => callback(new Error('Bind failed')));
253
+ }),
254
+ start: jest.fn(),
255
+ tryShutdown: jest.fn(),
256
+ forceShutdown: jest.fn(),
257
+ }));
258
+ const server = new grpc_server_1.GrpcServer();
259
+ server.configure({ protoPath, package: 'hero' });
260
+ class TestController {
261
+ findOne() {
262
+ return { id: 1, name: 'Hero' };
263
+ }
264
+ }
265
+ __decorate([
266
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
267
+ __metadata("design:type", Function),
268
+ __metadata("design:paramtypes", []),
269
+ __metadata("design:returntype", void 0)
270
+ ], TestController.prototype, "findOne", null);
271
+ server.registerHandlersFromProvider(new TestController());
272
+ await expect(server.start()).rejects.toThrow('Bind failed');
273
+ ServerMock.mockImplementation(originalImpl);
274
+ });
275
+ it('should skip service when not found in proto', async () => {
276
+ const server = new grpc_server_1.GrpcServer();
277
+ server.configure({ protoPath, package: 'hero' });
278
+ class TestController {
279
+ findOne() {
280
+ return { id: 1, name: 'Hero' };
281
+ }
282
+ }
283
+ __decorate([
284
+ (0, grpc_method_decorator_1.GrpcMethod)('NonExistentService', 'FindOne'),
285
+ __metadata("design:type", Function),
286
+ __metadata("design:paramtypes", []),
287
+ __metadata("design:returntype", void 0)
288
+ ], TestController.prototype, "findOne", null);
289
+ server.registerHandlersFromProvider(new TestController());
290
+ await server.start();
291
+ const mockServer = server.getServer();
292
+ expect(mockServer.addService).not.toHaveBeenCalled();
293
+ await server.close();
294
+ });
295
+ it('should use default url when not provided', async () => {
296
+ const server = new grpc_server_1.GrpcServer();
297
+ server.configure({ protoPath, package: 'hero' });
298
+ class TestController {
299
+ findOne(data) {
300
+ return { id: data.id, name: 'Hero' };
301
+ }
302
+ }
303
+ __decorate([
304
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
305
+ __metadata("design:type", Function),
306
+ __metadata("design:paramtypes", [Object]),
307
+ __metadata("design:returntype", void 0)
308
+ ], TestController.prototype, "findOne", null);
309
+ server.registerHandlersFromProvider(new TestController());
310
+ await server.start();
311
+ const mockServer = grpc.Server.mock.results[0]?.value;
312
+ expect(mockServer.bindAsync).toHaveBeenCalledWith('0.0.0.0:50051', expect.anything(), expect.any(Function));
313
+ await server.close();
314
+ });
315
+ });
316
+ describe('close', () => {
317
+ it('should shutdown server gracefully', async () => {
318
+ const server = new grpc_server_1.GrpcServer();
319
+ server.configure({ protoPath, package: 'hero' });
320
+ class TestController {
321
+ findOne() {
322
+ return { id: 1, name: 'Hero' };
323
+ }
324
+ }
325
+ __decorate([
326
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
327
+ __metadata("design:type", Function),
328
+ __metadata("design:paramtypes", []),
329
+ __metadata("design:returntype", void 0)
330
+ ], TestController.prototype, "findOne", null);
331
+ server.registerHandlersFromProvider(new TestController());
332
+ await server.start();
333
+ await server.close();
334
+ expect(server.getServer()).toBeNull();
335
+ });
336
+ it('should do nothing when server is not started', async () => {
337
+ const server = new grpc_server_1.GrpcServer();
338
+ server.configure({ protoPath, package: 'hero' });
339
+ await server.close();
340
+ expect(server.getServer()).toBeNull();
341
+ });
342
+ it('should force shutdown when tryShutdown fails', async () => {
343
+ const server = new grpc_server_1.GrpcServer();
344
+ server.configure({ protoPath, package: 'hero' });
345
+ class TestController {
346
+ findOne() {
347
+ return { id: 1, name: 'Hero' };
348
+ }
349
+ }
350
+ __decorate([
351
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
352
+ __metadata("design:type", Function),
353
+ __metadata("design:paramtypes", []),
354
+ __metadata("design:returntype", void 0)
355
+ ], TestController.prototype, "findOne", null);
356
+ server.registerHandlersFromProvider(new TestController());
357
+ await server.start();
358
+ const mockServer = server.getServer();
359
+ mockServer.tryShutdown.mockImplementation((callback) => {
360
+ setImmediate(() => callback(new Error('Shutdown failed')));
361
+ });
362
+ await server.close();
363
+ expect(mockServer.forceShutdown).toHaveBeenCalled();
364
+ });
365
+ });
366
+ describe('handler execution', () => {
367
+ it('should handle async handler that rejects', async () => {
368
+ const server = new grpc_server_1.GrpcServer();
369
+ server.configure({ protoPath, package: 'hero' });
370
+ class TestController {
371
+ async findOne() {
372
+ throw new Error('Async error');
373
+ }
374
+ }
375
+ __decorate([
376
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
377
+ __metadata("design:type", Function),
378
+ __metadata("design:paramtypes", []),
379
+ __metadata("design:returntype", Promise)
380
+ ], TestController.prototype, "findOne", null);
381
+ server.registerHandlersFromProvider(new TestController());
382
+ await server.start();
383
+ const mockServer = server.getServer();
384
+ const implementation = mockServer.addService.mock.calls[0][1];
385
+ const callback = jest.fn();
386
+ implementation.FindOne({ request: { id: 1 } }, callback);
387
+ await new Promise((r) => setImmediate(r));
388
+ expect(callback).toHaveBeenCalledWith(expect.any(Error), null);
389
+ await server.close();
390
+ });
391
+ it('should handle sync handler that throws', async () => {
392
+ const server = new grpc_server_1.GrpcServer();
393
+ server.configure({ protoPath, package: 'hero' });
394
+ class TestController {
395
+ findOne() {
396
+ throw new Error('Sync error');
397
+ }
398
+ }
399
+ __decorate([
400
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
401
+ __metadata("design:type", Function),
402
+ __metadata("design:paramtypes", []),
403
+ __metadata("design:returntype", void 0)
404
+ ], TestController.prototype, "findOne", null);
405
+ server.registerHandlersFromProvider(new TestController());
406
+ await server.start();
407
+ const mockServer = server.getServer();
408
+ const implementation = mockServer.addService.mock.calls[0][1];
409
+ const callback = jest.fn();
410
+ implementation.FindOne({ request: { id: 1 } }, callback);
411
+ expect(callback).toHaveBeenCalledWith(expect.any(Error), null);
412
+ await server.close();
413
+ });
414
+ });
415
+ describe('getServer', () => {
416
+ it('should return null before start', () => {
417
+ const server = new grpc_server_1.GrpcServer();
418
+ server.configure({ protoPath, package: 'hero' });
419
+ expect(server.getServer()).toBeNull();
420
+ });
421
+ it('should return server instance after start', async () => {
422
+ const server = new grpc_server_1.GrpcServer();
423
+ server.configure({ protoPath, package: 'hero' });
424
+ class TestController {
425
+ findOne() {
426
+ return { id: 1, name: 'Hero' };
427
+ }
428
+ }
429
+ __decorate([
430
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
431
+ __metadata("design:type", Function),
432
+ __metadata("design:paramtypes", []),
433
+ __metadata("design:returntype", void 0)
434
+ ], TestController.prototype, "findOne", null);
435
+ server.registerHandlersFromProvider(new TestController());
436
+ await server.start();
437
+ expect(server.getServer()).not.toBeNull();
438
+ await server.close();
439
+ });
440
+ });
441
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hazeljs/grpc",
3
- "version": "0.2.0-beta.38",
3
+ "version": "0.2.0-beta.41",
4
4
  "description": "gRPC module for HazelJS framework - RPC server and client support with decorators",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,6 +19,7 @@
19
19
  "@grpc/proto-loader": "^0.7.13"
20
20
  },
21
21
  "devDependencies": {
22
+ "@types/jest": "^29.5.14",
22
23
  "@types/node": "^20.17.50",
23
24
  "@typescript-eslint/eslint-plugin": "^8.18.2",
24
25
  "@typescript-eslint/parser": "^8.18.2",
@@ -52,5 +53,5 @@
52
53
  "url": "https://github.com/hazeljs/hazel-js/issues"
53
54
  },
54
55
  "homepage": "https://hazeljs.com",
55
- "gitHead": "ba1d66ecacc4943a08f85e3f9c077e53224838ff"
56
+ "gitHead": "b50699eaa4847a8cfe9c346d7e4464b4af7f3120"
56
57
  }