Procházet zdrojové kódy

refactor(client): remove useless client session state

Nicolas Meienberger před 2 roky
rodič
revize
2b853596c3

+ 1 - 27
src/client/components/Layout/Layout.test.tsx

@@ -1,7 +1,5 @@
 import React from 'react';
-import { render, screen, waitFor } from '../../../../tests/test-utils';
-import { getTRPCMock, getTRPCMockError } from '../../mocks/getTrpcMock';
-import { server } from '../../mocks/server';
+import { render, screen } from '../../../../tests/test-utils';
 import { Layout } from './Layout';
 
 const pushFn = jest.fn();
@@ -23,28 +21,4 @@ describe('Test: Layout', () => {
 
     expect(screen.getByText('test')).toBeInTheDocument();
   });
-
-  it('should correctly set token in localStorage when refreshToken is called', async () => {
-    // Arranger
-    server.use(getTRPCMock({ path: ['auth', 'refreshToken'], type: 'mutation', response: { token: 'fake-token' } }));
-    render(<Layout>test</Layout>);
-
-    // Act
-    await waitFor(() => {
-      expect(localStorage.getItem('token')).toBe('fake-token');
-    });
-  });
-
-  it('should remove token from local storage and redirect to login page on error', async () => {
-    // Arranger
-    server.use(getTRPCMockError({ path: ['auth', 'refreshToken'], type: 'mutation', message: 'fake-error' }));
-    render(<Layout>test</Layout>);
-    const removeItemSpy = jest.spyOn(localStorage, 'removeItem');
-
-    // Act
-    await waitFor(() => {
-      expect(removeItemSpy).toBeCalledWith('token');
-    });
-    expect(pushFn).toBeCalledWith('/login');
-  });
 });

+ 1 - 19
src/client/components/Layout/Layout.tsx

@@ -1,16 +1,13 @@
 import Head from 'next/head';
 import Link from 'next/link';
-import React, { useEffect } from 'react';
+import React from 'react';
 import clsx from 'clsx';
 import semver from 'semver';
-import { useRouter } from 'next/router';
 import { Header } from '../ui/Header';
 import styles from './Layout.module.scss';
 import { useSystemStore } from '../../state/systemStore';
-import { trpc } from '../../utils/trpc';
 
 interface IProps {
-  loading?: boolean;
   breadcrumbs?: { name: string; href: string; current?: boolean }[];
   children: React.ReactNode;
   title?: string;
@@ -18,21 +15,6 @@ interface IProps {
 }
 
 export const Layout: React.FC<IProps> = ({ children, breadcrumbs, title, actions }) => {
-  const router = useRouter();
-  const { mutate } = trpc.auth.refreshToken.useMutation({
-    onSuccess: (data) => {
-      if (data?.token) localStorage.setItem('token', data.token);
-    },
-    onError: () => {
-      localStorage.removeItem('token');
-      router.push('/login');
-    },
-  });
-
-  useEffect(() => {
-    mutate();
-  }, [mutate]);
-
   const { version } = useSystemStore();
   const defaultVersion = '0.0.0';
   const isLatest = semver.gte(version?.current || defaultVersion, version?.latest || defaultVersion);

+ 0 - 49
src/client/components/hoc/AuthProvider/AuthProvider.test.tsx

@@ -1,49 +0,0 @@
-import React from 'react';
-import { render, screen } from '../../../../../tests/test-utils';
-import { getTRPCMock } from '../../../mocks/getTrpcMock';
-import { server } from '../../../mocks/server';
-import { AuthProvider } from './AuthProvider';
-
-describe('Test: AuthProvider', () => {
-  it('should render login form if user is not logged in', async () => {
-    // arrange
-    render(
-      <AuthProvider>
-        <div>Should not render</div>
-      </AuthProvider>,
-    );
-    server.use(getTRPCMock({ path: ['auth', 'me'], type: 'query', response: null }));
-
-    // assert
-    await screen.findByText('Login');
-    expect(screen.queryByText('Should not render')).not.toBeInTheDocument();
-  });
-
-  it('should render children if user is logged in', async () => {
-    // arrange
-    render(
-      <AuthProvider>
-        <div>Should render</div>
-      </AuthProvider>,
-    );
-
-    // assert
-    await screen.findByText('Should render');
-  });
-
-  it('should render register form if app is not configured', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'me'], type: 'query', response: null }));
-    server.use(getTRPCMock({ path: ['auth', 'isConfigured'], type: 'query', response: false }));
-
-    render(
-      <AuthProvider>
-        <div>Should not render</div>
-      </AuthProvider>,
-    );
-
-    // assert
-    await screen.findByText('Register your account');
-    expect(screen.queryByText('Should not render')).not.toBeInTheDocument();
-  });
-});

