boostrapRequests.spec.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import type {Scope} from '@sentry/core';
  2. import * as Sentry from '@sentry/react';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {ProjectFixture} from 'sentry-fixture/project';
  5. import {TeamFixture} from 'sentry-fixture/team';
  6. import {renderHook, waitFor} from 'sentry-test/reactTestingLibrary';
  7. import type {ApiResult} from 'sentry/api';
  8. import {ORGANIZATION_FETCH_ERROR_TYPES} from 'sentry/constants';
  9. import LatestContextStore from 'sentry/stores/latestContextStore';
  10. import OrganizationStore from 'sentry/stores/organizationStore';
  11. import ProjectsStore from 'sentry/stores/projectsStore';
  12. import TeamStore from 'sentry/stores/teamStore';
  13. import type {Organization} from 'sentry/types/organization';
  14. import FeatureFlagOverrides from 'sentry/utils/featureFlagOverrides';
  15. import localStorageWrapper from 'sentry/utils/localStorage';
  16. import {
  17. DEFAULT_QUERY_CLIENT_CONFIG,
  18. QueryClient,
  19. QueryClientProvider,
  20. } from 'sentry/utils/queryClient';
  21. import {
  22. useBootstrapOrganizationQuery,
  23. useBootstrapProjectsQuery,
  24. useBootstrapTeamsQuery,
  25. } from './bootstrapRequests';
  26. const queryClient = new QueryClient(DEFAULT_QUERY_CLIENT_CONFIG);
  27. const wrapper = ({children}: {children?: React.ReactNode}) => (
  28. <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  29. );
  30. describe('useBootstrapOrganizationQuery', function () {
  31. const org = OrganizationFixture();
  32. const orgSlug = 'org-slug';
  33. beforeEach(function () {
  34. MockApiClient.clearMockResponses();
  35. OrganizationStore.reset();
  36. queryClient.clear();
  37. localStorageWrapper.clear();
  38. });
  39. it('updates organization store with fetched data', async function () {
  40. MockApiClient.addMockResponse({
  41. url: `/organizations/${orgSlug}/`,
  42. body: org,
  43. query: {detailed: 0, include_feature_flags: 1},
  44. });
  45. const {result} = renderHook(() => useBootstrapOrganizationQuery(orgSlug), {wrapper});
  46. await waitFor(() => expect(result.current.data).toBeDefined());
  47. expect(JSON.stringify(OrganizationStore.get().organization)).toEqual(
  48. JSON.stringify(org)
  49. );
  50. });
  51. it('handles api errors', async function () {
  52. MockApiClient.addMockResponse({
  53. url: `/organizations/${orgSlug}/`,
  54. statusCode: 401,
  55. body: {},
  56. });
  57. const {result} = renderHook(() => useBootstrapOrganizationQuery(orgSlug), {wrapper});
  58. await waitFor(() => expect(result.current.error).toBeDefined());
  59. expect(OrganizationStore.get().organization).toBeNull();
  60. await waitFor(() => expect(OrganizationStore.get().error).toBe(result.current.error));
  61. expect(OrganizationStore.get().errorType).toBe(
  62. ORGANIZATION_FETCH_ERROR_TYPES.ORG_NO_ACCESS
  63. );
  64. });
  65. it('does not fetch when orgSlug is null', function () {
  66. const {result} = renderHook(() => useBootstrapOrganizationQuery(null), {wrapper});
  67. expect(result.current.data).toBeUndefined();
  68. });
  69. it('removes the promise from window.__sentry_preload after use', async function () {
  70. window.__sentry_preload = {
  71. orgSlug: org.slug,
  72. organization: Promise.resolve<ApiResult<Organization>>([org, undefined, undefined]),
  73. };
  74. const {result} = renderHook(() => useBootstrapOrganizationQuery(orgSlug), {wrapper});
  75. await waitFor(() => expect(result.current.data).toBeDefined());
  76. expect(window.__sentry_preload?.organization).toBeUndefined();
  77. });
  78. it('sets feature flags, activates organization, and sets sentry tags', async function () {
  79. // Feature flag overrides are loaded from localstorage
  80. localStorageWrapper.setItem('feature-flag-overrides', '{"enable-issues":true}');
  81. const mockScope = {
  82. setTag: jest.fn(),
  83. setContext: jest.fn(),
  84. } as unknown as Scope;
  85. jest.spyOn(Sentry, 'getCurrentScope').mockReturnValue(mockScope);
  86. MockApiClient.addMockResponse({
  87. url: `/organizations/${orgSlug}/`,
  88. body: org,
  89. query: {detailed: 0, include_feature_flags: 1},
  90. });
  91. const {result} = renderHook(() => useBootstrapOrganizationQuery(orgSlug), {wrapper});
  92. await waitFor(() => expect(result.current.data).toBeDefined());
  93. expect(JSON.stringify(OrganizationStore.get().organization?.features)).toEqual(
  94. JSON.stringify(['enable-issues'])
  95. );
  96. expect(JSON.stringify(LatestContextStore.get().organization)).toEqual(
  97. JSON.stringify({...org, features: ['enable-issues']})
  98. );
  99. expect(FeatureFlagOverrides.singleton().getEnabledFeatureFlagList(org)).toEqual([
  100. 'enable-issues',
  101. ]);
  102. expect(mockScope.setTag).toHaveBeenCalledWith('organization', org.id);
  103. expect(mockScope.setTag).toHaveBeenCalledWith('organization.slug', org.slug);
  104. expect(mockScope.setContext).toHaveBeenCalledWith('organization', {
  105. id: org.id,
  106. slug: org.slug,
  107. });
  108. });
  109. });
  110. describe('useBootstrapTeamsQuery', function () {
  111. const mockTeams = [TeamFixture()];
  112. const orgSlug = 'org-slug';
  113. beforeEach(function () {
  114. MockApiClient.clearMockResponses();
  115. TeamStore.reset();
  116. queryClient.clear();
  117. });
  118. it('updates team store with fetched data', async function () {
  119. MockApiClient.addMockResponse({
  120. url: `/organizations/${orgSlug}/teams/`,
  121. body: mockTeams,
  122. headers: {
  123. Link: '<http://127.0.0.1:8000/api/0/organizations/org-slug/teams/?cursor=0:0:1>; rel="previous"; results="false"; cursor="0:0:1", <http://127.0.0.1:8000/api/0/organizations/org-slug/teams/?cursor=0:100:0>; rel="next"; results="true"; cursor="0:100:0"',
  124. },
  125. });
  126. const {result} = renderHook(() => useBootstrapTeamsQuery(orgSlug), {wrapper});
  127. await waitFor(() => expect(result.current.data).toBeDefined());
  128. expect(TeamStore.getState().teams).toEqual(mockTeams);
  129. expect(TeamStore.getState().hasMore).toBe(true);
  130. });
  131. it('handles api errors', async function () {
  132. MockApiClient.addMockResponse({
  133. url: `/organizations/${orgSlug}/teams/`,
  134. statusCode: 500,
  135. });
  136. const {result} = renderHook(() => useBootstrapTeamsQuery(orgSlug), {wrapper});
  137. await waitFor(() => expect(result.current.error).toBeDefined());
  138. expect(TeamStore.getState().teams).toEqual([]);
  139. });
  140. it('does not fetch when orgSlug is null', function () {
  141. const {result} = renderHook(() => useBootstrapTeamsQuery(null), {wrapper});
  142. expect(result.current.data).toBeUndefined();
  143. });
  144. });
  145. describe('useBootstrapProjectsQuery', function () {
  146. const mockProjects = [ProjectFixture()];
  147. const orgSlug = 'org-slug';
  148. beforeEach(function () {
  149. MockApiClient.clearMockResponses();
  150. ProjectsStore.reset();
  151. queryClient.clear();
  152. });
  153. it('updates projects store with fetched data', async function () {
  154. MockApiClient.addMockResponse({
  155. url: `/organizations/${orgSlug}/projects/`,
  156. body: mockProjects,
  157. query: {
  158. all_projects: 1,
  159. collapse: ['latestDeploys', 'unusedFeatures'],
  160. },
  161. });
  162. const {result} = renderHook(() => useBootstrapProjectsQuery(orgSlug), {wrapper});
  163. await waitFor(() => expect(result.current.data).toBeDefined());
  164. expect(ProjectsStore.getState().projects).toEqual(mockProjects);
  165. });
  166. it('handles api errors', async function () {
  167. MockApiClient.addMockResponse({
  168. url: `/organizations/${orgSlug}/projects/`,
  169. statusCode: 500,
  170. });
  171. const {result} = renderHook(() => useBootstrapProjectsQuery(orgSlug), {wrapper});
  172. await waitFor(() => expect(result.current.error).toBeDefined());
  173. expect(ProjectsStore.getState().projects).toEqual([]);
  174. });
  175. it('does not fetch when orgSlug is null', function () {
  176. const {result} = renderHook(() => useBootstrapProjectsQuery(null), {wrapper});
  177. expect(result.current.data).toBeUndefined();
  178. });
  179. });