Skip to content

Commit 3550707

Browse files
committed
test: add integration test for passport module
1 parent f46873b commit 3550707

File tree

12 files changed

+7097
-2087
lines changed

12 files changed

+7097
-2087
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ npm-debug.log
1212
.DS_Store
1313

1414
# tests
15-
/test
1615
/coverage
1716
/.nyc_output
1817

jest.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = {
2+
moduleFileExtensions: ['js', 'json', 'ts'],
3+
rootDir: '.',
4+
testMatch: ['<rootDir>/test/*.e2e-spec.ts'],
5+
transform: {
6+
'^.+\\.ts$': 'ts-jest'
7+
},
8+
testEnvironment: 'node',
9+
collectCoverage: true
10+
};

package-lock.json

Lines changed: 6868 additions & 2073 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
"author": "Kamil Mysliwiec",
66
"license": "MIT",
77
"scripts": {
8-
"build": "rimraf dist && tsc -p tsconfig.json",
8+
"build": "rimraf dist && tsc -p tsconfig.build.json",
99
"format": "prettier --write \"lib/**/*.ts\"",
1010
"lint": "eslint 'lib/**/*.ts' --fix",
1111
"precommit": "lint-staged",
1212
"prepublish:npm": "npm run build",
1313
"publish:npm": "npm publish --access public",
1414
"prerelease": "npm run build",
15-
"release": "release-it"
15+
"release": "release-it",
16+
"test": "jest"
1617
},
1718
"peerDependencies": {
1819
"@nestjs/common": "^6.0.0 || ^7.0.0 || ^8.0.0",
@@ -22,21 +23,33 @@
2223
"@commitlint/cli": "16.1.0",
2324
"@commitlint/config-angular": "16.0.0",
2425
"@nestjs/common": "8.2.6",
26+
"@nestjs/core": "^8.2.6",
27+
"@nestjs/jwt": "^8.0.0",
28+
"@nestjs/platform-express": "^8.2.6",
29+
"@nestjs/testing": "^8.2.6",
30+
"@types/jest": "^27.4.0",
2531
"@types/node": "16.11.22",
2632
"@types/passport": "1.0.7",
33+
"@types/passport-jwt": "^3.0.6",
34+
"@types/passport-local": "^1.0.34",
2735
"@typescript-eslint/eslint-plugin": "5.11.0",
2836
"@typescript-eslint/parser": "5.11.0",
2937
"eslint": "8.8.0",
3038
"eslint-config-prettier": "8.3.0",
3139
"eslint-plugin-import": "2.25.4",
3240
"husky": "7.0.4",
3341
"lint-staged": "12.3.3",
42+
"jest": "^27.5.0",
43+
"pactum": "^3.1.3",
3444
"passport": "0.5.2",
45+
"passport-jwt": "^4.0.0",
46+
"passport-local": "^1.0.0",
3547
"prettier": "2.5.1",
36-
"release-it": "14.12.4",
3748
"reflect-metadata": "0.1.13",
49+
"release-it": "14.12.4",
3850
"rimraf": "3.0.2",
3951
"rxjs": "7.5.3",
52+
"ts-jest": "^27.1.3",
4053
"typescript": "4.5.5"
4154
},
4255
"lint-staged": {

test/app.controller.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common';
2+
import { AuthGuard } from '../lib';
3+
4+
import { AppService } from './app.service';
5+
6+
@Controller()
7+
export class AppController {
8+
constructor(private readonly appService: AppService) {}
9+
@Get()
10+
getHello() {
11+
return this.appService.getHello();
12+
}
13+
14+
@UseGuards(AuthGuard('jwt'))
15+
@Get('private')
16+
getPrivateHello() {
17+
return this.appService.getPrivateMessage();
18+
}
19+
20+
@UseGuards(AuthGuard('local'))
21+
@Post('login')
22+
login(@Req() req: any, @Body() body: Record<string, string>) {
23+
return this.appService.getToken({
24+
username: body.username,
25+
id: req.user.id
26+
});
27+
}
28+
}

test/app.e2e-spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { INestApplication } from '@nestjs/common';
2+
import { Test } from '@nestjs/testing';
3+
import { spec, request } from 'pactum';
4+
import { AppModule } from './app.module';
5+
6+
describe('Passport Module', () => {
7+
let app: INestApplication;
8+
9+
beforeAll(async () => {
10+
const modRef = await Test.createTestingModule({
11+
imports: [AppModule]
12+
}).compile();
13+
app = modRef.createNestApplication();
14+
await app.listen(0);
15+
const url = (await app.getUrl()).replace('[::1]', 'localhost');
16+
request.setBaseUrl(url);
17+
});
18+
19+
describe('Authenticated flow', () => {
20+
it('should be able to log in and get a jwt, then hit the secret route', async () => {
21+
await spec()
22+
.post('/login')
23+
.withBody({ username: 'test1', password: 'test' })
24+
.expectStatus(201)
25+
.stores('token', 'token');
26+
await spec()
27+
.get('/private')
28+
.withHeaders('Authorization', 'Bearer $S{token}')
29+
.expectBody({ message: 'Hello secure world!' });
30+
});
31+
});
32+
describe('UnauthenticatedFlow', () => {
33+
it('should return a 401 for an invalid login', async () => {
34+
await spec()
35+
.post('/login')
36+
.withBody({ username: 'test1', password: 'not the right password' })
37+
.expectStatus(401);
38+
});
39+
it('should return a 401 for an invalid JWT', async () => {
40+
await spec()
41+
.get('/private')
42+
.withHeaders('Authorization', 'Bearer not-a-jwt')
43+
.expectStatus(401);
44+
});
45+
});
46+
47+
afterAll(async () => {
48+
await app.close();
49+
});
50+
});

test/app.module.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Module } from '@nestjs/common';
2+
import { JwtModule } from '@nestjs/jwt';
3+
import { PassportModule } from '../lib';
4+
import { AppController } from './app.controller';
5+
import { AppService } from './app.service';
6+
import { JwtStrategy } from './jwt.strategy';
7+
import { LocalStrategy } from './local.strategy';
8+
9+
@Module({
10+
controllers: [AppController],
11+
imports: [
12+
JwtModule.register({
13+
secret: 's3cr3t'
14+
}),
15+
PassportModule.register({})
16+
],
17+
providers: [AppService, LocalStrategy, JwtStrategy]
18+
})
19+
export class AppModule {}