+ 0 - 29
src/client/components/hoc/AuthProvider/AuthProvider.tsx

@@ -1,29 +0,0 @@
-import React from 'react';
-import { LoginContainer } from '../../../modules/Auth/containers/LoginContainer';
-import { RegisterContainer } from '../../../modules/Auth/containers/RegisterContainer';
-import { trpc } from '../../../utils/trpc';
-import { StatusScreen } from '../../StatusScreen';
-
-interface IProps {
-  children: React.ReactElement;
-}
-
-export const AuthProvider: React.FC<IProps> = ({ children }) => {
-  const me = trpc.auth.me.useQuery();
-  const isConfigured = trpc.auth.isConfigured.useQuery();
-  const loading = me.isLoading || isConfigured.isLoading;
-
-  if (loading) {
-    return <StatusScreen title="" subtitle="" />;
-  }
-
-  if (me.data) {
-    return children;
-  }
-
-  if (!isConfigured?.data) {
-    return <RegisterContainer />;
-  }
-
-  return <LoginContainer />;
-};

+ 0 - 1
src/client/components/hoc/AuthProvider/index.ts

@@ -1 +0,0 @@
-export { AuthProvider } from './AuthProvider';

+ 15 - 3
src/client/components/ui/Header/Header.test.tsx

@@ -3,6 +3,19 @@ import { fireEvent, render, renderHook, screen, waitFor } from '../../../../../t
 import { useUIStore } from '../../../state/uiStore';
 import { Header } from './Header';
 
+const pushFn = jest.fn();
+jest.mock('next/router', () => {
+  const actualRouter = jest.requireActual('next-router-mock');
+
+  return {
+    ...actualRouter,
+    useRouter: () => ({
+      ...actualRouter.useRouter(),
+      push: pushFn,
+    }),
+  };
+});
+
 describe('Header', () => {
   it('renders without crashing', () => {
     render(<Header />);
@@ -46,14 +59,13 @@ describe('Header', () => {
     expect(result.current.darkMode).toBe(false);
   });
 
-  it('Should remove the token from local storage on logout', async () => {
-    localStorage.setItem('token', 'token');
+  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(localStorage.getItem('token')).toBeNull();
+      expect(pushFn).toHaveBeenCalledWith('/login');
     });
   });
 });

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

