accountSecurityDetails.spec.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import {AccountEmails} from 'sentry-fixture/accountEmails';
  2. import {AllAuthenticators, Authenticators} from 'sentry-fixture/authenticators';
  3. import {Organizations} from 'sentry-fixture/organizations';
  4. import {initializeOrg} from 'sentry-test/initializeOrg';
  5. import {
  6. render,
  7. renderGlobalModal,
  8. screen,
  9. userEvent,
  10. } from 'sentry-test/reactTestingLibrary';
  11. import AccountSecurityDetails from 'sentry/views/settings/account/accountSecurity/accountSecurityDetails';
  12. import AccountSecurityWrapper from 'sentry/views/settings/account/accountSecurity/accountSecurityWrapper';
  13. const ENDPOINT = '/users/me/authenticators/';
  14. const ACCOUNT_EMAILS_ENDPOINT = '/users/me/emails/';
  15. const ORG_ENDPOINT = '/organizations/';
  16. describe('AccountSecurityDetails', function () {
  17. beforeEach(() => {
  18. MockApiClient.clearMockResponses();
  19. });
  20. describe('Totp', function () {
  21. beforeEach(function () {
  22. MockApiClient.addMockResponse({
  23. url: ENDPOINT,
  24. body: AllAuthenticators(),
  25. });
  26. MockApiClient.addMockResponse({
  27. url: ORG_ENDPOINT,
  28. body: Organizations(),
  29. });
  30. MockApiClient.addMockResponse({
  31. url: `${ENDPOINT}15/`,
  32. body: Authenticators().Totp(),
  33. });
  34. MockApiClient.addMockResponse({
  35. url: ACCOUNT_EMAILS_ENDPOINT,
  36. body: AccountEmails(),
  37. });
  38. });
  39. it('has enrolled circle indicator', async function () {
  40. const params = {
  41. authId: '15',
  42. };
  43. const {routerProps, routerContext} = initializeOrg({
  44. router: {
  45. params,
  46. },
  47. });
  48. render(
  49. <AccountSecurityWrapper {...routerProps}>
  50. <AccountSecurityDetails
  51. {...routerProps}
  52. onRegenerateBackupCodes={jest.fn()}
  53. deleteDisabled={false}
  54. />
  55. </AccountSecurityWrapper>,
  56. {context: routerContext}
  57. );
  58. expect(await screen.findByTestId('auth-status-enabled')).toBeInTheDocument();
  59. // has created and last used dates
  60. expect(screen.getByText('Created at')).toBeInTheDocument();
  61. expect(screen.getByText('Last used')).toBeInTheDocument();
  62. });
  63. it('can remove method', async function () {
  64. const deleteMock = MockApiClient.addMockResponse({
  65. url: `${ENDPOINT}15/`,
  66. method: 'DELETE',
  67. });
  68. const params = {
  69. authId: '15',
  70. };
  71. const {routerProps, routerContext} = initializeOrg({
  72. router: {
  73. params,
  74. },
  75. });
  76. render(
  77. <AccountSecurityWrapper {...routerProps}>
  78. <AccountSecurityDetails
  79. {...routerProps}
  80. onRegenerateBackupCodes={jest.fn()}
  81. deleteDisabled={false}
  82. />
  83. </AccountSecurityWrapper>,
  84. {context: routerContext}
  85. );
  86. await userEvent.click(await screen.findByRole('button', {name: 'Remove'}));
  87. renderGlobalModal();
  88. await userEvent.click(await screen.findByRole('button', {name: 'Confirm'}));
  89. expect(deleteMock).toHaveBeenCalled();
  90. });
  91. it('can remove one of multiple 2fa methods when org requires 2fa', async function () {
  92. MockApiClient.addMockResponse({
  93. url: ORG_ENDPOINT,
  94. body: Organizations({require2FA: true}),
  95. });
  96. const deleteMock = MockApiClient.addMockResponse({
  97. url: `${ENDPOINT}15/`,
  98. method: 'DELETE',
  99. });
  100. const params = {
  101. authId: '15',
  102. };
  103. const {routerProps, routerContext} = initializeOrg({
  104. router: {
  105. params,
  106. },
  107. });
  108. render(
  109. <AccountSecurityWrapper {...routerProps}>
  110. <AccountSecurityDetails
  111. {...routerProps}
  112. onRegenerateBackupCodes={jest.fn()}
  113. deleteDisabled={false}
  114. />
  115. </AccountSecurityWrapper>,
  116. {context: routerContext}
  117. );
  118. await userEvent.click(await screen.findByRole('button', {name: 'Remove'}));
  119. renderGlobalModal();
  120. await userEvent.click(await screen.findByRole('button', {name: 'Confirm'}));
  121. expect(deleteMock).toHaveBeenCalled();
  122. });
  123. it('can not remove last 2fa method when org requires 2fa', async function () {
  124. MockApiClient.addMockResponse({
  125. url: ORG_ENDPOINT,
  126. body: Organizations({require2FA: true}),
  127. });
  128. MockApiClient.addMockResponse({
  129. url: ENDPOINT,
  130. body: [Authenticators().Totp()],
  131. });
  132. const params = {
  133. authId: '15',
  134. };
  135. const {routerContext, routerProps} = initializeOrg({
  136. router: {
  137. params,
  138. },
  139. });
  140. render(
  141. <AccountSecurityWrapper {...routerProps}>
  142. <AccountSecurityDetails
  143. {...routerProps}
  144. onRegenerateBackupCodes={jest.fn()}
  145. deleteDisabled={false}
  146. />
  147. </AccountSecurityWrapper>,
  148. {context: routerContext}
  149. );
  150. expect(await screen.findByRole('button', {name: 'Remove'})).toBeDisabled();
  151. });
  152. });
  153. describe('Recovery', function () {
  154. beforeEach(function () {
  155. MockApiClient.addMockResponse({
  156. url: ENDPOINT,
  157. body: AllAuthenticators(),
  158. });
  159. MockApiClient.addMockResponse({
  160. url: ORG_ENDPOINT,
  161. body: Organizations(),
  162. });
  163. MockApiClient.addMockResponse({
  164. url: `${ENDPOINT}16/`,
  165. body: Authenticators().Recovery(),
  166. });
  167. MockApiClient.addMockResponse({
  168. url: ACCOUNT_EMAILS_ENDPOINT,
  169. body: AccountEmails(),
  170. });
  171. });
  172. it('has enrolled circle indicator', function () {
  173. const params = {
  174. authId: '16',
  175. };
  176. const {routerProps, routerContext} = initializeOrg({
  177. router: {
  178. params,
  179. },
  180. });
  181. render(
  182. <AccountSecurityWrapper {...routerProps}>
  183. <AccountSecurityDetails
  184. {...routerProps}
  185. onRegenerateBackupCodes={jest.fn()}
  186. deleteDisabled={false}
  187. />
  188. </AccountSecurityWrapper>,
  189. {context: routerContext}
  190. );
  191. // does not have remove button
  192. expect(screen.queryByRole('button', {name: 'Remove'})).not.toBeInTheDocument();
  193. });
  194. it('regenerates codes', async function () {
  195. const deleteMock = MockApiClient.addMockResponse({
  196. url: `${ENDPOINT}16/`,
  197. method: 'PUT',
  198. });
  199. const params = {
  200. authId: '16',
  201. };
  202. const {routerProps, routerContext} = initializeOrg({
  203. router: {
  204. params,
  205. },
  206. });
  207. render(
  208. <AccountSecurityWrapper {...routerProps}>
  209. <AccountSecurityDetails
  210. {...routerProps}
  211. onRegenerateBackupCodes={jest.fn()}
  212. deleteDisabled={false}
  213. />
  214. </AccountSecurityWrapper>,
  215. {context: routerContext}
  216. );
  217. await userEvent.click(
  218. await screen.findByRole('button', {name: 'Regenerate Codes'})
  219. );
  220. renderGlobalModal();
  221. expect(
  222. await screen.findByText(
  223. 'Are you sure you want to regenerate recovery codes? Your old codes will no longer work.'
  224. )
  225. ).toBeInTheDocument();
  226. await userEvent.click(screen.getByRole('button', {name: 'Confirm'}));
  227. expect(deleteMock).toHaveBeenCalled();
  228. });
  229. it('has copy, print and download buttons', async function () {
  230. const params = {
  231. authId: '16',
  232. };
  233. const {routerProps, routerContext} = initializeOrg({
  234. router: {
  235. params,
  236. },
  237. });
  238. Object.defineProperty(document, 'queryCommandSupported', {
  239. value: () => true,
  240. });
  241. render(
  242. <AccountSecurityWrapper {...routerProps}>
  243. <AccountSecurityDetails
  244. {...routerProps}
  245. onRegenerateBackupCodes={jest.fn()}
  246. deleteDisabled={false}
  247. />
  248. </AccountSecurityWrapper>,
  249. {context: routerContext}
  250. );
  251. expect(await screen.findByRole('button', {name: 'print'})).toBeInTheDocument();
  252. expect(screen.getByRole('button', {name: 'download'})).toHaveAttribute(
  253. 'href',
  254. 'data:text/plain;charset=utf-8,ABCD-1234 \nEFGH-5678'
  255. );
  256. expect(screen.getByTestId('frame')).toBeInTheDocument();
  257. expect(screen.getByRole('button', {name: 'Copy'})).toBeInTheDocument();
  258. });
  259. });
  260. });