organizationContextContainer.spec.jsx 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {openSudo} from 'sentry/actionCreators/modal';
  3. import * as OrganizationActionCreator from 'sentry/actionCreators/organization';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import OrganizationStore from 'sentry/stores/organizationStore';
  6. import ProjectsStore from 'sentry/stores/projectsStore';
  7. import TeamStore from 'sentry/stores/teamStore';
  8. import {OrganizationLegacyContext} from 'sentry/views/organizationContextContainer';
  9. jest.mock('sentry/stores/configStore', () => ({
  10. get: jest.fn(),
  11. }));
  12. jest.mock('sentry/actionCreators/modal', () => ({
  13. openSudo: jest.fn(),
  14. }));
  15. describe('OrganizationContextContainer', function () {
  16. let wrapper;
  17. const org = TestStubs.Organization();
  18. const teams = [TestStubs.Team()];
  19. const projects = [TestStubs.Project()];
  20. const api = new MockApiClient();
  21. let getOrgMock;
  22. let getProjectsMock;
  23. let getTeamsMock;
  24. const createWrapper = props => {
  25. wrapper = mountWithTheme(
  26. <OrganizationLegacyContext
  27. api={api}
  28. params={{orgId: 'org-slug'}}
  29. location={{query: {}}}
  30. routes={[]}
  31. {...props}
  32. >
  33. <div />
  34. </OrganizationLegacyContext>
  35. );
  36. return wrapper;
  37. };
  38. beforeEach(function () {
  39. MockApiClient.clearMockResponses();
  40. getOrgMock = MockApiClient.addMockResponse({
  41. url: '/organizations/org-slug/',
  42. body: org,
  43. });
  44. getProjectsMock = MockApiClient.addMockResponse({
  45. url: '/organizations/org-slug/projects/',
  46. body: projects,
  47. });
  48. getTeamsMock = MockApiClient.addMockResponse({
  49. url: '/organizations/org-slug/teams/',
  50. body: teams,
  51. });
  52. jest.spyOn(TeamStore, 'loadInitialData');
  53. jest.spyOn(ProjectsStore, 'loadInitialData');
  54. jest.spyOn(OrganizationActionCreator, 'fetchOrganizationDetails');
  55. });
  56. afterEach(function () {
  57. wrapper.unmount();
  58. OrganizationStore.reset();
  59. TeamStore.loadInitialData.mockRestore();
  60. ProjectsStore.loadInitialData.mockRestore();
  61. ConfigStore.get.mockRestore();
  62. OrganizationActionCreator.fetchOrganizationDetails.mockRestore();
  63. });
  64. it('renders and fetches org, projects, and teams', async function () {
  65. wrapper = createWrapper();
  66. // await dispatching the action to org store
  67. await tick();
  68. // await resolving the api promise from action creator and updating component
  69. await tick();
  70. expect(getOrgMock).toHaveBeenCalled();
  71. expect(getProjectsMock).toHaveBeenCalled();
  72. expect(getTeamsMock).toHaveBeenCalled();
  73. expect(wrapper.state('loading')).toBe(false);
  74. expect(wrapper.state('error')).toBe(null);
  75. expect(wrapper.state('organization')).toEqual(org);
  76. expect(TeamStore.loadInitialData).toHaveBeenCalledWith(teams);
  77. expect(ProjectsStore.loadInitialData).toHaveBeenCalledWith(projects);
  78. expect(OrganizationActionCreator.fetchOrganizationDetails).toHaveBeenCalledWith(
  79. api,
  80. 'org-slug',
  81. true,
  82. true
  83. );
  84. });
  85. it('fetches new org when router params change', async function () {
  86. const newOrg = TestStubs.Organization({slug: 'new-slug'});
  87. wrapper = createWrapper();
  88. const instance = wrapper.instance();
  89. jest.spyOn(instance, 'render');
  90. // initial render
  91. await tick();
  92. await tick();
  93. expect(instance.state.organization).toEqual(org);
  94. expect(instance.render).toHaveBeenCalledTimes(1);
  95. const mock = MockApiClient.addMockResponse({
  96. url: '/organizations/new-slug/',
  97. body: newOrg,
  98. });
  99. const projectsMock = MockApiClient.addMockResponse({
  100. url: '/organizations/new-slug/projects/',
  101. body: projects,
  102. });
  103. const teamsMock = MockApiClient.addMockResponse({
  104. url: '/organizations/new-slug/teams/',
  105. body: teams,
  106. });
  107. wrapper.setProps({params: {orgId: newOrg.slug}}, () => {
  108. // state should be reset based on props
  109. expect(instance.state.organization).toEqual(null);
  110. expect(instance.render).toHaveBeenCalledTimes(2);
  111. });
  112. // await fetching new org
  113. await tick();
  114. await tick();
  115. wrapper.update();
  116. expect(mock).toHaveBeenLastCalledWith('/organizations/new-slug/', expect.anything());
  117. expect(projectsMock).toHaveBeenCalled();
  118. expect(teamsMock).toHaveBeenCalled();
  119. expect(wrapper.state('loading')).toBe(false);
  120. expect(wrapper.state('error')).toBe(null);
  121. expect(wrapper.state('organization')).toEqual(newOrg);
  122. });
  123. it('shows loading error for non-superusers on 403s', async function () {
  124. getOrgMock = MockApiClient.addMockResponse({
  125. url: '/organizations/org-slug/',
  126. statusCode: 403,
  127. });
  128. jest.spyOn(console, 'error').mockImplementation(jest.fn()); // eslint-disable-line no-console
  129. wrapper = createWrapper();
  130. // await dispatching action
  131. await tick();
  132. // await resolving api, and updating component
  133. await tick();
  134. await tick();
  135. wrapper.update();
  136. expect(wrapper.find('LoadingError')).toHaveLength(1);
  137. console.error.mockRestore(); // eslint-disable-line no-console
  138. });
  139. it('opens sudo modal for superusers on 403s', async function () {
  140. ConfigStore.get.mockImplementation(() => ({
  141. isSuperuser: true,
  142. }));
  143. getOrgMock = MockApiClient.addMockResponse({
  144. url: '/organizations/org-slug/',
  145. statusCode: 403,
  146. });
  147. wrapper = createWrapper();
  148. // await dispatching action
  149. await tick();
  150. // await resolving api, and updating component
  151. await tick();
  152. await tick();
  153. wrapper.update();
  154. expect(openSudo).toHaveBeenCalled();
  155. });
  156. it('uses last organization from ConfigStore', async function () {
  157. getOrgMock = MockApiClient.addMockResponse({
  158. url: '/organizations/last-org/',
  159. body: org,
  160. });
  161. MockApiClient.addMockResponse({
  162. url: '/organizations/last-org/projects/',
  163. body: projects,
  164. });
  165. MockApiClient.addMockResponse({
  166. url: '/organizations/last-org/teams/',
  167. body: teams,
  168. });
  169. // mocking `.get('lastOrganization')`
  170. ConfigStore.get.mockImplementation(() => 'last-org');
  171. wrapper = createWrapper({useLastOrganization: true, params: {}});
  172. // await dispatching action
  173. await tick();
  174. // await dispatching the action to org store
  175. await tick();
  176. expect(getOrgMock).toHaveBeenLastCalledWith(
  177. '/organizations/last-org/',
  178. expect.anything()
  179. );
  180. });
  181. it('uses last organization from `organizations` prop', async function () {
  182. MockApiClient.addMockResponse({
  183. url: '/organizations/foo/environments/',
  184. body: TestStubs.Environments(),
  185. });
  186. getOrgMock = MockApiClient.addMockResponse({
  187. url: '/organizations/foo/',
  188. body: org,
  189. });
  190. getProjectsMock = MockApiClient.addMockResponse({
  191. url: '/organizations/foo/projects/',
  192. body: projects,
  193. });
  194. getTeamsMock = MockApiClient.addMockResponse({
  195. url: '/organizations/foo/teams/',
  196. body: teams,
  197. });
  198. ConfigStore.get.mockImplementation(() => '');
  199. wrapper = createWrapper({
  200. useLastOrganization: true,
  201. params: {orgId: ''},
  202. organizationsLoading: true,
  203. organizations: [],
  204. });
  205. expect(wrapper.find('LoadingTriangle')).toHaveLength(1);
  206. wrapper.setProps({
  207. organizationsLoading: false,
  208. organizations: [
  209. TestStubs.Organization({slug: 'foo'}),
  210. TestStubs.Organization({slug: 'bar'}),
  211. ],
  212. });
  213. await tick(); // action to start fetch org
  214. await tick(); // action after successfully fetching org
  215. wrapper.update();
  216. expect(wrapper.find('LoadingTriangle')).toHaveLength(0);
  217. expect(getOrgMock).toHaveBeenCalled();
  218. expect(getProjectsMock).toHaveBeenCalled();
  219. expect(getTeamsMock).toHaveBeenCalled();
  220. });
  221. it('uses last organization when no orgId in URL - and fetches org details once', async function () {
  222. ConfigStore.get.mockImplementation(() => 'my-last-org');
  223. getOrgMock = MockApiClient.addMockResponse({
  224. url: '/organizations/my-last-org/',
  225. body: TestStubs.Organization({slug: 'my-last-org'}),
  226. });
  227. getProjectsMock = MockApiClient.addMockResponse({
  228. url: '/organizations/my-last-org/projects/',
  229. body: projects,
  230. });
  231. getTeamsMock = MockApiClient.addMockResponse({
  232. url: '/organizations/my-last-org/teams/',
  233. body: teams,
  234. });
  235. wrapper = createWrapper({
  236. params: {},
  237. useLastOrganization: true,
  238. organizations: [],
  239. });
  240. // await dispatching action
  241. await tick();
  242. // await resolving api, and updating component
  243. await tick();
  244. wrapper.update();
  245. expect(wrapper.find('LoadingTriangle')).toHaveLength(0);
  246. expect(getOrgMock).toHaveBeenCalledTimes(1);
  247. // Simulate OrganizationsStore being loaded *after* `OrganizationContext` finishes
  248. // org details fetch
  249. wrapper.setProps({
  250. organizationsLoading: false,
  251. organizations: [
  252. TestStubs.Organization({slug: 'foo'}),
  253. TestStubs.Organization({slug: 'bar'}),
  254. ],
  255. });
  256. expect(getOrgMock).toHaveBeenCalledTimes(1);
  257. expect(getProjectsMock).toHaveBeenCalledTimes(1);
  258. expect(getTeamsMock).toHaveBeenCalledTimes(1);
  259. });
  260. it('fetches org details only once if organizations loading store changes', async function () {
  261. wrapper = createWrapper({
  262. params: {orgId: 'org-slug'},
  263. organizationsLoading: true,
  264. organizations: [],
  265. });
  266. // await dispatching action
  267. await tick();
  268. // await resolving api, and updating component
  269. await tick();
  270. wrapper.update();
  271. expect(wrapper.find('LoadingTriangle')).toHaveLength(0);
  272. expect(getOrgMock).toHaveBeenCalledTimes(1);
  273. // Simulate OrganizationsStore being loaded *after* `OrganizationContext` finishes
  274. // org details fetch
  275. wrapper.setProps({
  276. organizationsLoading: false,
  277. organizations: [
  278. TestStubs.Organization({slug: 'foo'}),
  279. TestStubs.Organization({slug: 'bar'}),
  280. ],
  281. });
  282. expect(getOrgMock).toHaveBeenCalledTimes(1);
  283. expect(getProjectsMock).toHaveBeenCalledTimes(1);
  284. expect(getTeamsMock).toHaveBeenCalledTimes(1);
  285. });
  286. });