test: refactored all test cases with new user type change

This commit is contained in:
Balu Babu
2023-02-01 17:52:33 +05:30
parent 4ca762344c
commit 2a00f41ef8
8 changed files with 72 additions and 37 deletions

View File

@@ -10,8 +10,8 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { signInMagicDto } from './dto/signin-magic.dto'; import { SignInMagicDto } from './dto/signin-magic.dto';
import { verifyMagicDto } from './dto/verify-magic.dto'; import { VerifyMagicDto } from './dto/verify-magic.dto';
import { Response } from 'express'; import { Response } from 'express';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { authCookieHandler, throwHTTPErr } from 'src/utils'; import { authCookieHandler, throwHTTPErr } from 'src/utils';
@@ -29,7 +29,7 @@ export class AuthController {
** Route to initiate magic-link auth for a users email ** Route to initiate magic-link auth for a users email
*/ */
@Post('signin') @Post('signin')
async signInMagicLink(@Body() authData: signInMagicDto) { async signInMagicLink(@Body() authData: SignInMagicDto) {
const deviceIdToken = await this.authService.signInMagicLink( const deviceIdToken = await this.authService.signInMagicLink(
authData.email, authData.email,
); );
@@ -41,7 +41,7 @@ export class AuthController {
** Route to verify and sign in a valid user via magic-link ** Route to verify and sign in a valid user via magic-link
*/ */
@Post('verify') @Post('verify')
async verify(@Body() data: verifyMagicDto, @Res() res: Response) { async verify(@Body() data: VerifyMagicDto, @Res() res: Response) {
const authTokens = await this.authService.verifyMagicLinkTokens(data); const authTokens = await this.authService.verifyMagicLinkTokens(data);
if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left); if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left);
authCookieHandler(res, authTokens.right, false); authCookieHandler(res, authTokens.right, false);

View File

@@ -16,9 +16,10 @@ import { AuthUser } from 'src/types/AuthUser';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { verifyMagicDto } from './dto/verify-magic.dto'; import { VerifyMagicDto } from './dto/verify-magic.dto';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import * as E from 'fp-ts/Either';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
const mockUser = mockDeep<UserService>(); const mockUser = mockDeep<UserService>();
@@ -50,7 +51,7 @@ const passwordlessData: VerificationToken = {
expiresOn: new Date(), expiresOn: new Date(),
}; };
const magicLinkVerify: verifyMagicDto = { const magicLinkVerify: VerifyMagicDto = {
deviceIdentifier: 'Dscdc', deviceIdentifier: 'Dscdc',
token: 'SDcsdc', token: 'SDcsdc',
}; };
@@ -154,7 +155,8 @@ describe('verifyMagicLinkTokens', () => {
// mockPrisma.account.findUnique.mockResolvedValueOnce(null); // mockPrisma.account.findUnique.mockResolvedValueOnce(null);
// generateAuthTokens // generateAuthTokens
mockJWT.sign.mockReturnValue(user.refreshToken); mockJWT.sign.mockReturnValue(user.refreshToken);
mockPrisma.user.update.mockResolvedValueOnce(user); // UpdateUserRefreshToken
mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user));
// deletePasswordlessVerificationToken // deletePasswordlessVerificationToken
mockPrisma.verificationToken.delete.mockResolvedValueOnce(passwordlessData); mockPrisma.verificationToken.delete.mockResolvedValueOnce(passwordlessData);
@@ -178,7 +180,8 @@ describe('verifyMagicLinkTokens', () => {
mockUser.createUserSSO.mockResolvedValueOnce(user); mockUser.createUserSSO.mockResolvedValueOnce(user);
// generateAuthTokens // generateAuthTokens
mockJWT.sign.mockReturnValue(user.refreshToken); mockJWT.sign.mockReturnValue(user.refreshToken);
mockPrisma.user.update.mockResolvedValueOnce(user); // UpdateUserRefreshToken
mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user));
// deletePasswordlessVerificationToken // deletePasswordlessVerificationToken
mockPrisma.verificationToken.delete.mockResolvedValueOnce(passwordlessData); mockPrisma.verificationToken.delete.mockResolvedValueOnce(passwordlessData);
@@ -219,7 +222,10 @@ describe('verifyMagicLinkTokens', () => {
// mockPrisma.account.findUnique.mockResolvedValueOnce(null); // mockPrisma.account.findUnique.mockResolvedValueOnce(null);
// generateAuthTokens // generateAuthTokens
mockJWT.sign.mockReturnValue(user.refreshToken); mockJWT.sign.mockReturnValue(user.refreshToken);
mockPrisma.user.update.mockRejectedValueOnce('RecordNotFound'); // UpdateUserRefreshToken
mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(
E.left(USER_NOT_FOUND),
);
const result = await authService.verifyMagicLinkTokens(magicLinkVerify); const result = await authService.verifyMagicLinkTokens(magicLinkVerify);
expect(result).toEqualLeft({ expect(result).toEqualLeft({
@@ -241,7 +247,8 @@ describe('verifyMagicLinkTokens', () => {
// mockPrisma.account.findUnique.mockResolvedValueOnce(null); // mockPrisma.account.findUnique.mockResolvedValueOnce(null);
// generateAuthTokens // generateAuthTokens
mockJWT.sign.mockReturnValue(user.refreshToken); mockJWT.sign.mockReturnValue(user.refreshToken);
mockPrisma.user.update.mockResolvedValueOnce(user); // UpdateUserRefreshToken
mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user));
// deletePasswordlessVerificationToken // deletePasswordlessVerificationToken
mockPrisma.verificationToken.delete.mockRejectedValueOnce('RecordNotFound'); mockPrisma.verificationToken.delete.mockRejectedValueOnce('RecordNotFound');
@@ -256,7 +263,8 @@ describe('verifyMagicLinkTokens', () => {
describe('generateAuthTokens', () => { describe('generateAuthTokens', () => {
test('should successfully generate tokens with valid inputs', async () => { test('should successfully generate tokens with valid inputs', async () => {
mockJWT.sign.mockReturnValue(user.refreshToken); mockJWT.sign.mockReturnValue(user.refreshToken);
mockPrisma.user.update.mockResolvedValueOnce(user); // UpdateUserRefreshToken
mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(E.right(user));
const result = await authService.generateAuthTokens(user.uid); const result = await authService.generateAuthTokens(user.uid);
expect(result).toEqualRight({ expect(result).toEqualRight({
@@ -267,7 +275,10 @@ describe('generateAuthTokens', () => {
test('should throw USER_NOT_FOUND when updating refresh tokens fails', async () => { test('should throw USER_NOT_FOUND when updating refresh tokens fails', async () => {
mockJWT.sign.mockReturnValue(user.refreshToken); mockJWT.sign.mockReturnValue(user.refreshToken);
mockPrisma.user.update.mockRejectedValueOnce('RecordNotFound'); // UpdateUserRefreshToken
mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(
E.left(USER_NOT_FOUND),
);
const result = await authService.generateAuthTokens(user.uid); const result = await authService.generateAuthTokens(user.uid);
expect(result).toEqualLeft({ expect(result).toEqualLeft({
@@ -291,7 +302,10 @@ describe('refreshAuthTokens', () => {
test('should throw USER_NOT_FOUND when updating refresh tokens fails', async () => { test('should throw USER_NOT_FOUND when updating refresh tokens fails', async () => {
// generateAuthTokens // generateAuthTokens
mockJWT.sign.mockReturnValue(user.refreshToken); mockJWT.sign.mockReturnValue(user.refreshToken);
mockPrisma.user.update.mockRejectedValueOnce('RecordNotFound'); // UpdateUserRefreshToken
mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(
E.left(USER_NOT_FOUND),
);
const result = await authService.refreshAuthTokens( const result = await authService.refreshAuthTokens(
'$argon2id$v=19$m=65536,t=3,p=4$MvVOam2clCOLtJFGEE26ZA$czvA5ez9hz+A/LML8QRgqgaFuWa5JcbwkH6r+imTQbs', '$argon2id$v=19$m=65536,t=3,p=4$MvVOam2clCOLtJFGEE26ZA$czvA5ez9hz+A/LML8QRgqgaFuWa5JcbwkH6r+imTQbs',
@@ -317,10 +331,13 @@ describe('refreshAuthTokens', () => {
test('should successfully refresh the tokens and generate a new auth token pair', async () => { test('should successfully refresh the tokens and generate a new auth token pair', async () => {
// generateAuthTokens // generateAuthTokens
mockJWT.sign.mockReturnValue('sdhjcbjsdhcbshjdcb'); mockJWT.sign.mockReturnValue('sdhjcbjsdhcbshjdcb');
mockPrisma.user.update.mockResolvedValueOnce({ // UpdateUserRefreshToken
...user, mockUser.UpdateUserRefreshToken.mockResolvedValueOnce(
refreshToken: 'sdhjcbjsdhcbshjdcb', E.right({
}); ...user,
refreshToken: 'sdhjcbjsdhcbshjdcb',
}),
);
const result = await authService.refreshAuthTokens( const result = await authService.refreshAuthTokens(
'$argon2id$v=19$m=65536,t=3,p=4$MvVOam2clCOLtJFGEE26ZA$czvA5ez9hz+A/LML8QRgqgaFuWa5JcbwkH6r+imTQbs', '$argon2id$v=19$m=65536,t=3,p=4$MvVOam2clCOLtJFGEE26ZA$czvA5ez9hz+A/LML8QRgqgaFuWa5JcbwkH6r+imTQbs',

View File

@@ -3,7 +3,7 @@ import { MailerService } from 'src/mailer/mailer.service';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { User } from 'src/user/user.model'; import { User } from 'src/user/user.model';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import { verifyMagicDto } from './dto/verify-magic.dto'; import { VerifyMagicDto } from './dto/verify-magic.dto';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
@@ -70,7 +70,7 @@ export class AuthService {
* @param magicLinkTokens Object containing deviceIdentifier and token * @param magicLinkTokens Object containing deviceIdentifier and token
* @returns Option of VerificationToken token * @returns Option of VerificationToken token
*/ */
private async validatePasswordlessTokens(magicLinkTokens: verifyMagicDto) { private async validatePasswordlessTokens(magicLinkTokens: VerifyMagicDto) {
try { try {
const tokens = const tokens =
await this.prismaService.verificationToken.findUniqueOrThrow({ await this.prismaService.verificationToken.findUniqueOrThrow({
@@ -234,7 +234,7 @@ export class AuthService {
* @returns Either of generated AuthTokens * @returns Either of generated AuthTokens
*/ */
async verifyMagicLinkTokens( async verifyMagicLinkTokens(
magicLinkIDTokens: verifyMagicDto, magicLinkIDTokens: VerifyMagicDto,
): Promise<E.Right<AuthTokens> | E.Left<AuthError>> { ): Promise<E.Right<AuthTokens> | E.Left<AuthError>> {
const passwordlessTokens = await this.validatePasswordlessTokens( const passwordlessTokens = await this.validatePasswordlessTokens(
magicLinkIDTokens, magicLinkIDTokens,

View File

@@ -5,6 +5,7 @@ import { UserSettingsService } from './user-settings.service';
import { JSON_INVALID, USER_SETTINGS_NULL_SETTINGS } from 'src/errors'; import { JSON_INVALID, USER_SETTINGS_NULL_SETTINGS } from 'src/errors';
import { UserSettings } from './user-settings.model'; import { UserSettings } from './user-settings.model';
import { User } from 'src/user/user.model'; import { User } from 'src/user/user.model';
import { AuthUser } from 'src/types/AuthUser';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
const mockPubSub = mockDeep<PubSubService>(); const mockPubSub = mockDeep<PubSubService>();
@@ -16,12 +17,20 @@ const userSettingsService = new UserSettingsService(
mockPubSub as any, mockPubSub as any,
); );
const user: User = { const currentTime = new Date();
const user: AuthUser = {
uid: 'aabb22ccdd', uid: 'aabb22ccdd',
displayName: 'user-display-name', displayName: 'user-display-name',
email: 'user-email', email: 'user-email',
photoURL: 'user-photo-url', photoURL: 'user-photo-url',
isAdmin: false,
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
currentGQLSession: {},
currentRESTSession: {},
createdOn: currentTime,
}; };
const settings: UserSettings = { const settings: UserSettings = {
id: '1', id: '1',
userUid: user.uid, userUid: user.uid,

View File

@@ -10,6 +10,7 @@ import {
USER_SETTINGS_NULL_SETTINGS, USER_SETTINGS_NULL_SETTINGS,
USER_SETTINGS_NOT_FOUND, USER_SETTINGS_NOT_FOUND,
} from 'src/errors'; } from 'src/errors';
import { AuthUser } from 'src/types/AuthUser';
@Injectable() @Injectable()
export class UserSettingsService { export class UserSettingsService {
@@ -46,7 +47,7 @@ export class UserSettingsService {
* @param properties stringified user settings properties * @param properties stringified user settings properties
* @returns an Either of `UserSettings` or error * @returns an Either of `UserSettings` or error
*/ */
async createUserSettings(user: User, properties: string) { async createUserSettings(user: AuthUser, properties: string) {
if (!properties) return E.left(USER_SETTINGS_NULL_SETTINGS); if (!properties) return E.left(USER_SETTINGS_NULL_SETTINGS);
const jsonProperties = stringToJson(properties); const jsonProperties = stringToJson(properties);
@@ -80,7 +81,7 @@ export class UserSettingsService {
* @param properties stringified user settings * @param properties stringified user settings
* @returns Promise of an Either of `UserSettings` or error * @returns Promise of an Either of `UserSettings` or error
*/ */
async updateUserSettings(user: User, properties: string) { async updateUserSettings(user: AuthUser, properties: string) {
if (!properties) return E.left(USER_SETTINGS_NULL_SETTINGS); if (!properties) return E.left(USER_SETTINGS_NULL_SETTINGS);
const jsonProperties = stringToJson(properties); const jsonProperties = stringToJson(properties);

View File

@@ -7,6 +7,7 @@ import { UserService } from './user.service';
import { throwErr } from 'src/utils'; import { throwErr } from 'src/utils';
import * as E from 'fp-ts/lib/Either'; import * as E from 'fp-ts/lib/Either';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { AuthUser } from 'src/types/AuthUser';
@Resolver(() => User) @Resolver(() => User)
export class UserResolver { export class UserResolver {
@@ -31,7 +32,7 @@ export class UserResolver {
}) })
@UseGuards(GqlAuthGuard) @UseGuards(GqlAuthGuard)
async updateUserSessions( async updateUserSessions(
@GqlUser() user: User, @GqlUser() user: AuthUser,
@Args({ @Args({
name: 'currentSession', name: 'currentSession',
description: 'JSON string of the saved REST/GQL session', description: 'JSON string of the saved REST/GQL session',

View File

@@ -21,8 +21,8 @@ const user: AuthUser = {
displayName: 'Dwight Schrute', displayName: 'Dwight Schrute',
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute', photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
isAdmin: false, isAdmin: false,
currentRESTSession: JSON.stringify({}), currentRESTSession: {},
currentGQLSession: JSON.stringify({}), currentGQLSession: {},
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb', refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
createdOn: currentTime, createdOn: currentTime,
}; };
@@ -227,21 +227,22 @@ describe('createProviderAccount', () => {
describe('updateUserSessions', () => { describe('updateUserSessions', () => {
test('Should resolve right and update users GQL session', async () => { test('Should resolve right and update users GQL session', async () => {
const sessionData = user.currentGQLSession; const sessionData = user.currentGQLSession;
mockPrisma.user.update.mockResolvedValue({ mockPrisma.user.update.mockResolvedValue({
...user, ...user,
currentGQLSession: JSON.parse(sessionData), currentGQLSession: sessionData,
currentRESTSession: null, currentRESTSession: null,
}); });
const result = await userService.updateUserSessions( const result = await userService.updateUserSessions(
user, user,
sessionData, JSON.stringify(sessionData),
'GQL', 'GQL',
); );
expect(result).toEqualRight({ expect(result).toEqualRight({
...user, ...user,
currentGQLSession: sessionData, currentGQLSession: JSON.stringify(sessionData),
currentRESTSession: null, currentRESTSession: null,
}); });
}); });
@@ -250,19 +251,19 @@ describe('updateUserSessions', () => {
mockPrisma.user.update.mockResolvedValue({ mockPrisma.user.update.mockResolvedValue({
...user, ...user,
currentGQLSession: null, currentGQLSession: null,
currentRESTSession: JSON.parse(sessionData), currentRESTSession: sessionData,
}); });
const result = await userService.updateUserSessions( const result = await userService.updateUserSessions(
user, user,
sessionData, JSON.stringify(sessionData),
'REST', 'REST',
); );
expect(result).toEqualRight({ expect(result).toEqualRight({
...user, ...user,
currentGQLSession: null, currentGQLSession: null,
currentRESTSession: sessionData, currentRESTSession: JSON.stringify(sessionData),
}); });
}); });
test('Should reject left and update user for invalid GQL session', async () => { test('Should reject left and update user for invalid GQL session', async () => {
@@ -291,16 +292,22 @@ describe('updateUserSessions', () => {
test('Should publish pubsub message on user update sessions', async () => { test('Should publish pubsub message on user update sessions', async () => {
mockPrisma.user.update.mockResolvedValue({ mockPrisma.user.update.mockResolvedValue({
...user, ...user,
currentGQLSession: JSON.parse(user.currentGQLSession),
currentRESTSession: JSON.parse(user.currentRESTSession),
}); });
await userService.updateUserSessions(user, user.currentGQLSession, 'GQL'); await userService.updateUserSessions(
user,
JSON.stringify(user.currentGQLSession),
'GQL',
);
expect(mockPubSub.publish).toHaveBeenCalledTimes(1); expect(mockPubSub.publish).toHaveBeenCalledTimes(1);
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`user/${user.uid}/updated`, `user/${user.uid}/updated`,
user, {
...user,
currentGQLSession: JSON.stringify(user.currentGQLSession),
currentRESTSession: JSON.stringify(user.currentRESTSession),
},
); );
}); });
}); });

View File

@@ -199,7 +199,7 @@ export class UserService {
* @returns a Either of User or error * @returns a Either of User or error
*/ */
async updateUserSessions( async updateUserSessions(
user: User, user: AuthUser,
currentSession: string, currentSession: string,
sessionType: string, sessionType: string,
): Promise<E.Right<User> | E.Left<string>> { ): Promise<E.Right<User> | E.Left<string>> {