Skip to content

Commit 67cb477

Browse files
author
wgd3
committed
updated/fixed/added tests with new factories
1 parent 78b546e commit 67cb477

6 files changed

Lines changed: 140 additions & 7 deletions

File tree

libs/server/feature-auth/src/lib/jwt-strategy.service.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
import { IAccessTokenPayload, IPublicUserData } from '@fst/shared/domain';
2+
import { createMockUser } from '@fst/shared/util-testing';
3+
import { ConfigModule } from '@nestjs/config';
14
import { Test, TestingModule } from '@nestjs/testing';
5+
import { randPassword } from '@ngneat/falso';
26
import { JwtStrategy } from './jwt-strategy.service';
37

48
describe('JwtStrategy', () => {
59
let service: JwtStrategy;
10+
let mockUser: IPublicUserData;
11+
12+
beforeAll(() => {
13+
process.env['JWT_SECRET'] = randPassword();
14+
mockUser = createMockUser();
15+
});
616

717
beforeEach(async () => {
818
const module: TestingModule = await Test.createTestingModule({
19+
imports: [ConfigModule],
920
providers: [JwtStrategy],
1021
}).compile();
1122

@@ -15,4 +26,16 @@ describe('JwtStrategy', () => {
1526
it('should be defined', () => {
1627
expect(service).toBeDefined();
1728
});
29+
30+
it('should return an access token payload object', async () => {
31+
const tokenPayload: IAccessTokenPayload = {
32+
sub: mockUser.id,
33+
email: mockUser.email,
34+
};
35+
const respData = await service.validate(tokenPayload);
36+
expect(respData).toStrictEqual({
37+
userId: mockUser.id,
38+
email: mockUser.email,
39+
});
40+
});
1841
});

libs/server/feature-auth/src/lib/jwt-strategy.service.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,27 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
1414
});
1515
}
1616

17-
async validate(
18-
payload: Pick<IAccessTokenPayload, 'sub'> &
19-
Record<string, string | number | boolean | object>
20-
) {
17+
/**
18+
* Simple method for converting an access token into data included in
19+
* a Request object.
20+
*
21+
* From https://docs.nestjs.com:
22+
* For the jwt-strategy, Passport first verifies the JWT's signature and
23+
* decodes the JSON. It then invokes our validate() method passing the
24+
* decoded JSON as its single parameter. Based on the way JWT signing
25+
* works, we're guaranteed that we're receiving a valid token that we
26+
* have previously signed and issued to a valid user.
27+
*
28+
* As a result of all this, our response to the validate() callback
29+
* is trivial: we simply return an object containing the userId and
30+
* username properties. Recall again that Passport will build a user
31+
* object based on the return value of our validate() method, and
32+
* attach it as a property on the Request object.
33+
*
34+
* @param payload
35+
* @returns
36+
*/
37+
async validate(payload: IAccessTokenPayload) {
2138
const { sub, ...rest } = payload;
2239
return { userId: sub, ...rest };
2340
}

libs/server/feature-auth/src/lib/server-feature-auth.controller.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,45 @@
1+
import { ServerFeatureUserService } from '@fst/server/feature-user';
2+
import { IUser } from '@fst/shared/domain';
3+
import { createMockUser } from '@fst/shared/util-testing';
4+
import { JwtModule } from '@nestjs/jwt';
15
import { Test } from '@nestjs/testing';
6+
import { randPassword } from '@ngneat/falso';
7+
import * as bcrypt from 'bcrypt';
28
import { ServerFeatureAuthController } from './server-feature-auth.controller';
39
import { ServerFeatureAuthService } from './server-feature-auth.service';
410

511
describe('ServerFeatureAuthController', () => {
612
let controller: ServerFeatureAuthController;
13+
let mockUser: IUser;
14+
let mockUserUnhashedPassword: string;
15+
16+
beforeAll(async () => {
17+
mockUser = createMockUser();
18+
mockUserUnhashedPassword = mockUser.password;
19+
mockUser.password = await bcrypt.hash(mockUserUnhashedPassword, 10);
20+
});
721

822
beforeEach(async () => {
923
const module = await Test.createTestingModule({
10-
providers: [ServerFeatureAuthService],
24+
imports: [
25+
JwtModule.register({
26+
secret: randPassword(),
27+
}),
28+
],
29+
providers: [
30+
ServerFeatureAuthService,
31+
{
32+
provide: ServerFeatureUserService,
33+
useValue: {
34+
getOneByEmail: jest.fn(async (email, password) => {
35+
if (email !== mockUser.email) {
36+
return null;
37+
}
38+
return mockUser;
39+
}),
40+
},
41+
},
42+
],
1143
controllers: [ServerFeatureAuthController],
1244
}).compile();
1345

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,44 @@
1+
import { ServerFeatureUserService } from '@fst/server/feature-user';
2+
import { IUser } from '@fst/shared/domain';
3+
import { createMockUser } from '@fst/shared/util-testing';
4+
import { JwtModule } from '@nestjs/jwt';
15
import { Test } from '@nestjs/testing';
6+
import { randPassword } from '@ngneat/falso';
7+
import * as bcrypt from 'bcrypt';
28
import { ServerFeatureAuthService } from './server-feature-auth.service';
39

410
describe('ServerFeatureAuthService', () => {
511
let service: ServerFeatureAuthService;
12+
let mockUser: IUser;
13+
let mockUserUnhashedPassword: string;
14+
15+
beforeAll(async () => {
16+
mockUser = createMockUser();
17+
mockUserUnhashedPassword = mockUser.password;
18+
mockUser.password = await bcrypt.hash(mockUserUnhashedPassword, 10);
19+
});
620

721
beforeEach(async () => {
822
const module = await Test.createTestingModule({
9-
providers: [ServerFeatureAuthService],
23+
imports: [
24+
JwtModule.register({
25+
secret: randPassword(),
26+
}),
27+
],
28+
providers: [
29+
ServerFeatureAuthService,
30+
{
31+
provide: ServerFeatureUserService,
32+
useValue: {
33+
getOneByEmail: jest.fn(async (email, password) => {
34+
if (email !== mockUser.email) {
35+
return null;
36+
}
37+
return mockUser;
38+
}),
39+
},
40+
},
41+
],
1042
}).compile();
1143

1244
service = module.get(ServerFeatureAuthService);
@@ -15,4 +47,27 @@ describe('ServerFeatureAuthService', () => {
1547
it('should be defined', () => {
1648
expect(service).toBeTruthy();
1749
});
50+
51+
it('should validate a user', async () => {
52+
const validUser = await service.validateUser(
53+
mockUser.email,
54+
mockUserUnhashedPassword
55+
);
56+
expect(validUser).toStrictEqual({
57+
id: mockUser.id,
58+
email: mockUser.email,
59+
todos: [],
60+
});
61+
});
62+
63+
it('should return null for invalid user', async () => {
64+
const invalidUser = await service.validateUser('foo', 'bar');
65+
expect(invalidUser).toBe(null);
66+
});
67+
68+
it('should generate an access token', async () => {
69+
const { access_token } = await service.generateAccessToken(mockUser);
70+
expect(access_token).toBeDefined();
71+
expect(typeof access_token).toBe('string');
72+
});
1873
});

libs/server/feature-auth/src/lib/server-feature-auth.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class ServerFeatureAuthService {
2323
password: string
2424
): Promise<IPublicUserData | null> {
2525
const user = await this.userService.getOneByEmail(email);
26-
if (await bcrypt.compare(password, user.password)) {
26+
if (user && (await bcrypt.compare(password, user.password))) {
2727
this.logger.debug(`User '${email}' authenticated successfully`);
2828
const { password, ...publicUserData } = user;
2929
return publicUserData;

libs/shared/domain/src/lib/models/jwt-payload.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,10 @@ export interface IAccessTokenPayload {
55
* user's ID will be used as the subject
66
*/
77
sub: string;
8+
9+
/**
10+
* Placeholder indicating that the payload can contain other arbitrary
11+
* data as needed.
12+
*/
13+
[key: string]: string | number | boolean | unknown;
814
}

0 commit comments

Comments
 (0)