@nmxjs/api 1.2.18 → 1.3.1
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/dist/ApiRouterCore/utils/RpcExceptionInterceptor.js +13 -6
- package/dist/ApiRouterCore/utils/RpcExceptionInterceptor.js.map +1 -1
- package/dist/ApiService/services/CreateApiService.js +5 -1
- package/dist/ApiService/services/CreateApiService.js.map +1 -1
- package/dist/ApiService/utils/index.d.ts +1 -0
- package/dist/ApiService/utils/index.js +1 -0
- package/dist/ApiService/utils/index.js.map +1 -1
- package/dist/ApiService/utils/parseGrpcError.d.ts +1 -0
- package/dist/ApiService/utils/parseGrpcError.js +25 -0
- package/dist/ApiService/utils/parseGrpcError.js.map +1 -0
- package/dist/ApiService/utils/transformParseJson.js +4 -1
- package/dist/ApiService/utils/transformParseJson.js.map +1 -1
- package/dist/ApiService/utils/transformStringifyJson.js +4 -1
- package/dist/ApiService/utils/transformStringifyJson.js.map +1 -1
- package/package.json +6 -3
- package/tests/ApiRouterCore/utils/RpcExceptionInterceptor.spec.ts +300 -0
- package/tests/ApiService/utils/getJsonFieldsKeys.spec.ts +107 -0
- package/tests/ApiService/utils/getQueryMutationByName.spec.ts +41 -0
- package/tests/ApiService/utils/parseGrpcError.spec.ts +80 -0
- package/tests/ApiService/utils/transformParseJson.spec.ts +76 -0
- package/tests/ApiService/utils/transformStringifyJson.spec.ts +76 -0
- package/tests/GrpcTransport/interceptors/GrpcInterceptor.spec.ts +82 -0
- package/tests/GrpcTransport/services/GenerateProtoMessagesService.spec.ts +181 -0
- package/tests/GrpcTransport/services/GetPackageNameService.spec.ts +25 -0
- package/tests/__mocks__/nestjs-common.ts +21 -0
- package/tests/__mocks__/nestjs-graphql.ts +2 -0
- package/tests/__mocks__/nestjs-microservices.ts +32 -0
|
@@ -36,8 +36,10 @@ let RpcExceptionInterceptor = class RpcExceptionInterceptor {
|
|
|
36
36
|
})}`);
|
|
37
37
|
}
|
|
38
38
|
}), (0, rxjs_1.catchError)(e => {
|
|
39
|
-
const
|
|
40
|
-
const
|
|
39
|
+
const message = typeof (e === null || e === void 0 ? void 0 : e.message) === 'string' ? e.message : String(e);
|
|
40
|
+
const splitResult = message.split(constants_1.endErrorText);
|
|
41
|
+
const isProcessed = splitResult.length > 1;
|
|
42
|
+
const errorMessage = isProcessed ? splitResult[0] : message;
|
|
41
43
|
if (this.debug) {
|
|
42
44
|
common_1.Logger.debug(`Rpc Response: ${(0, utils_1.readableJson)({
|
|
43
45
|
requestId,
|
|
@@ -46,17 +48,22 @@ let RpcExceptionInterceptor = class RpcExceptionInterceptor {
|
|
|
46
48
|
},
|
|
47
49
|
})}`);
|
|
48
50
|
}
|
|
49
|
-
common_1.Logger.error(errorMessage);
|
|
50
|
-
if (this.notifier && !e.silent) {
|
|
51
|
+
common_1.Logger.error(isProcessed ? errorMessage : (e === null || e === void 0 ? void 0 : e.stack) || errorMessage);
|
|
52
|
+
if (this.notifier && !(e === null || e === void 0 ? void 0 : e.silent)) {
|
|
51
53
|
this.notifier.sendError({
|
|
52
54
|
message: errorMessage.split('\n at')[0],
|
|
53
55
|
serviceName: this.serviceName,
|
|
54
56
|
path,
|
|
55
|
-
code: e.code || 'UNKNOWN RPC',
|
|
57
|
+
code: (e === null || e === void 0 ? void 0 : e.code) || 'UNKNOWN RPC',
|
|
56
58
|
params: context.getArgByIndex(0),
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
const errorPayload = JSON.stringify({
|
|
62
|
+
message: errorMessage.split('\n at')[0],
|
|
63
|
+
code: e === null || e === void 0 ? void 0 : e.code,
|
|
64
|
+
statusCode: e === null || e === void 0 ? void 0 : e.statusCode,
|
|
65
|
+
});
|
|
66
|
+
throw new microservices_1.RpcException(`${errorPayload}${constants_1.endErrorText}`);
|
|
60
67
|
}));
|
|
61
68
|
}
|
|
62
69
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RpcExceptionInterceptor.js","sourceRoot":"","sources":["../../../src/ApiRouterCore/utils/RpcExceptionInterceptor.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+BAAuC;AACvC,yDAAqD;AACrD,wCAAkD;AAClD,2CAAoG;AACpG,4CAA4C;AAIrC,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAClC,
|
|
1
|
+
{"version":3,"file":"RpcExceptionInterceptor.js","sourceRoot":"","sources":["../../../src/ApiRouterCore/utils/RpcExceptionInterceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -55,15 +55,19 @@ let CreateApiService = class CreateApiService {
|
|
|
55
55
|
const getData = async () => {
|
|
56
56
|
const payload = currentService[methodName](resultRequestData);
|
|
57
57
|
const result = await ((0, rxjs_1.isObservable)(payload) ? (0, rxjs_1.lastValueFrom)(payload) : payload).catch(e => {
|
|
58
|
+
var _a;
|
|
58
59
|
if (requestData.skipError || methodOptions.skipError) {
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
if (typeof e === 'string') {
|
|
62
63
|
throw new Error(e);
|
|
63
64
|
}
|
|
64
|
-
if (e.message.includes('Empty response. There are no subscribers listening to that message')) {
|
|
65
|
+
if ((_a = e.message) === null || _a === void 0 ? void 0 : _a.includes('Empty response. There are no subscribers listening to that message')) {
|
|
65
66
|
throw new errors_1.ServiceNotAvailableError(serviceName, methodName);
|
|
66
67
|
}
|
|
68
|
+
if (e.details) {
|
|
69
|
+
throw (0, utils_1.parseGrpcError)(e);
|
|
70
|
+
}
|
|
67
71
|
throw e;
|
|
68
72
|
});
|
|
69
73
|
return (0, utils_1.transformParseJson)(`${route}.response`, result);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateApiService.js","sourceRoot":"","sources":["../../../src/ApiService/services/CreateApiService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wCAAwC;AACxC,uCAAuC;AACvC,2CAAoD;AACpD,0CAAmD;AACnD,+BAAmD;AACnD,0CAAyD;AACzD,4CAAoE;AAEpE,mEAAgE;AAChE,
|
|
1
|
+
{"version":3,"file":"CreateApiService.js","sourceRoot":"","sources":["../../../src/ApiService/services/CreateApiService.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./getJsonFieldsKeys"), exports);
|
|
18
18
|
__exportStar(require("./getQueryMutationByName"), exports);
|
|
19
|
+
__exportStar(require("./parseGrpcError"), exports);
|
|
19
20
|
__exportStar(require("./transformParseJson"), exports);
|
|
20
21
|
__exportStar(require("./transformStringifyJson"), exports);
|
|
21
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ApiService/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,2DAAyC;AACzC,uDAAqC;AACrC,2DAAyC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ApiService/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sDAAoC;AACpC,2DAAyC;AACzC,mDAAiC;AACjC,uDAAqC;AACrC,2DAAyC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseGrpcError(e: any): Error;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseGrpcError = void 0;
|
|
4
|
+
const constants_1 = require("../../ApiRouterCore/constants");
|
|
5
|
+
function parseGrpcError(e) {
|
|
6
|
+
const details = e.details;
|
|
7
|
+
const cleanDetails = details.split(constants_1.endErrorText)[0];
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(cleanDetails);
|
|
10
|
+
const error = new Error(parsed.message || cleanDetails);
|
|
11
|
+
if (parsed.code)
|
|
12
|
+
error.code = parsed.code;
|
|
13
|
+
if (parsed.statusCode)
|
|
14
|
+
error.statusCode = parsed.statusCode;
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
catch (parseError) {
|
|
18
|
+
if (parseError instanceof SyntaxError) {
|
|
19
|
+
return new Error(cleanDetails);
|
|
20
|
+
}
|
|
21
|
+
throw parseError;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.parseGrpcError = parseGrpcError;
|
|
25
|
+
//# sourceMappingURL=parseGrpcError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseGrpcError.js","sourceRoot":"","sources":["../../../src/ApiService/utils/parseGrpcError.ts"],"names":[],"mappings":";;;AAAA,6DAA6D;AAE7D,SAAgB,cAAc,CAAC,CAAM;IACnC,MAAM,OAAO,GAAW,CAAC,CAAC,OAAO,CAAC;IAClC,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,wBAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAEpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxC,MAAM,KAAK,GAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,IAAI,YAAY,CAAC,CAAC;QAC7D,IAAI,MAAM,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QAC1C,IAAI,MAAM,CAAC,UAAU;YAAE,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QAC5D,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,UAAU,EAAE,CAAC;QACpB,IAAI,UAAU,YAAY,WAAW,EAAE,CAAC;YACtC,OAAO,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,UAAU,CAAC;IACnB,CAAC;AACH,CAAC;AAhBD,wCAgBC"}
|
|
@@ -3,13 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.transformParseJson = void 0;
|
|
4
4
|
const getJsonFieldsKeys_1 = require("./getJsonFieldsKeys");
|
|
5
5
|
function transformParseJson(key, data) {
|
|
6
|
+
if (!data) {
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
6
9
|
const jsonFieldsKeys = (0, getJsonFieldsKeys_1.getJsonFieldsKeys)(key);
|
|
7
10
|
if (!jsonFieldsKeys || jsonFieldsKeys.length === 0) {
|
|
8
11
|
return data;
|
|
9
12
|
}
|
|
10
13
|
for (const path of jsonFieldsKeys) {
|
|
11
14
|
const [firstKey, secondKey] = path.split('.');
|
|
12
|
-
if (
|
|
15
|
+
if (typeof data[firstKey] === 'undefined') {
|
|
13
16
|
continue;
|
|
14
17
|
}
|
|
15
18
|
if (firstKey && !secondKey) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformParseJson.js","sourceRoot":"","sources":["../../../src/ApiService/utils/transformParseJson.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AAExD,SAAgB,kBAAkB,CAAI,GAAW,EAAE,IAAO;IACxD,MAAM,cAAc,GAAG,IAAA,qCAAiB,EAAC,GAAG,CAAC,CAAC;IAE9C,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE9C,IAAI,
|
|
1
|
+
{"version":3,"file":"transformParseJson.js","sourceRoot":"","sources":["../../../src/ApiService/utils/transformParseJson.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AAExD,SAAgB,kBAAkB,CAAI,GAAW,EAAE,IAAO;IACxD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,cAAc,GAAG,IAAA,qCAAiB,EAAC,GAAG,CAAC,CAAC;IAE9C,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE9C,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,WAAW,EAAE,CAAC;YAC1C,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9C,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;YAChD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AA9BD,gDA8BC"}
|
|
@@ -3,13 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.transformStringifyJson = void 0;
|
|
4
4
|
const getJsonFieldsKeys_1 = require("./getJsonFieldsKeys");
|
|
5
5
|
function transformStringifyJson(key, data) {
|
|
6
|
+
if (!data) {
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
6
9
|
const jsonFieldsKeys = (0, getJsonFieldsKeys_1.getJsonFieldsKeys)(key);
|
|
7
10
|
if (!jsonFieldsKeys || jsonFieldsKeys.length === 0) {
|
|
8
11
|
return data;
|
|
9
12
|
}
|
|
10
13
|
for (const path of jsonFieldsKeys) {
|
|
11
14
|
const [firstKey, secondKey] = path.split('.');
|
|
12
|
-
if (
|
|
15
|
+
if (typeof data[firstKey] === 'undefined') {
|
|
13
16
|
continue;
|
|
14
17
|
}
|
|
15
18
|
if (firstKey && !secondKey) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformStringifyJson.js","sourceRoot":"","sources":["../../../src/ApiService/utils/transformStringifyJson.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AAExD,SAAgB,sBAAsB,CAAI,GAAW,EAAE,IAAO;IAC5D,MAAM,cAAc,GAAG,IAAA,qCAAiB,EAAC,GAAG,CAAC,CAAC;IAE9C,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE9C,IAAI,
|
|
1
|
+
{"version":3,"file":"transformStringifyJson.js","sourceRoot":"","sources":["../../../src/ApiService/utils/transformStringifyJson.ts"],"names":[],"mappings":";;;AAAA,2DAAwD;AAExD,SAAgB,sBAAsB,CAAI,GAAW,EAAE,IAAO;IAC5D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,cAAc,GAAG,IAAA,qCAAiB,EAAC,GAAG,CAAC,CAAC;IAE9C,IAAI,CAAC,cAAc,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;QAClC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAE9C,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,WAAW,EAAE,CAAC;YAC1C,SAAS;QACX,CAAC;QAED,IAAI,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClD,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AA9BD,wDA8BC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nmxjs/api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
"homepage": "https://github.com/n1ghtm6r9/nm-api#readme",
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/deepmerge": "^2.1.0",
|
|
26
|
+
"@types/jest": "^30.0.0",
|
|
26
27
|
"@types/node": "^22.13.0",
|
|
28
|
+
"jest": "^30.2.0",
|
|
29
|
+
"ts-jest": "^29.4.6",
|
|
27
30
|
"typescript": "^5.1.6"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
33
|
+
"@nmxjs/constants": "^1.0.6",
|
|
30
34
|
"@nmxjs/errors": "^1.0.13",
|
|
31
35
|
"@nmxjs/utils": "^1.1.4",
|
|
32
|
-
"@nmxjs/constants": "^1.0.6",
|
|
33
36
|
"deepmerge": "^4.3.1",
|
|
34
37
|
"express": "^4.21.2",
|
|
35
38
|
"node-cache": "^5.1.2",
|
|
@@ -38,9 +41,9 @@
|
|
|
38
41
|
"rxjs": "^7.8.1"
|
|
39
42
|
},
|
|
40
43
|
"peerDependencies": {
|
|
44
|
+
"@nestjs/common": "^11.0.7",
|
|
41
45
|
"@nestjs/graphql": "^13.0.2",
|
|
42
46
|
"@nmxjs/config": "^1.0.15",
|
|
43
|
-
"@nestjs/common": "^11.0.7",
|
|
44
47
|
"@nmxjs/notifications": "^1.0.7"
|
|
45
48
|
}
|
|
46
49
|
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { of, throwError } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
jest.mock('@nmxjs/utils', () => ({
|
|
4
|
+
uuid: () => 'test-uuid-123',
|
|
5
|
+
readableJson: (data: any) => JSON.stringify(data),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
import { RpcExceptionInterceptor } from '../../../src/ApiRouterCore/utils/RpcExceptionInterceptor';
|
|
9
|
+
import { Logger } from '@nestjs/common';
|
|
10
|
+
import { endErrorText } from '../../../src/ApiRouterCore/constants/endErrorText';
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const createContext = (data: any, pathOrEvent?: { path?: string; event?: string }) => ({
|
|
17
|
+
getArgByIndex: (index: number) => {
|
|
18
|
+
if (index === 0) return data;
|
|
19
|
+
if (index === 1) return { args: [pathOrEvent?.event || 'test.event'] };
|
|
20
|
+
if (index === 2) return pathOrEvent?.path ? { path: pathOrEvent.path } : undefined;
|
|
21
|
+
return undefined;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const createNext = (returnValue: any) => ({
|
|
26
|
+
handle: () => of(returnValue),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const createErrorNext = (error: Error) => ({
|
|
30
|
+
handle: () => throwError(() => error),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('RpcExceptionInterceptor', () => {
|
|
34
|
+
it('should pass through successful responses', done => {
|
|
35
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
36
|
+
const ctx = createContext({ id: 1 });
|
|
37
|
+
const next = createNext({ result: 'ok' });
|
|
38
|
+
|
|
39
|
+
interceptor.intercept(ctx as any, next as any).subscribe(result => {
|
|
40
|
+
expect(result).toEqual({ result: 'ok' });
|
|
41
|
+
done();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should log debug on request when debug=true', done => {
|
|
46
|
+
const interceptor = new RpcExceptionInterceptor('test-service', true);
|
|
47
|
+
const ctx = createContext({ id: 1 }, { event: 'test.get' });
|
|
48
|
+
const next = createNext({ ok: true });
|
|
49
|
+
|
|
50
|
+
interceptor.intercept(ctx as any, next as any).subscribe(() => {
|
|
51
|
+
expect(Logger.debug).toHaveBeenCalled();
|
|
52
|
+
done();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should not log debug when debug=false', done => {
|
|
57
|
+
const interceptor = new RpcExceptionInterceptor('test-service', false);
|
|
58
|
+
const ctx = createContext({ id: 1 });
|
|
59
|
+
const next = createNext({ ok: true });
|
|
60
|
+
|
|
61
|
+
interceptor.intercept(ctx as any, next as any).subscribe(() => {
|
|
62
|
+
expect(Logger.debug).not.toHaveBeenCalled();
|
|
63
|
+
done();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should throw RpcException with JSON payload on error', done => {
|
|
68
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
69
|
+
const ctx = createContext({ id: 1 });
|
|
70
|
+
const error = new Error('Something went wrong');
|
|
71
|
+
const next = createErrorNext(error);
|
|
72
|
+
|
|
73
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
74
|
+
error: err => {
|
|
75
|
+
expect(err.message).toContain(endErrorText);
|
|
76
|
+
const payload = err.message.split(endErrorText)[0];
|
|
77
|
+
const parsed = JSON.parse(payload);
|
|
78
|
+
expect(parsed.message).toBe('Something went wrong');
|
|
79
|
+
done();
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should include code and statusCode in JSON payload', done => {
|
|
85
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
86
|
+
const ctx = createContext({});
|
|
87
|
+
const error: any = new Error('Validation failed');
|
|
88
|
+
error.code = 'VALIDATION_ERROR';
|
|
89
|
+
error.statusCode = 400;
|
|
90
|
+
const next = createErrorNext(error);
|
|
91
|
+
|
|
92
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
93
|
+
error: err => {
|
|
94
|
+
const payload = err.message.split(endErrorText)[0];
|
|
95
|
+
const parsed = JSON.parse(payload);
|
|
96
|
+
expect(parsed.message).toBe('Validation failed');
|
|
97
|
+
expect(parsed.code).toBe('VALIDATION_ERROR');
|
|
98
|
+
expect(parsed.statusCode).toBe(400);
|
|
99
|
+
done();
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should log error message', done => {
|
|
105
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
106
|
+
const ctx = createContext({ id: 1 });
|
|
107
|
+
const error = new Error('Test error');
|
|
108
|
+
const next = createErrorNext(error);
|
|
109
|
+
|
|
110
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
111
|
+
error: () => {
|
|
112
|
+
expect(Logger.error).toHaveBeenCalled();
|
|
113
|
+
done();
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should handle already-processed error with endErrorText', done => {
|
|
119
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
120
|
+
const ctx = createContext({});
|
|
121
|
+
const originalPayload = JSON.stringify({ message: 'Original error', code: 'ERR' });
|
|
122
|
+
const error = new Error(`${originalPayload}${endErrorText}`);
|
|
123
|
+
const next = createErrorNext(error);
|
|
124
|
+
|
|
125
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
126
|
+
error: err => {
|
|
127
|
+
const payload = err.message.split(endErrorText)[0];
|
|
128
|
+
const parsed = JSON.parse(payload);
|
|
129
|
+
expect(parsed.message).toContain('Original error');
|
|
130
|
+
done();
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should log stack for fresh errors', done => {
|
|
136
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
137
|
+
const ctx = createContext({});
|
|
138
|
+
const error = new Error('Fresh error');
|
|
139
|
+
const next = createErrorNext(error);
|
|
140
|
+
|
|
141
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
142
|
+
error: () => {
|
|
143
|
+
const loggedMessage = (Logger.error as jest.Mock).mock.calls[0][0];
|
|
144
|
+
expect(loggedMessage).toContain('Fresh error');
|
|
145
|
+
done();
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should log only message for processed errors', done => {
|
|
151
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
152
|
+
const ctx = createContext({});
|
|
153
|
+
const error = new Error(`Processed message${endErrorText}`);
|
|
154
|
+
const next = createErrorNext(error);
|
|
155
|
+
|
|
156
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
157
|
+
error: () => {
|
|
158
|
+
const loggedMessage = (Logger.error as jest.Mock).mock.calls[0][0];
|
|
159
|
+
expect(loggedMessage).toBe('Processed message');
|
|
160
|
+
expect(loggedMessage).not.toContain('at ');
|
|
161
|
+
done();
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should call notifier.sendError when notifier is provided and error is not silent', done => {
|
|
167
|
+
const notifier = { sendError: jest.fn() };
|
|
168
|
+
const interceptor = new RpcExceptionInterceptor('test-service', false, notifier as any);
|
|
169
|
+
const ctx = createContext({ id: 1 }, { path: '/test/path' });
|
|
170
|
+
const error: any = new Error('Notify error');
|
|
171
|
+
error.code = 'ERR_CODE';
|
|
172
|
+
const next = createErrorNext(error);
|
|
173
|
+
|
|
174
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
175
|
+
error: () => {
|
|
176
|
+
expect(notifier.sendError).toHaveBeenCalledWith(
|
|
177
|
+
expect.objectContaining({
|
|
178
|
+
serviceName: 'test-service',
|
|
179
|
+
path: '/test/path',
|
|
180
|
+
code: 'ERR_CODE',
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
done();
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should not call notifier when error is silent', done => {
|
|
189
|
+
const notifier = { sendError: jest.fn() };
|
|
190
|
+
const interceptor = new RpcExceptionInterceptor('test-service', false, notifier as any);
|
|
191
|
+
const ctx = createContext({});
|
|
192
|
+
const error: any = new Error('Silent error');
|
|
193
|
+
error.silent = true;
|
|
194
|
+
const next = createErrorNext(error);
|
|
195
|
+
|
|
196
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
197
|
+
error: () => {
|
|
198
|
+
expect(notifier.sendError).not.toHaveBeenCalled();
|
|
199
|
+
done();
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should use UNKNOWN RPC code when error has no code', done => {
|
|
205
|
+
const notifier = { sendError: jest.fn() };
|
|
206
|
+
const interceptor = new RpcExceptionInterceptor('test-service', false, notifier as any);
|
|
207
|
+
const ctx = createContext({}, { path: '/test' });
|
|
208
|
+
const error = new Error('No code error');
|
|
209
|
+
const next = createErrorNext(error);
|
|
210
|
+
|
|
211
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
212
|
+
error: () => {
|
|
213
|
+
expect(notifier.sendError).toHaveBeenCalledWith(
|
|
214
|
+
expect.objectContaining({
|
|
215
|
+
code: 'UNKNOWN RPC',
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
done();
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should log debug response when debug=true and path is provided', done => {
|
|
224
|
+
const interceptor = new RpcExceptionInterceptor('test-service', true);
|
|
225
|
+
const ctx = createContext({ id: 1 }, { path: '/grpc/method' });
|
|
226
|
+
const next = createNext({ data: 'response' });
|
|
227
|
+
|
|
228
|
+
interceptor.intercept(ctx as any, next as any).subscribe(() => {
|
|
229
|
+
const debugCalls = (Logger.debug as jest.Mock).mock.calls;
|
|
230
|
+
expect(debugCalls.length).toBeGreaterThanOrEqual(2);
|
|
231
|
+
done();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should handle non-Error thrown value (e.g. string)', done => {
|
|
236
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
237
|
+
const ctx = createContext({});
|
|
238
|
+
const next = {
|
|
239
|
+
handle: () => throwError(() => 'string error'),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
243
|
+
error: err => {
|
|
244
|
+
const payload = err.message.split(endErrorText)[0];
|
|
245
|
+
const parsed = JSON.parse(payload);
|
|
246
|
+
expect(parsed.message).toBe('string error');
|
|
247
|
+
done();
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should handle error without message property', done => {
|
|
253
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
254
|
+
const ctx = createContext({});
|
|
255
|
+
const next = {
|
|
256
|
+
handle: () => throwError(() => ({ code: 'SOME_CODE' })),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
260
|
+
error: err => {
|
|
261
|
+
const payload = err.message.split(endErrorText)[0];
|
|
262
|
+
const parsed = JSON.parse(payload);
|
|
263
|
+
expect(parsed.code).toBe('SOME_CODE');
|
|
264
|
+
done();
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should handle null thrown value', done => {
|
|
270
|
+
const interceptor = new RpcExceptionInterceptor('test-service');
|
|
271
|
+
const ctx = createContext({});
|
|
272
|
+
const next = {
|
|
273
|
+
handle: () => throwError(() => null),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
277
|
+
error: err => {
|
|
278
|
+
expect(err.message).toContain(endErrorText);
|
|
279
|
+
done();
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should strip stack trace from notifier message', done => {
|
|
285
|
+
const notifier = { sendError: jest.fn() };
|
|
286
|
+
const interceptor = new RpcExceptionInterceptor('test-service', false, notifier as any);
|
|
287
|
+
const ctx = createContext({}, { path: '/test' });
|
|
288
|
+
const error = new Error('Error message');
|
|
289
|
+
const next = createErrorNext(error);
|
|
290
|
+
|
|
291
|
+
interceptor.intercept(ctx as any, next as any).subscribe({
|
|
292
|
+
error: () => {
|
|
293
|
+
const sendErrorCall = notifier.sendError.mock.calls[0][0];
|
|
294
|
+
expect(sendErrorCall.message).toBe('Error message');
|
|
295
|
+
expect(sendErrorCall.message).not.toContain('\n at');
|
|
296
|
+
done();
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { setJsonFieldsKey, getJsonFieldsKeys } from '../../../src/ApiService/utils/getJsonFieldsKeys';
|
|
3
|
+
|
|
4
|
+
describe('getJsonFieldsKeys', () => {
|
|
5
|
+
it('should return undefined for unknown key', () => {
|
|
6
|
+
expect(getJsonFieldsKeys('unknown-key')).toBeUndefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return empty array when objSchema is null', () => {
|
|
10
|
+
setJsonFieldsKey('null-schema', null);
|
|
11
|
+
expect(getJsonFieldsKeys('null-schema')).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return empty array when objSchema has no field metadata', () => {
|
|
15
|
+
const schema = {};
|
|
16
|
+
setJsonFieldsKey('empty-schema', schema);
|
|
17
|
+
expect(getJsonFieldsKeys('empty-schema')).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should register JSON type field as json field key', () => {
|
|
21
|
+
const schema = {};
|
|
22
|
+
Reflect.defineMetadata('field:value', { type: JSON }, schema);
|
|
23
|
+
setJsonFieldsKey('json-field', schema);
|
|
24
|
+
expect(getJsonFieldsKeys('json-field')).toEqual(['value']);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should register Object type field as json field key', () => {
|
|
28
|
+
const schema = {};
|
|
29
|
+
Reflect.defineMetadata('field:data', { type: Object }, schema);
|
|
30
|
+
setJsonFieldsKey('object-field', schema);
|
|
31
|
+
expect(getJsonFieldsKeys('object-field')).toEqual(['data']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should skip String type fields', () => {
|
|
35
|
+
const schema = {};
|
|
36
|
+
Reflect.defineMetadata('field:name', { type: String }, schema);
|
|
37
|
+
setJsonFieldsKey('string-field', schema);
|
|
38
|
+
expect(getJsonFieldsKeys('string-field')).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should skip Number type fields', () => {
|
|
42
|
+
const schema = {};
|
|
43
|
+
Reflect.defineMetadata('field:count', { type: Number }, schema);
|
|
44
|
+
setJsonFieldsKey('number-field', schema);
|
|
45
|
+
expect(getJsonFieldsKeys('number-field')).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should skip Boolean type fields', () => {
|
|
49
|
+
const schema = {};
|
|
50
|
+
Reflect.defineMetadata('field:active', { type: Boolean }, schema);
|
|
51
|
+
setJsonFieldsKey('bool-field', schema);
|
|
52
|
+
expect(getJsonFieldsKeys('bool-field')).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should skip enum fields', () => {
|
|
56
|
+
const schema = {};
|
|
57
|
+
Reflect.defineMetadata('field:status', { type: String, enum: true }, schema);
|
|
58
|
+
setJsonFieldsKey('enum-field', schema);
|
|
59
|
+
expect(getJsonFieldsKeys('enum-field')).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should recurse into nested types and collect with prefix', () => {
|
|
63
|
+
const nestedSchema = {};
|
|
64
|
+
Reflect.defineMetadata('field:value', { type: JSON }, nestedSchema);
|
|
65
|
+
|
|
66
|
+
const schema = {};
|
|
67
|
+
Reflect.defineMetadata('field:filters', { type: nestedSchema }, schema);
|
|
68
|
+
|
|
69
|
+
setJsonFieldsKey('nested-field', schema);
|
|
70
|
+
expect(getJsonFieldsKeys('nested-field')).toEqual(['filters.value']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle multiple fields', () => {
|
|
74
|
+
const schema = {};
|
|
75
|
+
Reflect.defineMetadata('field:name', { type: String }, schema);
|
|
76
|
+
Reflect.defineMetadata('field:data', { type: JSON }, schema);
|
|
77
|
+
Reflect.defineMetadata('field:count', { type: Number }, schema);
|
|
78
|
+
|
|
79
|
+
setJsonFieldsKey('multi-field', schema);
|
|
80
|
+
expect(getJsonFieldsKeys('multi-field')).toEqual(['data']);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should not recurse deeper than 2 levels', () => {
|
|
84
|
+
const deepSchema = {};
|
|
85
|
+
Reflect.defineMetadata('field:deep', { type: JSON }, deepSchema);
|
|
86
|
+
|
|
87
|
+
const midSchema = {};
|
|
88
|
+
Reflect.defineMetadata('field:mid', { type: deepSchema }, midSchema);
|
|
89
|
+
|
|
90
|
+
const schema = {};
|
|
91
|
+
Reflect.defineMetadata('field:top', { type: midSchema }, schema);
|
|
92
|
+
|
|
93
|
+
setJsonFieldsKey('deep-nested', schema);
|
|
94
|
+
const result = getJsonFieldsKeys('deep-nested');
|
|
95
|
+
expect(result).toContain('mid.deep');
|
|
96
|
+
expect(result.length).toBe(1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should ignore non-field metadata keys', () => {
|
|
100
|
+
const schema = {};
|
|
101
|
+
Reflect.defineMetadata('other:key', { type: JSON }, schema);
|
|
102
|
+
Reflect.defineMetadata('field:value', { type: JSON }, schema);
|
|
103
|
+
|
|
104
|
+
setJsonFieldsKey('filter-meta', schema);
|
|
105
|
+
expect(getJsonFieldsKeys('filter-meta')).toEqual(['value']);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getQueryMutationByName } from '../../../src/ApiService/utils/getQueryMutationByName';
|
|
2
|
+
|
|
3
|
+
describe('getQueryMutationByName', () => {
|
|
4
|
+
it('should return Query for method containing "get"', () => {
|
|
5
|
+
const result = getQueryMutationByName('getUsers');
|
|
6
|
+
expect(result.type).toBe('Query');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('should return Mutation for method not containing "get"', () => {
|
|
10
|
+
const result = getQueryMutationByName('createUser');
|
|
11
|
+
expect(result.type).toBe('Mutation');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return Query for "getSomething"', () => {
|
|
15
|
+
const result = getQueryMutationByName('getSomething');
|
|
16
|
+
expect(result.type).toBe('Query');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return Mutation for "deleteItem"', () => {
|
|
20
|
+
const result = getQueryMutationByName('deleteItem');
|
|
21
|
+
expect(result.type).toBe('Mutation');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return Mutation for "updateUser"', () => {
|
|
25
|
+
const result = getQueryMutationByName('updateUser');
|
|
26
|
+
expect(result.type).toBe('Mutation');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return Query for method with "get" in middle', () => {
|
|
30
|
+
const result = getQueryMutationByName('forgetPassword');
|
|
31
|
+
expect(result.type).toBe('Query');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should have decorator property', () => {
|
|
35
|
+
const queryResult = getQueryMutationByName('getUsers');
|
|
36
|
+
expect(queryResult.decorator).toBeDefined();
|
|
37
|
+
|
|
38
|
+
const mutationResult = getQueryMutationByName('createUser');
|
|
39
|
+
expect(mutationResult.decorator).toBeDefined();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { parseGrpcError } from '../../../src/ApiService/utils/parseGrpcError';
|
|
2
|
+
import { endErrorText } from '../../../src/ApiRouterCore/constants/endErrorText';
|
|
3
|
+
|
|
4
|
+
describe('parseGrpcError', () => {
|
|
5
|
+
it('should parse JSON details with message, code and statusCode', () => {
|
|
6
|
+
const payload = JSON.stringify({ message: 'Validation failed', code: 'VALIDATION_ERROR', statusCode: 400 });
|
|
7
|
+
const grpcError = { details: `${payload}${endErrorText}` };
|
|
8
|
+
const result: any = parseGrpcError(grpcError);
|
|
9
|
+
|
|
10
|
+
expect(result.message).toBe('Validation failed');
|
|
11
|
+
expect(result.code).toBe('VALIDATION_ERROR');
|
|
12
|
+
expect(result.statusCode).toBe(400);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should parse JSON details with only message', () => {
|
|
16
|
+
const payload = JSON.stringify({ message: 'Something broke' });
|
|
17
|
+
const grpcError = { details: `${payload}${endErrorText}` };
|
|
18
|
+
const result = parseGrpcError(grpcError);
|
|
19
|
+
|
|
20
|
+
expect(result.message).toBe('Something broke');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle plain text details (not JSON)', () => {
|
|
24
|
+
const grpcError = { details: `Plain error text${endErrorText}` };
|
|
25
|
+
const result = parseGrpcError(grpcError);
|
|
26
|
+
|
|
27
|
+
expect(result.message).toBe('Plain error text');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should handle details without endErrorText marker', () => {
|
|
31
|
+
const payload = JSON.stringify({ message: 'Error without marker', code: 'ERR' });
|
|
32
|
+
const grpcError = { details: payload };
|
|
33
|
+
const result: any = parseGrpcError(grpcError);
|
|
34
|
+
|
|
35
|
+
expect(result.message).toBe('Error without marker');
|
|
36
|
+
expect(result.code).toBe('ERR');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should fallback to details as message when JSON has no message field', () => {
|
|
40
|
+
const payload = JSON.stringify({ code: 'UNKNOWN' });
|
|
41
|
+
const grpcError = { details: `${payload}${endErrorText}` };
|
|
42
|
+
const result = parseGrpcError(grpcError);
|
|
43
|
+
|
|
44
|
+
expect(result.message).toBe(payload);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should not set code if not present in parsed JSON', () => {
|
|
48
|
+
const payload = JSON.stringify({ message: 'No code error' });
|
|
49
|
+
const grpcError = { details: `${payload}${endErrorText}` };
|
|
50
|
+
const result: any = parseGrpcError(grpcError);
|
|
51
|
+
|
|
52
|
+
expect(result.message).toBe('No code error');
|
|
53
|
+
expect(result.code).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should not set statusCode if not present in parsed JSON', () => {
|
|
57
|
+
const payload = JSON.stringify({ message: 'No status error' });
|
|
58
|
+
const grpcError = { details: `${payload}${endErrorText}` };
|
|
59
|
+
const result: any = parseGrpcError(grpcError);
|
|
60
|
+
|
|
61
|
+
expect(result.statusCode).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle cascade errors (multiple endErrorText markers)', () => {
|
|
65
|
+
const payload = JSON.stringify({ message: 'Original cascade error', code: 'CASCADE' });
|
|
66
|
+
const grpcError = { details: `${payload}${endErrorText}${endErrorText}` };
|
|
67
|
+
const result: any = parseGrpcError(grpcError);
|
|
68
|
+
|
|
69
|
+
expect(result.message).toBe('Original cascade error');
|
|
70
|
+
expect(result.code).toBe('CASCADE');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return Error instance', () => {
|
|
74
|
+
const payload = JSON.stringify({ message: 'test' });
|
|
75
|
+
const grpcError = { details: payload };
|
|
76
|
+
const result = parseGrpcError(grpcError);
|
|
77
|
+
|
|
78
|
+
expect(result).toBeInstanceOf(Error);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { transformParseJson } from '../../../src/ApiService/utils/transformParseJson';
|
|
2
|
+
import * as jsonFieldsKeysModule from '../../../src/ApiService/utils/getJsonFieldsKeys';
|
|
3
|
+
|
|
4
|
+
jest.spyOn(jsonFieldsKeysModule, 'getJsonFieldsKeys');
|
|
5
|
+
|
|
6
|
+
const mockGetJsonFieldsKeys = jsonFieldsKeysModule.getJsonFieldsKeys as jest.Mock;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockGetJsonFieldsKeys.mockReset();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('transformParseJson', () => {
|
|
13
|
+
it('should return data as-is when no jsonFieldsKeys', () => {
|
|
14
|
+
mockGetJsonFieldsKeys.mockReturnValue(undefined);
|
|
15
|
+
const data = { name: 'test' };
|
|
16
|
+
expect(transformParseJson('key', data)).toEqual({ name: 'test' });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should not crash when data={} and jsonFieldsKeys=["filters.value"]', () => {
|
|
20
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
21
|
+
const result = transformParseJson('key', {});
|
|
22
|
+
expect(result).toEqual({});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should not crash when data={ filters: undefined } and jsonFieldsKeys=["filters.value"]', () => {
|
|
26
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
27
|
+
const result = transformParseJson('key', { filters: undefined });
|
|
28
|
+
expect(result).toEqual({ filters: undefined });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should parse nested array values for "filters.value"', () => {
|
|
32
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
33
|
+
const data = { filters: [{ value: '["a","b"]' }] };
|
|
34
|
+
const result = transformParseJson('key', data);
|
|
35
|
+
expect(result.filters[0].value).toEqual(['a', 'b']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should parse nested string values for "filters.value"', () => {
|
|
39
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
40
|
+
const data = { filters: [{ value: '"test"' }] };
|
|
41
|
+
const result = transformParseJson('key', data);
|
|
42
|
+
expect(result.filters[0].value).toBe('test');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should parse top-level key for "name"', () => {
|
|
46
|
+
mockGetJsonFieldsKeys.mockReturnValue(['name']);
|
|
47
|
+
const data = { name: '"test"' };
|
|
48
|
+
const result = transformParseJson('key', data);
|
|
49
|
+
expect(result.name).toBe('test');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should not crash when data={} and jsonFieldsKeys=["name"]', () => {
|
|
53
|
+
mockGetJsonFieldsKeys.mockReturnValue(['name']);
|
|
54
|
+
const result = transformParseJson('key', {});
|
|
55
|
+
expect(result).toEqual({});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should not crash when data is undefined', () => {
|
|
59
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
60
|
+
const result = transformParseJson('key', undefined);
|
|
61
|
+
expect(result).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should not crash when data is null', () => {
|
|
65
|
+
mockGetJsonFieldsKeys.mockReturnValue(['name']);
|
|
66
|
+
const result = transformParseJson('key', null);
|
|
67
|
+
expect(result).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should parse non-array object value with secondKey', () => {
|
|
71
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
72
|
+
const data = { filters: { value: '{"nested":true}' } };
|
|
73
|
+
const result = transformParseJson('key', data);
|
|
74
|
+
expect(result.filters.value).toEqual({ nested: true });
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { transformStringifyJson } from '../../../src/ApiService/utils/transformStringifyJson';
|
|
2
|
+
import * as jsonFieldsKeysModule from '../../../src/ApiService/utils/getJsonFieldsKeys';
|
|
3
|
+
|
|
4
|
+
jest.spyOn(jsonFieldsKeysModule, 'getJsonFieldsKeys');
|
|
5
|
+
|
|
6
|
+
const mockGetJsonFieldsKeys = jsonFieldsKeysModule.getJsonFieldsKeys as jest.Mock;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
mockGetJsonFieldsKeys.mockReset();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('transformStringifyJson', () => {
|
|
13
|
+
it('should return data as-is when no jsonFieldsKeys', () => {
|
|
14
|
+
mockGetJsonFieldsKeys.mockReturnValue(undefined);
|
|
15
|
+
const data = { name: 'test' };
|
|
16
|
+
expect(transformStringifyJson('key', data)).toEqual({ name: 'test' });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should not crash when data={} and jsonFieldsKeys=["filters.value"]', () => {
|
|
20
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
21
|
+
const result = transformStringifyJson('key', {});
|
|
22
|
+
expect(result).toEqual({});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should not crash when data={ filters: undefined } and jsonFieldsKeys=["filters.value"]', () => {
|
|
26
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
27
|
+
const result = transformStringifyJson('key', { filters: undefined });
|
|
28
|
+
expect(result).toEqual({ filters: undefined });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should stringify nested array values for "filters.value"', () => {
|
|
32
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
33
|
+
const data = { filters: [{ value: ['a', 'b'] }] };
|
|
34
|
+
const result = transformStringifyJson('key', data);
|
|
35
|
+
expect(result.filters[0].value).toBe('["a","b"]');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should stringify nested string values for "filters.value"', () => {
|
|
39
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
40
|
+
const data = { filters: [{ value: 'test' }] };
|
|
41
|
+
const result = transformStringifyJson('key', data);
|
|
42
|
+
expect(result.filters[0].value).toBe('"test"');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should stringify top-level key for "name"', () => {
|
|
46
|
+
mockGetJsonFieldsKeys.mockReturnValue(['name']);
|
|
47
|
+
const data = { name: 'test' };
|
|
48
|
+
const result = transformStringifyJson('key', data);
|
|
49
|
+
expect(result.name).toBe('"test"');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should not crash when data={} and jsonFieldsKeys=["name"]', () => {
|
|
53
|
+
mockGetJsonFieldsKeys.mockReturnValue(['name']);
|
|
54
|
+
const result = transformStringifyJson('key', {});
|
|
55
|
+
expect(result).toEqual({});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should not crash when data is undefined', () => {
|
|
59
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
60
|
+
const result = transformStringifyJson('key', undefined);
|
|
61
|
+
expect(result).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should not crash when data is null', () => {
|
|
65
|
+
mockGetJsonFieldsKeys.mockReturnValue(['name']);
|
|
66
|
+
const result = transformStringifyJson('key', null);
|
|
67
|
+
expect(result).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should stringify non-array object value with secondKey', () => {
|
|
71
|
+
mockGetJsonFieldsKeys.mockReturnValue(['filters.value']);
|
|
72
|
+
const data = { filters: { value: { nested: true } } };
|
|
73
|
+
const result = transformStringifyJson('key', data);
|
|
74
|
+
expect(result.filters.value).toBe('{"nested":true}');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { of } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
jest.mock('../../../src/ApiService/utils/transformParseJson', () => ({
|
|
4
|
+
transformParseJson: jest.fn((key: string, data: any) => data),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
jest.mock('../../../src/ApiService/utils/transformStringifyJson', () => ({
|
|
8
|
+
transformStringifyJson: jest.fn((key: string, data: any) => data),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { GrpcInterceptor } from '../../../src/GrpcTransport/interceptors/GrpcInterceptor';
|
|
12
|
+
import { transformParseJson } from '../../../src/ApiService/utils/transformParseJson';
|
|
13
|
+
import { transformStringifyJson } from '../../../src/ApiService/utils/transformStringifyJson';
|
|
14
|
+
|
|
15
|
+
const mockTransformParseJson = transformParseJson as jest.Mock;
|
|
16
|
+
const mockTransformStringifyJson = transformStringifyJson as jest.Mock;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
mockTransformParseJson.mockClear();
|
|
20
|
+
mockTransformStringifyJson.mockClear();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('GrpcInterceptor', () => {
|
|
24
|
+
const key = 'service.method';
|
|
25
|
+
const interceptor = new GrpcInterceptor(key);
|
|
26
|
+
|
|
27
|
+
const createContext = (data: any) => ({
|
|
28
|
+
switchToRpc: () => ({
|
|
29
|
+
getData: () => data,
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const createNext = (returnValue: any) => ({
|
|
34
|
+
handle: () => of(returnValue),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should call transformParseJson with request key and data', done => {
|
|
38
|
+
const requestData = { name: 'test' };
|
|
39
|
+
const ctx = createContext(requestData);
|
|
40
|
+
const next = createNext({ result: 'ok' });
|
|
41
|
+
|
|
42
|
+
interceptor.intercept(ctx as any, next as any).subscribe(() => {
|
|
43
|
+
expect(mockTransformParseJson).toHaveBeenCalledWith(`${key}.request`, requestData);
|
|
44
|
+
done();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should call transformStringifyJson on response', done => {
|
|
49
|
+
const responseData = { result: 'ok' };
|
|
50
|
+
const ctx = createContext({});
|
|
51
|
+
const next = createNext(responseData);
|
|
52
|
+
|
|
53
|
+
interceptor.intercept(ctx as any, next as any).subscribe(() => {
|
|
54
|
+
expect(mockTransformStringifyJson).toHaveBeenCalledWith(`${key}.response`, responseData);
|
|
55
|
+
done();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return transformed response', done => {
|
|
60
|
+
mockTransformStringifyJson.mockImplementation((_, data) => ({ ...data, transformed: true }));
|
|
61
|
+
|
|
62
|
+
const ctx = createContext({});
|
|
63
|
+
const next = createNext({ value: 42 });
|
|
64
|
+
|
|
65
|
+
interceptor.intercept(ctx as any, next as any).subscribe(result => {
|
|
66
|
+
expect(result).toEqual({ value: 42, transformed: true });
|
|
67
|
+
done();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should use correct key prefix', done => {
|
|
72
|
+
const customInterceptor = new GrpcInterceptor('myService.myMethod');
|
|
73
|
+
const ctx = createContext({ id: 1 });
|
|
74
|
+
const next = createNext({ ok: true });
|
|
75
|
+
|
|
76
|
+
customInterceptor.intercept(ctx as any, next as any).subscribe(() => {
|
|
77
|
+
expect(mockTransformParseJson).toHaveBeenCalledWith('myService.myMethod.request', { id: 1 });
|
|
78
|
+
expect(mockTransformStringifyJson).toHaveBeenCalledWith('myService.myMethod.response', { ok: true });
|
|
79
|
+
done();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { GenerateProtoMessagesService } from '../../../src/GrpcTransport/services/GenerateProtoMessagesService';
|
|
3
|
+
|
|
4
|
+
describe('GenerateProtoMessagesService', () => {
|
|
5
|
+
const service = new GenerateProtoMessagesService();
|
|
6
|
+
|
|
7
|
+
it('should generate empty message when objSchema is null', () => {
|
|
8
|
+
const result = service.call({
|
|
9
|
+
messageName: 'EmptyRequest',
|
|
10
|
+
objSchema: null,
|
|
11
|
+
existMessageNames: [],
|
|
12
|
+
});
|
|
13
|
+
expect(result.data).toEqual(['message EmptyRequest {}\n\n']);
|
|
14
|
+
expect(result.messageNames).toEqual(['EmptyRequest']);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should skip already existing message names', () => {
|
|
18
|
+
const result = service.call({
|
|
19
|
+
messageName: 'ExistingMessage',
|
|
20
|
+
objSchema: null,
|
|
21
|
+
existMessageNames: ['ExistingMessage'],
|
|
22
|
+
});
|
|
23
|
+
expect(result.data).toEqual([]);
|
|
24
|
+
expect(result.messageNames).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should generate message with string field', () => {
|
|
28
|
+
const schema = {};
|
|
29
|
+
Reflect.defineMetadata('field:name', { type: String }, schema);
|
|
30
|
+
|
|
31
|
+
const result = service.call({
|
|
32
|
+
messageName: 'TestMessage',
|
|
33
|
+
objSchema: schema,
|
|
34
|
+
existMessageNames: [],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const output = result.data.join('');
|
|
38
|
+
expect(output).toContain('message TestMessage {');
|
|
39
|
+
expect(output).toContain('string name = 1;');
|
|
40
|
+
expect(result.messageNames).toEqual(['TestMessage']);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should generate message with number field as double', () => {
|
|
44
|
+
const schema = {};
|
|
45
|
+
Reflect.defineMetadata('field:count', { type: Number }, schema);
|
|
46
|
+
|
|
47
|
+
const result = service.call({
|
|
48
|
+
messageName: 'NumberMessage',
|
|
49
|
+
objSchema: schema,
|
|
50
|
+
existMessageNames: [],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const output = result.data.join('');
|
|
54
|
+
expect(output).toContain('double count = 1;');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should generate message with boolean field', () => {
|
|
58
|
+
const schema = {};
|
|
59
|
+
Reflect.defineMetadata('field:active', { type: Boolean }, schema);
|
|
60
|
+
|
|
61
|
+
const result = service.call({
|
|
62
|
+
messageName: 'BoolMessage',
|
|
63
|
+
objSchema: schema,
|
|
64
|
+
existMessageNames: [],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const output = result.data.join('');
|
|
68
|
+
expect(output).toContain('bool active = 1;');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should generate message with repeated field', () => {
|
|
72
|
+
const schema = {};
|
|
73
|
+
Reflect.defineMetadata('field:items', { type: String, array: true }, schema);
|
|
74
|
+
|
|
75
|
+
const result = service.call({
|
|
76
|
+
messageName: 'ArrayMessage',
|
|
77
|
+
objSchema: schema,
|
|
78
|
+
existMessageNames: [],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const output = result.data.join('');
|
|
82
|
+
expect(output).toContain('repeated string items = 1;');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should generate message with enum field as string', () => {
|
|
86
|
+
const schema = {};
|
|
87
|
+
Reflect.defineMetadata('field:status', { type: String, enum: true }, schema);
|
|
88
|
+
|
|
89
|
+
const result = service.call({
|
|
90
|
+
messageName: 'EnumMessage',
|
|
91
|
+
objSchema: schema,
|
|
92
|
+
existMessageNames: [],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const output = result.data.join('');
|
|
96
|
+
expect(output).toContain('string status = 1;');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should generate message with Object type as string', () => {
|
|
100
|
+
const schema = {};
|
|
101
|
+
Reflect.defineMetadata('field:data', { type: Object }, schema);
|
|
102
|
+
|
|
103
|
+
const result = service.call({
|
|
104
|
+
messageName: 'ObjectMessage',
|
|
105
|
+
objSchema: schema,
|
|
106
|
+
existMessageNames: [],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const output = result.data.join('');
|
|
110
|
+
expect(output).toContain('string data = 1;');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should generate message with JSON type as string', () => {
|
|
114
|
+
const schema = {};
|
|
115
|
+
Reflect.defineMetadata('field:payload', { type: JSON }, schema);
|
|
116
|
+
|
|
117
|
+
const result = service.call({
|
|
118
|
+
messageName: 'JsonMessage',
|
|
119
|
+
objSchema: schema,
|
|
120
|
+
existMessageNames: [],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const output = result.data.join('');
|
|
124
|
+
expect(output).toContain('string payload = 1;');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should generate nested message for custom type', () => {
|
|
128
|
+
const nestedSchema: any = { name: 'NestedType' };
|
|
129
|
+
Reflect.defineMetadata('field:inner', { type: String }, nestedSchema);
|
|
130
|
+
|
|
131
|
+
const schema = {};
|
|
132
|
+
Reflect.defineMetadata('field:nested', { type: nestedSchema }, schema);
|
|
133
|
+
|
|
134
|
+
const result = service.call({
|
|
135
|
+
messageName: 'ParentMessage',
|
|
136
|
+
objSchema: schema,
|
|
137
|
+
existMessageNames: [],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const output = result.data.join('');
|
|
141
|
+
expect(output).toContain('message ParentMessage {');
|
|
142
|
+
expect(output).toContain('NestedType nested = 1;');
|
|
143
|
+
expect(output).toContain('message NestedType {');
|
|
144
|
+
expect(output).toContain('string inner = 1;');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should handle multiple fields with correct indices', () => {
|
|
148
|
+
const schema = {};
|
|
149
|
+
Reflect.defineMetadata('field:name', { type: String }, schema);
|
|
150
|
+
Reflect.defineMetadata('field:age', { type: Number }, schema);
|
|
151
|
+
Reflect.defineMetadata('field:active', { type: Boolean }, schema);
|
|
152
|
+
|
|
153
|
+
const result = service.call({
|
|
154
|
+
messageName: 'MultiFieldMessage',
|
|
155
|
+
objSchema: schema,
|
|
156
|
+
existMessageNames: [],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const output = result.data.join('');
|
|
160
|
+
expect(output).toContain('= 1;');
|
|
161
|
+
expect(output).toContain('= 2;');
|
|
162
|
+
expect(output).toContain('= 3;');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should replace $ and _ in type names', () => {
|
|
166
|
+
const nestedSchema: any = { name: 'My$Type_Name' };
|
|
167
|
+
Reflect.defineMetadata('field:val', { type: String }, nestedSchema);
|
|
168
|
+
|
|
169
|
+
const schema = {};
|
|
170
|
+
Reflect.defineMetadata('field:item', { type: nestedSchema }, schema);
|
|
171
|
+
|
|
172
|
+
const result = service.call({
|
|
173
|
+
messageName: 'SpecialCharsMessage',
|
|
174
|
+
objSchema: schema,
|
|
175
|
+
existMessageNames: [],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const output = result.data.join('');
|
|
179
|
+
expect(output).toContain('MySSTypeTTName item = 1;');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GetPackageNameService } from '../../../src/GrpcTransport/services/GetPackageNameService';
|
|
2
|
+
|
|
3
|
+
describe('GetPackageNameService', () => {
|
|
4
|
+
const service = new GetPackageNameService();
|
|
5
|
+
|
|
6
|
+
it('should return single word as-is', () => {
|
|
7
|
+
expect(service.call('users')).toBe('users');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should camelCase hyphenated service name', () => {
|
|
11
|
+
expect(service.call('user-service')).toBe('userService');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should handle multiple hyphens', () => {
|
|
15
|
+
expect(service.call('my-long-service')).toBe('myLongService');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should capitalize first letter of each part except first', () => {
|
|
19
|
+
expect(service.call('api-gateway-service')).toBe('apiGatewayService');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle two-letter parts', () => {
|
|
23
|
+
expect(service.call('a-b')).toBe('aB');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const Injectable = () => (target: any) => target;
|
|
2
|
+
export const Inject = () => () => {};
|
|
3
|
+
export const Module = () => (target: any) => target;
|
|
4
|
+
export const Global = () => (target: any) => target;
|
|
5
|
+
export const Controller = () => (target: any) => target;
|
|
6
|
+
export const UseInterceptors = () => () => {};
|
|
7
|
+
|
|
8
|
+
export const Logger = {
|
|
9
|
+
debug: jest.fn(),
|
|
10
|
+
error: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface OnApplicationBootstrap {}
|
|
14
|
+
export interface OnApplicationShutdown {}
|
|
15
|
+
export type CallHandler = any;
|
|
16
|
+
export type ExecutionContext = any;
|
|
17
|
+
export interface NestInterceptor {}
|
|
18
|
+
export interface ModuleMetadata {
|
|
19
|
+
imports: any[];
|
|
20
|
+
}
|
|
21
|
+
export type DynamicModule = any;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class RpcException extends Error {
|
|
2
|
+
constructor(msg: string) {
|
|
3
|
+
super(msg);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class ClientProxyFactory {
|
|
8
|
+
static create(data: any) {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum Transport {
|
|
14
|
+
TCP = 0,
|
|
15
|
+
NATS = 1,
|
|
16
|
+
GRPC = 2,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ClientNats = any;
|
|
20
|
+
export type ClientGrpc = any;
|
|
21
|
+
export type GrpcOptions = any;
|
|
22
|
+
export type TcpClientOptions = any;
|
|
23
|
+
export type NatsOptions = any;
|
|
24
|
+
export type MicroserviceOptions = any;
|
|
25
|
+
export interface CustomStrategy {}
|
|
26
|
+
|
|
27
|
+
export const GrpcMethod =
|
|
28
|
+
(...args: any[]) =>
|
|
29
|
+
(...params: any[]) => {};
|
|
30
|
+
export const MessagePattern =
|
|
31
|
+
(data: any) =>
|
|
32
|
+
(...params: any[]) => {};
|