Skip to content

Commit

Permalink
test: added test for interceptor
Browse files Browse the repository at this point in the history
fix: fixed parse prefixPath
  • Loading branch information
tykachev committed Jan 16, 2025
1 parent 38d1c3a commit 3814500
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 8 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@commitlint/config-conventional": "17.3.0",
"@nestjs/common": "10.4.11",
"@nestjs/core": "10.4.11",
"@nestjs/testing": "^10.4.15",
"@types/express": "5.0.0",
"@types/jest": "29.5.14",
"@types/node": "20.17.9",
Expand All @@ -75,6 +76,7 @@
"pinst": "3.0.0",
"prettier": "3.3.2",
"prettier-plugin-packagejson": "2.2.15",
"reflect-metadata": "^0.2.2",
"rxjs": "7.8.0",
"semantic-release": "19.0.5",
"ts-jest": "29.2.5",
Expand Down
5 changes: 4 additions & 1 deletion src/promInterceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export class PromInterceptor implements NestInterceptor {

const request = context.switchToHttp().getRequest<Request>();

const controllerPathIndex = request.url.indexOf(controllerPath);
const controllerPathIndex = request.url.indexOf(
// eslint-disable-next-line unicorn/prefer-string-replace-all
controllerPath.replace(/^\/|\/$/g, ""),
);
const prefixPath =
controllerPathIndex === -1
? ""
Expand Down
168 changes: 168 additions & 0 deletions test/promInterceptor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 2024 Byndyusoft
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Test, TestingModule } from "@nestjs/testing";
import { Request } from "express";
import { Histogram } from "prom-client";
import { of } from "rxjs";

import {
DEFAULT_HTTP_REQUESTS_METRIC_NAME,
DEFAULT_IGNORED_URLS,
DEFAULT_PROM_OPTIONS_TOKEN,
} from "../src";
import { PromInterceptor } from "../src/promInterceptor";

const mockOptions = {
httpRequestBucket: {
ignoredUrls: DEFAULT_IGNORED_URLS,
},
};

describe("PromInterceptor", () => {
let interceptor: PromInterceptor;
let mockHistogram: Histogram;
let mockReflector: Reflector;

beforeEach(async () => {
mockHistogram = {
observe: jest.fn(),
} as unknown as jest.Mocked<Histogram>;

mockReflector = new Reflector();

const module: TestingModule = await Test.createTestingModule({
providers: [
PromInterceptor,
{ provide: Reflector, useValue: mockReflector },
{ provide: DEFAULT_PROM_OPTIONS_TOKEN, useValue: mockOptions },
{
provide: `PROM_METRIC_${DEFAULT_HTTP_REQUESTS_METRIC_NAME.toUpperCase()}`,
useValue: mockHistogram,
},
],
}).compile();

interceptor = module.get<PromInterceptor>(PromInterceptor);
});

test.each([
// eslint-disable-next-line sonarjs/no-duplicate-string
["/api/v1/users/1", "users", ":user:", "/api/v1/users/:user:"],
["/api/v1/users/1", "/users", ":user:", "/api/v1/users/:user:"],
["/api/v1/users/1", "users", "/:user:", "/api/v1/users/:user:"],
["/example", "", "example", "/example"],
["/example", "", "/example", "/example"],
["/example", "/", "example", "/example"],
["/example", "/", "/example", "/example"],
])(
"should observe metrics with url %s controller %s method %s resultPath %s",
async (url, controller, method, resultPath) => {
jest.spyOn(mockReflector, "get").mockImplementation((key, target) => {
if (key === "path") {
if (target.name === controller) return controller;
if (target.name === method) return method;
}
return "";
});

const mockContext: Partial<ExecutionContext> = {
switchToHttp: jest.fn().mockReturnValue({
getRequest: (): Partial<Request> => ({
url,
method: "GET",
}),
getResponse: () => ({
statusCode: 200,
}),
}),
getHandler: jest.fn().mockReturnValue({ name: method }),
getClass: jest.fn().mockReturnValue({ name: controller }),
};

const next = {
handle: jest.fn().mockReturnValue(of("response")),
};

await new Promise<void>((resolve) => {
interceptor.intercept(mockContext as ExecutionContext, next).subscribe({
complete() {
return resolve();
},
});
});

expect(next.handle).toHaveBeenCalled();

// eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockHistogram.observe).toHaveBeenCalledWith(
{ method: "GET", status: "2XX", path: resultPath },
expect.any(Number),
);
},
);

test.each([
["/_healthz", "", "_healthz"],
["/_healthz", "", "/_healthz"],
["/_healthz", "/", "_healthz"],
["/_healthz", "/", "/_healthz"],
])(
"should ignored observe metrics with url %s controller %s method %s resultPath %s",
async (url, controller, method) => {
jest.spyOn(mockReflector, "get").mockImplementation((key, target) => {
if (key === "path") {
if (target.name === controller) return controller;
if (target.name === method) return method;
}
return "";
});

const mockContext: Partial<ExecutionContext> = {
switchToHttp: jest.fn().mockReturnValue({
getRequest: (): Partial<Request> => ({
url,
method: "GET",
}),
getResponse: () => ({
statusCode: 200,
}),
}),
getHandler: jest.fn().mockReturnValue({ name: method }),
getClass: jest.fn().mockReturnValue({ name: controller }),
};

const next = {
handle: jest.fn().mockReturnValue(of("response")),
};

await new Promise<void>((resolve) => {
interceptor.intercept(mockContext as ExecutionContext, next).subscribe({
complete() {
return resolve();
},
});
});

expect(next.handle).toHaveBeenCalled();

// eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockHistogram.observe).not.toHaveBeenCalled();
},
);
});
42 changes: 35 additions & 7 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ __metadata:
"@commitlint/config-conventional": "npm:17.3.0"
"@nestjs/common": "npm:10.4.11"
"@nestjs/core": "npm:10.4.11"
"@nestjs/testing": "npm:^10.4.15"
"@types/express": "npm:5.0.0"
"@types/jest": "npm:29.5.14"
"@types/node": "npm:20.17.9"
Expand All @@ -448,6 +449,7 @@ __metadata:
prettier: "npm:3.3.2"
prettier-plugin-packagejson: "npm:2.2.15"
prom-client: "npm:^15.1.3"
reflect-metadata: "npm:^0.2.2"
rxjs: "npm:7.8.0"
semantic-release: "npm:19.0.5"
ts-jest: "npm:29.2.5"
Expand Down Expand Up @@ -1198,6 +1200,25 @@ __metadata:
languageName: node
linkType: hard

