accountSecurityDetails.spec.tsx 10 KB

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