sudoModal.spec.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import App from 'sentry/views/app';
  6. describe('Sudo Modal', function () {
  7. const setHasPasswordAuth = hasPasswordAuth =>
  8. ConfigStore.set('user', {...ConfigStore.get('user'), hasPasswordAuth});
  9. beforeEach(function () {
  10. window.__initialData = {
  11. ...window.__initialData,
  12. links: {
  13. organizationUrl: 'https://albertos-apples.sentry.io',
  14. regionUrl: 'https://albertos-apples.sentry.io',
  15. sentryUrl: 'https://sentry.io',
  16. },
  17. };
  18. const organization = OrganizationFixture();
  19. MockApiClient.clearMockResponses();
  20. MockApiClient.addMockResponse({
  21. url: '/assistant/',
  22. body: [],
  23. });
  24. MockApiClient.addMockResponse({
  25. url: '/organizations/',
  26. body: [organization],
  27. });
  28. MockApiClient.addMockResponse({
  29. url: '/organizations/org-slug/',
  30. body: organization,
  31. });
  32. MockApiClient.addMockResponse({
  33. url: '/organizations/org-slug/teams/',
  34. body: [],
  35. });
  36. MockApiClient.addMockResponse({
  37. url: '/organizations/org-slug/projects/',
  38. body: [],
  39. });
  40. MockApiClient.addMockResponse({
  41. url: '/organizations/org-slug/',
  42. method: 'DELETE',
  43. statusCode: 401,
  44. body: {
  45. detail: {
  46. code: 'sudo-required',
  47. username: 'test@test.com',
  48. },
  49. },
  50. });
  51. MockApiClient.addMockResponse({
  52. url: '/authenticators/',
  53. body: [],
  54. });
  55. });
  56. it('can delete an org with sudo flow', async function () {
  57. const {routerProps} = initializeOrg({router: {params: {}}});
  58. setHasPasswordAuth(true);
  59. const successCb = jest.fn();
  60. const errorCb = jest.fn();
  61. // Should return w/ `sudoRequired`
  62. new MockApiClient().request('/organizations/org-slug/', {
  63. method: 'DELETE',
  64. success: successCb,
  65. error: errorCb,
  66. });
  67. render(
  68. <App {...routerProps}>
  69. <div>placeholder content</div>
  70. </App>
  71. );
  72. // Should have Modal + input
  73. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  74. // Original callbacks should not have been called
  75. expect(successCb).not.toHaveBeenCalled();
  76. expect(errorCb).not.toHaveBeenCalled();
  77. // Clear mocks and allow DELETE
  78. MockApiClient.clearMockResponses();
  79. MockApiClient.addMockResponse({
  80. url: '/authenticators/',
  81. body: [],
  82. });
  83. const orgDeleteMock = MockApiClient.addMockResponse({
  84. url: '/organizations/org-slug/',
  85. method: 'DELETE',
  86. statusCode: 200,
  87. });
  88. const sudoMock = MockApiClient.addMockResponse({
  89. url: '/auth/',
  90. method: 'PUT',
  91. statusCode: 200,
  92. });
  93. expect(sudoMock).not.toHaveBeenCalled();
  94. // "Sudo" auth
  95. await userEvent.type(screen.getByRole('textbox', {name: 'Password'}), 'password');
  96. await userEvent.click(screen.getByRole('button', {name: 'Confirm Password'}));
  97. expect(sudoMock).toHaveBeenCalledWith(
  98. '/auth/',
  99. expect.objectContaining({
  100. method: 'PUT',
  101. data: {isSuperuserModal: false, password: 'password'},
  102. })
  103. );
  104. // Retry API request
  105. await waitFor(() => expect(successCb).toHaveBeenCalled());
  106. expect(orgDeleteMock).toHaveBeenCalledWith(
  107. '/organizations/org-slug/',
  108. expect.objectContaining({
  109. method: 'DELETE',
  110. })
  111. );
  112. // Sudo Modal should be closed
  113. await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
  114. });
  115. it('shows button to redirect if user does not have password auth', async function () {
  116. const {routerProps} = initializeOrg({router: {params: {}}});
  117. setHasPasswordAuth(false);
  118. // Should return w/ `sudoRequired` and trigger the modal to open
  119. new MockApiClient().request('/organizations/org-slug/', {method: 'DELETE'});
  120. render(
  121. <App {...routerProps}>
  122. <div>placeholder content</div>
  123. </App>
  124. );
  125. // Should have Modal + input
  126. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  127. expect(screen.queryByLabelText('Password')).not.toBeInTheDocument();
  128. expect(screen.getByRole('button', {name: 'Continue'})).toHaveAttribute(
  129. 'href',
  130. '/auth/login/?next=http%3A%2F%2Flocalhost%2F'
  131. );
  132. });
  133. });