My prisma.service.ts looks like this:
#Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}
According to Prisma docs I am supposed to put them outside the context of the request handler. That would be the app that I create on main.ts. Putting a middleware there before the app itself is defined looks wierd to me and doesn't work. I'd prefer to put it on the prisma.service.ts file itself
Not sure whether this is the "best" place to register them, but we do it in the constructor of the service along with the logging configuration and it works:
import { INestApplication, Injectable, Logger, OnModuleInit } from "#nestjs/common";
import { Prisma, PrismaClient } from "#prisma/client";
import { ConcurrencyErrorMiddleware } from "./concurrency-error.middleware";
#Injectable()
export class PrismaService extends PrismaClient<Prisma.PrismaClientOptions, "query"> implements OnModuleInit {
private readonly logger = new Logger(PrismaService.name);
constructor() {
super({ log: [{ emit: "event", level: "query" }] });
this.logger.log(`Prisma v${Prisma.prismaVersion.client}`);
this.$on("query", (e) => this.logger.debug(`${e.query} ${e.params}`));
this.$use(ConcurrencyErrorMiddleware());
}
async onModuleInit(): Promise<void> {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication): Promise<void> {
this.$on("beforeExit", async () => {
await app.close();
});
}
}
// An example of such a middleware.
import { Prisma } from "#prisma/client";
export function ConcurrencyErrorMiddleware<T extends Prisma.BatchPayload = Prisma.BatchPayload>(): Prisma.Middleware {
return async (params: Prisma.MiddlewareParams, next: (params: Prisma.MiddlewareParams) => Promise<T>): Promise<T> => {
const result = await next(params);
if (
(params.action === "updateMany" || params.action === "deleteMany") &&
params.args.where.version &&
result.count === 0
) {
throw new ConcurrencyError();
}
return result;
};
}
Related
I have the following global guard:
authorization.guard.ts
import { ExecutionContext, Injectable } from "#nestjs/common"
import { Reflector } from "#nestjs/core"
import { AuthGuard } from "#nestjs/passport"
#Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
public constructor(private readonly reflector: Reflector) {
super()
}
async canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler(),
)
if (isPublic) {
return true
}
const req = context.switchToHttp().getRequest()
if(req.headers.isbypass){
//help needed
}
const result = (await super.canActivate(context)) as boolean
await super.logIn(req)
return result
}
}
and the following auth module and strategy:
import { Module } from "#nestjs/common";
import { PassportModule } from "#nestjs/passport";
import { UsersModule } from "modules/users/users.module";
import { AzureADStrategy } from "./azureAD.strategy";
import { SessionSerializer } from "./session.serializer";
#Module({
imports: [PassportModule, UsersModule],
providers: [AzureADStrategy, SessionSerializer],
})
export class AuthModule {}
import {
BearerStrategy,
IBearerStrategyOption,
ITokenPayload,
VerifyCallback,
} from "passport-azure-ad";
import {
Inject,
Injectable,
OnModuleInit,
UnauthorizedException,
} from "#nestjs/common";
import passport = require("passport");
import { UsersService } from "modules/users/users.service";
import env from "../../config";
const tenantId = env.TENANT_ID;
const clientID = env.CLIENT_ID || "";
const azureCredentials: IBearerStrategyOption = {
//
};
#Injectable()
export class AzureADStrategy extends BearerStrategy implements OnModuleInit {
onModuleInit() {
passport.use("azure-ad", this);
}
constructor(
#Inject(UsersService) private usersService: UsersService
) {
super(
azureCredentials,
async (token: ITokenPayload, done: VerifyCallback) => {
if (Date.now() / 1000 > token.exp) {
return done(new UnauthorizedException("access token is expired"));
}
const tokenUsername = token?.preferred_username?.slice(0, 9);
const tokenAppId = !tokenUsername && token?.azp;
if (!tokenUsername && !tokenAppId) {
return done(new UnauthorizedException("Missing User"));
}
let user;
if (tokenUsername) {
try {
user = await this.usersService.getUser(
tokenUsername
);
if (!user) {
return done(
new UnauthorizedException("User is not a test user")
);
}
} catch (err) {
return done(err);
}
user.tz = tokenUsername;
} else {
user.appId = tokenAppId;
}
return done(null, user, token);
}
);
}
}
The guard is defined globally using:
const reflector = app.get(Reflector);
app.useGlobalGuards(
new AuthorizationGuard(reflector),
);
And the auth module is imported in app.module.ts:
#Module({
imports: [
AuthModule,
...
]
Now, for the question.
I would like to have a way to "backdoor" the global authorization by checking if req.headers.isbypass exists in the request's headers, and if it does use userService in authorizationGuard, so i can inject the user from the DB to req.user myself and continue the request.
How do I achieve that?
I would change the app.useGlobalGuards() to be a global guard provider, adding
{
provider: APP_GUARD,
useClass: AuthorizationGuard,
}
Into the providers of your AppModule so that Nest handles all of the DI for you. From there, it's just adding the UsersService to the constructor like you already have for the Reflector
#Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
public constructor(
private readonly reflector: Reflector,
private readonly usersService: UsersServce
) {
super()
}
async canActivate(context: ExecutionContext) {
const isPublic = this.reflector.get<boolean>(
"isPublic",
context.getHandler(),
)
if (isPublic) {
return true
}
const req = context.switchToHttp().getRequest()
if(req.headers.isbypass){
//help needed
}
const result = (await super.canActivate(context)) as boolean
await super.logIn(req)
return result
}
}
Hi I'm trying to test my service but got stuck
import { HttpException, Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { getManager, Repository } from 'typeorm';
import { PointsEntity } from './entities/point_store.entity';
#Injectable()
export class PointStoreService {
constructor(
#InjectRepository(PointsEntity)
private readonly pointsRepository: Repository<PointsEntity>,
) {}
async searchPoints(startDate: Date, endDate: Date, userId: string) {
try {
return await getManager().query(`
SELECT * FROM points WHERE expire_at
BETWEEN '${startDate.toISOString()}' AND '${endDate.toISOString()}'
AND "userId"='${userId}'
`);
} catch (err) {
throw new HttpException(err.message, err.state ? err.state : 500);
}
}
}
This is my Service code. As you know there is only one method.
import { Test, TestingModule } from '#nestjs/testing';
import { getRepositoryToken } from '#nestjs/typeorm';
import * as typeorm from 'typeorm';
import { PointsEntity } from './entities/point_store.entity';
import { PointStoreService } from './point_store.service';
const mockPointsRepository = {
save: jest.fn(),
};
describe('PointStoreService', () => {
let service: PointStoreService;
let repository: typeorm.Repository<PointsEntity>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PointStoreService,
{
provide: getRepositoryToken(PointsEntity),
useValue: mockPointsRepository,
},
],
}).compile();
jest.resetModules(); // Most important - it clears the cache
service = module.get<PointStoreService>(PointStoreService);
repository = module.get(getRepositoryToken(PointsEntity));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('searchPoints', () => {
it('should searchPoints', async () => {
// can not mock getManager!!!
jest.spyOn(getManager, 'query');
});
});
});
Now I code this test case. 'should be defined' works fine. But after I try to test 'should searchPoints', I have no idea to mock it.
Is there a way to mock getManager().query()?
gitHub repository
This is my nodejs typescript class and written jest unit test for isHealthy() public method.
Test coverage shows that this.pingCheck() then block, catch and last return statement are not covered.
Please advise.
Can we do unit test for pingCheck private method ?
This my class
import { HttpService, Injectable } from '#nestjs/common';
import { DependencyUtlilizationService } from '../dependency-utlilization/dependency-utlilization.service';
import { ComponentType } from '../enums/component-type.enum';
import { HealthStatus } from '../enums/health-status.enum';
import { ComponentHealthCheckResult } from '../interfaces/component-health-check-result.interface';
import { ApiHealthCheckOptions } from './interfaces/api-health-check-options.interface';
#Injectable()
export class ApiHealthIndicator {
private healthIndicatorResponse: {
[key: string]: ComponentHealthCheckResult;
};
constructor(
private readonly httpService: HttpService,
private readonly dependencyUtilizationService: DependencyUtlilizationService,
) {
this.healthIndicatorResponse = {};
}
private async pingCheck(api: ApiHealthCheckOptions): Promise<boolean> {
let result = this.dependencyUtilizationService.isRecentlyUsed(api.key);
if (result) {
await this.httpService.request({ url: api.url }).subscribe(() => {
return true;
});
}
return false;
}
async isHealthy(
listOfAPIs: ApiHealthCheckOptions[],
): Promise<{ [key: string]: ComponentHealthCheckResult }> {
for (const api of listOfAPIs) {
const apiHealthStatus = {
status: HealthStatus.fail,
type: ComponentType.url,
componentId: api.key,
description: `Health Status of ${api.url} is: fail`,
time: Date.now(),
output: '',
links: {},
};
await this.pingCheck(api)
.then(response => {
apiHealthStatus.status = HealthStatus.pass;
apiHealthStatus.description = `Health Status of ${api.url} is: pass`;
this.healthIndicatorResponse[api.key] = apiHealthStatus;
})
.catch(rejected => {
this.healthIndicatorResponse[api.key] = apiHealthStatus;
});
}
return this.healthIndicatorResponse;
}
}
This is my unit test code.
I get the following error when I run npm run test
(node:7876) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'status' of undefined
(node:7876) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 6)
import { HttpService } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { DependencyUtlilizationService } from '../dependency-utlilization/dependency-utlilization.service';
import { ApiHealthIndicator } from './api-health-indicator';
import { ApiHealthCheckOptions } from './interfaces/api-health-check-options.interface';
import { HealthStatus } from '../enums/health-status.enum';
describe('ApiHealthIndicator', () => {
let apiHealthIndicator: ApiHealthIndicator;
let httpService: HttpService;
let dependencyUtlilizationService: DependencyUtlilizationService;
let dnsList: [{ key: 'domain_api'; url: 'http://localhost:3001' }];
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ApiHealthIndicator,
{
provide: HttpService,
useValue: new HttpService(),
},
{
provide: DependencyUtlilizationService,
useValue: new DependencyUtlilizationService(),
},
],
}).compile();
apiHealthIndicator = module.get<ApiHealthIndicator>(ApiHealthIndicator);
httpService = module.get<HttpService>(HttpService);
dependencyUtlilizationService = module.get<DependencyUtlilizationService>(
DependencyUtlilizationService,
);
});
it('should be defined', () => {
expect(apiHealthIndicator).toBeDefined();
});
it('isHealthy should return status as true when pingCheck return true', () => {
jest
.spyOn(dependencyUtlilizationService, 'isRecentlyUsed')
.mockReturnValue(true);
const result = apiHealthIndicator.isHealthy(dnsList);
result.then(response =>
expect(response['domain_api'].status).toBe(HealthStatus.pass),
);
});
it('isHealthy should return status as false when pingCheck return false', () => {
jest
.spyOn(dependencyUtlilizationService, 'isRecentlyUsed')
.mockReturnValue(false);
jest.spyOn(httpService, 'request').mockImplementation(config => {
throw new Error('could not call api');
});
const result = apiHealthIndicator.isHealthy(dnsList);
result
.then(response => {
expect(response['domain_api'].status).toBe(HealthStatus.fail);
})
.catch(reject => {
expect(reject['domain_api'].status).toBe(HealthStatus.fail);
});
});
});
Looks like you should define the status before initialize the unit test, try to grab some more logs using console.log and for the second test, added catch block to make sure you're grabing the failures
Hello i am writing simple web application using design similar to facade design pattern. Application is written in Typescript using nodejs, expressjs, node-postres and inversify. Let say i have this simple example
Router.ts
router.get('/test', testController.test);
TestController.ts
import { Request, Response } from 'express';
import { ITestUC } from '../usecase/TestUC';
import { di } from '../core/Di';
import { TYPES } from '../core/Types';
class TestController {
public async test(req: Request, res: Response, next: Function) {
const uc = di.get<ITestUC>(TYPES.ITestUC);
await uc.run();
res.send({ data:1 });
}
}
export const testController = new TestController();
TestUC.ts
import "reflect-metadata";
import { injectable, interfaces } from "inversify";
import { di } from "../core/Di";
import { TYPES } from "../core/Types";
import { ITestManager1 } from "../library/Test/TestManager1";
import { ITestManager2 } from "../library/Test/TestManager2";
import { PoolClient } from "pg";
import { PostgresClient, IPostgresClient } from "../core/PostgresClient";
import { IPostgresPool } from "../core/PostgresPool";
function db(transaction: boolean) {
return (target: any, property: string, descriptor: TypedPropertyDescriptor<() => void>) => {
const fn = descriptor.value;
if(!fn) return;
descriptor.value = async function (){
let poolClient: PoolClient,
postgresClient: PostgresClient = new PostgresClient();
try {
poolClient = await di.get<IPostgresPool>(TYPES.IPostgresPool).pool.connect();
postgresClient.set(poolClient);
di.rebind<IPostgresClient>(TYPES.IPostgresClient).toDynamicValue((context: interfaces.Context) => { return postgresClient });
if (transaction) postgresClient.begin();
await fn.apply(this);
if (transaction) postgresClient.commit();
} catch (e) {
if (transaction) postgresClient.rollback();
throw e;
} finally {
postgresClient.get().release();
}
}
}
}
#injectable()
export class TestUC implements ITestUC {
#db(true)
public async run(): Promise<void> {
const manager1 = await di.get<ITestManager1>(TYPES.ITestManager1);
manager1.test1('m1');
const manager2 = await di.get<ITestManager2>(TYPES.ITestManager2);
manager2.test1('m2');
}
}
export interface ITestUC {
run(): Promise<void>
}
TestManager1.ts
import { injectable, inject} from "inversify";
import "reflect-metadata";
import { TYPES } from "../../core/Types";
import { ITestSql1 } from "./TestSql1";
#injectable()
export class TestManager1 implements ITestManager1 {
#inject(TYPES.ITestSql1) private sql: ITestSql1;
public async test1(value: string) {
await this.sql.test1(value);
}
}
export interface ITestManager1 {
test1(value: string)
}
TestSql1.ts
import { injectable, inject } from "inversify";
import "reflect-metadata";
import { IPostgresClient } from "../../core/PostgresClient";
import { TYPES } from "../../core/Types";
#injectable()
export class TestSql1 implements ITestSql1{
#inject(TYPES.IPostgresClient) db: IPostgresClient;
public async test1(value: string) {
const query = {
name: 'insert-test',
text: `
INSERT INTO pr.test (
process,
operation,
key
) VALUES (
$1,
$2,
$3
)`,
values: [
this.db.get()['processID'],
1,
value
]
};
await this.db.get().query(query);
}
}
export interface ITestSql1 {
test1(value: string)
}
PostgresClient.ts
import { PoolClient } from "pg";
export class PostgresClient implements IPostgresClient {
private client: PoolClient;
get(): PoolClient {
return this.client;
}
set(client: PoolClient) {
this.client = client;
}
async begin() {
await this.client.query('BEGIN');
}
async commit() {
await this.client.query('COMMIT');
}
async rollback() {
await this.client.query('ROLLBACK');
}
}
export interface IPostgresClient {
get(): PoolClient;
set(client: PoolClient);
commit();
rollback();
begin();
}
TestManager2.ts and TestSql2.ts are basically same as TestManager1.ts and TestSql1.ts
My problem is that every request seems to use only one same postgresql connection from pool (Tested with JMeter) and serialize all api request.
Pool doesn't even create other connections to postgresql. It looks like other requests waits for previous request end or postgresql connection release.
How to instantiate one connection (transaction) for every request using node-postgres pool and at the same time don't block other requests?
Is this code blocking? Or i misunderstood somthing in documentation? Or simply this design isn't suitable for nodejs? I really don't now and stuck for week.
I try to introduce e2e tests for my simple NestJS backend services. I am providing a custom userService and a custom UserRepository mocked with sinon.
This is my user.e2e-spec.ts file:
import * as request from 'supertest';
import * as sinon from 'sinon';
import { Test } from '#nestjs/testing';
import { INestApplication } from '#nestjs/common';
import { UserService } from '../../src/user/user.service';
import { getRepositoryToken } from '#nestjs/typeorm';
import { User } from '../../src/user/user.entity';
import { TestUtil } from '../../src/utils/TestUtil';
import { createFakeUser } from '../../src/user/test/userTestUtil';
let sandbox: sinon.SinonSandbox;
let testUtil;
describe('User', () => {
let app: INestApplication;
const fakeUser = createFakeUser();
const userService = { findOne: () => fakeUser };
beforeAll(async () => {
sandbox = sinon.createSandbox();
testUtil = new TestUtil(sandbox);
const module = await Test.createTestingModule({
providers: [
{
provide: UserService,
useValue: userService,
},
{
provide: getRepositoryToken(User),
useValue: testUtil.getMockRepository().object,
},
],
}).compile();
app = module.createNestApplication();
await app.init();
});
it(`/GET user`, () => {
return request(app.getHttpServer())
.get('/user/:id')
.expect(200)
.expect({
data: userService.findOne(),
});
});
afterAll(async () => {
await app.close();
});
});
and this is my user.controller.ts:
import { ApiBearerAuth, ApiUseTags } from '#nestjs/swagger';
import { Controller, Get, Param } from '#nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';
#ApiUseTags('Users')
#ApiBearerAuth()
#Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
#Get('/:id')
findOne(#Param('id') id: number): Promise<User> {
return this.userService.find(id);
}
}
I wrote a bunch of Unit tests with the same pattern and it works. Have no clue what is wrong with this e2e supertest.
Thanks for your help!
UPDATE:
This is the error message I get:
TypeError: request is not a function
at Object.it (/Users/florian/Development/Houzy/nestjs-backend/e2e/user/user.e2e-spec.ts:40:16)
at Object.asyncFn (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/jasmine_async.js:124:345)
at resolve (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:46:12)
at new Promise (<anonymous>)
at mapper (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:34:499)
at promise.then (/Users/florian/Development/Houzy/nestjs-backend/node_modules/jest-jasmine2/build/queue_runner.js:74:39)
at <anonymous>
Change import of request to:
import request from 'supertest';
In your test, replace :id with number:
it(`/GET user`, () => {
return request(app.getHttpServer())
.get('/user/1') // pass here id, not a string
.expect(200)
.expect({
data: userService.findOne(),
});
});
And in controller:
#Get('/:id')
findOne(#Param('id') id: number): Promise<User> {
return this.userService.find(id);
}
This should work now.