accountSecurityDetails.spec.tsx 8.3 KB

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