integrationRepos.spec.tsx 6.7 KB

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