index.spec.tsx 6.8 KB

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