How to test email sending with Mailhog on local? - testing

Setup mailhog with docker-compose like:
version: '3'
services:
mailhog:
image: mailhog/mailhog
ports:
- 8025:8025
- 1025:1025
It's possible to access localhost:8025 from browser. Maybe the SMTP server 1025 also works but don't know how to confirm it.
In a NestJS application, for testing the email code as:
#Module({
imports: [NodeMailerModule],
providers: [MailHogEmailRepository],
exports: [MailHogEmailRepository],
})
class MailHogEmailRepositoryModule {}
#Module({
imports: [MailHogEmailRepositoryModule],
providers: [
{
provide: EmailRepository,
useFactory: (
config: ConfigService,
mailHog: MailHogEmailRepository,
) => {
return mailHog;
}
},
inject: [ConfigService, MailHogEmailRepository],
},
],
exports: [EmailRepository],
})
export class EmailRepositoryModule {}
MailHogEmailRepository send with nodemailer:
#Injectable()
export class MailHogEmailRepository implements EmailRepository {
constructor(
#Inject(NodeMailerToken) private readonly nodemailer: Transporter,
) {}
async send(email: Email) {
const options = {
to: email.to,
from: email.from,
subject: email.subject,
};
await this.nodemailer.sendMail(options);
}
}
nodemailer config:
import { Module } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { createTransport } from 'nodemailer';
export const NodeMailerToken = Symbol('nodemailer');
#Module({
providers: [
{
provide: NodeMailerToken,
useFactory: (config: ConfigService) =>
createTransport({
host: 'localhost',
port: 1025,
secure: true,
}),
inject: [ConfigService],
},
],
exports: [NodeMailerToken],
})
export class NodeMailerModule {}
In test source, it always timeout:
import { Test, TestingModule } from '#nestjs/testing';
import request from 'supertest';
import {
FastifyAdapter,
NestFastifyApplication,
} from '#nestjs/platform-fastify';
describe('Test sender', () => {
let app: NestFastifyApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication(new FastifyAdapter());
await app.init();
await app.getHttpAdapter().getInstance().ready();
});
describe('/handler (POST)', () => {
describe('should send data to mail server', () => {
it('success', () => {
const message = ...
return request(app.getHttpServer())
.post('/handler')
.send({ message })
.expect(200);
});
});
});
});
$ npm run test
thrown: "Exceeded timeout of xxx ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
It seems the test case couldn't access the mailhog server running in docker container. How to set it correctly?

Related

NestJS - No metadata for "EmployeeRepository" was found with authentication

I try to do some authentication for my nestjs app but I got stuck with this error and I don't know where to look at
My app.module.ts
#Module({
imports: [
AgenciesModule,
ActionsModule,
AuthModule,
TypeOrmModule.forRoot({
type: 'mysql',
host: '************',
username: '*********',
password: '*********',
database: '*********',
synchronize: true,
autoLoadEntities: true,
}),
EmployeesModule,
],
})
export class AppModule {}
My auth.service.ts
import { EmployeeRepository } from 'src/employees/entities/employee.repository';
#Injectable()
export class AuthService {
constructor(
#InjectRepository(EmployeeRepository)
private employeeRepository: EmployeeRepository,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.employeeRepository.findOne({
where: { email, password },
});
// this work with postman if I put false data
//const user = {
// email: "email",
// password: "password",
//}
if (user && user.email === email && user.password === password) {
const { password, ...result } = user;
return result;
}
return null;
}
}
My auth.controller.ts
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#Post('login')
async login(#Body() body) {
return this.authService.login(body.email, body.password);
}
}
My auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
And my employee.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { Employee } from './employee.entity';
#EntityRepository(Employee)
export class EmployeeRepository extends Repository<Employee> {}
I didn't put the different import for each file but I can provide them if needed
I checked all the file name and path from the differents import and they are all correct and I also updated my packages just in case.
These posts dosen't help :
NestJS - No metadata for "<Entity>" was found
No metadata for "User" was found using TypeOrm
Try this in auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])]
Change like this
imports: [TypeOrmModule.forFeature([Employee])]

ThrottlerStorageRedisService on integration service

