Skip to content

Commit

Permalink
refactor: tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tykachev committed Jan 17, 2025
1 parent 21daa7c commit 24a6168
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 97 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"@commitlint/config-conventional": "17.3.0",
"@nestjs/common": "10.4.11",
"@nestjs/core": "10.4.11",
"@nestjs/testing": "^10.4.15",
"@nestjs/testing": "10.4.15",
"@types/express": "5.0.0",
"@types/jest": "29.5.14",
"@types/node": "20.17.9",
Expand All @@ -76,7 +76,7 @@
"pinst": "3.0.0",
"prettier": "3.3.2",
"prettier-plugin-packagejson": "2.2.15",
"reflect-metadata": "^0.2.2",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.0",
"semantic-release": "19.0.5",
"ts-jest": "29.2.5",
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,3 @@ export const DEFAULT_IGNORED_URLS = [
"/_healthz",
"/favicon.ico",
];
export const TRIM_SLASHES_PATTERN = /^\/|\/$/g;
5 changes: 4 additions & 1 deletion src/promInterceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ import {
DEFAULT_HTTP_REQUESTS_METRIC_NAME,
DEFAULT_IGNORED_URLS,
DEFAULT_PROM_OPTIONS_TOKEN,
TRIM_SLASHES_PATTERN,
} from "./constants";
import { PromModuleOptions } from "./interfaces";
import { Normalizer } from "./utils";

const TRIM_SLASHES_PATTERN = /^\/|\/$/g;

@Injectable()
export class PromInterceptor implements NestInterceptor {
public constructor(
Expand Down Expand Up @@ -66,13 +67,15 @@ export class PromInterceptor implements NestInterceptor {
const request = context.switchToHttp().getRequest<Request>();

const controllerPathIndex = request.url.indexOf(
// eslint-disable-next-line unicorn/prefer-string-replace-all
controllerPath.replace(TRIM_SLASHES_PATTERN, ""),
);
const prefixPath =
controllerPathIndex === -1
? ""
: request.url.slice(0, controllerPathIndex);

// eslint-disable-next-line unicorn/prefer-string-replace-all
const path = `${prefixPath}${controllerPath.replace(TRIM_SLASHES_PATTERN, "")}/${methodPath.replace(TRIM_SLASHES_PATTERN, "")}`;

const ignoredUrls =
Expand Down
230 changes: 137 additions & 93 deletions test/promInterceptor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,107 +62,151 @@ describe("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,
}),
[
{
// eslint-disable-next-line sonarjs/no-duplicate-string
url: "/api/v1/users/1",
controller: "users",
method: ":user:",
// eslint-disable-next-line sonarjs/no-duplicate-string
resultPath: "/api/v1/users/:user:",
},
],
[
{
url: "/api/v1/users/1",
controller: "/users",
method: ":user:",
resultPath: "/api/v1/users/:user:",
},
],
[
{
url: "/api/v1/users/1",
controller: "users",
method: "/:user:",
resultPath: "/api/v1/users/:user:",
},
],
[
{
url: "/example",
controller: "",
method: "example",
resultPath: "/example",
},
],
[
{
url: "/example",
controller: "",
method: "/example",
resultPath: "/example",
},
],
[
{
url: "/example",
controller: "/",
method: "example",
resultPath: "/example",
},
],
[
{
url: "/example",
controller: "/",
method: "/example",
resultPath: "/example",
},
],
])("should observe metrics with %s", async (data) => {
jest.spyOn(mockReflector, "get").mockImplementation((key, target) => {
if (key === "path") {
if (target.name === data.controller) return data.controller;
if (target.name === data.method) return data.method;
}
return "";
});

const mockContext: Partial<ExecutionContext> = {
switchToHttp: jest.fn().mockReturnValue({
getRequest: (): Partial<Request> => ({
url: data.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();
},
});
}),
getHandler: jest.fn().mockReturnValue({ name: data.method }),
getClass: jest.fn().mockReturnValue({ name: data.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();
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),
);
},
);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockHistogram.observe).toHaveBeenCalledWith(
{ method: "GET", status: "2XX", path: data.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,
}),
[{ url: "/_healthz", controller: "", method: "_healthz" }],
[{ url: "/_healthz", controller: "", method: "/_healthz" }],
[{ url: "/_healthz", controller: "/", method: "_healthz" }],
[{ url: "/_healthz", controller: "/", method: "/_healthz" }],
])("should ignored observe metrics with %s", async (data) => {
jest.spyOn(mockReflector, "get").mockImplementation((key, target) => {
if (key === "path") {
if (target.name === data.controller) return data.controller;
if (target.name === data.method) return data.method;
}
return "";
});

const mockContext: Partial<ExecutionContext> = {
switchToHttp: jest.fn().mockReturnValue({
getRequest: (): Partial<Request> => ({
url: data.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();
},
});
}),
getHandler: jest.fn().mockReturnValue({ name: data.method }),
getClass: jest.fn().mockReturnValue({ name: data.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();
expect(next.handle).toHaveBeenCalled();

// eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockHistogram.observe).not.toHaveBeenCalled();
},
);
// eslint-disable-next-line @typescript-eslint/unbound-method
expect(mockHistogram.observe).not.toHaveBeenCalled();
});
});

0 comments on commit 24a6168

Please sign in to comment.