index.spec.tsx 8.4 KB

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