tell me please why appController working and itemsController no (from imported module)
I learn nestjs and i did it according to documentation. This controller working its uncomment endpoint.
import {Controller, Get} from '#nestjs/common';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor() {}
// #Get()
// getHome():string {
// return 'Hello world!';
// }
}
itemsController.ts - working if change appController.ts on this
import {Controller, Get, HttpException, HttpStatus, Param, Post, Res} from "#nestjs/common";
import {Response} from "express";
import {ItemsService} from "./items.service";
import {ItemDto} from "./dto/item.dto";
#Controller()
export class ItemsController {
constructor(private readonly itemsService: ItemsService) {}
#Get()
getAll(#Res({passthrough: true}) res: Response):string | object {
const items = this.itemsService.getAll();
if(!!items.length) {
res.status(HttpStatus.OK);
return new HttpException({
items: items
}, HttpStatus.OK).getResponse();
}
res.status(HttpStatus.INTERNAL_SERVER_ERROR);
return new HttpException({
items: 'Items length: ' + items.length,
status: HttpStatus.INTERNAL_SERVER_ERROR
}, HttpStatus.INTERNAL_SERVER_ERROR).getResponse();
}
#Post()
create(#Param() params):ItemDto {
return this.itemsService.create(params);
}
}
Test jest working:
import { Test } from '#nestjs/testing';
import { ItemsController } from './items/items.controller';
import { ItemsService } from './items/items.service';
import * as request from 'supertest';
import { INestApplication } from "#nestjs/common";
describe('ItemsModule', () => {
let itemsController: ItemsController;
let itemsService: ItemsService;
let app: INestApplication;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [ItemsController],
providers: [ItemsService],
}).compile();
itemsService = moduleRef.get<ItemsService>(ItemsService);
itemsController = moduleRef.get<ItemsController>(ItemsController);
app = moduleRef.createNestApplication();
await app.init();
});
describe('End-points', () => {
it('/GET Status 200', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect({
"items": [
{
id: '0',
title: '',
message: ''
}
]
});
});
it('/GET Status 500', () => {
return request(app.getHttpServer())
.get('/')
.expect(500)
.expect({
items: 'Items length: 0',
status: 500
});
});
});
});
I pushed all on github, you can see all code
After looking at your source code, you're missing the # for #Module() in your ItemsModule
Related
Hello I spend a lot of time trying to find out a solution without posting something but I don't understand what's going wrong, so I decided to post question.
Here the file I want to test :
home.page.ts
import { Component, OnInit } from '#angular/core';
import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
import { AlertController, LoadingController } from '#ionic/angular';
import { DataService } from '../service/data.service';
import { League } from '../interfaces/League';
import { LeaguesImageLink } from '../types/leaguesImgLink.enum';
import { ActivatedRoute, Router } from '#angular/router';
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
searchTerm: string = '';
leaguesItems: League[] = [];
constructor(
private apiService: ApiService,
private route: ActivatedRoute,
private router: Router,
private dataService: DataService,
private loadingCtrl: LoadingController,
private alertController: AlertController
) { }
ngOnInit() {
this.loadLeagues();
}
async loadLeagues(): Promise<void> {
(await this.showSpinner()).present
this.apiService
.getAllLeagues()
.pipe(distinctUntilChanged())
.subscribe((res: League[]) => {
this.leaguesItems = res;
this.addImgLink();
});
(await this.showSpinner()).dismiss()
}
async showSpinner(): Promise<HTMLIonLoadingElement> {
const loading = await this.loadingCtrl.create({
message: 'Loading..',
spinner: 'bubbles',
});
return loading;
}
addImgLink(): void {
this.leaguesItems = this.leaguesItems.map((item: League) => {
return {
...item,
imgLink:
LeaguesImageLink[
item.name.split(' ').join('') as keyof typeof LeaguesImageLink
],
};
});
}
async showAlert(): Promise<void> {
const alert = await this.alertController.create({
header: "Alert",
message: "Il n'y a pas encore d'équipes !",
buttons: ['OK'],
});
await alert.present();
}
setLeaguesData(league: League): void {
if (league.teams?.length === 0) {
this.showAlert()
return;
} else {
this.dataService.setleaguesData(this.leaguesItems);
this.router.navigate(['../teams', league._id], {
relativeTo: this.route,
});
}
}
}
here my test file home.page.spect.ts
import { MockLoadingController } from './../../mocks/IonicMock';
import { League } from './../interfaces/League';
import { mockLeagueArray, mockLeagueArrayImageLink } from './../../mocks/mockLeagues';
import { ApiService } from 'src/app/service/api.service';
import { FormsModule } from '#angular/forms';
import { Ng2SearchPipeModule } from 'ng2-search-filter/';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import {
ComponentFixture,
fakeAsync,
TestBed,
tick,
waitForAsync,
} from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { HomePage } from './home.page';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { IonicModule } from '#ionic/angular';
import { of } from 'rxjs';
fdescribe('HomePage', () => {
let component: HomePage;
let fixture: ComponentFixture<HomePage>;
let apiService: ApiService;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HomePage],
teardown: { destroyAfterEach: false },
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [
IonicModule.forRoot(),
HttpClientTestingModule,
RouterTestingModule,
Ng2SearchPipeModule,
FormsModule,
],
providers: [
ApiService
]
}).compileComponents();
fixture = TestBed.createComponent(HomePage);
component = fixture.componentInstance;
apiService = TestBed.inject(ApiService)
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should call ngOnInit', () => {
let loadLeagues = spyOn(component, 'loadLeagues');
component.ngOnInit();
expect(loadLeagues).toHaveBeenCalled();
});
it('should call getAllLeagues service and fill leaguesItems array', fakeAsync(() => {
//ARRANGE
spyOn(apiService, 'getAllLeagues').and.returnValue(of(mockLeagueArray))
//ACT
component.loadLeagues()
tick(1000)
fixture.detectChanges()
//ASSERT
expect(component.leaguesItems).toEqual(mockLeagueArray)
}))
});
my mock file :
mockLeagues.ts
import { League } from "src/app/interfaces/League";
export const mockLeagueArray: League[] = [{
_id: "1",
name: "Supercopa de Espana",
sport: "sport1",
teams: ["t1", "t11"],
}, {
_id: "2",
name: "English Premier League",
sport: "sport2",
teams: ["t2", "t22"],
}]
export const mockLeagueArrayImageLink: League[] = [{
_id: "1",
name: "Supercopa de Espana",
sport: "sport1",
teams: ["t1", "t11"],
imgLink: "https://www.thesportsdb.com/images/media/league/badge/sp4q7d1641378531.png",
}, {
_id: "2",
name: "English Premier League",
sport: "sport2",
teams: ["t2", "t22"],
imgLink: "https://www.thesportsdb.com/images/media/league/badge/pdd43f1610891709.png",
}]
Nothing fancy ! And the response I get when I run ng test :
Chrome 107.0.0.0 (Windows 10) HomePage should call getAllLeagues service and fill leaguesItems array FAILED
Expected $.length = 0 to equal 2.
Expected $[0] = undefined to equal Object({ _id: '1', name: 'Supercopa de Espana', sport: 'sport1', teams: [ 't1', 't11' ] }).
Expected $[1] = undefined to equal Object({ _id: '2', name: 'English Premier League', sport: 'sport2', teams: [ 't2', 't22' ] }).
at <Jasmine>
at UserContext.apply (src/app/home/home.page.spec.ts:81:36)
at UserContext.apply (node_modules/zone.js/fesm2015/zone-testing.js:2030:30)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:372:26)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:287:39)
Chrome 107.0.0.0 (Windows 10): Executed 3 of 12 (1 FAILED) (skipped 9) (0.799 secs / 0.601 secs)
TOTAL: 1 FAILED, 2 SUCCESS
I spend 7 days (reading angular doc, checking on internet..) , now I'm done, someone has a solution ?
Thanks.
Should present not have brackets?
// !! Like this
(await this.showSpinner()).present();
Also, this distinctUntilChanged does nothing because what is being emitted is an array and every time it emits, it will be a new array. It's like saying [] === [], it will always be false so it will always emit. Check out With Object Streams here: https://www.leonelngande.com/an-in-depth-look-at-rxjss-distinctuntilchanged-operator/ but for you it is an array. You can do more research and you can pass a callback in distinctUntilChanged to fine tune when you want it to emit and when you don't.
If I were you, I would change loadLeagues like so:
async loadLeagues(): Promise<void> {
// (await this.showSpinner()).present
this.apiService
.getAllLeagues()
// !! This distinctUntilChanged does nothing since the source is an array
// .pipe(distinctUntilChanged())
.subscribe((res: League[]) => {
this.leaguesItems = res;
this.addImgLink();
});
// (await this.showSpinner()).dismiss()
}
Comment out this.showSpinner dismiss and present and see if the test passes. If it does, you know those 2 lines could be the issue.
This is a good resource in learning unit testing with Angular: https://testing-angular.com/.
I have a problem with overriding provider in nest.js application for testing.
My stats.controller.spec.ts:
import { StatsService } from './services/stats.service';
import { StatsController } from './stats.controller';
describe('StatsController', () => {
let controller: StatsController;
const mockStatsService = {};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [StatsController],
providers: [StatsService],
})
.overrideProvider(StatsService)
.useValue(mockStatsService)
.compile();
controller = module.get<StatsController>(StatsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
My stats.controller.ts:
import { Controller, Get } from '#nestjs/common';
import { StatsService } from './services/stats.service';
#Controller('stats')
export class StatsController {
constructor(private statsService: StatsService) {}
#Get('weekly')
getWeeklyStats() {
return this.statsService.getWeeklyStats();
}
#Get('monthly')
getMonthlyStats() {
return this.statsService.getMonthlyStats();
}
}
And my stats.service.ts:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Trip } from 'src/trips/trip.entity';
import { from, map } from 'rxjs';
import { DatesService } from 'src/shared/services/dates.service';
#Injectable()
export class StatsService {
constructor(
#InjectRepository(Trip) private tripRepository: Repository<Trip>,
private datesServices: DatesService,
) {}
//some code here
}
And after running test I get following errors:
Cannot find module 'src/trips/trip.entity' from 'stats/services/stats.service.ts'
I would really appreciate some help.
The error is unrelated to if you're using a custom provider or overrideProvider. Jest by default doesn't understand absolute imports by default, and you need to use the moduleNameMapper option to tell Jest how to resolve src/ imports. Usually something like
{
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/$1"
}
}
Assuming rootDir has been set to src
You can override the provider by using the below code in nest js:
import { StatsService } from './services/stats.service';
import { StatsController } from './stats.controller';
describe('StatsController', () => {
let controller: StatsController;
const mockStatsService = {};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [StatsController],
providers: [{
provide: StatsService,
useValue: mockStatsService
}],
})
.compile();
controller = module.get<StatsController>(StatsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
I'm trying to implement JWT into my project. I've followed the steps as outline in https://www.npmjs.com/package/#nestjs/jwt#usage
auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '#nestjs/typeorm';
import { JwtModule } from '#nestjs/jwt';
import { AuthRepository } from './auth.repository';
#Module({
imports: [
JwtModule.register({ secret: process.env.JWT_SECRET || 'ABCDE12345' }),
TypeOrmModule.forFeature([AuthRepository]),
],
exports: [TypeOrmModule, AuthService],
providers: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
auth.service.ts
import { Injectable, NotFoundException, UnauthorizedException } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { AuthEntity } from './auth.entity';
import { LoginDTO } from './dto/login.dto';
import * as bcrypt from 'bcrypt';
import { JwtService } from '#nestjs/jwt';
import crypto from 'crypto';
import { AuthRepository } from './auth.repository';
// export interface FindWhereData {
// readonly email: string;
// readonly password: string;
// }
export interface LoginResponse {
readonly token: string;
readonly refresh_token: string;
}
#Injectable()
export class AuthService {
constructor(
#InjectRepository(AuthRepository)
private authRepository: AuthRepository,
private jwtService: JwtService
) {}
/**
* Login user service
*
* #param doc
*/
public async login(doc: LoginDTO): Promise<LoginResponse> {
// verify user email
const user = await this.authRepository.findOne({ email: doc.email });
if (!user) {
throw new NotFoundException('Could not find user');
}
// verify password
const passwordsMatch = await this.passwordsAreEqual(doc.password, user.password);
if (!passwordsMatch) {
throw new UnauthorizedException('Incorrect login credentials');
}
// generate JWT
const token = await this.jwtService.signAsync({ id: user.id });
// create the refresh token
const refreshToken = crypto.randomBytes(256).toString('hex');
// store the refresh token
return {
token: token,
refresh_token: refreshToken,
};
}
/**
* Generate a hashed password
*
* #param password
*/
public async hashPassword(password: string): Promise<string> {
const salt = await bcrypt.genSalt();
return await bcrypt.hash(password, salt);
}
/**
* Compare password against an encrypted string
*
* #param password
* #param encryptedPassword
*/
public async passwordsAreEqual(password: string, encryptedPassword: string): Promise<boolean> {
return await bcrypt.compare(password, encryptedPassword);
}
/**
* Find a record by column attribute and value
*
* #param queryObject
*
*/
public async findWhere(queryObject): Promise<AuthEntity> {
const authEntity = await this.authRepository.findOne({ where: queryObject });
if (!authEntity) {
return null;
}
return authEntity;
}
public async findOne(id: string): Promise<AuthEntity> {
return this.authRepository.findOne(id);
}
}
auth.repository.ts
import { EntityRepository, Repository } from "typeorm";
import { AuthEntity } from "./auth.entity";
#EntityRepository(AuthEntity)
export class AuthRepository extends Repository<AuthEntity> {}
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RouterModule } from 'nest-router';
import { routes } from './routes';
import { ConfigModule } from '#nestjs/config';
import configuration from './config/configuration';
import { TypeOrmModule } from '#nestjs/typeorm';
import { Connection } from 'typeorm';
import { AuthModule } from './auth/auth.module';
#Module({
imports: [
RouterModule.forRoutes(routes),
ConfigModule.forRoot({
load: [configuration],
}),
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.POSTGRES_HOST || 'localhost',
port: 5432,
username: process.env.POSTGRES_USERNAME || 'postgres',
password: process.env.POSTGRES_PASSWORD || 'password',
database: process.env.POSTGRES_DATABASE || 'service-auth',
autoLoadEntities: true,
synchronize: true,
}),
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor(private readonly connection: Connection) {
console.log('connection status: ' + connection.isConnected);
}
}
auth.service.spec.ts
import { JwtModule, JwtService } from '#nestjs/jwt';
import { Test, TestingModule } from '#nestjs/testing';
import { AuthEntity } from './auth.entity';
import { AuthRepository } from './auth.repository';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let authService: AuthService;
let authRepository: AuthRepository;
const mockAuthRepository = () => ({
login: jest.fn(),
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
// JwtModule,
{
provide: getRepositoryToken(AuthRepository),
useFactory: mockAuthRepository,
},
{
provide: JwtService,
useValue: {
signAsync: jest.fn(() => 'token'),
}
}
]
}).compile();
authService = await module.get<AuthService>(AuthService);
authRepository = await module.get<AuthRepository>(AuthRepository);
});
/**
* Test that the service is defined
*/
it('should be defined', () => {
expect(authService).toBeDefined();
});
});
When I run npm run test I get the following error message:
FAIL src/auth/auth.service.spec.ts
● AuthService › should be defined
Nest can't resolve dependencies of the AuthService (AuthRepository, ?). Please make sure that the argument JwtService at index [1] is available in the RootTestModule context.
Potential solutions:
- If JwtService is a provider, is it part of the current RootTestModule?
- If JwtService is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing JwtService */ ]
})
I know the error is probably pretty clear to seasoned Node/Nest developer but I cannot figure out what the RootTestModule is and how to get JwtModule imported.
I believe I have followed the setup correctly but adding this JwtModule to the AuthService is causing the service to be undefined in my unit tests.
Repo
https://github.com/marcuschristiansen/nestjs-auth
You should be adding a custom provider for the JwtService so that you can mock it. A custom provider could look like
{
provide: JwtService,
useValue: {
signAsync: jest.fn(() => 'token'),
}
}
To tell Nest to inject an object that has a signAsync() method that when called returns the string 'token' so that it will always be the same in your tests.
This object goes in the providers array, just like the AuthRepository mock
I have been working on a feature where the goal is to allow a user to login via Auth0. I am using a passport such as passport-auth0 package to implement it. I was able to get working. However, I am not able to test it. I would like to know how I can test auth/login and auth/callback controllers methods.
Moreover, I would like to understand how to mock #UseGuards(AuthGuard('auth0')) and a middleware since I have used them.
Different ways I have tried I got the following error
[Nest] 23402 - 03/30/2020, 5:45:37 PM [ExceptionHandler] Cannot set property 'authParams' of undefined
**TypeError: Cannot set property 'authParams' of undefined**
at Auth0Strategy.Strategy.authenticate (/Users/directory/node_modules/passport-auth0/lib/index.js:82:28)
at attempt (/Users/directory/node_modules/passport/lib/middleware/authenticate.js:366:16)
at authenticate (/Users/directory/node_modules/passport/lib/middleware/authenticate.js:367:7)
at /Users/directory/node_modules/#nestjs/passport/dist/auth.guard.js:84:3
at new Promise (<anonymous>)
at /Users/directory/node_modules/#nestjs/passport/dist/auth.guard.js:76:83
at MixinAuthGuard.<anonymous> (/Users/directory/node_modules/#nestjs/passport/dist/auth.guard.js:48:36)
at Generator.next (<anonymous>)
at /Users/directory/node_modules/#nestjs/passport/dist/auth.guard.js:20:71
at new Promise (<anonymous>)
✨ Done in 38.11s.
// Auth.module.ts
import { Module, NestModule, MiddlewareConsumer } from '#nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { Auth0Strategy } from './auth.strategy';
import { AuthMiddleware } from './ middlewares/auth.middleware'
#Module({
controllers: [AuthController],
providers: [
AuthService,
Auth0Strategy
],
exports: [AuthService],
})
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.forRoutes('auth/callback');
}
}
// auth.strategy.ts
import { Injectable, Query } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-auth0';
import { AuthService, Provider } from './auth.service';
import { config } from 'dotenv';
config();
#Injectable()
export class Auth0Strategy extends PassportStrategy(Strategy, 'auth0') {
constructor(
private readonly authService:AuthService
)
{
super(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: process.env.AUTH0_CALLBACK_URL,
redirectUri: process.env.AUTH0_CALLBACK_URL,
audience: process.env.AUTH0_AUDIENCE,
responseType: 'code',
scope: 'openid profile email',
},
)
}
async validate(request: any, accessToken: string, refreshToken: string, profile, done: Function): Promise<any> {
try {
const jwt: string = await this.authService.validateOAuthLogin(profile, Provider.Auth0);
const user =
{
jwt
}
return done(null, user);
}
catch (err) {
return done(err, false);
}
}
}
//auth.service.ts
import { Injectable, InternalServerErrorException, HttpException } from '#nestjs/common';
import { sign } from 'jsonwebtoken';
export enum Provider {
Auth0 = 'auth0'
}
#Injectable()
export class AuthService {
private readonly JWT_SECRET_KEY = process.env.JWT_SECRET_KEY
async validateOAuthLogin(profile: object, provider: Provider): Promise<string> {
try {
const isProfileExist = Object.entries(profile).length;
if (isProfileExist === 0) {
throw new HttpException('User profile is empty please login again', 400);
}
const payload = {
profile,
provider
}
const jwt: string = sign(payload, this.JWT_SECRET_KEY, { expiresIn: '1h' });
return jwt;
}
catch (err) {
throw new InternalServerErrorException('validateOAuthLogin', err.message);
}
}
}
import { Controller, Get, UseGuards, Res, Req } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Controller('auth')
export class AuthController {
#Get('login')
#UseGuards(AuthGuard('auth0'))
auth0Login() {}
#Get('callback')
#UseGuards(AuthGuard('auth0'))
auth0LoginCallback(#Req() req, #Res() res) {
const jwt: string = req.user.jwt;
if (jwt) {
res.redirect(`${process.env.AUTH0_SUCCESS_REDIRECTION_URL}/${jwt}`);
} else {
res.redirect(process.env.AUTH0_FAILURE_REDIRECTION_URL);
}
}
}
// auth.controller.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { Auth0Strategy } from '../auth.strategy';
import { AuthModule } from '../auth.module';
import { auth0SuccessRequest, fekeToken } from './auth.mock';
describe('AuthService', () => {
let strategy: Auth0Strategy;
const { request, profile, accessToken, refreshToken } = auth0SuccessRequest;
let done: Function = jest.fn();
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AuthModule],
providers: [Auth0Strategy]
}).compile();
strategy = module.get<Auth0Strategy>(Auth0Strategy);
});
afterAll(async () => {
jest.clearAllMocks();
});
it('should validate Auth 0 data', async () => {
await strategy.validate(request, accessToken, refreshToken, profile, done)
expect(done).toBeCalledTimes(1);
});
it('should not proceed without a profile function', async () => {
const failuredAuth = await strategy.validate(request, accessToken, refreshToken, {}, done);
expect(failuredAuth).toBeFalsy();
expect(failuredAuth).toBeUndefined();
});
});
// custom-guard.ts
import { ExecutionContext, Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class CustomGuard extends AuthGuard('auth0') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
handleRequest(err, user, info) {
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
use your CustomGuard and test with it
#Controller('auth')
export class AuthController {
#UseGuards(CustomGuard) // use you guard
auth0LoginCallback(#Req() req, #Res() res) {
}
}
Hope it will help you and it is https://docs.nestjs.com/guards documentation
you are right #harut Barseghyan. with the custom guard you shared with me I was able to activate or override guard in the test environment when I needed it.
// auth.controller.ts
import { Controller, Get, UseGuards, Res, Req } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
import { AuthCustomGuard } from '../auth/guards/auth.guard';
#Controller('auth')
export class AuthController {
#Get('login')
// #UseGuards(AuthGuard('auth0')). // I no longer use this guard
#UseGuards(AuthCustomGuard). // I instead use Auth0 custom guards
auth0Login() {}
#Get('callback')
// #UseGuards(AuthGuard('auth0'))
#UseGuards(AuthCustomGuard)
auth0LoginCallback(#Req() req, #Res() res) {
const jwt: string = req.user.jwt;
if (jwt) {
res.redirect(`${process.env.AUTH0_SUCCESS_REDIRECTION_URL}/${jwt}`);
} else {
res.redirect(process.env.AUTH0_FAILURE_REDIRECTION_URL);
}
}
}
Since I want to authorize the Auth0 routes, I still need Auth 0 guards. The snippet code-shared by #harut Barseghyan.
// auth.guard.ts
import { ExecutionContext, Injectable, UnauthorizedException } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class AuthCustomGuard extends AuthGuard('auth0') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}
The fact I have a custom guard, I have the power to activate it vice versa in a testing environment
// auth.controller.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { AuthController } from '../auth.controller';
import { INestApplication } from '#nestjs/common';
import { AuthModule } from '../auth.module';
import * as request from 'supertest';
import { AuthCustomGuard } from '../guards/auth.guard';
describe('Auth Controller', () => {
let app: INestApplication;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AuthModule],
controllers: [AuthController],
})
.overrideGuard(AuthCustomGuard) // can you see that I override it
.useValue({ canActivate: () => true }) // true helped me to skip
.compile();
app = module.createNestApplication();
app.init()
});
it('should be visit auth login route', async () => {
return request(app.getHttpServer())
.get('/auth/login')
.expect(302)
});
it('should not redirect user to login page if auth 0 throws an error such as an invalid error request', async () => {
return request(app.getHttpServer())
.get('/auth/callback?error=invalid_request')
.expect(302)
});
afterAll(async () => {
jest.clearAllMocks();
await app.close();
});
});
I know how to test http with a mock backend, as well as promises. Though I am struggling to find a solution to test http methods in a promise. Any advice will be much appreciated. Here's the function which contain the function with the http method inside the promise:
import { Injectable } from '#angular/core';
import { AbstractControl, FormGroup, FormControl, ValidatorFn, AsyncValidatorFn } from '#angular/forms';
import { Headers, RequestOptions } from '#angular/http';
import { Store } from '#ngrx/store';
import { HttpService, IHttpResponse } from '#mystique/mystique-utils/http';
import { IRootState } from '#mystique/mystique-state/root';
#Injectable()
export class ValidatorsService {
regex: { email: string; password: string } = { email: null, password: null };
constructor(private _http: HttpService, private _store: Store<IRootState>) {
this._store.select('config', 'regex').subscribe(regex => (this.regex = regex));
}
recordExistsOnServer(model: string, lookupField: string, savedValue: string, authToken: string): AsyncValidatorFn {
model += 's';
let validationDebounce;
return (control: AbstractControl) => {
const queryParams = [{ key: lookupField, value: control.value }];
clearTimeout(validationDebounce);
return new Promise((resolve, reject) => {
validationDebounce = setTimeout(() => {
if (control.value === '' || control.value === savedValue) {
return resolve(null);
}
this._http.get$(`/${model}`, authToken, queryParams).subscribe((httpResponse: IHttpResponse) => {
if (!httpResponse.data) {
savedValue = control.value;
}
return !httpResponse.data ? resolve(null) : resolve({ recordExistsOnServer: true });
});
}, 400);
});
};
}
Throws this error: Uncaught TypeError:
_this._http.get$ is not a function at localhost:9876/_karma_webpack_/polyfills.bundle.js:2281
Here is my test cases, the last it() fails:
import { TestBed, inject } from '#angular/core/testing';
import { FormGroup, FormControl } from '#angular/forms';
import { StoreModule, Store } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
import { HttpService } from '#mystique/mystique-utils/http';
import { HttpServiceStub } from '#mystique/mystique-stubs';
import { ValidatorsService } from './validators.service';
import { rootReducer } from '#mystique/mystique-state/root';
describe('ValidatorsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({
config: rootReducer.config
})
],
providers: [{ provide: HttpService, useClass: HttpServiceStub }, ValidatorsService]
});
});
let service, http, store;
beforeEach(() => {
http = TestBed.get(HttpService);
store = TestBed.get(Store);
service = TestBed.get(ValidatorsService);
});
describe('when checking if a record exists on the server', () => {
let control, result, getSpy;
beforeEach(() => {
getSpy = spyOn(http, 'getAll$');
});
it('returns null if the user types the same value', done => {
control = new FormControl('bob');
result = service.recordExistsOnServer('user', 'username', 'bob', 'token');
result(control)['then'](r => {
expect(r).toEqual(null);
done();
});
});
it('returns null if the user types an empty string', done => {
control = new FormControl('');
result = service.recordExistsOnServer('user', 'username', 'bob');
result(control)['then'](r => {
console.log('r: ' + r)
expect(r).toEqual(null);
done();
});
});
it('returns null if the http call cannot find a record', done => {
getSpy.and.returnValue(Observable.of({ data: null }));
control = new FormControl('bobby');
result = service.recordExistsOnServer('user', 'username', 'bob');
result(control)['then'](r => {
expect(r).toEqual(null);
done();
});
});
});
});
Here is my http.service.ts:
import { Injectable } from '#angular/core';
import { Store } from '#ngrx/store';
import { IRootState } from '#mystique/mystique-state/root';
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '#angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/retry';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { IBaseModel } from '#spawntech/xmen-core-domain-models';
export interface IHttpQuery {
key: string;
value: string | number;
}
export interface IHttpResponse {
success: boolean;
status: number;
statusText: string;
message: string;
data?: any | any[];
error?: string;
token?: string;
}
#Injectable()
export class HttpService {
apiBaseUrl: string = null;
httpRetries = 3;
constructor(private _http: HttpClient, private _store: Store<IRootState>) {
this._store.select('config', 'apiBaseUrl').subscribe(url => (url ? (this.apiBaseUrl = url) : this.apiBaseUrl));
}
get$(restUrl: string, authToken: string, queryParams?: IHttpQuery[]): Observable<IHttpResponse> {
if (!restUrl) {
throw new Error('A restful url extension must be supplied');
}
const headers = this._prepareAuthHeader(authToken);
const params = this._prepareQueryParams(queryParams);
console.log('in http service---------------')
return this._http
.get<IHttpResponse>(this.apiBaseUrl + restUrl, { headers, params })
.retry(this.httpRetries)
.catch((response: HttpErrorResponse) => this._handleError(response));
}
}
use HttpClientTestingModule for mock http request
TestBed.configureTestingModule({
imports: [..., HttpClientTestingModule],
providers: [...]
})
in the development of applications, I call the service method which returns a query, then I subscribe () and I already consider successful and erroneous queries in the current component, displaying some notifications to the user
then you can take a query from a function to a separate function and do something like this:
spyOn (service, 'confirmEmail').
.returnValue (Observable.of (new HttpResponse ({body: '', status: 204}))));