Ver código fonte

test(auth.service): refactor to use new pattern without req/res

Nicolas Meienberger 1 ano atrás
pai
commit
2e8c8883c5

+ 1 - 11
src/client/components/ui/Header/Header.test.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { fireEvent, render, renderHook, screen, waitFor } from '../../../../../tests/test-utils';
+import { fireEvent, render, renderHook, screen } from '../../../../../tests/test-utils';
 import { useUIStore } from '../../../state/uiStore';
 import { Header } from './Header';
 
@@ -58,14 +58,4 @@ describe('Header', () => {
 
     expect(result.current.darkMode).toBe(false);
   });
-
-  it('Should redirect to /login after successful logout', async () => {
-    render(<Header />);
-    const logoutButton = screen.getByTestId('logout-button');
-    fireEvent.click(logoutButton as Element);
-
-    await waitFor(() => {
-      expect(pushFn).toHaveBeenCalledWith('/login');
-    });
-  });
 });

+ 1 - 11
src/client/components/ui/Header/Header.tsx

@@ -3,28 +3,18 @@ import { IconBrandGithub, IconHeart, IconLogout, IconMoon, IconSun } from '@tabl
 import Image from 'next/image';
 import clsx from 'clsx';
 import Link from 'next/link';
-import { useRouter } from 'next/router';
 import { Tooltip } from 'react-tooltip';
 import { useTranslations } from 'next-intl';
 import { getUrl } from '../../../core/helpers/url-helpers';
 import { useUIStore } from '../../../state/uiStore';
 import { NavBar } from '../NavBar';