im trying to build an integration test for a module in NestJS and im having a problem with this package.
I created a redis intances with docker on my local but nothing seems to work.
what am i doing wrong ?
import { config } from '#clients-service/common/config';
import {
DEFAULT_THROTTLE_TTL_SECONDS,
DEFAULT_THROTTLE_LIMIT,
} from '#clients-service/common/constants';
import { RedisCacheModule } from '#clients-service/common/providers/redis-cache';
import { INestApplication } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { ThrottlerModule } from '#nestjs/throttler';
import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis';
const MAX_TIME = 5 * 1000;
describe('[Module] Clients Service', () => {
jest.setTimeout(MAX_TIME);
let app: INestApplication;
beforeAll(async () => {
const test = new ThrottlerStorageRedisService({
host: config.redis.host,
port: config.redis.port,
password: config.redis.password,
});
const module: TestingModule = await Test.createTestingModule({
imports: [
RedisCacheModule,
ThrottlerModule.forRoot({
ttl: DEFAULT_THROTTLE_TTL_SECONDS,
limit: DEFAULT_THROTTLE_LIMIT,
storage: test,
}),
],
}).compile();
app = module.createNestApplication();
await app.init();
});
it('should be defined', () => {
expect(app).toBeDefined();
});
});

How can I get the tests to run normally (nest, unittestm fcm)?

