organizationGeneralSettings.spec.jsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import {browserHistory} from 'react-router';
  2. import {mount} from 'enzyme';
  3. import React from 'react';
  4. import OrganizationGeneralSettings from 'app/views/settings/organizationGeneralSettings';
  5. jest.mock('jquery');
  6. jest.mock('react-router', () => {
  7. return {
  8. browserHistory: {
  9. push: jest.fn(),
  10. replace: jest.fn(),
  11. },
  12. };
  13. });
  14. describe('OrganizationGeneralSettings', function() {
  15. const org = TestStubs.Organization();
  16. const ENDPOINT = `/organizations/${org.slug}/`;
  17. beforeEach(function() {
  18. MockApiClient.clearMockResponses();
  19. MockApiClient.addMockResponse({
  20. url: ENDPOINT,
  21. body: TestStubs.Organization(),
  22. });
  23. browserHistory.push.mockReset();
  24. browserHistory.replace.mockReset();
  25. });
  26. it('has LoadingError on error', async function() {
  27. MockApiClient.clearMockResponses();
  28. MockApiClient.addMockResponse({
  29. url: ENDPOINT,
  30. statusCode: 500,
  31. body: {},
  32. });
  33. const wrapper = mount(
  34. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  35. TestStubs.routerContext()
  36. );
  37. await tick();
  38. wrapper.update();
  39. expect(wrapper.find('LoadingIndicator')).toHaveLength(0);
  40. expect(wrapper.find('LoadingError')).toHaveLength(1);
  41. });
  42. it('can enable "early adopter"', async function() {
  43. const wrapper = mount(
  44. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  45. TestStubs.routerContext()
  46. );
  47. const mock = MockApiClient.addMockResponse({
  48. url: ENDPOINT,
  49. method: 'PUT',
  50. });
  51. wrapper.setState({loading: false});
  52. await tick();
  53. wrapper.update();
  54. wrapper.find('Switch[id="isEarlyAdopter"]').simulate('click');
  55. expect(mock).toHaveBeenCalledWith(
  56. ENDPOINT,
  57. expect.objectContaining({
  58. data: {isEarlyAdopter: true},
  59. })
  60. );
  61. });
  62. it('changes org slug and redirects to new slug', async function() {
  63. const wrapper = mount(
  64. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  65. TestStubs.routerContext()
  66. );
  67. const mock = MockApiClient.addMockResponse({
  68. url: ENDPOINT,
  69. method: 'PUT',
  70. });
  71. wrapper.setState({loading: false});
  72. await tick();
  73. wrapper.update();
  74. // Change slug
  75. wrapper
  76. .find('input[id="slug"]')
  77. .simulate('change', {target: {value: 'new-slug'}})
  78. .simulate('blur');
  79. wrapper.find('SaveButton').simulate('click');
  80. expect(mock).toHaveBeenCalledWith(
  81. ENDPOINT,
  82. expect.objectContaining({
  83. data: {slug: 'new-slug'},
  84. })
  85. );
  86. await tick();
  87. // Not sure why this needs to be async, but it does
  88. expect(browserHistory.replace).toHaveBeenCalledWith('/settings/new-slug/');
  89. });
  90. it('disables the entire form if user does not have write access', async function() {
  91. const readOnlyOrg = TestStubs.Organization({access: ['org:read']});
  92. MockApiClient.clearMockResponses();
  93. MockApiClient.addMockResponse({
  94. url: ENDPOINT,
  95. body: readOnlyOrg,
  96. });
  97. const wrapper = mount(
  98. <OrganizationGeneralSettings routes={[]} params={{orgId: readOnlyOrg.slug}} />,
  99. TestStubs.routerContext([{organization: readOnlyOrg}])
  100. );
  101. wrapper.setState({loading: false});
  102. await tick();
  103. wrapper.update();
  104. expect(wrapper.find('Form FormField[disabled=false]')).toHaveLength(0);
  105. expect(
  106. wrapper
  107. .find('PermissionAlert')
  108. .first()
  109. .text()
  110. ).toEqual(
  111. 'These settings can only be edited by users with the organization owner or manager role.'
  112. );
  113. });
  114. it('does not have remove organization button', async function() {
  115. MockApiClient.clearMockResponses();
  116. MockApiClient.addMockResponse({
  117. url: ENDPOINT,
  118. body: TestStubs.Organization({
  119. projects: [{slug: 'project'}],
  120. access: ['org:write'],
  121. }),
  122. });
  123. const wrapper = mount(
  124. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  125. TestStubs.routerContext()
  126. );
  127. wrapper.setState({loading: false});
  128. await tick();
  129. wrapper.update();
  130. expect(wrapper.find('Confirm[priority="danger"]')).toHaveLength(0);
  131. });
  132. it('can remove organization when org admin', async function() {
  133. MockApiClient.clearMockResponses();
  134. MockApiClient.addMockResponse({
  135. url: ENDPOINT,
  136. body: TestStubs.Organization({
  137. projects: [{slug: 'project'}],
  138. access: ['org:admin'],
  139. }),
  140. });
  141. const wrapper = mount(
  142. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  143. TestStubs.routerContext()
  144. );
  145. const mock = MockApiClient.addMockResponse({
  146. url: ENDPOINT,
  147. method: 'DELETE',
  148. });
  149. wrapper.setState({loading: false});
  150. await tick();
  151. wrapper.update();
  152. wrapper.find('Confirm[priority="danger"]').simulate('click');
  153. // Lists projects in modal
  154. expect(wrapper.find('Modal .ref-projects')).toHaveLength(1);
  155. expect(wrapper.find('Modal .ref-projects li').text()).toBe('project');
  156. // Confirm delete
  157. wrapper.find('Modal Portal Button[priority="danger"]').simulate('click');
  158. expect(mock).toHaveBeenCalledWith(
  159. ENDPOINT,
  160. expect.objectContaining({
  161. method: 'DELETE',
  162. })
  163. );
  164. });
  165. it('shows require2fa switch w/ feature flag', async function() {
  166. const wrapper = mount(
  167. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  168. TestStubs.routerContext()
  169. );
  170. wrapper.setState({loading: false});
  171. await tick();
  172. wrapper.update();
  173. expect(wrapper.find('Switch[name="require2FA"]')).toHaveLength(1);
  174. });
  175. it('enables require2fa but cancels confirm modal', async function() {
  176. const mock = MockApiClient.addMockResponse({
  177. url: '/organizations/org-slug/',
  178. method: 'PUT',
  179. });
  180. const wrapper = mount(
  181. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  182. TestStubs.routerContext()
  183. );
  184. wrapper.setState({loading: false});
  185. await tick();
  186. wrapper.update();
  187. expect(wrapper.find('Switch[name="require2FA"]')).toHaveLength(1);
  188. wrapper.find('Switch[name="require2FA"]').simulate('click');
  189. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(1);
  190. // Cancel
  191. wrapper
  192. .find('Field[name="require2FA"] ModalDialog .modal-footer Button')
  193. .first()
  194. .simulate('click');
  195. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(0);
  196. expect(wrapper.find('Switch[name="require2FA"]').prop('isActive')).toBe(false);
  197. expect(mock).not.toHaveBeenCalled();
  198. });
  199. it('enables require2fa with confirm modal', async function() {
  200. const mock = MockApiClient.addMockResponse({
  201. url: '/organizations/org-slug/',
  202. method: 'PUT',
  203. });
  204. const wrapper = mount(
  205. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  206. TestStubs.routerContext()
  207. );
  208. wrapper.setState({loading: false});
  209. await tick();
  210. wrapper.update();
  211. expect(wrapper.find('Switch[name="require2FA"]')).toHaveLength(1);
  212. wrapper.find('Switch[name="require2FA"]').simulate('click');
  213. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(1);
  214. // Confirm
  215. wrapper
  216. .find(
  217. 'Field[name="require2FA"] ModalDialog .modal-footer Button[priority="primary"]'
  218. )
  219. .simulate('click');
  220. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(0);
  221. expect(wrapper.find('Switch[name="require2FA"]').prop('isActive')).toBe(true);
  222. expect(mock).toHaveBeenCalledWith(
  223. '/organizations/org-slug/',
  224. expect.objectContaining({
  225. method: 'PUT',
  226. data: {
  227. require2FA: true,
  228. },
  229. })
  230. );
  231. });
  232. it('returns to "off" if switch enable fails (e.g. API error)', async function() {
  233. MockApiClient.addMockResponse({
  234. url: '/organizations/org-slug/',
  235. method: 'PUT',
  236. statusCode: 500,
  237. });
  238. const wrapper = mount(
  239. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  240. TestStubs.routerContext()
  241. );
  242. wrapper.setState({loading: false});
  243. await tick();
  244. wrapper.update();
  245. wrapper.find('Switch[name="require2FA"]').simulate('click');
  246. // hide console.error for this test
  247. jest.spyOn(console, 'error').mockImplementation(() => {});
  248. // Confirm but has API failure
  249. wrapper
  250. .find(
  251. 'Field[name="require2FA"] ModalDialog .modal-footer Button[priority="primary"]'
  252. )
  253. .simulate('click');
  254. await tick();
  255. wrapper.update();
  256. expect(wrapper.find('Switch[name="require2FA"]').prop('isActive')).toBe(false);
  257. // eslint-disable-next-line no-console
  258. console.error.mockRestore();
  259. });
  260. });