accountSecurityDetails.spec.tsx 7.8 KB

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