"@nestjs/testing@npm:^10.4.15":
version: 10.4.15
resolution: "@nestjs/testing@npm:10.4.15"
dependencies:
tslib: "npm:2.8.1"
peerDependencies:
"@nestjs/common": ^10.0.0
"@nestjs/core": ^10.0.0
"@nestjs/microservices": ^10.0.0
"@nestjs/platform-express": ^10.0.0
peerDependenciesMeta:
"@nestjs/microservices":
optional: true
"@nestjs/platform-express":
optional: true
checksum: 4026011b689b180d3eafd70b935f9fc4f607a7f50ea3335ca534cb1f3b0e623ffe7a81f58aa83297d5b7d52a2eb49ec201a17a1ba82fa88fb8a3dd45d0b4b922
languageName: node
linkType: hard

"@nodelib/fs.scandir@npm:2.1.5":
version: 2.1.5
resolution: "@nodelib/fs.scandir@npm:2.1.5"
Expand Down Expand Up @@ -9358,6 +9379,13 @@ __metadata:
languageName: node
linkType: hard

"reflect-metadata@npm:^0.2.2":
version: 0.2.2
resolution: "reflect-metadata@npm:0.2.2"
checksum: d1757ffab9e1b55f7858501c663774c7b7b3abe313e24a25f4ae3caf63e79142e5fcff4fdc04daecbd74fd83d03732c916139d23f0f3838296ba933300208746
languageName: node
linkType: hard

"reflect.getprototypeof@npm:^1.0.4, reflect.getprototypeof@npm:^1.0.6":
version: 1.0.7
resolution: "reflect.getprototypeof@npm:1.0.7"
Expand Down Expand Up @@ -10674,20 +10702,20 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:2.8.1, tslib@npm:^2.1.0, tslib@npm:^2.6.2":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 080be9a9b50485142bd6dc8f159187fc90b8a0cd7e83b29a38d63f3a6a59a86ab729ece36ab5ced67c775416c9cf4465e8fb52ff03f3aa6a2e661c9c69e26909
languageName: node
linkType: hard

"tslib@npm:^1.8.1":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
checksum: 441af59dc42ad4ae57140e62cb362369620c6076845c2c2b0ecc863c1d719ce24fdbc301e9053433fef43075e061bf84b702318ff1204b496a5bba10baf9eb9f
languageName: node
linkType: hard

"tslib@npm:^2.1.0, tslib@npm:^2.6.2":
version: 2.8.1
resolution: "tslib@npm:2.8.1"
checksum: 080be9a9b50485142bd6dc8f159187fc90b8a0cd7e83b29a38d63f3a6a59a86ab729ece36ab5ced67c775416c9cf4465e8fb52ff03f3aa6a2e661c9c69e26909
languageName: node
linkType: hard

"tsutils@npm:^3.21.0":
version: 3.21.0
resolution: "tsutils@npm:3.21.0"
Expand Down

0 comments on commit 3814500

Please sign in to comment.