overview.polling.spec.jsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  3. import StreamGroup from 'sentry/components/stream/group';
  4. import TagStore from 'sentry/stores/tagStore';
  5. import IssueList from 'sentry/views/issueList/overview';
  6. jest.mock('sentry/views/issueList/filters', () => jest.fn(() => null));
  7. jest.mock('sentry/components/stream/group', () => jest.fn(() => null));
  8. jest.mock('js-cookie', () => ({
  9. get: jest.fn(),
  10. set: jest.fn(),
  11. }));
  12. const PREVIOUS_PAGE_CURSOR = '1443575731';
  13. const DEFAULT_LINKS_HEADER =
  14. `<http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1>; rel="previous"; results="false"; cursor="${PREVIOUS_PAGE_CURSOR}:0:1", ` +
  15. '<http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=1443575000:0:0>; rel="next"; results="true"; cursor="1443575000:0:0"';
  16. jest.useFakeTimers();
  17. describe('IssueList -> Polling', function () {
  18. let issuesRequest;
  19. let pollRequest;
  20. const {organization, project, router, routerContext} = initializeOrg({
  21. organization: {
  22. access: ['releases'],
  23. },
  24. });
  25. const savedSearch = TestStubs.Search({
  26. id: '789',
  27. query: 'is:unresolved',
  28. name: 'Unresolved Issues',
  29. projectId: project.id,
  30. });
  31. const group = TestStubs.Group({project});
  32. const defaultProps = {
  33. location: {query: {query: 'is:unresolved'}, search: 'query=is:unresolved'},
  34. params: {orgId: organization.slug},
  35. organization,
  36. };
  37. /* helpers */
  38. const renderComponent = async ({params, location, ...p} = {}) => {
  39. const newRouter = {
  40. ...router,
  41. params: {
  42. ...router.params,
  43. ...params,
  44. },
  45. location: {
  46. ...router.location,
  47. ...location,
  48. },
  49. };
  50. render(<IssueList {...newRouter} {...defaultProps} {...p} />, {
  51. context: routerContext,
  52. });
  53. await Promise.resolve();
  54. jest.runAllTimers();
  55. };
  56. beforeEach(function () {
  57. // The tests fail because we have a "component update was not wrapped in act" error.
  58. // It should be safe to ignore this error, but we should remove the mock once we move to react testing library
  59. // eslint-disable-next-line no-console
  60. jest.spyOn(console, 'error').mockImplementation(jest.fn());
  61. MockApiClient.clearMockResponses();
  62. MockApiClient.addMockResponse({
  63. url: '/organizations/org-slug/searches/',
  64. body: [savedSearch],
  65. });
  66. MockApiClient.addMockResponse({
  67. url: '/organizations/org-slug/recent-searches/',
  68. body: [],
  69. });
  70. MockApiClient.addMockResponse({
  71. url: '/organizations/org-slug/issues-count/',
  72. method: 'GET',
  73. body: [{}],
  74. });
  75. MockApiClient.addMockResponse({
  76. url: '/organizations/org-slug/processingissues/',
  77. method: 'GET',
  78. body: [
  79. {
  80. project: 'test-project',
  81. numIssues: 1,
  82. hasIssues: true,
  83. lastSeen: '2019-01-16T15:39:11.081Z',
  84. },
  85. ],
  86. });
  87. MockApiClient.addMockResponse({
  88. url: '/organizations/org-slug/tags/',
  89. method: 'GET',
  90. body: TestStubs.Tags(),
  91. });
  92. MockApiClient.addMockResponse({
  93. url: '/organizations/org-slug/users/',
  94. method: 'GET',
  95. body: [TestStubs.Member({projects: [project.slug]})],
  96. });
  97. MockApiClient.addMockResponse({
  98. url: '/organizations/org-slug/recent-searches/',
  99. method: 'GET',
  100. body: [],
  101. });
  102. MockApiClient.addMockResponse({
  103. url: '/organizations/org-slug/searches/',
  104. body: [savedSearch],
  105. });
  106. issuesRequest = MockApiClient.addMockResponse({
  107. url: '/organizations/org-slug/issues/',
  108. body: [group],
  109. headers: {
  110. Link: DEFAULT_LINKS_HEADER,
  111. },
  112. });
  113. MockApiClient.addMockResponse({
  114. url: '/organizations/org-slug/issues-stats/',
  115. body: [TestStubs.GroupStats()],
  116. });
  117. pollRequest = MockApiClient.addMockResponse({
  118. url: `/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`,
  119. body: [],
  120. headers: {
  121. Link: DEFAULT_LINKS_HEADER,
  122. },
  123. });
  124. StreamGroup.mockClear();
  125. TagStore.init();
  126. });
  127. afterEach(function () {
  128. MockApiClient.clearMockResponses();
  129. });
  130. it('toggles polling for new issues', async function () {
  131. await renderComponent();
  132. expect(issuesRequest).toHaveBeenCalledWith(
  133. expect.anything(),
  134. expect.objectContaining({
  135. // Should be called with default query
  136. data: expect.stringContaining('is%3Aunresolved'),
  137. })
  138. );
  139. // Enable realtime updates
  140. userEvent.click(screen.getByRole('button', {name: 'Enable real-time updates'}));
  141. // Each poll request gets delayed by additional 3s, up to max of 60s
  142. jest.advanceTimersByTime(3001);
  143. expect(pollRequest).toHaveBeenCalledTimes(1);
  144. jest.advanceTimersByTime(6001);
  145. expect(pollRequest).toHaveBeenCalledTimes(2);
  146. // Pauses
  147. userEvent.click(screen.getByRole('button', {name: 'Pause real-time updates'}));
  148. jest.advanceTimersByTime(12001);
  149. expect(pollRequest).toHaveBeenCalledTimes(2);
  150. });
  151. it('stops polling for new issues when endpoint returns a 401', async function () {
  152. pollRequest = MockApiClient.addMockResponse({
  153. url: `/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`,
  154. body: [],
  155. statusCode: 401,
  156. });
  157. await renderComponent();
  158. // Enable real time control
  159. userEvent.click(screen.getByRole('button', {name: 'Enable real-time updates'}));
  160. // Each poll request gets delayed by additional 3s, up to max of 60s
  161. jest.advanceTimersByTime(3001);
  162. expect(pollRequest).toHaveBeenCalledTimes(1);
  163. jest.advanceTimersByTime(9001);
  164. expect(pollRequest).toHaveBeenCalledTimes(1);
  165. });
  166. it('stops polling for new issues when endpoint returns a 403', async function () {
  167. pollRequest = MockApiClient.addMockResponse({
  168. url: `/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`,
  169. body: [],
  170. statusCode: 403,
  171. });
  172. await renderComponent();
  173. // Enable real time control
  174. userEvent.click(screen.getByRole('button', {name: 'Enable real-time updates'}));
  175. // Each poll request gets delayed by additional 3s, up to max of 60s
  176. jest.advanceTimersByTime(3001);
  177. expect(pollRequest).toHaveBeenCalledTimes(1);
  178. jest.advanceTimersByTime(9001);
  179. expect(pollRequest).toHaveBeenCalledTimes(1);
  180. });
  181. it('stops polling for new issues when endpoint returns a 404', async function () {
  182. pollRequest = MockApiClient.addMockResponse({
  183. url: `/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`,
  184. body: [],
  185. statusCode: 404,
  186. });
  187. await renderComponent();
  188. // Enable real time control
  189. userEvent.click(screen.getByRole('button', {name: 'Enable real-time updates'}));
  190. // Each poll request gets delayed by additional 3s, up to max of 60s
  191. jest.advanceTimersByTime(3001);
  192. expect(pollRequest).toHaveBeenCalledTimes(1);
  193. jest.advanceTimersByTime(9001);
  194. expect(pollRequest).toHaveBeenCalledTimes(1);
  195. });
  196. });