@@ -20,7 +20,6 @@ export const Header: React.FC<IProps> = ({ isUpdateAvailable }) => {
   const utils = trpc.useContext();
   const logout = trpc.auth.logout.useMutation({
     onSuccess: () => {
-      localStorage.removeItem('token');
       utils.auth.me.invalidate();
       router.push('/login');
     },

+ 2 - 7
src/client/mocks/handlers.ts

@@ -40,22 +40,17 @@ export const handlers = [
   getTRPCMock({
     path: ['auth', 'login'],
     type: 'mutation',
-    response: { token: 'token' },
+    response: {},
   }),
   getTRPCMock({
     path: ['auth', 'logout'],
     type: 'mutation',
     response: true,
   }),
-  getTRPCMock({
-    path: ['auth', 'refreshToken'],
-    type: 'mutation',
-    response: { token: 'token' },
-  }),
   getTRPCMock({
     path: ['auth', 'register'],
     type: 'mutation',
-    response: { token: 'token' },
+    response: true,
   }),
   getTRPCMock({
     path: ['auth', 'me'],

+ 0 - 8
src/client/modules/Apps/pages/AppDetailsPage/AppDetailsPage.tsx

@@ -37,11 +37,3 @@ export const AppDetailsPage: NextPage<IProps> = ({ appId }) => {
     </Layout>
   );
 };
-
-AppDetailsPage.getInitialProps = (ctx) => {
-  const { query } = ctx;
-
-  const appId = String(query.id);
-
-  return { appId };
-};

+ 19 - 10
src/client/modules/Auth/containers/LoginContainer/LoginContainer.test.tsx

@@ -5,6 +5,19 @@ import { getTRPCMock, getTRPCMockError } from '../../../../mocks/getTrpcMock';
 import { server } from '../../../../mocks/server';
 import { LoginContainer } from './LoginContainer';
 
+const pushFn = jest.fn();
+jest.mock('next/router', () => {
+  const actualRouter = jest.requireActual('next-router-mock');
+
+  return {
+    ...actualRouter,
+    useRouter: () => ({
+      ...actualRouter.useRouter(),
+      push: pushFn,
+    }),
+  };
+});
+
 describe('Test: LoginContainer', () => {
   it('should render without error', () => {
     // Arrange
@@ -38,12 +51,11 @@ describe('Test: LoginContainer', () => {
     expect(loginButton).toBeEnabled();
   });
 
-  it('should add token in localStorage on submit', async () => {
+  it('should redirect to / upon successful login', async () => {
     // Arrange
     const email = faker.internet.email();
     const password = faker.internet.password();
-    const token = faker.datatype.uuid();
-    server.use(getTRPCMock({ path: ['auth', 'login'], type: 'mutation', response: { token } }));
+    server.use(getTRPCMock({ path: ['auth', 'login'], type: 'mutation', response: {} }));
     render(<LoginContainer />);
 
     // Act
@@ -57,7 +69,7 @@ describe('Test: LoginContainer', () => {
 
     // Assert
     await waitFor(() => {
-      expect(localStorage.getItem('token')).toEqual(token);
+      expect(pushFn).toHaveBeenCalledWith('/');
     });
   });
 
@@ -79,8 +91,6 @@ describe('Test: LoginContainer', () => {
     await waitFor(() => {
       expect(screen.getByText(/my big error/)).toBeInTheDocument();
     });
-    const token = localStorage.getItem('token');
-    expect(token).toBeNull();
   });
 
   it('should show totp form if totpSessionId is returned', async () => {
@@ -149,14 +159,13 @@ describe('Test: LoginContainer', () => {
     });
   });
 
-  it('should add token in localStorage if totp code is valid', async () => {
+  it('should redirect to / if totp is valid', async () => {
     // arrange
     const email = faker.internet.email();
     const password = faker.internet.password();
     const totpSessionId = faker.datatype.uuid();
-    const token = faker.datatype.uuid();
     server.use(getTRPCMock({ path: ['auth', 'login'], type: 'mutation', response: { totpSessionId } }));
-    server.use(getTRPCMock({ path: ['auth', 'verifyTotp'], type: 'mutation', response: { token } }));
+    server.use(getTRPCMock({ path: ['auth', 'verifyTotp'], type: 'mutation', response: true }));
     render(<LoginContainer />);
 
     // act
@@ -183,7 +192,7 @@ describe('Test: LoginContainer', () => {
 
     // assert
     await waitFor(() => {
-      expect(localStorage.getItem('token')).toEqual(token);
+      expect(pushFn).toHaveBeenCalledWith('/');
     });
   });
 });

+ 3 - 6
src/client/modules/Auth/containers/LoginContainer/LoginContainer.tsx

@@ -1,6 +1,6 @@
-import { useRouter } from 'next/router';
 import React, { useState } from 'react';
 import { toast } from 'react-hot-toast';
+import { useRouter } from 'next/router';
 import { trpc } from '../../../../utils/trpc';
 import { AuthFormLayout } from '../../components/AuthFormLayout';
 import { LoginForm } from '../../components/LoginForm';
@@ -14,14 +14,12 @@ export const LoginContainer: React.FC = () => {
   const utils = trpc.useContext();
   const login = trpc.auth.login.useMutation({
     onError: (e) => {
-      localStorage.removeItem('token');
       toast.error(`Login failed: ${e.message}`);
     },
     onSuccess: (data) => {
       if (data.totpSessionId) {
         setTotpSessionId(data.totpSessionId);
-      } else if (data.token) {
-        localStorage.setItem('token', data.token);
+      } else {
         utils.auth.me.invalidate();
         router.push('/');
       }
@@ -32,8 +30,7 @@ export const LoginContainer: React.FC = () => {
     onError: (e) => {
       toast.error(`Verification failed: ${e.message}`);
     },
-    onSuccess: (data) => {
-      localStorage.setItem('token', data.token);
+    onSuccess: () => {
       utils.auth.me.invalidate();
       router.push('/');
     },

+ 18 - 4
src/client/modules/Auth/containers/RegisterContainer/RegisterContainer.test.tsx

@@ -5,6 +5,19 @@ import { getTRPCMock, getTRPCMockError } from '../../../../mocks/getTrpcMock';
 import { server } from '../../../../mocks/server';
 import { RegisterContainer } from './RegisterContainer';
 
+const pushFn = jest.fn();
+jest.mock('next/router', () => {
+  const actualRouter = jest.requireActual('next-router-mock');
+
+  return {
+    ...actualRouter,
+    useRouter: () => ({
+      ...actualRouter.useRouter(),
+      push: pushFn,
+    }),
+  };
+});
+
 describe('Test: RegisterContainer', () => {
   it('should render without error', () => {
     render(<RegisterContainer />);
@@ -12,13 +25,12 @@ describe('Test: RegisterContainer', () => {
     expect(screen.getByText('Register')).toBeInTheDocument();
   });
 
-  it('should add token in localStorage on submit', async () => {
+  it.only('should redirect to / upon successful registration', async () => {
     // Arrange
     const email = faker.internet.email();
     const password = faker.internet.password();
-    const token = faker.datatype.uuid();
 
-    server.use(getTRPCMock({ path: ['auth', 'register'], type: 'mutation', response: { token }, delay: 100 }));
+    server.use(getTRPCMock({ path: ['auth', 'register'], type: 'mutation', response: true, delay: 100 }));
     render(<RegisterContainer />);
 
     // Act
@@ -33,7 +45,9 @@ describe('Test: RegisterContainer', () => {
     fireEvent.click(registerButton);
 
     // Assert
-    await waitFor(() => expect(localStorage.getItem('token')).toEqual(token));
+    await waitFor(() => {
+      expect(pushFn).toHaveBeenCalledWith('/');
+    });
   });
 
   it('should show toast if register mutation fails', async () => {

+ 1 - 3
src/client/modules/Auth/containers/RegisterContainer/RegisterContainer.tsx

@@ -12,11 +12,9 @@ export const RegisterContainer: React.FC = () => {
   const utils = trpc.useContext();
   const register = trpc.auth.register.useMutation({
     onError: (e) => {
-      localStorage.removeItem('token');
       toast.error(`Registration failed: ${e.message}`);
     },
-    onSuccess: (data) => {
-      localStorage.setItem('token', data.token);
+    onSuccess: () => {
       utils.auth.me.invalidate();
       router.push('/');
     },

+ 1 - 0
src/client/modules/Auth/pages/LoginPage/LoginPage.test.tsx

@@ -16,6 +16,7 @@ jest.mock('next/router', () => {
     }),
   };
 });
+
 describe('Test: LoginPage', () => {
   it('should render correctly', async () => {
     render(<LoginPage />);

+ 0 - 42
src/client/modules/Settings/containers/GeneralActions/GeneralActions.test.tsx

@@ -32,28 +32,6 @@ describe('Test: GeneralActions', () => {
     });
   });
 
-  it('should log user out if update is successful', async () => {
-    // arrange
-    localStorage.setItem('token', '123');
-    server.use(getTRPCMock({ path: ['system', 'getVersion'], response: { current: '1.0.0', latest: '2.0.0', body: '' } }));
-    server.use(getTRPCMock({ path: ['system', 'update'], response: true }));
-    render(<GeneralActions />);
-    await waitFor(() => {
-      expect(screen.getByRole('button', { name: 'Update to 2.0.0' })).toBeInTheDocument();
-    });
-    const updateButton = screen.getByRole('button', { name: /Update to 2.0.0/i });
-    fireEvent.click(updateButton);
-
-    // act
-    const updateButtonModal = screen.getByRole('button', { name: /Update/i });
-    fireEvent.click(updateButtonModal);
-
-    // assert
-    await waitFor(() => {
-      expect(localStorage.getItem('token')).toBeNull();
-    });
-  });
-
   it('should show toast if restart mutation fails', async () => {
     // arrange
     server.use(getTRPCMockError({ path: ['system', 'restart'], type: 'mutation', status: 500, message: 'Something went wrong' }));
@@ -70,24 +48,4 @@ describe('Test: GeneralActions', () => {
       expect(screen.getByText(/Something went wrong/)).toBeInTheDocument();
     });
   });
-
-  it('should log user out if restart is successful', async () => {
-    // arrange
-    localStorage.setItem('token', '1234');
-    server.use(getTRPCMock({ path: ['system', 'restart'], response: true }));
-    render(<GeneralActions />);
-
-    // Find button near the top of the page
-    const restartButton = screen.getByRole('button', { name: /Restart/i });
-
-    // act
-    fireEvent.click(restartButton);
-    const restartButtonModal = screen.getByRole('button', { name: /Restart/i });
-    fireEvent.click(restartButtonModal);
-
-    // assert
-    await waitFor(() => {
-      expect(localStorage.getItem('token')).toBeNull();
-    });
-  });
 });

+ 0 - 2
src/client/modules/Settings/containers/GeneralActions/GeneralActions.tsx

@@ -27,7 +27,6 @@ export const GeneralActions = () => {
     },
     onSuccess: async () => {
       setPollStatus(true);
-      localStorage.removeItem('token');
     },
     onError: (error) => {
       updateDisclosure.close();
@@ -42,7 +41,6 @@ export const GeneralActions = () => {
     },
     onSuccess: async () => {
       setPollStatus(true);
-      localStorage.removeItem('token');
     },
     onError: (error) => {
       restartDisclosure.close();