OptForm.test.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import React from 'react';
  2. import { server } from '@/client/mocks/server';
  3. import { getTRPCMock, getTRPCMockError } from '@/client/mocks/getTrpcMock';
  4. import { render, screen, waitFor, fireEvent } from '../../../../../../tests/test-utils';
  5. import { OtpForm } from './OtpForm';
  6. describe('<OtpForm />', () => {
  7. it('should render', () => {
  8. render(<OtpForm />);
  9. });
  10. it('should prompt for password when enabling 2FA', async () => {
  11. // arrange
  12. render(<OtpForm />);
  13. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  14. await waitFor(() => {
  15. expect(twoFactorAuthButton).toBeEnabled();
  16. });
  17. // act
  18. twoFactorAuthButton.click();
  19. // assert
  20. await waitFor(() => {
  21. expect(screen.getByText('Password needed')).toBeInTheDocument();
  22. });
  23. });
  24. it('should prompt for password when disabling 2FA', async () => {
  25. // arrange
  26. server.use(getTRPCMock({ path: ['auth', 'me'], response: { totpEnabled: true, id: 12, username: 'test', locale: 'en', operator: true } }));
  27. render(<OtpForm />);
  28. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  29. await waitFor(() => {
  30. expect(twoFactorAuthButton).toBeEnabled();
  31. });
  32. // act
  33. twoFactorAuthButton.click();
  34. // assert
  35. await waitFor(() => {
  36. expect(screen.getByText('Password needed')).toBeInTheDocument();
  37. });
  38. });
  39. it('should show show error toast if password is incorrect while enabling 2FA', async () => {
  40. // arrange
  41. server.use(getTRPCMock({ path: ['auth', 'me'], response: { totpEnabled: false, id: 12, username: 'test', locale: 'en', operator: true } }));
  42. server.use(getTRPCMockError({ path: ['auth', 'getTotpUri'], type: 'mutation', message: 'Invalid password' }));
  43. render(<OtpForm />);
  44. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  45. await waitFor(() => {
  46. expect(twoFactorAuthButton).toBeEnabled();
  47. });
  48. // act
  49. twoFactorAuthButton.click();
  50. await waitFor(() => {
  51. expect(screen.getByText('Password needed')).toBeInTheDocument();
  52. });
  53. const passwordInput = screen.getByRole('textbox', { name: 'password' });
  54. fireEvent.change(passwordInput, { target: { value: 'test' } });
  55. const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
  56. submitButton.click();
  57. // assert
  58. await waitFor(() => {
  59. expect(screen.getByText(/Invalid password/)).toBeInTheDocument();
  60. });
  61. });
  62. it('should show show error toast if password is incorrect while disabling 2FA', async () => {
  63. // arrange
  64. server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'en', totpEnabled: true, id: 12, username: 'test', operator: true } }));
  65. server.use(getTRPCMockError({ path: ['auth', 'disableTotp'], type: 'mutation', message: 'Invalid password' }));
  66. render(<OtpForm />);
  67. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  68. await waitFor(() => {
  69. expect(twoFactorAuthButton).toBeEnabled();
  70. });
  71. // act
  72. twoFactorAuthButton.click();
  73. await waitFor(() => {
  74. expect(screen.getByText('Password needed')).toBeInTheDocument();
  75. });
  76. const passwordInput = screen.getByRole('textbox', { name: 'password' });
  77. fireEvent.change(passwordInput, { target: { value: 'test' } });
  78. const submitButton = screen.getByRole('button', { name: /Disable two-factor authentication/i });
  79. submitButton.click();
  80. // assert
  81. await waitFor(() => {
  82. expect(screen.getByText(/Invalid password/)).toBeInTheDocument();
  83. });
  84. });
  85. it('should show success toast if password is correct while disabling 2FA', async () => {
  86. // arrange
  87. server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'en', totpEnabled: true, id: 12, username: 'test', operator: true } }));
  88. server.use(getTRPCMock({ path: ['auth', 'disableTotp'], type: 'mutation', response: true }));
  89. render(<OtpForm />);
  90. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  91. await waitFor(() => {
  92. expect(twoFactorAuthButton).toBeEnabled();
  93. });
  94. // act
  95. twoFactorAuthButton.click();
  96. await waitFor(() => {
  97. expect(screen.getByText('Password needed')).toBeInTheDocument();
  98. });
  99. const passwordInput = screen.getByRole('textbox', { name: 'password' });
  100. fireEvent.change(passwordInput, { target: { value: 'test' } });
  101. const submitButton = screen.getByRole('button', { name: /Disable two-factor authentication/i });
  102. submitButton.click();
  103. // assert
  104. await waitFor(() => {
  105. expect(screen.getByText('Two-factor authentication disabled')).toBeInTheDocument();
  106. });
  107. });
  108. it('should show secret key and QR code when enabling 2FA', async () => {
  109. // arrange
  110. server.use(getTRPCMock({ path: ['auth', 'getTotpUri'], type: 'mutation', response: { key: 'test', uri: 'test' } }));
  111. render(<OtpForm />);
  112. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  113. await waitFor(() => {
  114. expect(twoFactorAuthButton).toBeEnabled();
  115. });
  116. // act
  117. twoFactorAuthButton.click();
  118. await waitFor(() => {
  119. expect(screen.getByText('Password needed')).toBeInTheDocument();
  120. });
  121. const passwordInput = screen.getByRole('textbox', { name: 'password' });
  122. fireEvent.change(passwordInput, { target: { value: 'test' } });
  123. const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
  124. submitButton.click();
  125. // assert
  126. await waitFor(() => {
  127. expect(screen.getByText('Scan this QR code with your authenticator app.')).toBeInTheDocument();
  128. });
  129. expect(screen.getByRole('textbox', { name: 'secret key' })).toHaveValue('test');
  130. expect(screen.getByRole('button', { name: 'Enable two-factor authentication' })).toBeDisabled();
  131. });
  132. it('should show error toast if submitted totp code is invalid', async () => {
  133. // arrange
  134. server.use(getTRPCMock({ path: ['auth', 'getTotpUri'], type: 'mutation', response: { key: 'test', uri: 'test' } }));
  135. server.use(getTRPCMockError({ path: ['auth', 'setupTotp'], type: 'mutation', message: 'Invalid code' }));
  136. render(<OtpForm />);
  137. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  138. await waitFor(() => {
  139. expect(twoFactorAuthButton).toBeEnabled();
  140. });
  141. // act
  142. twoFactorAuthButton.click();
  143. await waitFor(() => {
  144. expect(screen.getByText('Password needed')).toBeInTheDocument();
  145. });
  146. const passwordInput = screen.getByRole('textbox', { name: 'password' });
  147. fireEvent.change(passwordInput, { target: { value: 'test' } });
  148. const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
  149. submitButton.click();
  150. await waitFor(() => {
  151. expect(screen.getByText('Scan this QR code with your authenticator app.')).toBeInTheDocument();
  152. });
  153. const inputEls = screen.getAllByRole('textbox', { name: /digit-/ });
  154. inputEls.forEach((inputEl) => {
  155. fireEvent.change(inputEl, { target: { value: '1' } });
  156. });
  157. const enable2FAButton = screen.getByRole('button', { name: 'Enable two-factor authentication' });
  158. enable2FAButton.click();
  159. // assert
  160. await waitFor(() => {
  161. expect(screen.getByText(/Invalid code/)).toBeInTheDocument();
  162. });
  163. });
  164. it('should show success toast if submitted totp code is valid', async () => {
  165. // arrange
  166. server.use(getTRPCMock({ path: ['auth', 'getTotpUri'], type: 'mutation', response: { key: 'test', uri: 'test' } }));
  167. server.use(getTRPCMock({ path: ['auth', 'setupTotp'], type: 'mutation', response: true }));
  168. render(<OtpForm />);
  169. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  170. await waitFor(() => {
  171. expect(twoFactorAuthButton).toBeEnabled();
  172. });
  173. // act
  174. twoFactorAuthButton.click();
  175. await waitFor(() => {
  176. expect(screen.getByText('Password needed')).toBeInTheDocument();
  177. });
  178. const passwordInput = screen.getByRole('textbox', { name: 'password' });
  179. fireEvent.change(passwordInput, { target: { value: 'test' } });
  180. const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
  181. submitButton.click();
  182. await waitFor(() => {
  183. expect(screen.getByText('Scan this QR code with your authenticator app.')).toBeInTheDocument();
  184. });
  185. const inputEls = screen.getAllByRole('textbox', { name: /digit-/ });
  186. inputEls.forEach((inputEl) => {
  187. fireEvent.change(inputEl, { target: { value: '1' } });
  188. });
  189. const enable2FAButton = screen.getByRole('button', { name: 'Enable two-factor authentication' });
  190. enable2FAButton.click();
  191. // assert
  192. await waitFor(() => {
  193. expect(screen.getByText('Two-factor authentication enabled')).toBeInTheDocument();
  194. });
  195. });
  196. it('can close the setup modal by clicking on the esc key', async () => {
  197. // arrange
  198. render(<OtpForm />);
  199. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  200. await waitFor(() => {
  201. expect(twoFactorAuthButton).toBeEnabled();
  202. });
  203. // act
  204. twoFactorAuthButton.click();
  205. await waitFor(() => {
  206. expect(screen.getByText('Password needed')).toBeInTheDocument();
  207. });
  208. fireEvent.keyDown(document, { key: 'Escape' });
  209. // assert
  210. await waitFor(() => {
  211. expect(screen.queryByText('Password needed')).not.toBeInTheDocument();
  212. });
  213. });
  214. it('can close the disable modal by clicking on the esc key', async () => {
  215. // arrange
  216. server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'en', totpEnabled: true, username: '', id: 1, operator: true } }));
  217. render(<OtpForm />);
  218. const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
  219. await waitFor(() => {
  220. expect(twoFactorAuthButton).toBeEnabled();
  221. });
  222. // act
  223. twoFactorAuthButton.click();
  224. await waitFor(() => {
  225. expect(screen.getByText('Password needed')).toBeInTheDocument();
  226. });
  227. fireEvent.keyDown(document, { key: 'Escape' });
  228. // assert
  229. await waitFor(() => {
  230. expect(screen.queryByText('Password needed')).not.toBeInTheDocument();
  231. });
  232. });
  233. });