-import { trpc } from '../../../utils/trpc';
 
 interface IProps {
   isUpdateAvailable?: boolean;
 }
 
 export const Header: React.FC<IProps> = ({ isUpdateAvailable }) => {
-  const router = useRouter();
   const { setDarkMode } = useUIStore();
-  const utils = trpc.useContext();
-  const logout = trpc.auth.logout.useMutation({
-    onSuccess: () => {
-      utils.auth.me.invalidate();
-      router.push('/login');
-    },
-  });
   const t = useTranslations('header');
 
   return (
@@ -74,7 +64,7 @@ export const Header: React.FC<IProps> = ({ isUpdateAvailable }) => {
               <IconSun data-testid="icon-sun" size={20} />
             </div>
             <Tooltip anchorSelect=".logOut">{t('logout')}</Tooltip>
-            <div onClick={() => logout.mutate()} tabIndex={0} onKeyPress={() => logout.mutate()} role="button" className="logOut nav-link px-0 cursor-pointer" data-testid="logout-button">
+            <div tabIndex={0} role="button" className="logOut nav-link px-0 cursor-pointer" data-testid="logout-button">
               <IconLogout size={20} />
             </div>
           </div>

+ 0 - 10
src/client/mocks/handlers.ts

@@ -31,16 +31,6 @@ export const handlers = [
     response: undefined,
   }),
   // Auth
-  getTRPCMock({
-    path: ['auth', 'login'],
-    type: 'mutation',
-    response: { sessionId: faker.datatype.uuid() },
-  }),
-  getTRPCMock({
-    path: ['auth', 'logout'],
-    type: 'mutation',
-    response: true,
-  }),
   getTRPCMock({
     path: ['auth', 'register'],
     type: 'mutation',

+ 42 - 67
src/server/services/auth/auth.service.test.ts

@@ -3,7 +3,7 @@ import * as argon2 from 'argon2';
 import { faker } from '@faker-js/faker';
 import { TotpAuthenticator } from '@/server/utils/totp';
 import { generateSessionId } from '@/server/common/session.helpers';
-import { fromAny, fromPartial } from '@total-typescript/shoehorn';
+import { fromAny } from '@total-typescript/shoehorn';
 import { mockInsert, mockQuery, mockSelect } from '@/tests/mocks/drizzle';
 import { createDatabase, clearDatabase, closeDatabase, TestDatabase } from '@/server/tests/test-utils';
 import { v4 } from 'uuid';
@@ -19,6 +19,18 @@ const TEST_SUITE = 'authservice';
 
 const cache = new TipiCache('auth.service.test.ts');
 
+let cookieStore: Record<string, string> = {};
+jest.mock('next/headers', () => ({
+  cookies: jest.fn(() => ({
+    set: (name: string, value: string) => {
+      cookieStore[name] = value;
+    },
+    get: (name: string) => {
+      return cookieStore[name];
+    },
+  })),
+}));
+
 beforeAll(async () => {
   setConfig('jwtSecret', 'test');
   database = await createDatabase(TEST_SUITE);
@@ -28,6 +40,7 @@ beforeAll(async () => {
 beforeEach(async () => {
   await setConfig('demoMode', false);
   await clearDatabase(database);
+  cookieStore = {};
 });
 
 afterAll(async () => {
@@ -36,40 +49,33 @@ afterAll(async () => {
 });
 
 describe('Login', () => {
-  it('Should correclty set session on request object', async () => {
+  it('Should correclty set session cookie', async () => {
     // arrange
-    let session = '';
-    const res = {
-      getHeader: () => {},
-      setHeader: (_: unknown, o: string[]) => {
-        // eslint-disable-next-line prefer-destructuring
-        session = o[0] as string;
-      },
-    };
     const email = faker.internet.email();
     const user = await createUser({ email }, database);
 
     // act
-    await AuthService.login({ username: email, password: 'password' }, fromPartial({}), fromPartial(res));
+    const { sessionId } = await AuthService.login({ username: email, password: 'password' });
 
-    const sessionId = session.split(';')[0]?.split('=')[1];
     const sessionKey = `session:${sessionId}`;
     const userId = await cache.get(sessionKey);
+    const cookie = cookieStore['tipi.sid'];
 
     // assert
     expect(userId).toBeDefined();
     expect(userId).not.toBeNull();
     expect(userId).toBe(user.id.toString());
+    expect(cookie).toBeDefined();
   });
 
   it('Should throw if user does not exist', async () => {
-    await expect(AuthService.login({ username: 'test', password: 'test' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.user-not-found');
+    await expect(AuthService.login({ username: 'test', password: 'test' })).rejects.toThrowError('server-messages.errors.user-not-found');
   });
 
   it('Should throw if password is incorrect', async () => {
     const email = faker.internet.email();
     await createUser({ email }, database);
-    await expect(AuthService.login({ username: email, password: 'wrong' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.invalid-credentials');
+    await expect(AuthService.login({ username: email, password: 'wrong' })).rejects.toThrowError('server-messages.errors.invalid-credentials');
   });
 
   // TOTP
@@ -80,7 +86,7 @@ describe('Login', () => {
     await createUser({ email, totpEnabled: true, totpSecret }, database);
 
     // act
-    const { totpSessionId } = await AuthService.login({ username: email, password: 'password' }, fromPartial({}), fromPartial({}));
+    const { totpSessionId } = await AuthService.login({ username: email, password: 'password' });
 
     // assert
     expect(totpSessionId).toBeDefined();
@@ -91,14 +97,6 @@ describe('Login', () => {
 describe('Test: verifyTotp', () => {
   it('should correctly log in user after totp is verified', async () => {
     // arrange
-    let session = '';
-    const res = {
-      getHeader: () => {},
-      setHeader: (_: unknown, o: string[]) => {
-        // eslint-disable-next-line prefer-destructuring
-        session = o[0] as string;
-      },
-    };
     const email = faker.internet.email();
     const salt = faker.lorem.word();
     const totpSecret = TotpAuthenticator.generateSecret();
@@ -111,16 +109,12 @@ describe('Test: verifyTotp', () => {
     await cache.set(totpSessionId, user.id.toString());
 
     // act
-    const result = await AuthService.verifyTotp({ totpSessionId, totpCode: otp }, fromPartial({}), fromPartial(res));
-    const sessionId = session.split(';')[0]?.split('=')[1];
-    const userId = await cache.get(`session:${sessionId}`);
+    await AuthService.verifyTotp({ totpSessionId, totpCode: otp });
+    const cookie = cookieStore['tipi.sid'];
 
     // assert
-    expect(result).toBeTruthy();
-    expect(result).not.toBeNull();
-    expect(sessionId).toBeDefined();
-    expect(sessionId).not.toBeNull();
-    expect(userId).toBe(user.id.toString());
+    expect(cookie).toBeDefined();
+    expect(cookie).not.toBeNull();
   });
 
   it('should throw if the totp is incorrect', async () => {
@@ -134,7 +128,7 @@ describe('Test: verifyTotp', () => {
     await cache.set(totpSessionId, user.id.toString());
 
     // act & assert
-    await expect(AuthService.verifyTotp({ totpSessionId, totpCode: 'wrong' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.totp-invalid-code');
+    await expect(AuthService.verifyTotp({ totpSessionId, totpCode: 'wrong' })).rejects.toThrowError('server-messages.errors.totp-invalid-code');
   });
 
   it('should throw if the totpSessionId is invalid', async () => {
@@ -150,7 +144,7 @@ describe('Test: verifyTotp', () => {
     await cache.set(totpSessionId, user.id.toString());
 
     // act & assert
-    await expect(AuthService.verifyTotp({ totpSessionId: 'wrong', totpCode: otp }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.totp-session-not-found');
+    await expect(AuthService.verifyTotp({ totpSessionId: 'wrong', totpCode: otp })).rejects.toThrowError('server-messages.errors.totp-session-not-found');
   });
 
   it('should throw if the user does not exist', async () => {
@@ -159,7 +153,7 @@ describe('Test: verifyTotp', () => {
     await cache.set(totpSessionId, '1234');
 
     // act & assert
-    await expect(AuthService.verifyTotp({ totpSessionId, totpCode: '1234' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.user-not-found');
+    await expect(AuthService.verifyTotp({ totpSessionId, totpCode: '1234' })).rejects.toThrowError('server-messages.errors.user-not-found');
   });
 
   it('should throw if the user totpEnabled is false', async () => {
@@ -175,7 +169,7 @@ describe('Test: verifyTotp', () => {
     await cache.set(totpSessionId, user.id.toString());
 
     // act & assert
-    await expect(AuthService.verifyTotp({ totpSessionId, totpCode: otp }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.totp-not-enabled');
+    await expect(AuthService.verifyTotp({ totpSessionId, totpCode: otp })).rejects.toThrowError('server-messages.errors.totp-not-enabled');
   });
 });
 
@@ -378,39 +372,25 @@ describe('Test: disableTotp', () => {
 });
 
 describe('Register', () => {
-  it('Should correctly set session on response object', async () => {
+  it('Should correctly set session cookie', async () => {
     // arrange
-    let session = '';
-    const res = {
-      getHeader: () => {},
-      setHeader: (_: unknown, o: string[]) => {
-        // eslint-disable-next-line prefer-destructuring
-        session = o[0] as string;
-      },
-    };
     const email = faker.internet.email();
 
     // act
-    const result = await AuthService.register({ username: email, password: 'password' }, fromPartial({}), fromPartial(res));
-    const sessionId = session.split(';')[0]?.split('=')[1];
+    await AuthService.register({ username: email, password: 'password' });
+    const cookie = cookieStore['tipi.sid'];
 
     // assert
-    expect(result).toBeTruthy();
-    expect(result).not.toBeNull();
-    expect(sessionId).toBeDefined();
-    expect(sessionId).not.toBeNull();
+    expect(cookie).toBeDefined();
+    expect(cookie).not.toBeNull();
   });
 
   it('Should correctly trim and lowercase email', async () => {
     // arrange
     const email = faker.internet.email();
-    const res = {
-      getHeader: () => {},
-      setHeader: () => {},
-    };
 
     // act
-    await AuthService.register({ username: email, password: 'test' }, fromPartial({}), fromPartial(res));
+    await AuthService.register({ username: email, password: 'test' });
     const user = await getUserByEmail(email.toLowerCase().trim(), database);
 
     // assert
@@ -424,7 +404,7 @@ describe('Register', () => {
 
     // Act & Assert
     await createUser({ email, operator: true }, database);
-    await expect(AuthService.register({ username: email, password: 'test' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.admin-already-exists');
+    await expect(AuthService.register({ username: email, password: 'test' })).rejects.toThrowError('server-messages.errors.admin-already-exists');
   });
 
   it('Should throw if user already exists', async () => {
@@ -433,27 +413,23 @@ describe('Register', () => {
 
     // Act & Assert
     await createUser({ email, operator: false }, database);
-    await expect(AuthService.register({ username: email, password: 'test' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.user-already-exists');
+    await expect(AuthService.register({ username: email, password: 'test' })).rejects.toThrowError('server-messages.errors.user-already-exists');
   });
 
   it('Should throw if email is not provided', async () => {
-    await expect(AuthService.register({ username: '', password: 'test' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.missing-email-or-password');
+    await expect(AuthService.register({ username: '', password: 'test' })).rejects.toThrowError('server-messages.errors.missing-email-or-password');
   });
 
   it('Should throw if password is not provided', async () => {
-    await expect(AuthService.register({ username: faker.internet.email(), password: '' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.missing-email-or-password');
+    await expect(AuthService.register({ username: faker.internet.email(), password: '' })).rejects.toThrowError('server-messages.errors.missing-email-or-password');
   });
 
   it('Password is correctly hashed', async () => {
     // arrange
     const email = faker.internet.email().toLowerCase().trim();
-    const res = {
-      getHeader: () => {},
-      setHeader: () => {},
-    };
 
     // act
-    await AuthService.register({ username: email, password: 'test' }, fromPartial({}), fromPartial(res));
+    await AuthService.register({ username: email, password: 'test' });
     const user = await getUserByEmail(email, database);
     const isPasswordValid = await argon2.verify(user?.password || '', 'test');
 
@@ -462,18 +438,17 @@ describe('Register', () => {
   });
 
   it('Should throw if email is invalid', async () => {
-    await expect(AuthService.register({ username: 'test', password: 'test' }, fromPartial({}), fromPartial({}))).rejects.toThrowError('server-messages.errors.invalid-username');
+    await expect(AuthService.register({ username: 'test', password: 'test' })).rejects.toThrowError('server-messages.errors.invalid-username');
   });
 
   it('should throw if db fails to insert user', async () => {
     // Arrange
-    const req = {};
     const email = faker.internet.email();
     const mockDatabase = { select: mockSelect([]), insert: mockInsert([]), query: mockQuery(undefined) };
     const newAuthService = new AuthServiceClass(fromAny(mockDatabase));
 
     // Act & Assert
-    await expect(newAuthService.register({ username: email, password: 'test' }, fromPartial(req), fromPartial({}))).rejects.toThrowError('server-messages.errors.error-creating-user');
+    await expect(newAuthService.register({ username: email, password: 'test' })).rejects.toThrowError('server-messages.errors.error-creating-user');
   });
 });