index.spec.tsx 8.4 KB

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