mirror of
https://github.com/hoppscotch/hoppscotch
synced 2024-11-23 07:39:55 +00:00
test: wrote tests for auth service file
This commit is contained in:
parent
ee3fbabece
commit
96ed2f2119
@ -1,10 +1,24 @@
|
||||
import { HttpStatus } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import { Account, PasswordlessVerification } from '@prisma/client';
|
||||
import { mockDeep, mockFn } from 'jest-mock-extended';
|
||||
import {
|
||||
INVALID_EMAIL,
|
||||
INVALID_MAGIC_LINK_DATA,
|
||||
INVALID_REFRESH_TOKEN,
|
||||
MAGIC_LINK_EXPIRED,
|
||||
PASSWORDLESS_DATA_NOT_FOUND,
|
||||
USER_NOT_FOUND,
|
||||
} from 'src/errors';
|
||||
import { MailerService } from 'src/mailer/mailer.service';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
import { AuthService } from './auth.service';
|
||||
import * as O from 'fp-ts/Option';
|
||||
import { verifyMagicDto } from './dto/verify-magic.dto';
|
||||
import { DateTime } from 'luxon';
|
||||
import * as argon2 from 'argon2';
|
||||
|
||||
const mockPrisma = mockDeep<PrismaService>();
|
||||
const mockUser = mockDeep<UserService>();
|
||||
@ -27,9 +41,321 @@ const user: AuthUser = {
|
||||
createdOn: currentTime,
|
||||
};
|
||||
|
||||
const passwordlessData: PasswordlessVerification = {
|
||||
deviceIdentifier: 'k23hb7u7gdcujhb',
|
||||
token: 'jhhj24sdjvl',
|
||||
userUid: user.uid,
|
||||
expiresOn: new Date(),
|
||||
};
|
||||
|
||||
const magicLinkVerify: verifyMagicDto = {
|
||||
deviceIdentifier: 'Dscdc',
|
||||
token: 'SDcsdc',
|
||||
};
|
||||
|
||||
const accountDetails: Account = {
|
||||
id: '123dcdc',
|
||||
userId: user.uid,
|
||||
provider: 'email',
|
||||
providerAccountId: user.uid,
|
||||
providerRefreshToken: 'dscsdc',
|
||||
providerAccessToken: 'sdcsdcsdc',
|
||||
providerScope: 'user.email',
|
||||
loggedIn: currentTime,
|
||||
};
|
||||
|
||||
let nowPlus30 = new Date();
|
||||
nowPlus30.setMinutes(nowPlus30.getMinutes() + 30);
|
||||
nowPlus30 = new Date(nowPlus30);
|
||||
|
||||
const encodedRefreshToken =
|
||||
'$argon2id$v=19$m=65536,t=3,p=4$JTP8yZ8YXMHdafb5pB9Rfg$tdZrILUxMb9dQbu0uuyeReLgKxsgYnyUNbc5ZxQmy5I';
|
||||
|
||||
describe('signIn', () => {
|
||||
// should throw error if email is not in valid format
|
||||
// should successfully create a new user account and return the passwordless details
|
||||
// should successfully return the passwordless details for a existing user account
|
||||
|
||||
test('should throw error if email is not in valid format', async () => {
|
||||
const result = await authService.signIn('bbbgmail.com');
|
||||
expect(result).toEqualLeft({
|
||||
message: INVALID_EMAIL,
|
||||
statusCode: HttpStatus.BAD_REQUEST,
|
||||
});
|
||||
});
|
||||
|
||||
test('should successfully create a new user account and return the passwordless details', async () => {
|
||||
// check to see if user exists, return error
|
||||
mockUser.findUserByEmail.mockResolvedValue(O.none);
|
||||
// create new user
|
||||
mockUser.createUserMagic.mockResolvedValue(user);
|
||||
// create new entry in passwordlessVerification table
|
||||
mockPrisma.passwordlessVerification.create.mockResolvedValueOnce(
|
||||
passwordlessData,
|
||||
);
|
||||
|
||||
const result = await authService.signIn('dwight@dundermifflin.com');
|
||||
expect(result).toEqualRight({
|
||||
deviceIdentifier: passwordlessData.deviceIdentifier,
|
||||
});
|
||||
});
|
||||
|
||||
test('should successfully return the passwordless details for a pre-existing user account', async () => {
|
||||
// check to see if user exists, return error
|
||||
mockUser.findUserByEmail.mockResolvedValueOnce(O.some(user));
|
||||
// create new entry in passwordlessVerification table
|
||||
mockPrisma.passwordlessVerification.create.mockResolvedValueOnce(
|
||||
passwordlessData,
|
||||
);
|
||||
|
||||
const result = await authService.signIn('dwight@dundermifflin.com');
|
||||
expect(result).toEqualRight({
|
||||
deviceIdentifier: passwordlessData.deviceIdentifier,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyPasswordlessTokens', () => {
|
||||
test('should throw INVALID_MAGIC_LINK_DATA if data is invalid', async () => {
|
||||
mockPrisma.passwordlessVerification.findUniqueOrThrow.mockRejectedValueOnce(
|
||||
'NotFoundError',
|
||||
);
|
||||
|
||||
const result = await authService.verifyPasswordlessTokens(magicLinkVerify);
|
||||
expect(result).toEqualLeft({
|
||||
message: INVALID_MAGIC_LINK_DATA,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw USER_NOT_FOUND if user is invalid', async () => {
|
||||
// validatePasswordlessTokens
|
||||
mockPrisma.passwordlessVerification.findUniqueOrThrow.mockResolvedValueOnce(
|
||||
passwordlessData,
|
||||
);
|
||||
// findUserById
|
||||
mockUser.findUserById.mockResolvedValue(O.none);
|
||||
|
||||
const result = await authService.verifyPasswordlessTokens(magicLinkVerify);
|
||||
expect(result).toEqualLeft({
|
||||
message: USER_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
|
||||
test('should successfully return auth token pair with provider account existing', async () => {
|
||||
// validatePasswordlessTokens
|
||||
mockPrisma.passwordlessVerification.findUniqueOrThrow.mockResolvedValueOnce(
|
||||
{
|
||||
...passwordlessData,
|
||||
expiresOn: nowPlus30,
|
||||
},
|
||||
);
|
||||
// findUserById
|
||||
mockUser.findUserById.mockResolvedValue(O.some(user));
|
||||
// checkIfProviderAccountExists
|
||||
mockPrisma.account.findUnique.mockResolvedValueOnce(accountDetails);
|
||||
// mockPrisma.account.findUnique.mockResolvedValueOnce(null);
|
||||
// generateAuthTokens
|
||||
mockJWT.sign.mockReturnValue(user.refreshToken);
|
||||
mockPrisma.user.update.mockResolvedValueOnce(user);
|
||||
// deletePasswordlessVerificationToken
|
||||
mockPrisma.passwordlessVerification.delete.mockResolvedValueOnce(
|
||||
passwordlessData,
|
||||
);
|
||||
|
||||
const result = await authService.verifyPasswordlessTokens(magicLinkVerify);
|
||||
expect(result).toEqualRight({
|
||||
access_token: user.refreshToken,
|
||||
refresh_token: user.refreshToken,
|
||||
});
|
||||
});
|
||||
|
||||
test('should successfully return auth token pair with provider account not existing', async () => {
|
||||
// validatePasswordlessTokens
|
||||
mockPrisma.passwordlessVerification.findUniqueOrThrow.mockResolvedValueOnce(
|
||||
{
|
||||
...passwordlessData,
|
||||
expiresOn: nowPlus30,
|
||||
},
|
||||
);
|
||||
// findUserById
|
||||
mockUser.findUserById.mockResolvedValue(O.some(user));
|
||||
// checkIfProviderAccountExists
|
||||
mockPrisma.account.findUnique.mockResolvedValueOnce(null);
|
||||
mockUser.createUserSSO.mockResolvedValueOnce(user);
|
||||
// generateAuthTokens
|
||||
mockJWT.sign.mockReturnValue(user.refreshToken);
|
||||
mockPrisma.user.update.mockResolvedValueOnce(user);
|
||||
// deletePasswordlessVerificationToken
|
||||
mockPrisma.passwordlessVerification.delete.mockResolvedValueOnce(
|
||||
passwordlessData,
|
||||
);
|
||||
|
||||
const result = await authService.verifyPasswordlessTokens(magicLinkVerify);
|
||||
expect(result).toEqualRight({
|
||||
access_token: user.refreshToken,
|
||||
refresh_token: user.refreshToken,
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw MAGIC_LINK_EXPIRED if passwordless token is expired', async () => {
|
||||
// validatePasswordlessTokens
|
||||
mockPrisma.passwordlessVerification.findUniqueOrThrow.mockResolvedValueOnce(
|
||||
passwordlessData,
|
||||
);
|
||||
// findUserById
|
||||
mockUser.findUserById.mockResolvedValue(O.some(user));
|
||||
// checkIfProviderAccountExists
|
||||
mockPrisma.account.findUnique.mockResolvedValueOnce(accountDetails);
|
||||
|
||||
const result = await authService.verifyPasswordlessTokens(magicLinkVerify);
|
||||
expect(result).toEqualLeft({
|
||||
message: MAGIC_LINK_EXPIRED,
|
||||
statusCode: HttpStatus.UNAUTHORIZED,
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw USER_NOT_FOUND when updating refresh tokens fails', async () => {
|
||||
// validatePasswordlessTokens
|
||||
mockPrisma.passwordlessVerification.findUniqueOrThrow.mockResolvedValueOnce(
|
||||
{
|
||||
...passwordlessData,
|
||||
expiresOn: nowPlus30,
|
||||
},
|
||||
);
|
||||
// findUserById
|
||||
mockUser.findUserById.mockResolvedValue(O.some(user));
|
||||
// checkIfProviderAccountExists
|
||||
mockPrisma.account.findUnique.mockResolvedValueOnce(accountDetails);
|
||||
// mockPrisma.account.findUnique.mockResolvedValueOnce(null);
|
||||
// generateAuthTokens
|
||||
mockJWT.sign.mockReturnValue(user.refreshToken);
|
||||
mockPrisma.user.update.mockRejectedValueOnce('RecordNotFound');
|
||||
|
||||
const result = await authService.verifyPasswordlessTokens(magicLinkVerify);
|
||||
expect(result).toEqualLeft({
|
||||
message: USER_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw PASSWORDLESS_DATA_NOT_FOUND when deleting passwordlessVerification entry from DB', async () => {
|
||||
// validatePasswordlessTokens
|
||||
mockPrisma.passwordlessVerification.findUniqueOrThrow.mockResolvedValueOnce(
|
||||
{
|
||||
...passwordlessData,
|
||||
expiresOn: nowPlus30,
|
||||
},
|
||||
);
|
||||
// findUserById
|
||||
mockUser.findUserById.mockResolvedValue(O.some(user));
|
||||
// checkIfProviderAccountExists
|
||||
mockPrisma.account.findUnique.mockResolvedValueOnce(accountDetails);
|
||||
// mockPrisma.account.findUnique.mockResolvedValueOnce(null);
|
||||
// generateAuthTokens
|
||||
mockJWT.sign.mockReturnValue(user.refreshToken);
|
||||
mockPrisma.user.update.mockResolvedValueOnce(user);
|
||||
// deletePasswordlessVerificationToken
|
||||
mockPrisma.passwordlessVerification.delete.mockRejectedValueOnce(
|
||||
'RecordNotFound',
|
||||
);
|
||||
|
||||
const result = await authService.verifyPasswordlessTokens(magicLinkVerify);
|
||||
expect(result).toEqualLeft({
|
||||
message: PASSWORDLESS_DATA_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateAuthTokens', () => {
|
||||
test('should successfully generate tokens with valid inputs', async () => {
|
||||
mockJWT.sign.mockReturnValue(user.refreshToken);
|
||||
mockPrisma.user.update.mockResolvedValueOnce(user);
|
||||
|
||||
const result = await authService.generateAuthTokens(user.uid);
|
||||
expect(result).toEqualRight({
|
||||
access_token: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
refresh_token: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw USER_NOT_FOUND when updating refresh tokens fails', async () => {
|
||||
mockJWT.sign.mockReturnValue(user.refreshToken);
|
||||
mockPrisma.user.update.mockRejectedValueOnce('RecordNotFound');
|
||||
|
||||
const result = await authService.generateAuthTokens(user.uid);
|
||||
expect(result).toEqualLeft({
|
||||
message: USER_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
jest.mock('argon2', () => {
|
||||
return {
|
||||
verify: jest.fn((x, y) => {
|
||||
if (y === null) return false;
|
||||
return true;
|
||||
}),
|
||||
hash: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('refreshAuthTokens', () => {
|
||||
test('should throw USER_NOT_FOUND when updating refresh tokens fails', async () => {
|
||||
// generateAuthTokens
|
||||
mockJWT.sign.mockReturnValue(user.refreshToken);
|
||||
mockPrisma.user.update.mockRejectedValueOnce('RecordNotFound');
|
||||
|
||||
const result = await authService.refreshAuthTokens(
|
||||
'$argon2id$v=19$m=65536,t=3,p=4$MvVOam2clCOLtJFGEE26ZA$czvA5ez9hz+A/LML8QRgqgaFuWa5JcbwkH6r+imTQbs',
|
||||
user,
|
||||
);
|
||||
expect(result).toEqualLeft({
|
||||
message: USER_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw USER_NOT_FOUND when user is invalid', async () => {
|
||||
const result = await authService.refreshAuthTokens(
|
||||
'jshdcbjsdhcbshdbc',
|
||||
null,
|
||||
);
|
||||
expect(result).toEqualLeft({
|
||||
message: USER_NOT_FOUND,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
|
||||
test('should successfully refresh the tokens and generate a new auth token pair', async () => {
|
||||
// generateAuthTokens
|
||||
mockJWT.sign.mockReturnValue('sdhjcbjsdhcbshjdcb');
|
||||
mockPrisma.user.update.mockResolvedValueOnce({
|
||||
...user,
|
||||
refreshToken: 'sdhjcbjsdhcbshjdcb',
|
||||
});
|
||||
|
||||
const result = await authService.refreshAuthTokens(
|
||||
'$argon2id$v=19$m=65536,t=3,p=4$MvVOam2clCOLtJFGEE26ZA$czvA5ez9hz+A/LML8QRgqgaFuWa5JcbwkH6r+imTQbs',
|
||||
user,
|
||||
);
|
||||
expect(result).toEqualRight({
|
||||
access_token: 'sdhjcbjsdhcbshjdcb',
|
||||
refresh_token: 'sdhjcbjsdhcbshjdcb',
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw INVALID_REFRESH_TOKEN when the refresh token is invalid', async () => {
|
||||
// generateAuthTokens
|
||||
mockJWT.sign.mockReturnValue('sdhjcbjsdhcbshjdcb');
|
||||
mockPrisma.user.update.mockResolvedValueOnce({
|
||||
...user,
|
||||
refreshToken: 'sdhjcbjsdhcbshjdcb',
|
||||
});
|
||||
|
||||
const result = await authService.refreshAuthTokens(null, user);
|
||||
expect(result).toEqualLeft({
|
||||
message: INVALID_REFRESH_TOKEN,
|
||||
statusCode: HttpStatus.NOT_FOUND,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user