I am trying to set up Authorisation in NestJs using Mongoose via a User Service. I'm getting the following errors when trying to Unit Test:
I understand that the UserModel is injected into the UsersService and needs to be resolved when using the UsersModule but I can't work out how to do this when creating the TestingModule. Please can someone shed some light on this for me?
Code is below, thanks:
USER MODULE
## users.module.ts
import { Module } from '#nestjs/common';
import { UsersService } from './users.service';
#Module({
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
## users.service.ts
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './interfaces/user.interface';
import { CreateUserDto } from './dto/create-user.dto';
#Injectable()
export class UsersService {
constructor(#InjectModel('User') private readonly userModel: Model<User>) {}
async store(userData: CreateUserDto): Promise<User> {
const newUser = new this.userModel(userData);
return newUser.save();
}
async find(data: Record<string, any> = {}) {
return this.userModel
.findOne(data)
.exec();
}
async findById(id: string): Promise<User> {
return this.userModel
.findById(id)
.exec();
}
async findByIdOrFail(id: string): Promise<User> {
return this.userModel
.findById(id)
.orFail()
.exec();
}
async update(id: string, data: Record<string, any> = {}): Promise<User> {
return this.userModel
.findByIdAndUpdate(id, data, {
runValidators: true,
useFindAndModify: false,
new: true,
})
.orFail()
.exec();
}
async destroy(id: string): Promise<User> {
return this.userModel
.findByIdAndRemove(id, {
useFindAndModify: false,
})
.orFail()
.exec();
}
}
AUTH MODULE
## auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
#Module({
imports: [UsersModule],
providers: [AuthService]
})
export class AuthModule {}
## auth.service.ts
import { Injectable } from '#nestjs/common';
import { UsersService } from '../users/users.service';
#Injectable()
export class AuthService {
constructor(private usersService: UsersService) {}
}
AUTH SERVICE TEST
## auth.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { MongooseModule } from '#nestjs/mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { UserSchema } from '../users/schemas/user.schema';
const mongod = new MongoMemoryServer();
describe('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const uri = await mongod.getUri();
const module: TestingModule = await Test.createTestingModule({
imports: [
MongooseModule.forRoot(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
}),
MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
UsersModule,
],
providers: [
AuthService
],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
In unit tests, you should mock all necessary dependencies for AuthService. It's not e2e test, you shouldn't initialize MongooseModule here.
Your beforeEach should look like that:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
{ provide: UsersService, useValue: createSpyObj(UsersService) }
]
}).compile();
service = module.get<AuthService>(AuthService);
});
Combining Maciej's suggestion above and this blog: https://medium.com/#davguij/mocking-typescript-classes-with-jest-8ef992170d1 , I arrived at the following solution:
## /auth/auth.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
jest.mock('../users/users.service');
describe.only('AuthService', () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
UsersModule,
],
providers: [
AuthService,
],
}).compile();
service = module.get<AuthService>(AuthService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should validate user ok', async () => {
const res = await service.validateUser('username');
expect(res).toBeDefined();
});
});
I also had to create a mock class, under mocks as suggested in the above article:
## /users/__mocks__/users.service.ts
import { Injectable } from '#nestjs/common';
#Injectable()
export class UsersService {
async find(data: Record<string, any> = {}) {
return {
"firstname": "firstname",
"lastname": "lastname"
}
}
}
which mocks the below class (same as above, but reduced for the sake of this explanation):
## /users/users.service.ts
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './interfaces/user.interface';
#Injectable()
export class UsersService {
constructor(#InjectModel('User') private readonly userModel: Model<User>) {}
async find(data: Record<string, any> = {}) {
return this.userModel
.findOne(data)
.exec();
}
}
Related
Issue with JWT service I'm trying to use for user auth
ERROR [ExceptionHandler] Nest can't resolve dependencies of the AuthService (?, JwtService). Please make sure that the argument UsersService at index [0] is available in the AuthModule context.
Here is the auth.module
import { Module } from "#nestjs/common"
import { JwtModule } from '#nestjs/jwt';
import { UsersModule } from "../users/users.module";
import { AuthService } from "./auth.service"
import { PassportModule } from "#nestjs/passport";
import { AuthController } from './auth.controller';
import { MongooseModule } from "#nestjs/mongoose"
import { UserSchema } from "users/schemas/users.schema";
import { LocalStrategy } from './local.auth';
#Module({
imports: [UsersModule, PassportModule, JwtModule.register({
secret: 'secretKey',
signOptions: { expiresIn: '60s' },
}), MongooseModule.forFeature([{ name: "Users", schema: UserSchema }])],
providers: [AuthService, LocalStrategy],
controllers: [AuthController],
})
export class AuthModule { }
Here is the App.module
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { MongooseModule } from '#nestjs/mongoose';
import { KpiModule } from './kpi/kpi.module';
import { AuthModule } from './auth/auth.module';
#Module({
imports: [
MongooseModule.forRoot('mongodb://127.0.0.1:27017/vstkpi?readPreference=primary'),
UsersModule,
KpiModule,
AuthModule
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
and userService
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from 'users/interfaces/users.interface';
#Injectable()
export class UsersService {
constructor(#InjectModel('Users') private readonly userModel: Model<User>) {}
//Get all users
async getUsers(): Promise<User[]> {
const users = await this.userModel.find().exec();
return users
}
//Get single user
async getUser(query: object ): Promise<User> {
return this.userModel.findOne(query);
}
async addUser(
firstname: string,
lastname: string,
jobtitle: string,
startdate: string,
password: string,
email: string): Promise<User> {
return this.userModel.create({
firstname,
lastname,
jobtitle,
startdate,
password,
email
});
}
}
I have tried to reorder imports and such but its not firing past that error
added userModule
import { Module } from '#nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { UserSchema } from 'users/schemas/users.schema';
import { MongooseModule } from '#nestjs/mongoose';
#Module({
imports: [
MongooseModule.forFeature([{ name: 'Users', schema: UserSchema }])
],
providers: [UsersService],
controllers: [UsersController]
})
export class UsersModule { }
you didn't exported UsersService from UsersModule module, so that provider is not visible to other modules.
Just add this to UsersModule config:
exports: [UsersService]
So my next step in NestJs is being able to use asymetric jwt validation with Passport. I've made it work with asymetric validation without using Passport and with symetric validation in Passport but when I change the fields to what I think it's correct to set to asymetric validation in Passport it doesn't work and I'm not being able to find any working asymetric examples.
Here's what I got:
AuthModule:
import { Module } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { DbRepo } from 'src/dataObjects/dbRepo';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '#nestjs/jwt';
import { PassportModule } from '#nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
const jwtFactory = {
useFactory: async (configService: ConfigService) => {
let privateKey = configService.get<string>('JWT_PRIVATE_KEY_BASE64', '');
let publicKey = configService.get<string>('JWT_PUBLIC_KEY_BASE64', '');
// let privateKey = configService.get<string>('JWT_SECRET', '');
return {
privateKey,
publicKey,
signOptions: {
expiresIn: configService.get('JWT_EXP_H'),
},
};
},
inject: [ConfigService],
};
#Module({
imports: [
JwtModule.registerAsync(jwtFactory),
PassportModule.register({ defaultStrategy: 'jwt' }),
],
controllers: [AuthController],
providers: [AuthService, DbRepo, JwtStrategy],
exports: [DbRepo, JwtModule, JwtStrategy, PassportModule],
})
export class AuthModule { }
AuthController:
import { Body, Controller, Post } from '#nestjs/common';
import { CreateUserDto } from 'src/dataObjects/users-create-new.dto';
import { AuthService } from './auth.service';
import { User } from 'src/dataObjects/user.entity';
import { AuthCredentialsDto } from 'src/dataObjects/user-auth-credentials.dto';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#Post('/signin')
signin(
#Body() authCredentialsDto: AuthCredentialsDto,
): Promise<{ accessToken: string }> {
return this.authService.signin(authCredentialsDto);
}
#Post('/signup')
signup(#Body() createUserDto: CreateUserDto): Promise<User> {
return this.authService.signup(createUserDto);
}
}
AuthService:
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthCredentialsDto } from 'src/dataObjects/user-auth-credentials.dto';
import { User } from 'src/dataObjects/user.entity';
import { CreateUserDto } from 'src/dataObjects/users-create-new.dto';
import { DbRepo } from 'src/dataObjects/dbRepo';
import { JwtService } from '#nestjs/jwt';
import { UserJwtPayload } from 'src/dataObjects/user-jwt-payload.interface';
import { ConfigService } from '#nestjs/config';
#Injectable()
export class AuthService {
constructor(private dbRepo: DbRepo, private jwtService: JwtService, configService: ConfigService) {}
async signup(createUserDto: CreateUserDto): Promise<User> {
return await this.dbRepo.createUser(createUserDto);
}
async signin(
authCredentialsDto: AuthCredentialsDto,
): Promise<{ accessToken: string }> {
const username: string = authCredentialsDto.username;
const user = await this.dbRepo.userFindByNameAndMatchingPassword(
authCredentialsDto,
);
if (user) {
const typeid = user.typeid;
const payload: UserJwtPayload = { username, typeid };
const accessToken: string = this.jwtService.sign(payload);
return { accessToken };
} else {
throw new UnauthorizedException('Incorrect login credentials!');
}
}
}
JwtStrategy:
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UserJwtPayload } from 'src/dataObjects/user-jwt-payload.interface';
import { User } from 'src/dataObjects/user.entity';
import { DbRepo } from 'src/dataObjects/dbRepo';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private dbRepo: DbRepo,
private configService: ConfigService,
) {
let publicKey = configService.get<string>('JWT_PUBLIC_KEY_BASE64', '');
// let publicKey = configService.get<string>('JWT_SECRET', '');
super({
secretOrKey: publicKey,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
algorithms: ['ES512']
});
}
async validate(payload: UserJwtPayload): Promise<User> {
console.log('payload', payload);
const { username, typeid } = payload;
const users: User[] = await this.dbRepo.getUsers({ username });
const user: User = users[0];
if (typeid > 2 || Object.keys(user).length <= 0) {
throw new UnauthorizedException();
}
return user;
}
}
.env.dev: (this is a study project, nothing here is production, so it doesn't matter to show the keys)
APP_PORT=3000
APP_GLOBAL_PREFIX=tickets
JWT_SECRET=abcdABCD1234554321
JWT_PUBLIC_KEY_BASE64=-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA5w7oeLUYmCBB6kvpfU1fp5nq93SI 3nZ/Ihv8fxIgYlK1XEIp6MxjdzK1+O9ykIGuSFVAzo8xvSbmkHOyGYHn+AoBKFat Cmfn2hUw41xQcQiHV7ZCljAobmFfHNH0U5SXlqvNv4urZWcDmKOThB1sOsQhju79 5gjYoauIaR741sVlf9o= -----END PUBLIC KEY-----
JWT_PRIVATE_KEY_BASE64=-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIA1yAjkQ36YE8fzrqorkP++eFQkTHY4RGdXXkI7EsnyW9mS3lpPvd5 y4+oZyPfr3wEvgpendFV13CJzgGG5Oy2jVWgBwYFK4EEACOhgYkDgYYABADnDuh4 tRiYIEHqS+l9TV+nmer3dIjedn8iG/x/EiBiUrVcQinozGN3MrX473KQga5IVUDO jzG9JuaQc7IZgef4CgEoVq0KZ+faFTDjXFBxCIdXtkKWMChuYV8c0fRTlJeWq82/ i6tlZwOYo5OEHWw6xCGO7v3mCNihq4hpHvjWxWV/2g== -----END EC PRIVATE KEY-----
JWT_EXP_H=3600s
JWT_EXP_D=1d
Guarded class:
<...>
#Controller('users')
#UseGuards(AuthGuard())
export class UsersController {
constructor(private userService: UsersService) {}
#Get()
async getUsers(#Headers('Authorization') authorization = '', #Query() filterDto: UserDataDto): Promise<User[]> {
return this.userService.getUsers(filterDto);
}
<...> more methods
}
What happens is that when I call signin it does emit a token, but if I put it in bearer token it returns unauthorized.
I tried to remove BEGIN/END text in the keys, but the result was the same. This very code works fine in the symetric version. I mean, if I remove public/privateKey and algorithms options from AuthModule and JwtStrategy and using only secretOrKey with JWT_SECRET environment variable.
Finally if I include algorithm: ['ES512'] in AuthModule's signOptions I get this error:
src/auth/auth.module.ts:33:27 - error TS2345: Argument of type '{ useFactory: (configService: ConfigService) => Promise<{ privateKey: string; publicKey: string; signOptions: { expiresIn: any; algorithm: string; }; }>; inject: (typeof ConfigService)[]; }' is not assignable to parameter of type 'JwtModuleAsyncOptions'.
The types returned by 'useFactory(...)' are incompatible between these types.
Type 'Promise<{ privateKey: string; publicKey: string; signOptions: { expiresIn: any; algorithm: string; }; }>' is not assignable to type 'JwtModuleOptions | Promise<JwtModuleOptions>'.
Type 'Promise<{ privateKey: string; publicKey: string; signOptions: { expiresIn: any; algorithm: string; }; }>' is not assignable to type 'Promise<JwtModuleOptions>'.
Type '{ privateKey: string; publicKey: string; signOptions: { expiresIn: any; algorithm: string; }; }' is not assignable to type 'JwtModuleOptions'.
The types of 'signOptions.algorithm' are incompatible between these types.
Type 'string' is not assignable to type 'Algorithm | undefined'.
33 JwtModule.registerAsync(jwtFactory),
I did this final test because it's advised in this SO question.
How can I make this work ?
Edit
I tried to change AuthService sign method to:
const accessToken: string = this.jwtService.sign(payload, { algorithm: 'ES512' });
But I got a very strange error:
Error: error:1E08010C:DECODER routines::unsupported
at Sign.sign (node:internal/crypto/sig:131:29)
at sign (/vagrant/node_modules/jwa/index.js:152:45)
at Object.sign (/vagrant/node_modules/jwa/index.js:200:27)
at Object.jwsSign [as sign] (/vagrant/node_modules/jws/lib/sign-stream.js:32:24)
at Object.module.exports [as sign] (/vagrant/node_modules/jsonwebtoken/sign.js:204:16)
at JwtService.sign (/vagrant/node_modules/#nestjs/jwt/dist/jwt.service.js:28:20)
at AuthService.signin (/vagrant/src/auth/auth.service.ts:31:51)
ES512 is supported by jsonwebtoken and by node. If it wasn´t my non-passport-jwt asymetric version wouldn't work.
Edit 2
I put the project in this github repo: https://github.com/nelson777/nest-asymetric-validation
Maybe it's useful if someone wants to run the project
This is a repository with symetric validation working: https://github.com/nelson777/nest-symetric-validation
But I finally figured out how to do it. I got almost everything right, except for the right way to put the keys in .env. This is the correct way:
JWT_PUBLIC_KEY_BASE64="-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA5w7oeLUYmCBB6kvpfU1fp5nq93SI\n3nZ/Ihv8fxIgYlK1XEIp6MxjdzK1+O9ykIGuSFVAzo8xvSbmkHOyGYHn+AoBKFat\nCmfn2hUw41xQcQiHV7ZCljAobmFfHNH0U5SXlqvNv4urZWcDmKOThB1sOsQhju79\n5gjYoauIaR741sVlf9o=\n-----END PUBLIC KEY-----\n"
JWT_PRIVATE_KEY_BASE64="-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIA1yAjkQ36YE8fzrqorkP++eFQkTHY4RGdXXkI7EsnyW9mS3lpPvd5\ny4+oZyPfr3wEvgpendFV13CJzgGG5Oy2jVWgBwYFK4EEACOhgYkDgYYABADnDuh4\ntRiYIEHqS+l9TV+nmer3dIjedn8iG/x/EiBiUrVcQinozGN3MrX473KQga5IVUDO\njzG9JuaQc7IZgef4CgEoVq0KZ+faFTDjXFBxCIdXtkKWMChuYV8c0fRTlJeWq82/\ni6tlZwOYo5OEHWw6xCGO7v3mCNihq4hpHvjWxWV/2g==\n-----END EC PRIVATE KEY-----\n"
Please note the start/end double quotes, the \n where was a new line (there are several along the line, the \n before -----END * and the \n on the very end.
Thanks to user #MarceloFonseca for this answer here:https://stackoverflow.com/a/61978298/2752520
Here goes the corrected code:
AuthModule:
import { Module } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { DbRepo } from 'src/dataObjects/dbRepo';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '#nestjs/jwt';
import { PassportModule } from '#nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
const jwtFactory = {
useFactory: async (configService: ConfigService) => {
return {
privateKey: configService.get<string>('JWT_PRIVATE_KEY_BASE64', ''),
publicKey: configService.get<string>('JWT_PUBLIC_KEY_BASE64', ''),
signOptions: {
expiresIn: configService.get('JWT_EXP_H'),
},
};
},
inject: [ConfigService],
};
#Module({
imports: [
JwtModule.registerAsync(jwtFactory),
PassportModule.register({ defaultStrategy: 'jwt' }),
],
controllers: [AuthController],
providers: [AuthService, DbRepo, JwtStrategy],
exports: [DbRepo, JwtModule, JwtStrategy, PassportModule],
})
export class AuthModule { }
AuthController:
import { Body, Controller, Post } from '#nestjs/common';
import { CreateUserDto } from 'src/dataObjects/users-create-new.dto';
import { AuthService } from './auth.service';
import { User } from 'src/dataObjects/user.entity';
import { AuthCredentialsDto } from 'src/dataObjects/user-auth-credentials.dto';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#Post('/signin')
signin(
#Body() authCredentialsDto: AuthCredentialsDto,
): Promise<{ accessToken: string }> {
return this.authService.signin(authCredentialsDto);
}
#Post('/signup')
signup(#Body() createUserDto: CreateUserDto): Promise<User> {
return this.authService.signup(createUserDto);
}
}
AuthService:
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthCredentialsDto } from 'src/dataObjects/user-auth-credentials.dto';
import { User } from 'src/dataObjects/user.entity';
import { CreateUserDto } from 'src/dataObjects/users-create-new.dto';
import { DbRepo } from 'src/dataObjects/dbRepo';
import { JwtService } from '#nestjs/jwt';
import { UserJwtPayload } from 'src/dataObjects/user-jwt-payload.interface';
import { ConfigService } from '#nestjs/config';
#Injectable()
export class AuthService {
constructor(private dbRepo: DbRepo, private jwtService: JwtService, private configService: ConfigService) { }
async signup(createUserDto: CreateUserDto): Promise<User> {
return await this.dbRepo.createUser(createUserDto);
}
async signin(
authCredentialsDto: AuthCredentialsDto,
): Promise<{ accessToken: string }> {
const username: string = authCredentialsDto.username;
const user = await this.dbRepo.userFindByNameAndMatchingPassword(
authCredentialsDto,
);
if (user) {
const typeid = user.typeid;
const payload: UserJwtPayload = { username, typeid };
const accessToken: string = this.jwtService.sign(payload, {
secret: this.configService.get('JWT_PRIVATE_KEY_BASE64', ''),
algorithm: 'ES512'
});
return { accessToken };
} else {
throw new UnauthorizedException('Incorrect login credentials!');
}
}
}
JwtStrategy:
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UserJwtPayload } from 'src/dataObjects/user-jwt-payload.interface';
import { User } from 'src/dataObjects/user.entity';
import { DbRepo } from 'src/dataObjects/dbRepo';
import { readFileSync } from 'node:fs';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private dbRepo: DbRepo,
private configService: ConfigService,
) {
super({
secretOrKey: configService.get<string>('JWT_PUBLIC_KEY_BASE64', ''),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
algorithms: ['ES512']
});
}
async validate(payload: UserJwtPayload): Promise<User> {
console.log('payload', payload);
const { username, typeid } = payload;
const users: User[] = await this.dbRepo.getUsers({ username });
const user: User = users[0];
if (typeid > 2 || Object.keys(user).length <= 0) {
throw new UnauthorizedException();
}
return user;
}
}
.env.dev:
APP_PORT=3000
APP_GLOBAL_PREFIX=tickets
JWT_SECRET=abcdABCD1234554321
JWT_PUBLIC_KEY_BASE64="-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA5w7oeLUYmCBB6kvpfU1fp5nq93SI\n3nZ/Ihv8fxIgYlK1XEIp6MxjdzK1+O9ykIGuSFVAzo8xvSbmkHOyGYHn+AoBKFat\nCmfn2hUw41xQcQiHV7ZCljAobmFfHNH0U5SXlqvNv4urZWcDmKOThB1sOsQhju79\n5gjYoauIaR741sVlf9o=\n-----END PUBLIC KEY-----\n"
JWT_PRIVATE_KEY_BASE64="-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIA1yAjkQ36YE8fzrqorkP++eFQkTHY4RGdXXkI7EsnyW9mS3lpPvd5\ny4+oZyPfr3wEvgpendFV13CJzgGG5Oy2jVWgBwYFK4EEACOhgYkDgYYABADnDuh4\ntRiYIEHqS+l9TV+nmer3dIjedn8iG/x/EiBiUrVcQinozGN3MrX473KQga5IVUDO\njzG9JuaQc7IZgef4CgEoVq0KZ+faFTDjXFBxCIdXtkKWMChuYV8c0fRTlJeWq82/\ni6tlZwOYo5OEHWw6xCGO7v3mCNihq4hpHvjWxWV/2g==\n-----END EC PRIVATE KEY-----\n"
JWT_EXP_H=3600s
JWT_EXP_D=1d
Guarded class:
<...>
#Controller('users')
#UseGuards(AuthGuard())
export class UsersController {
constructor(private userService: UsersService) {}
#Get()
async getUsers(#Headers('Authorization') authorization = '', #Query() filterDto: UserDataDto): Promise<User[]> {
return this.userService.getUsers(filterDto);
}
<...> more methods
}
I pushed the changes to the repo I had created. So now there's a working example there with asymetric validation on NestJs and Passport.
Does anyone know how to mock import { Auth, createUserWithEmailAndPassword, signInWithEmailAndPassword, UserCredential } from '#angular/fire/auth'; I'm getting this error in my Jasmine unit tests:
Failed: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app).
error properties: Object({ code: 'app/no-app', customData: Object({ appName: '[DEFAULT]' }) })
FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app).
Here is the actual service:
import { Injectable } from '#angular/core';
import { Auth, createUserWithEmailAndPassword, signInWithEmailAndPassword, UserCredential } from '#angular/fire/auth';
import { FormGroup } from '#angular/forms';
import { Router } from '#angular/router';
import { signOut } from '#firebase/auth';
import { AlertController } from '#ionic/angular';
import { getFirestore, collection, addDoc } from 'firebase/firestore';
import { getApp } from 'firebase/app';
import { DocumentData, DocumentReference } from '#angular/fire/firestore';
#Injectable({
providedIn: 'root'
})
export class AuthService {
userCollection = collection(getFirestore(getApp()), 'users');
constructor(private auth: Auth,
private router: Router,
private alertController: AlertController) { }
async register(registrationForm: FormGroup): Promise<UserCredential> {
return await createUserWithEmailAndPassword(this.auth, registrationForm.value.email, registrationForm.value.password);
}
async login(email: string, password: string): Promise<UserCredential> {
return await signInWithEmailAndPassword(this.auth, email, password);
}
logout(): Promise<void> {
return signOut(this.auth);
}
async authenticationAlert(message: any, header: string, route: string): Promise<void> {
const alert = await this.alertController.create({
subHeader: `${header}`,
message: `${message}`,
buttons: [
{
text: 'Ok',
role: 'Ok',
cssClass: 'secondary',
handler: () => {
this.router.navigateByUrl(`${route}`);
}
}
]
});
await alert.present();
}
saveProfile(uid: string, email: string, displayName: string): Promise<DocumentReference<DocumentData>> {
return addDoc(this.userCollection, {uid, email, displayName});
}
}
and my attempt to mock the createUserWithEmailAndPassword & signInWithEmailAndPassword in the spec.ts:
import { TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { AuthService } from './auth.service';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { AlertController } from '#ionic/angular';
import { Auth, createUserWithEmailAndPassword, signInWithEmailAndPassword } from '#angular/fire/auth';
describe('AuthService', () => {
let service: AuthService;
let mockAuth: jasmine.SpyObj<Auth>;
let mockAlertController: jasmine.SpyObj<AlertController>;
let mockCreateUser: jasmine.SpyObj<typeof createUserWithEmailAndPassword>;
let mockSignIn: jasmine.SpyObj<typeof signInWithEmailAndPassword>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule, RouterTestingModule],
providers: [ AuthService,
{ provide: Auth, useValue: mockAuth },
{ provide: AlertController, useValue: mockAlertController },
{ provide: createUserWithEmailAndPassword, useValue: mockCreateUser },
{ provide: signInWithEmailAndPassword, useValue: mockSignIn }]
});
service = TestBed.inject(AuthService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
I'm new to unit tests so any help would be appreciated. Here's the only thing I could find but this is for Jest:
Get Firebase Error: No Firebase App '[DEFAULT]' has been created when using React-hooks-testing-library with jest
I can't find anything for Jasmine.
Hi I have the following code in NestJS.I am using moduleRef and have declared a custom provider named 'blogService'.However I am getting an error that says:
'Nest can't resolve dependencies of the blogService (?). Please make
sure that the argument BlogModel at index [0] is available in the
AppModule context.'
.What exactly am I doing wrong while declaring the custom provider which is leading to this error as it seems that I am injecting the Mongoose Model as well?
app.module.ts
import { BlogService } from './blog/service/blog.service';
import { Blog } from './blog/schema/blog.schema';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '#nestjs/config';
import { MongooseModule, getModelToken } from '#nestjs/mongoose';
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, load: [configuration], }),
MongooseModule.forRoot(process.env.DATABASE_URL)
],
controllers: [AppController],
providers: [AppService, {
provide: 'blogService',
useFactory: () => BlogService,
inject: [getModelToken(Blog.name)]
}],
})
app.controller.ts
import { Controller, Get } from '#nestjs/common';
import { ModuleRef } from '#nestjs/core';
import { BlogService } from './blog/service/blog.service';
#Controller()
export class AppController {
constructor(private readonly modelRef: ModuleRef) { }
#Get('blogs')
async getAllBlogs(): Promise<any> {
const response = await this.modelRef.get('blogService', { strict: false }).getAllBlogs();
return response;
}
}
blog.service.ts
import { Injectable, HttpException, HttpStatus, Logger } from '#nestjs/common';
import { Blog } from '../schema/blog.schema';
import { InjectModel } from '#nestjs/mongoose';
import { Model, Types } from 'mongoose';
#Injectable()
export class BlogService {
private readonly logger = new Logger(BlogService.name);
constructor(#InjectModel(Blog.name) private blogModel: Model<Blog>) { }
async getAllBlogs() {
try {
const blogs = await this.blogModel.find().exec();
return blogs;
} catch (error) {
this.logger.error(error.message);
}
}
}
Not really sure why you're making a custom provider for this, other than possibly academic purposes. Either way, for anything you want to #InjectModel() or use getModelToken() for, you needx to have a MongooseModule.forFeature() call to register the custom provider the #nestjs/mongoose package will create for you. Once you have this you can use #InjectModel() or a custom provider like
{
provide: 'blogService',
inject: [getModelToken(Blog.name)],
useFactory: (model: Model<Blog>) => new BlogService(model)
}
I have an Anuglar (version 8) application, that uses ngrx/store and RxJs for angular with this component:
export class CarComponent implements OnInit, OnDestroy {
// subscriptions
private unsubscribe$ = new Subject<void>();
constructor(
private activatedRoute: ActivatedRoute,
private readonly store: Store<any>,
private routingService: RoutingService,
private readonly carService: CarService) {}
ngOnInit(): void {
this.activatedRoute.queryParamMap.pipe(
takeUntil(this.unsubscribe$)).subscribe((paramMap: ParamMap) => {
this.store
.pipe(
select(selectDog, { dog: paramMap.get('carCode')})
)
.subscribe((car: Car) => {
this.setCar(Car);
}).unsubscribe();
});
}}
}
I have created an unit-test created with Karma and Jasmine:
import { ChangeDetectionStrategy } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { ReactiveFormsModule } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { NgSelectModule } from '#ng-select/ng-select';
import { EffectsModule } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { Observable } from 'rxjs';
const scope = (): Scope => {
return {
carCode: '5288-5547-5247-4',
brandCar: '480',
};
};
describe('CarComponent ', () => {
let activatedRoute: ActivatedRoute;
let component: CarComponent;
let fixture: ComponentFixture<CarComponent>;
let store: Store<any>;
let routingService: RoutingService;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
RouterTestingModule,
RouterTestingModule.withRoutes([]),
SharedModule,
StoreModule.forRoot({}),
StoreModule.forFeature(stayRestrictionsFeatureKey, stayRestrictionReducer),
StoreModule.forFeature(scopeFeatureKey, scopeReducer),
StoreModule.forFeature(inventoryFeatureKey, inventoryReducer),
StoreModule.forFeature(codeCarFeatureKey, codeCarReducer),
StoreModule.forFeature('scope', scopeReducer),
EffectsModule.forRoot([]),
NgbModule,
NgSelectModule,
NgOptionHighlightModule
],
providers: [{
provide: ActivatedRoute,
useValue: {
snapshot: {
queryParams: {
carCode: 'ss'
}
}
}
},
{
provide: Router,
useValue: {
routerState: {
snapshot : {
url : 'month/day/123'
}
}
}
}]
})
}));
beforeEach(() => {
activatedRoute = TestBed.get(ActivatedRoute.prototype.queryParamMap.pipe());
router = TestBed.get(Router);
routingService = TestBed.get(RoutingService);
store = TestBed.get(Store);
spyOn(store, 'dispatch').and.callThrough();
store.dispatch(new LoadScopeSuccess(scope()));
fixture = TestBed.createComponent(CarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('init', () => {
spyOn(activatedRoute.queryParamMap, 'pipe').and.returnValue(new Observable());
component.ngOnInit();
const start = DateUtil.firstOfMonth();
const end = endDate(start, 3);
fixture.whenStable().then(_ => {
expect(component.searchCriteriaForm.get('carCode').value).toEqual('5288-5547-5247-4');
expect(component.searchCriteriaForm.get('brandCode').value).toEqual('480');
});
});
});
But when I launch the test I have this error:
1) init
CarComponent
TypeError: Cannot read property 'pipe' of undefined
at Object.get queryParamMap [as queryParamMap] (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/#angular/router/fesm2015/router.js:2747:1)
at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/monthly-view/monthly-view.component.spec.ts:90:59)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:359:1)
at ProxyZoneSpec.push.../../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-testing.js:308:
1)
at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:358:1)
at Zone.run (http://localhost:9876/_karma_webpack_/C:/Root/root-projects/[...]/client/node_modules/zone.js/dist/zone-evergreen.js:124:1)
[...]
I have tried to mock the activate route but I have not found a solution. Have you a solution in order to bypass this problem? I thank you very much.
You have to mock activatedRoute.queryParamMap and unfortunately since it's not a method, you can't use pipe. I would do this:
import { ChangeDetectionStrategy } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { ReactiveFormsModule } from '#angular/forms';
import { ActivatedRoute, Router } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { NgSelectModule } from '#ng-select/ng-select';
import { EffectsModule } from '#ngrx/effects';
import { Store, StoreModule } from '#ngrx/store';
import { Observable } from 'rxjs';
const scope = (): Scope => {
return {
carCode: '5288-5547-5247-4',
brandCar: '480',
};
};
describe('CarComponent ', () => {
let activatedRoute: ActivatedRoute;
let component: CarComponent;
let fixture: ComponentFixture<CarComponent>;
let store: Store<any>;
let routingService: RoutingService;
let router: Router;
// !! add this line
const mockQueryParamMap = new BehaviorSubject<any>({
get(value: string) => {
if (value === 'carCode') {
return '123'; // your mock for `carCode`
} else {
return '456';
}
}
});
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
RouterTestingModule,
RouterTestingModule.withRoutes([]),
SharedModule,
StoreModule.forRoot({}),
StoreModule.forFeature(stayRestrictionsFeatureKey, stayRestrictionReducer),
StoreModule.forFeature(scopeFeatureKey, scopeReducer),
StoreModule.forFeature(inventoryFeatureKey, inventoryReducer),
StoreModule.forFeature(codeCarFeatureKey, codeCarReducer),
StoreModule.forFeature('scope', scopeReducer),
EffectsModule.forRoot([]),
NgbModule,
NgSelectModule,
NgOptionHighlightModule
],
providers: [{
provide: ActivatedRoute,
useValue: {
snapshot: {
queryParams: {
carCode: 'ss'
}
}
}
},
{
provide: Router,
useValue: {
routerState: {
snapshot : {
url : 'month/day/123'
}
},
// !! add this line
queryParamMap: mockQueryParamMap,
},
}]
})
}));
it('init', () => {
// this is why we use BehaviorSubject so we have full control
// on what to push into the subscription
mockQueryParamMap.next({
get(value: string) => {
if (value === 'carCode') {
return '5288-5547-5247-4'; // your mock for `carCode`
} else {
return '456';
}
}
});
component.ngOnInit();
const start = DateUtil.firstOfMonth();
const end = endDate(start, 3);
fixture.whenStable().then(_ => {
expect(component.searchCriteriaForm.get('carCode').value).toEqual('5288-5547-5247-4');
expect(component.searchCriteriaForm.get('brandCode').value).toEqual('480');
});
});