index.spec.tsx 8.3 KB

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