integrationRepos.spec.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. // we only attempt to fetch repositories upon typing
  33. await userEvent.click(screen.getByText('Add Repository'));
  34. await userEvent.type(screen.getByRole('textbox'), 'asdf');
  35. expect(
  36. await screen.findByText(
  37. /We were unable to fetch repositories for this integration/
  38. )
  39. ).toBeInTheDocument();
  40. });
  41. it('does not fetch repositories with empty query', async function () {
  42. MockApiClient.addMockResponse({
  43. url: `/organizations/${org.slug}/repos/`,
  44. method: 'GET',
  45. body: [],
  46. });
  47. render(<IntegrationRepos integration={integration} />);
  48. await userEvent.click(screen.getByText('Add Repository'));
  49. expect(
  50. await screen.findByText(/Please enter a repository name/)
  51. ).toBeInTheDocument();
  52. });
  53. });
  54. describe('Adding repositories', function () {
  55. it('can save successfully', async function () {
  56. const addRepo = MockApiClient.addMockResponse({
  57. url: `/organizations/${org.slug}/repos/`,
  58. method: 'POST',
  59. body: RepositoryFixture({integrationId: '1'}),
  60. });
  61. MockApiClient.addMockResponse({
  62. url: `/organizations/${org.slug}/integrations/1/repos/`,
  63. body: {
  64. repos: [{identifier: 'example/repo-name', name: 'repo-name'}],
  65. },
  66. });
  67. MockApiClient.addMockResponse({
  68. url: `/organizations/${org.slug}/repos/`,
  69. method: 'GET',
  70. body: [],
  71. });
  72. render(<IntegrationRepos integration={integration} />);
  73. await userEvent.click(screen.getByText('Add Repository'));
  74. await userEvent.type(screen.getByRole('textbox'), 'repo-name');
  75. await userEvent.click(screen.getByText('repo-name'));
  76. expect(addRepo).toHaveBeenCalledWith(
  77. `/organizations/${org.slug}/repos/`,
  78. expect.objectContaining({
  79. data: {
  80. installation: '1',
  81. provider: 'integrations:github',
  82. identifier: 'example/repo-name',
  83. },
  84. })
  85. );
  86. expect(await screen.findByText('example/repo-name')).toBeInTheDocument();
  87. expect(resetReposSpy).toHaveBeenCalled();
  88. });
  89. it('handles failure during save', async function () {
  90. const addRepo = MockApiClient.addMockResponse({
  91. url: `/organizations/${org.slug}/repos/`,
  92. method: 'POST',
  93. statusCode: 400,
  94. body: {
  95. errors: {
  96. __all__: 'Repository already exists.',
  97. },
  98. },
  99. });
  100. MockApiClient.addMockResponse({
  101. url: `/organizations/${org.slug}/integrations/1/repos/`,
  102. body: {
  103. repos: [{identifier: 'getsentry/sentry', name: 'sentry-repo'}],
  104. },
  105. });
  106. MockApiClient.addMockResponse({
  107. url: `/organizations/${org.slug}/repos/`,
  108. method: 'GET',
  109. body: [],
  110. });
  111. render(<IntegrationRepos integration={integration} />);
  112. await userEvent.click(screen.getByText('Add Repository'));
  113. await userEvent.type(screen.getByRole('textbox'), 'sentry-repo');
  114. await userEvent.click(screen.getByText('sentry-repo'));
  115. expect(addRepo).toHaveBeenCalled();
  116. expect(screen.queryByText('getsentry/sentry')).not.toBeInTheDocument();
  117. });
  118. it('does not disable add repo for members', async function () {
  119. MockApiClient.addMockResponse({
  120. url: `/organizations/${org.slug}/integrations/1/repos/`,
  121. body: {
  122. repos: [{identifier: 'example/repo-name', name: 'repo-name'}],
  123. },
  124. });
  125. MockApiClient.addMockResponse({
  126. url: `/organizations/${org.slug}/repos/`,
  127. method: 'GET',
  128. body: [],
  129. });
  130. render(
  131. <IntegrationRepos
  132. integration={integration}
  133. organization={OrganizationFixture({access: []})}
  134. />
  135. );
  136. await waitFor(() => expect(screen.getByText('Add Repository')).toBeEnabled());
  137. });
  138. });
  139. describe('migratable repo', function () {
  140. it('associates repository with integration', async () => {
  141. MockApiClient.addMockResponse({
  142. url: `/organizations/${org.slug}/repos/`,
  143. body: [
  144. RepositoryFixture({
  145. integrationId: undefined,
  146. externalSlug: 'example/repo-name',
  147. provider: {
  148. id: 'integrations:github',
  149. name: 'GitHub',
  150. },
  151. }),
  152. ],
  153. });
  154. MockApiClient.addMockResponse({
  155. url: `/organizations/${org.slug}/integrations/${integration.id}/repos/`,
  156. body: {repos: [{identifier: 'example/repo-name', name: 'repo-name'}]},
  157. });
  158. const updateRepo = MockApiClient.addMockResponse({
  159. method: 'PUT',
  160. url: `/organizations/${org.slug}/repos/4/`,
  161. body: {id: 244},
  162. });
  163. render(<IntegrationRepos integration={integration} />);
  164. await userEvent.click(screen.getByText('Add Repository'));
  165. await userEvent.type(screen.getByRole('textbox'), 'repo-name');
  166. await userEvent.click(screen.getByText('repo-name'));
  167. expect(updateRepo).toHaveBeenCalledWith(
  168. `/organizations/${org.slug}/repos/4/`,
  169. expect.objectContaining({
  170. data: {integrationId: '1'},
  171. })
  172. );
  173. await waitFor(() => expect(resetReposSpy).toHaveBeenCalled());
  174. });
  175. it('uses externalSlug not name for comparison', async () => {
  176. MockApiClient.addMockResponse({
  177. url: `/organizations/${org.slug}/repos/`,
  178. method: 'GET',
  179. body: [RepositoryFixture({name: 'repo-name-other', externalSlug: '9876'})],
  180. });
  181. const getItems = MockApiClient.addMockResponse({
  182. url: `/organizations/${org.slug}/integrations/${integration.id}/repos/`,
  183. method: 'GET',
  184. body: {
  185. repos: [{identifier: '9876', name: 'repo-name'}],
  186. },
  187. });
  188. const updateRepo = MockApiClient.addMockResponse({
  189. method: 'PUT',
  190. url: `/organizations/${org.slug}/repos/4/`,
  191. body: {id: 4320},
  192. });
  193. render(<IntegrationRepos integration={integration} />);
  194. await userEvent.click(screen.getByText('Add Repository'));
  195. await userEvent.type(screen.getByRole('textbox'), 'repo-name');
  196. await userEvent.click(screen.getByText('repo-name'));
  197. expect(getItems).toHaveBeenCalled();
  198. expect(updateRepo).toHaveBeenCalledWith(
  199. `/organizations/${org.slug}/repos/4/`,
  200. expect.objectContaining({
  201. data: {integrationId: '1'},
  202. })
  203. );
  204. });
  205. });
  206. });