index.spec.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import {GitHubIntegrationFixture} from 'sentry-fixture/githubIntegration';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {ProjectFixture} from 'sentry-fixture/project';
  4. import {initializeOrg} from 'sentry-test/initializeOrg';
  5. import {
  6. act,
  7. render,
  8. renderGlobalModal,
  9. screen,
  10. userEvent,
  11. waitFor,
  12. within,
  13. } from 'sentry-test/reactTestingLibrary';
  14. import OrganizationsStore from 'sentry/stores/organizationsStore';
  15. import ProjectsStore from 'sentry/stores/projectsStore';
  16. import {trackAnalytics} from 'sentry/utils/analytics';
  17. import {browserHistory} from 'sentry/utils/browserHistory';
  18. import OrganizationGeneralSettings from 'sentry/views/settings/organizationGeneralSettings';
  19. jest.mock('sentry/utils/analytics');
  20. describe('OrganizationGeneralSettings', function () {
  21. const ENDPOINT = '/organizations/org-slug/';
  22. const {organization, router} = initializeOrg();
  23. const defaultProps = {
  24. organization,
  25. router,
  26. location: router.location,
  27. params: {orgId: organization.slug},
  28. routes: router.routes,
  29. route: {},
  30. routeParams: router.params,
  31. };
  32. beforeEach(function () {
  33. OrganizationsStore.addOrReplace(organization);
  34. MockApiClient.addMockResponse({
  35. url: `/organizations/${organization.slug}/auth-provider/`,
  36. method: 'GET',
  37. });
  38. MockApiClient.addMockResponse({
  39. url: `/organizations/${organization.slug}/integrations/?provider_key=github`,
  40. method: 'GET',
  41. body: [GitHubIntegrationFixture()],
  42. });
  43. });
  44. it('can enable "early adopter"', async function () {
  45. render(<OrganizationGeneralSettings {...defaultProps} />);
  46. const mock = MockApiClient.addMockResponse({
  47. url: ENDPOINT,
  48. method: 'PUT',
  49. });
  50. await userEvent.click(screen.getByRole('checkbox', {name: /early adopter/i}));
  51. await waitFor(() => {
  52. expect(mock).toHaveBeenCalledWith(
  53. ENDPOINT,
  54. expect.objectContaining({
  55. data: {isEarlyAdopter: true},
  56. })
  57. );
  58. });
  59. });
  60. it('can enable "codecov access"', async function () {
  61. const organizationWithCodecovFeature = OrganizationFixture({
  62. features: ['codecov-integration'],
  63. codecovAccess: false,
  64. });
  65. render(<OrganizationGeneralSettings {...defaultProps} />, {
  66. organization: organizationWithCodecovFeature,
  67. });
  68. const mock = MockApiClient.addMockResponse({
  69. url: ENDPOINT,
  70. method: 'PUT',
  71. });
  72. await userEvent.click(
  73. screen.getByRole('checkbox', {name: /Enable Code Coverage Insights/i})
  74. );
  75. await waitFor(() => {
  76. expect(mock).toHaveBeenCalledWith(
  77. ENDPOINT,
  78. expect.objectContaining({
  79. data: {codecovAccess: true},
  80. })
  81. );
  82. });
  83. expect(trackAnalytics).toHaveBeenCalled();
  84. });
  85. it('changes org slug and redirects to new slug', async function () {
  86. render(<OrganizationGeneralSettings {...defaultProps} />);
  87. const mock = MockApiClient.addMockResponse({
  88. url: ENDPOINT,
  89. method: 'PUT',
  90. body: {...organization, slug: 'new-slug'},
  91. });
  92. await userEvent.clear(screen.getByRole('textbox', {name: /slug/i}));
  93. await userEvent.type(screen.getByRole('textbox', {name: /slug/i}), 'new-slug');
  94. await userEvent.click(screen.getByLabelText('Save'));
  95. await waitFor(() => {
  96. expect(mock).toHaveBeenCalledWith(
  97. ENDPOINT,
  98. expect.objectContaining({
  99. data: {slug: 'new-slug'},
  100. })
  101. );
  102. expect(browserHistory.replace).toHaveBeenCalledWith('/settings/new-slug/');
  103. });
  104. });
  105. it('changes org slug and redirects to new customer-domain', async function () {
  106. const org = OrganizationFixture({features: ['customer-domains']});
  107. const updateMock = MockApiClient.addMockResponse({
  108. url: `/organizations/${organization.slug}/`,
  109. method: 'PUT',
  110. body: {...org, slug: 'acme', links: {organizationUrl: 'https://acme.sentry.io'}},
  111. });
  112. render(<OrganizationGeneralSettings {...defaultProps} />, {organization: org});
  113. const input = screen.getByRole('textbox', {name: /slug/i});
  114. await userEvent.clear(input);
  115. await userEvent.type(input, 'acme');
  116. await userEvent.click(screen.getByLabelText('Save'));
  117. await waitFor(() => {
  118. expect(updateMock).toHaveBeenCalledWith(
  119. '/organizations/org-slug/',
  120. expect.objectContaining({
  121. data: {
  122. slug: 'acme',
  123. },
  124. })
  125. );
  126. expect(window.location.replace).toHaveBeenCalledWith(
  127. 'https://acme.sentry.io/settings/organization/'
  128. );
  129. });
  130. });
  131. it('disables the entire form if user does not have write access', function () {
  132. const readOnlyOrg = OrganizationFixture({access: ['org:read']});
  133. render(<OrganizationGeneralSettings {...defaultProps} />, {
  134. organization: readOnlyOrg,
  135. });
  136. const formElements = [
  137. ...screen.getAllByRole('textbox'),
  138. ...screen.getAllByRole('button'),
  139. ...screen.getAllByRole('checkbox'),
  140. ];
  141. for (const formElement of formElements) {
  142. expect(formElement).toBeDisabled();
  143. }
  144. expect(
  145. screen.getByText(
  146. 'These settings can only be edited by users with the organization owner or manager role.'
  147. )
  148. ).toBeInTheDocument();
  149. });
  150. it('does not have remove organization button without org:admin permission', function () {
  151. render(<OrganizationGeneralSettings {...defaultProps} />, {
  152. organization: OrganizationFixture({
  153. access: ['org:write'],
  154. }),
  155. });
  156. expect(
  157. screen.queryByRole('button', {name: /remove organization/i})
  158. ).not.toBeInTheDocument();
  159. });
  160. it('can remove organization when org admin', async function () {
  161. act(() => ProjectsStore.loadInitialData([ProjectFixture({slug: 'project'})]));
  162. render(<OrganizationGeneralSettings {...defaultProps} />, {
  163. organization: OrganizationFixture({access: ['org:admin']}),
  164. });
  165. renderGlobalModal();
  166. const mock = MockApiClient.addMockResponse({
  167. url: ENDPOINT,
  168. method: 'DELETE',
  169. });
  170. await userEvent.click(screen.getByRole('button', {name: /remove organization/i}));
  171. const modal = screen.getByRole('dialog');
  172. expect(
  173. within(modal).getByText('This will also remove the following associated projects:')
  174. ).toBeInTheDocument();
  175. expect(within(modal).getByText('project')).toBeInTheDocument();
  176. await userEvent.click(
  177. within(modal).getByRole('button', {name: /remove organization/i})
  178. );
  179. await waitFor(() => {
  180. expect(mock).toHaveBeenCalledWith(
  181. ENDPOINT,
  182. expect.objectContaining({
  183. method: 'DELETE',
  184. })
  185. );
  186. });
  187. });
  188. });