index.spec.tsx 6.5 KB

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