Skip to content

Commit

Permalink
updated/fixed/added tests with new factories
Browse files Browse the repository at this point in the history
  • Loading branch information
wgd3 committed Mar 22, 2023
1 parent 78b546e commit 67cb477
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 7 deletions.
23 changes: 23 additions & 0 deletions libs/server/feature-auth/src/lib/jwt-strategy.service.spec.ts
@@ -1,11 +1,22 @@
import { IAccessTokenPayload, IPublicUserData } from '@fst/shared/domain';
import { createMockUser } from '@fst/shared/util-testing';
import { ConfigModule } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { randPassword } from '@ngneat/falso';
import { JwtStrategy } from './jwt-strategy.service';

describe('JwtStrategy', () => {
let service: JwtStrategy;
let mockUser: IPublicUserData;

beforeAll(() => {
process.env['JWT_SECRET'] = randPassword();
mockUser = createMockUser();
});

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule],
providers: [JwtStrategy],
}).compile();

Expand All @@ -15,4 +26,16 @@ describe('JwtStrategy', () => {
it('should be defined', () => {
expect(service).toBeDefined();
});

it('should return an access token payload object', async () => {
const tokenPayload: IAccessTokenPayload = {
sub: mockUser.id,
email: mockUser.email,
};
const respData = await service.validate(tokenPayload);
expect(respData).toStrictEqual({
userId: mockUser.id,
email: mockUser.email,
});
});
});
25 changes: 21 additions & 4 deletions libs/server/feature-auth/src/lib/jwt-strategy.service.ts
Expand Up @@ -14,10 +14,27 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
}

async validate(
payload: Pick<IAccessTokenPayload, 'sub'> &
Record<string, string | number | boolean | object>
) {
/**
* Simple method for converting an access token into data included in
* a Request object.
*
* From https://docs.nestjs.com:
* For the jwt-strategy, Passport first verifies the JWT's signature and
* decodes the JSON. It then invokes our validate() method passing the
* decoded JSON as its single parameter. Based on the way JWT signing
* works, we're guaranteed that we're receiving a valid token that we
* have previously signed and issued to a valid user.
*
* As a result of all this, our response to the validate() callback
* is trivial: we simply return an object containing the userId and
* username properties. Recall again that Passport will build a user
* object based on the return value of our validate() method, and
* attach it as a property on the Request object.
*
* @param payload
* @returns
*/
async validate(payload: IAccessTokenPayload) {
const { sub, ...rest } = payload;
return { userId: sub, ...rest };
}
Expand Down
@@ -1,13 +1,45 @@
import { ServerFeatureUserService } from '@fst/server/feature-user';
import { IUser } from '@fst/shared/domain';
import { createMockUser } from '@fst/shared/util-testing';
import { JwtModule } from '@nestjs/jwt';
import { Test } from '@nestjs/testing';
import { randPassword } from '@ngneat/falso';
import * as bcrypt from 'bcrypt';
import { ServerFeatureAuthController } from './server-feature-auth.controller';
import { ServerFeatureAuthService } from './server-feature-auth.service';

describe('ServerFeatureAuthController', () => {
let controller: ServerFeatureAuthController;
let mockUser: IUser;
let mockUserUnhashedPassword: string;

beforeAll(async () => {
mockUser = createMockUser();
mockUserUnhashedPassword = mockUser.password;
mockUser.password = await bcrypt.hash(mockUserUnhashedPassword, 10);
});

beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [ServerFeatureAuthService],
imports: [
JwtModule.register({
secret: randPassword(),
}),
],
providers: [
ServerFeatureAuthService,
{
provide: ServerFeatureUserService,
useValue: {
getOneByEmail: jest.fn(async (email, password) => {
if (email !== mockUser.email) {
return null;
}
return mockUser;
}),
},
},
],
controllers: [ServerFeatureAuthController],
}).compile();

Expand Down
@@ -1,12 +1,44 @@
import { ServerFeatureUserService } from '@fst/server/feature-user';
import { IUser } from '@fst/shared/domain';
import { createMockUser } from '@fst/shared/util-testing';
import { JwtModule } from '@nestjs/jwt';
import { Test } from '@nestjs/testing';
import { randPassword } from '@ngneat/falso';
import * as bcrypt from 'bcrypt';
import { ServerFeatureAuthService } from './server-feature-auth.service';

describe('ServerFeatureAuthService', () => {
let service: ServerFeatureAuthService;
let mockUser: IUser;
let mockUserUnhashedPassword: string;

beforeAll(async () => {
mockUser = createMockUser();
mockUserUnhashedPassword = mockUser.password;
mockUser.password = await bcrypt.hash(mockUserUnhashedPassword, 10);
});

beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [ServerFeatureAuthService],
imports: [
JwtModule.register({
secret: randPassword(),
}),
],
providers: [
ServerFeatureAuthService,
{
provide: ServerFeatureUserService,
useValue: {
getOneByEmail: jest.fn(async (email, password) => {
if (email !== mockUser.email) {
return null;
}
return mockUser;
}),
},
},
],
}).compile();

service = module.get(ServerFeatureAuthService);
Expand All @@ -15,4 +47,27 @@ describe('ServerFeatureAuthService', () => {
it('should be defined', () => {
expect(service).toBeTruthy();
});

it('should validate a user', async () => {
const validUser = await service.validateUser(
mockUser.email,
mockUserUnhashedPassword
);
expect(validUser).toStrictEqual({
id: mockUser.id,
email: mockUser.email,
todos: [],
});
});

it('should return null for invalid user', async () => {
const invalidUser = await service.validateUser('foo', 'bar');
expect(invalidUser).toBe(null);
});

it('should generate an access token', async () => {
const { access_token } = await service.generateAccessToken(mockUser);
expect(access_token).toBeDefined();
expect(typeof access_token).toBe('string');
});
});
Expand Up @@ -23,7 +23,7 @@ export class ServerFeatureAuthService {
password: string
): Promise<IPublicUserData | null> {
const user = await this.userService.getOneByEmail(email);
if (await bcrypt.compare(password, user.password)) {
if (user && (await bcrypt.compare(password, user.password))) {
this.logger.debug(`User '${email}' authenticated successfully`);
const { password, ...publicUserData } = user;
return publicUserData;
Expand Down
6 changes: 6 additions & 0 deletions libs/shared/domain/src/lib/models/jwt-payload.interface.ts
Expand Up @@ -5,4 +5,10 @@ export interface IAccessTokenPayload {
* user's ID will be used as the subject
*/
sub: string;

/**
* Placeholder indicating that the payload can contain other arbitrary
* data as needed.
*/
[key: string]: string | number | boolean | unknown;
}

0 comments on commit 67cb477

Please sign in to comment.