accountSecurityDetails.spec.tsx 10 KB

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