index.spec.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import {SentryApp} from 'sentry-fixture/sentryApp';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {
  4. render,
  5. renderGlobalModal,
  6. screen,
  7. userEvent,
  8. waitFor,
  9. within,
  10. } from 'sentry-test/reactTestingLibrary';
  11. import OrganizationDeveloperSettings from 'sentry/views/settings/organizationDeveloperSettings/index';
  12. describe('Organization Developer Settings', function () {
  13. const {organization: org, routerProps, router} = initializeOrg();
  14. const sentryApp = SentryApp({
  15. scopes: [
  16. 'team:read',
  17. 'project:releases',
  18. 'event:read',
  19. 'event:write',
  20. 'org:read',
  21. 'org:write',
  22. ],
  23. });
  24. beforeEach(() => {
  25. MockApiClient.clearMockResponses();
  26. });
  27. describe('when no Apps exist', () => {
  28. it('displays empty state', async () => {
  29. MockApiClient.addMockResponse({
  30. url: `/organizations/${org.slug}/sentry-apps/`,
  31. body: [],
  32. });
  33. render(<OrganizationDeveloperSettings {...routerProps} organization={org} />);
  34. await waitFor(() => {
  35. expect(
  36. screen.getByText('No internal integrations have been created yet.')
  37. ).toBeInTheDocument();
  38. });
  39. });
  40. });
  41. describe('with unpublished apps', () => {
  42. beforeEach(() => {
  43. MockApiClient.addMockResponse({
  44. url: `/organizations/${org.slug}/sentry-apps/`,
  45. body: [sentryApp],
  46. });
  47. });
  48. it('internal integrations list is empty', () => {
  49. render(<OrganizationDeveloperSettings {...routerProps} organization={org} />, {
  50. organization: org,
  51. });
  52. expect(
  53. screen.getByText('No internal integrations have been created yet.')
  54. ).toBeInTheDocument();
  55. });
  56. it('public integrations list contains 1 item', () => {
  57. render(
  58. <OrganizationDeveloperSettings
  59. {...routerProps}
  60. organization={org}
  61. location={{...router.location, query: {type: 'public'}}}
  62. />,
  63. {organization: org}
  64. );
  65. expect(screen.getByText('Sample App')).toBeInTheDocument();
  66. expect(screen.getByText('unpublished')).toBeInTheDocument();
  67. });
  68. it('allows for deletion', async () => {
  69. MockApiClient.addMockResponse({
  70. url: `/sentry-apps/${sentryApp.slug}/`,
  71. method: 'DELETE',
  72. body: [],
  73. });
  74. render(
  75. <OrganizationDeveloperSettings
  76. {...routerProps}
  77. organization={org}
  78. location={{...router.location, query: {type: 'public'}}}
  79. />
  80. );
  81. const deleteButton = await screen.findByRole('button', {name: 'Delete'});
  82. expect(deleteButton).toHaveAttribute('aria-disabled', 'false');
  83. await userEvent.click(deleteButton);
  84. renderGlobalModal();
  85. const dialog = await screen.findByRole('dialog');
  86. expect(dialog).toBeInTheDocument();
  87. const input = await within(dialog).findByPlaceholderText('sample-app');
  88. await userEvent.type(input, 'sample-app');
  89. const confirmDeleteButton = await screen.findByRole('button', {name: 'Confirm'});
  90. await userEvent.click(confirmDeleteButton);
  91. await screen.findByText('No public integrations have been created yet.');
  92. });
  93. it('can make a request to publish an integration', async () => {
  94. const mock = MockApiClient.addMockResponse({
  95. url: `/sentry-apps/${sentryApp.slug}/publish-request/`,
  96. method: 'POST',
  97. });
  98. render(
  99. <OrganizationDeveloperSettings
  100. {...routerProps}
  101. organization={org}
  102. location={{...router.location, query: {type: 'public'}}}
  103. />
  104. );
  105. const publishButton = await screen.findByRole('button', {name: 'Publish'});
  106. expect(publishButton).toHaveAttribute('aria-disabled', 'false');
  107. await userEvent.click(publishButton);
  108. renderGlobalModal();
  109. const dialog = await screen.findByRole('dialog');
  110. expect(dialog).toBeInTheDocument();
  111. const questionnaire = [
  112. {
  113. answer: 'Answer 0',
  114. question: 'What does your integration do? Please be as detailed as possible.',
  115. },
  116. {answer: 'Answer 1', question: 'What value does it offer customers?'},
  117. {
  118. answer: 'Answer 2',
  119. question: 'Do you operate the web service your integration communicates with?',
  120. },
  121. {
  122. answer: 'Answer 3',
  123. question:
  124. 'Please justify why you are requesting each of the following permissions: Team Read, Release Admin, Event Write, Organization Write.',
  125. },
  126. ];
  127. for (const {question, answer} of questionnaire) {
  128. const element = within(dialog).getByRole('textbox', {name: question});
  129. await userEvent.type(element, answer);
  130. }
  131. const requestPublishButton =
  132. await within(dialog).findByLabelText('Request Publication');
  133. expect(requestPublishButton).toHaveAttribute('aria-disabled', 'false');
  134. await userEvent.click(requestPublishButton);
  135. expect(mock).toHaveBeenCalledWith(
  136. `/sentry-apps/${sentryApp.slug}/publish-request/`,
  137. expect.objectContaining({
  138. data: {questionnaire},
  139. })
  140. );
  141. });
  142. });
  143. describe('with published apps', () => {
  144. beforeEach(() => {
  145. const publishedSentryApp = SentryApp({status: 'published'});
  146. MockApiClient.addMockResponse({
  147. url: `/organizations/${org.slug}/sentry-apps/`,
  148. body: [publishedSentryApp],
  149. });
  150. });
  151. it('shows the published status', () => {
  152. render(
  153. <OrganizationDeveloperSettings
  154. {...routerProps}
  155. organization={org}
  156. location={{...router.location, query: {type: 'public'}}}
  157. />
  158. );
  159. expect(screen.getByText('published')).toBeInTheDocument();
  160. });
  161. it('trash button is disabled', async () => {
  162. render(
  163. <OrganizationDeveloperSettings
  164. {...routerProps}
  165. organization={org}
  166. location={{...router.location, query: {type: 'public'}}}
  167. />
  168. );
  169. const deleteButton = await screen.findByRole('button', {name: 'Delete'});
  170. expect(deleteButton).toHaveAttribute('aria-disabled', 'true');
  171. });
  172. it('publish button is disabled', async () => {
  173. render(
  174. <OrganizationDeveloperSettings
  175. {...routerProps}
  176. organization={org}
  177. location={{...router.location, query: {type: 'public'}}}
  178. />
  179. );
  180. const publishButton = await screen.findByRole('button', {name: 'Publish'});
  181. expect(publishButton).toHaveAttribute('aria-disabled', 'true');
  182. });
  183. });
  184. describe('with Internal Integrations', () => {
  185. beforeEach(() => {
  186. const internalIntegration = SentryApp({status: 'internal'});
  187. MockApiClient.addMockResponse({
  188. url: `/organizations/${org.slug}/sentry-apps/`,
  189. body: [internalIntegration],
  190. });
  191. });
  192. it('allows deleting', async () => {
  193. render(<OrganizationDeveloperSettings {...routerProps} organization={org} />);
  194. const deleteButton = await screen.findByRole('button', {name: 'Delete'});
  195. expect(deleteButton).toHaveAttribute('aria-disabled', 'false');
  196. });
  197. it('publish button does not exist', () => {
  198. render(<OrganizationDeveloperSettings {...routerProps} organization={org} />);
  199. expect(screen.queryByText('Publish')).not.toBeInTheDocument();
  200. });
  201. });
  202. describe('without Owner permissions', () => {
  203. const newOrg = TestStubs.Organization({access: ['org:read']});
  204. beforeEach(() => {
  205. MockApiClient.addMockResponse({
  206. url: `/organizations/${newOrg.slug}/sentry-apps/`,
  207. body: [sentryApp],
  208. });
  209. });
  210. it('trash button is disabled', async () => {
  211. render(
  212. <OrganizationDeveloperSettings
  213. {...routerProps}
  214. organization={newOrg}
  215. location={{...router.location, query: {type: 'public'}}}
  216. />,
  217. {organization: newOrg}
  218. );
  219. const deleteButton = await screen.findByRole('button', {name: 'Delete'});
  220. expect(deleteButton).toHaveAttribute('aria-disabled', 'true');
  221. });
  222. it('publish button is disabled', async () => {
  223. render(
  224. <OrganizationDeveloperSettings
  225. {...routerProps}
  226. organization={newOrg}
  227. location={{...router.location, query: {type: 'public'}}}
  228. />,
  229. {organization: newOrg}
  230. );
  231. const publishButton = await screen.findByRole('button', {name: 'Publish'});
  232. expect(publishButton).toHaveAttribute('aria-disabled', 'true');
  233. });
  234. });
  235. });