test/app.service.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {
2+
ForbiddenException,
3+
Injectable,
4+
UnauthorizedException
5+
} from '@nestjs/common';
6+
import { JwtService } from '@nestjs/jwt';
7+
8+
@Injectable()
9+
export class AppService {
10+
private users = [
11+
{
12+
id: '1',
13+
username: 'test1',
14+
password: 'test'
15+
},
16+
{
17+
id: '2',
18+
username: 'nottest1',
19+
password: 'secret'
20+
}
21+
];
22+
constructor(private readonly jwtService: JwtService) {}
23+
getHello() {
24+
return { message: 'Hello open world!' };
25+
}
26+
27+
getPrivateMessage() {
28+
return { message: 'Hello secure world!' };
29+
}
30+
31+
getToken({ username, id }: { username: string; id: string }): {
32+
token: string;
33+
} {
34+
return { token: this.jwtService.sign({ username, id }) };
35+
}
36+
37+
findUser({ username, password }: { username: string; password: string }): {
38+
id: string;
39+
username: string;
40+
} {
41+
const user = this.users.find((u) => u.username === username);
42+
if (!user || user.password !== password) {
43+
return null;
44+
}
45+
return user;
46+
}
47+
}

test/jwt.strategy.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { ExtractJwt, Strategy } from 'passport-jwt';
3+
import { PassportStrategy } from '../lib';
4+
5+
@Injectable()
6+
export class JwtStrategy extends PassportStrategy(Strategy) {
7+
constructor() {
8+
super({
9+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
10+
secretOrKey: 's3cr3t'
11+
});
12+
}
13+
14+
validate(payload) {
15+
return { id: payload.id, email: payload.email };
16+
}
17+
}

test/local.strategy.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { Strategy } from 'passport-local';
3+
import { PassportStrategy } from '../lib';
4+
import { AppService } from './app.service';
5+
6+
@Injectable()
7+
export class LocalStrategy extends PassportStrategy(Strategy) {
8+
constructor(private readonly appService: AppService) {
9+
super();
10+
}
11+
validate(username: string, password: string) {
12+
return this.appService.findUser({ username, password });
13+
}
14+
}

0 commit comments

Comments
 (0)