I made unit test for sendFCM in push.service.spec.ts. but it's not working on the nest server.
Issue #1 : How can I normally give the return value of sendmulticast method?
Property 'success' does not exist on type 'void' below 'success' code.
```expect((await response).success).toBe(true);```
Issue #2 : I looked at many similar issue solutions, but couldn't find a solution to the below error. How can I test normally?
Nest can't resolve dependencies of the PushService (FIREBASE_ADMIN_INJECT, ?). Please make sure that the argument HttpService at index [1] is available in the RootTestModule
[Error Message]
FAIL push/push.service.spec.ts (24.852 s)
● PushService › should be defined
Nest can't resolve dependencies of the PushService (FIREBASE_ADMIN_INJECT, ?). Please make sure that the argument HttpService at index [1] is available in the RootTestModule context.
Potential solutions:
- If HttpService is a provider, is it part of the current RootTestModule?
- If HttpService is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing HttpService */ ]
})
at TestingInjector.lookupComponentInParentModules (../node_modules/#nestjs/core/injector/injector.js:231:19)
at TestingInjector.resolveComponentInstance (../node_modules/#nestjs/core/injector/injector.js:184:33)
at TestingInjector.resolveComponentInstance (../node_modules/#nestjs/testing/testing-injector.js:16:45)
at resolveParam (../node_modules/#nestjs/core/injector/injector.js:106:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../node_modules/#nestjs/core/injector/injector.js:121:27)
at TestingInjector.loadInstance (../node_modules/#nestjs/core/injector/injector.js:52:9)
at TestingInjector.loadProvider (../node_modules/#nestjs/core/injector/injector.js:74:9)
at async Promise.all (index 3)
● PushService › sendFCM › should return an array of Semesters
Nest can't resolve dependencies of the PushService (FIREBASE_ADMIN_INJECT, ?). Please make sure that the argument HttpService at index [1] is available in the RootTestModule context.
Potential solutions:
- If HttpService is a provider, is it part of the current RootTestModule?
- If HttpService is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing HttpService */ ]
})
at TestingInjector.lookupComponentInParentModules (../node_modules/#nestjs/core/injector/injector.js:231:19)
at TestingInjector.resolveComponentInstance (../node_modules/#nestjs/core/injector/injector.js:184:33)
at TestingInjector.resolveComponentInstance (../node_modules/#nestjs/testing/testing-injector.js:16:45)
at resolveParam (../node_modules/#nestjs/core/injector/injector.js:106:38)
at async Promise.all (index 1)
at TestingInjector.resolveConstructorParams (../node_modules/#nestjs/core/injector/injector.js:121:27)
at TestingInjector.loadInstance (../node_modules/#nestjs/core/injector/injector.js:52:9)
at TestingInjector.loadProvider (../node_modules/#nestjs/core/injector/injector.js:74:9)
at async Promise.all (index 3)
[push.service.spec.ts]
import { Test, TestingModule } from '#nestjs/testing';
import { FirebaseAdminModule } from '#tfarras/nestjs-firebase-admin';
import { SendFcmReqDto } from './dtos/send-fcm.dto';
import { PushService } from './push.service';
import * as admin from 'firebase-admin';
describe('PushService', () => {
let service: PushService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
FirebaseAdminModule.forRootAsync({
useFactory: () => ({
credential: admin.credential.applicationDefault()
})
}),
],
providers: [PushService],
}).compile();
service = module.get<PushService>(PushService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('sendFCM', () => {
const sendFcmReqDto: SendFcmReqDto = {
notification_type: 'type_1',
subject: 'FCM Push Unit Test',
content: 'type1',
small_icon_url: '',
large_icon_url: '',
action1: '',
action2: '',
big_pic_url: '',
is_expand: 'true',
is_sicon: 'false',
is_licon: 'false'
}
it('should return an array of Semesters', async () => {
`const response = service.sendFCM(sendFcmReqDto);`
expect((await response).success).toBe(true);
})
})
});
[push.module.ts]
import { Module } from '#nestjs/common';
import { PushController } from './push.controller';
import { PushService } from './push.service';
import { HttpModule } from '#nestjs/axios';
import { FirebaseAdminModule } from '#tfarras/nestjs-firebase-admin';
import * as admin from 'firebase-admin';
#Module({
imports: [
HttpModule.register({
timeout: 5000,
maxRedirects: 5,
}),
FirebaseAdminModule.forRootAsync({
useFactory: () => ({
credential: admin.credential.applicationDefault()
})
}),
],
controllers: [PushController],
exports: [PushService],
providers: [PushService],
})
export class PushModule {}
import { Module } from '#nestjs/common';
import { PushController } from './push.controller';
import { PushService } from './push.service';
import { HttpModule } from '#nestjs/axios';
import { FirebaseAdminModule } from '#tfarras/nestjs-firebase-admin';
import * as admin from 'firebase-admin';
#Module({
imports: [
HttpModule.register({
timeout: 5000,
maxRedirects: 5,
}),
FirebaseAdminModule.forRootAsync({
useFactory: () => ({
credential: admin.credential.applicationDefault()
})
}),
],
controllers: [PushController],
exports: [PushService],
providers: [PushService],
})
export class PushModule {}
[push.controller.ts]
import { Body, Controller, Post } from '#nestjs/common';
import { PushService } from './push.service';
import { SendFcmReqDto } from './dtos/send-fcm.dto';
#Controller('firebase')
#Controller({ path: 'push', version: '10' })
export class PushController {
constructor(private pushService: PushService) {}
#Post('push')
sendFCM(#Body() fcmReq: SendFcmReqDto) {
return this.pushService.sendFCM(fcmReq);
}
}
[push.service.ts]
import {
BadRequestException,
Injectable,
Inject,
Logger,
NotImplementedException,
} from '#nestjs/common';
import { HttpService } from '#nestjs/axios';
import { resourceLimits } from 'worker_threads';
import { FIREBASE_ADMIN_INJECT, FirebaseAdminSDK } from '#tfarras/nestjs-firebase-admin';
import { SendFcmReqDto } from './dtos/send-fcm.dto';
import { User } from 'src/entities/User';
import { Repository } from 'typeorm';
import { InjectRepository } from '#nestjs/typeorm';
#Injectable()
export class PushService {
[x: string]: any;
private readonly logger = new Logger('PushService');
constructor(
#Inject(FIREBASE_ADMIN_INJECT)
private firebaseAdmin: FirebaseAdminSDK,
private readonly httpService: HttpService,
) {}
async sendFCM({
notification_type,
subject,
content,
small_icon_url,
large_icon_url,
action1,
action2,
big_pic_url,
is_expand,
is_sicon,
is_licon
}: SendFcmReqDto) {
this.logger.log('Start sendFCM()!');
const users = await this.usersRepository.find(
{select: ['fbToken']},
);
const ids: string[] = [];
users.forEach ((user) => {
ids.push(user.fbToken);
});
// const aps
const apsPayload = {
sound:'default',
contentAvailable:1,
alert:'default'
};
const dataPayload = {
noti_type: notification_type,
title: subject,
body: content,
s_icon_url: small_icon_url,
l_icon_url: large_icon_url,
act1: action1,
act2: action2,
b_pic_url: big_pic_url,
is_expand: is_expand,
is_sicon: is_sicon,
is_licon: is_licon
}
const multicast_message = {
tokens: ids,
priority:'high',
aps:apsPayload,
data:dataPayload,
};
await this.firebaseAdmin
.messaging()
.sendMulticast(multicast_message)
.then((response)=> {
this.logger.log('send_response:' + response);
return {
success: true,
result: response,
};
})
.catch((error) => {
this.logger.log('send_response:' + error);
return {
success: false,
result: error,
};
})
}
}

Adding serialport and usb in NestJS?

Is it possible to add a serial port and USB package in NestJS? I can't seem to find anything regarding these things.
In your serial.module.ts, create your custom serial handler service by custom factory providers
import { SerialHandlerService } from './serial-handler.service';
#Module({
providers: [
{
provide: 'SerialHandlerService',
useFactory: SerialHandlerService,
},
],
})
export class SerialModule {}
Create serial-handler.service.ts in same folder
import * as SerialPort from 'serialport';
const Readline = SerialPort.parsers.Readline;
export const SerialHandlerService = () => {
const port = new SerialPort(
{YOUR_SERIAL_PORT},
{
baudRate: {YOUR_SERIAL_BOADRATE},
dataBits: {YOUR_SERIAL_DATABITS},
stopBits: {YOUR_SERIAL_STOPBITS},
parity: {YOUR_SERIAL_PARITY},
},
(err) => {
if (err) {
console.error(err)
// Handle Error
}
console.log('success')
},
);
// I'm using Readline parser here. But, You can change parser that you want!
const parser = new Readline({ delimiter: '\r\n' });
port.pipe(parser);
port.on('open', () => {
console.info('port opened');
});
parser.on('data', (data) => {
console.log(data);
// Data is string, process your data below!
}
}
Add your serial.module.ts in your app.module.ts
#Module({
imports: [
// your other modules...
SerialModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
[Reference: https://docs.nestjs.com/fundamentals/custom-providers#factory-providers-usefactory]

How to configure middleware in e2e test in nestjs

In real app, we write:
export class AppModule implements NestModule {
constructor() {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtExtractionMiddleware).forRoutes({
path: 'graphql',
method: RequestMethod.ALL,
});
}
}
In e2e test, I do something like this:
const module = await Test.createTestingModule({
imports: [ GraphQLModule.forRoot(e2eGqlConfig) ],
providers: [ PubUserResolver ],
}).compile();
app = await module.createNestApplication().init();
So how can I specific middleware in e2e test?
Maybe try to create a specific TestModule class only for e2e and provide it to the createTestingModule?
#Module({
imports: [ GraphQLModule.forRoot(e2eGqlConfig) ],
providers: [ PubUserResolver ],
})
export class TestModule implements NestModule {
constructor() {}
configure(consumer: MiddlewareConsumer) {
consumer.apply(JwtExtractionMiddleware).forRoutes({
path: 'graphql',
method: RequestMethod.ALL,
});
}
}
And then in e2e:
const module = await Test.createTestingModule({
imports: [TestModule]
}).compile();
app = await module.createNestApplication().init();
I had similar problem, I needed to attach global middlewares. There is no info on the Internet about that as well, but by chance I've found the solution. Maybe someone will be looking for it, so here it is:
To use global middleware in e2e in NestJS:
Firstly create the app, but don't init it. Only compile:
const app = Test
.createTestingModule({ imports: [AppModule] })
.compile()
.createNestApplication();
After that you can add all your global middlewares:
app.enableCors();
app.use(json());
app.use(formDataMiddleware(config));
Now init the app and that's it:
await app.init();
You'll need to put app.use(new AuthMiddleware().use); before app.init().
describe('Module E2E', () => {
const mockedTest = {
create: jest.fn().mockImplementation((t) => Promise.resolve(t)),
};
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
],
controllers: [TestController],
providers: [
TestService, // the service contains a MySQL Model
{
provide: getModelToken(Test), // Test is the name of Model
useValue: mockedTest,
},
],
}).compile();
app = moduleRef.createNestApplication();
app.use(new AuthMiddleware().use); // auth middleware
await app.init();
});
});