integrationRepos.spec.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import {GitHubIntegrationFixture} from 'sentry-fixture/githubIntegration';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {RepositoryFixture} from 'sentry-fixture/repository';
  4. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  5. import RepositoryStore from 'sentry/stores/repositoryStore';
  6. import IntegrationRepos from 'sentry/views/settings/organizationIntegrations/integrationRepos';
  7. describe('IntegrationRepos', function () {
  8. const org = OrganizationFixture();
  9. const integration = GitHubIntegrationFixture();
  10. let resetReposSpy: jest.SpyInstance;
  11. beforeEach(() => {
  12. MockApiClient.clearMockResponses();
  13. RepositoryStore.init();
  14. resetReposSpy = jest.spyOn(RepositoryStore, 'resetRepositories');
  15. });
  16. afterEach(() => {
  17. resetReposSpy.mockClear();
  18. });
  19. describe('Getting repositories', function () {
  20. it('handles broken integrations', async function () {
  21. MockApiClient.addMockResponse({
  22. url: `/organizations/${org.slug}/integrations/1/repos/`,
  23. statusCode: 400,
  24. body: {detail: 'Invalid grant'},
  25. });
  26. MockApiClient.addMockResponse({
  27. url: `/organizations/${org.slug}/repos/`,
  28. method: 'GET',
  29. body: [],
  30. });
  31. render(<IntegrationRepos integration={integration} />);
  32. expect(
  33. await screen.findByText(
  34. /We were unable to fetch repositories for this integration/
  35. )
  36. ).toBeInTheDocument();
  37. });
  38. });
  39. describe('Adding repositories', function () {
  40. it('can save successfully', async function () {
  41. const addRepo = MockApiClient.addMockResponse({
  42. url: `/organizations/${org.slug}/repos/`,
  43. method: 'POST',
  44. body: RepositoryFixture({integrationId: '1'}),
  45. });
  46. MockApiClient.addMockResponse({
  47. url: `/organizations/${org.slug}/integrations/1/repos/`,
  48. body: {
  49. repos: [{identifier: 'example/repo-name', name: 'repo-name'}],
  50. },
  51. });
  52. MockApiClient.addMockResponse({
  53. url: `/organizations/${org.slug}/repos/`,
  54. method: 'GET',
  55. body: [],
  56. });
  57. render(<IntegrationRepos integration={integration} />);
  58. await userEvent.click(screen.getByText('Add Repository'));
  59. await userEvent.click(screen.getByText('repo-name'));
  60. expect(addRepo).toHaveBeenCalledWith(
  61. `/organizations/${org.slug}/repos/`,
  62. expect.objectContaining({
  63. data: {
  64. installation: '1',
  65. provider: 'integrations:github',
  66. identifier: 'example/repo-name',
  67. },
  68. })
  69. );
  70. expect(await screen.findByText('example/repo-name')).toBeInTheDocument();
  71. expect(resetReposSpy).toHaveBeenCalled();
  72. });
  73. it('handles failure during save', async function () {
  74. const addRepo = MockApiClient.addMockResponse({
  75. url: `/organizations/${org.slug}/repos/`,
  76. method: 'POST',
  77. statusCode: 400,
  78. body: {
  79. errors: {
  80. __all__: 'Repository already exists.',
  81. },
  82. },
  83. });
  84. MockApiClient.addMockResponse({
  85. url: `/organizations/${org.slug}/integrations/1/repos/`,
  86. body: {
  87. repos: [{identifier: 'getsentry/sentry', name: 'sentry-repo'}],
  88. },
  89. });
  90. MockApiClient.addMockResponse({
  91. url: `/organizations/${org.slug}/repos/`,
  92. method: 'GET',
  93. body: [],
  94. });
  95. render(<IntegrationRepos integration={integration} />);
  96. await userEvent.click(screen.getByText('Add Repository'));
  97. await userEvent.click(screen.getByText('sentry-repo'));
  98. expect(addRepo).toHaveBeenCalled();
  99. expect(screen.queryByText('getsentry/sentry')).not.toBeInTheDocument();
  100. });
  101. it('does not disable add repo for members', async function () {
  102. MockApiClient.addMockResponse({
  103. url: `/organizations/${org.slug}/integrations/1/repos/`,
  104. body: {
  105. repos: [{identifier: 'example/repo-name', name: 'repo-name'}],
  106. },
  107. });
  108. MockApiClient.addMockResponse({
  109. url: `/organizations/${org.slug}/repos/`,
  110. method: 'GET',
  111. body: [],
  112. });
  113. render(
  114. <IntegrationRepos
  115. integration={integration}
  116. organization={OrganizationFixture({access: []})}
  117. />
  118. );
  119. await waitFor(() => expect(screen.getByText('Add Repository')).toBeEnabled());
  120. });
  121. });
  122. describe('migratable repo', function () {
  123. it('associates repository with integration', async () => {
  124. MockApiClient.addMockResponse({
  125. url: `/organizations/${org.slug}/repos/`,
  126. body: [
  127. RepositoryFixture({
  128. integrationId: undefined,
  129. externalSlug: 'example/repo-name',
  130. provider: {
  131. id: 'integrations:github',
  132. name: 'GitHub',
  133. },
  134. }),
  135. ],
  136. });
  137. MockApiClient.addMockResponse({
  138. url: `/organizations/${org.slug}/integrations/${integration.id}/repos/`,
  139. body: {repos: [{identifier: 'example/repo-name', name: 'repo-name'}]},
  140. });
  141. const updateRepo = MockApiClient.addMockResponse({
  142. method: 'PUT',
  143. url: `/organizations/${org.slug}/repos/4/`,
  144. body: {id: 244},
  145. });
  146. render(<IntegrationRepos integration={integration} />);
  147. await userEvent.click(screen.getByText('Add Repository'));
  148. await userEvent.click(screen.getByText('repo-name'));
  149. expect(updateRepo).toHaveBeenCalledWith(
  150. `/organizations/${org.slug}/repos/4/`,
  151. expect.objectContaining({
  152. data: {integrationId: '1'},
  153. })
  154. );
  155. await waitFor(() => expect(resetReposSpy).toHaveBeenCalled());
  156. });
  157. it('uses externalSlug not name for comparison', async () => {
  158. MockApiClient.addMockResponse({
  159. url: `/organizations/${org.slug}/repos/`,
  160. method: 'GET',
  161. body: [RepositoryFixture({name: 'repo-name-other', externalSlug: '9876'})],
  162. });
  163. const getItems = MockApiClient.addMockResponse({
  164. url: `/organizations/${org.slug}/integrations/${integration.id}/repos/`,
  165. method: 'GET',
  166. body: {
  167. repos: [{identifier: '9876', name: 'repo-name'}],
  168. },
  169. });
  170. const updateRepo = MockApiClient.addMockResponse({
  171. method: 'PUT',
  172. url: `/organizations/${org.slug}/repos/4/`,
  173. body: {id: 4320},
  174. });
  175. render(<IntegrationRepos integration={integration} />);
  176. await userEvent.click(screen.getByText('Add Repository'));
  177. await userEvent.click(screen.getByText('repo-name'));
  178. expect(getItems).toHaveBeenCalled();
  179. expect(updateRepo).toHaveBeenCalledWith(
  180. `/organizations/${org.slug}/repos/4/`,
  181. expect.objectContaining({
  182. data: {integrationId: '1'},
  183. })
  184. );
  185. });
  186. });
  187. });