organizationGeneralSettings.spec.jsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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. let 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. let wrapper = mount(
  44. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  45. TestStubs.routerContext()
  46. );
  47. let 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. let wrapper = mount(
  64. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  65. TestStubs.routerContext()
  66. );
  67. let 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. let 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 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. let 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. let wrapper = mount(
  142. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  143. TestStubs.routerContext()
  144. );
  145. let 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. let wrapper = mount(
  167. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  168. TestStubs.routerContext([
  169. {
  170. organization: TestStubs.Organization({
  171. features: ['require-2fa'],
  172. }),
  173. },
  174. ])
  175. );
  176. wrapper.setState({loading: false});
  177. await tick();
  178. wrapper.update();
  179. expect(wrapper.find('Switch[name="require2FA"]')).toHaveLength(1);
  180. });
  181. it('enables require2fa but cancels confirm modal', async function() {
  182. let mock = MockApiClient.addMockResponse({
  183. url: '/organizations/org-slug/',
  184. method: 'PUT',
  185. });
  186. let wrapper = mount(
  187. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  188. TestStubs.routerContext([
  189. {
  190. organization: TestStubs.Organization({
  191. features: ['require-2fa'],
  192. }),
  193. },
  194. ])
  195. );
  196. wrapper.setState({loading: false});
  197. await tick();
  198. wrapper.update();
  199. expect(wrapper.find('Switch[name="require2FA"]')).toHaveLength(1);
  200. wrapper.find('Switch[name="require2FA"]').simulate('click');
  201. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(1);
  202. // Cancel
  203. wrapper
  204. .find('Field[name="require2FA"] ModalDialog .modal-footer Button')
  205. .first()
  206. .simulate('click');
  207. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(0);
  208. expect(wrapper.find('Switch[name="require2FA"]').prop('isActive')).toBe(false);
  209. expect(mock).not.toHaveBeenCalled();
  210. });
  211. it('enables require2fa with confirm modal', async function() {
  212. let mock = MockApiClient.addMockResponse({
  213. url: '/organizations/org-slug/',
  214. method: 'PUT',
  215. });
  216. let wrapper = mount(
  217. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  218. TestStubs.routerContext([
  219. {
  220. organization: TestStubs.Organization({
  221. features: ['require-2fa'],
  222. }),
  223. },
  224. ])
  225. );
  226. wrapper.setState({loading: false});
  227. await tick();
  228. wrapper.update();
  229. expect(wrapper.find('Switch[name="require2FA"]')).toHaveLength(1);
  230. wrapper.find('Switch[name="require2FA"]').simulate('click');
  231. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(1);
  232. // Confirm
  233. wrapper
  234. .find(
  235. 'Field[name="require2FA"] ModalDialog .modal-footer Button[priority="primary"]'
  236. )
  237. .simulate('click');
  238. expect(wrapper.find('Field[name="require2FA"] ModalDialog')).toHaveLength(0);
  239. expect(wrapper.find('Switch[name="require2FA"]').prop('isActive')).toBe(true);
  240. expect(mock).toHaveBeenCalledWith(
  241. '/organizations/org-slug/',
  242. expect.objectContaining({
  243. method: 'PUT',
  244. data: {
  245. require2FA: true,
  246. },
  247. })
  248. );
  249. });
  250. it('returns to "off" if switch enable fails (e.g. API error)', async function() {
  251. MockApiClient.addMockResponse({
  252. url: '/organizations/org-slug/',
  253. method: 'PUT',
  254. statusCode: 500,
  255. });
  256. let wrapper = mount(
  257. <OrganizationGeneralSettings params={{orgId: org.slug}} />,
  258. TestStubs.routerContext([
  259. {
  260. organization: TestStubs.Organization({
  261. features: ['require-2fa'],
  262. }),
  263. },
  264. ])
  265. );
  266. wrapper.setState({loading: false});
  267. await tick();
  268. wrapper.update();
  269. wrapper.find('Switch[name="require2FA"]').simulate('click');
  270. // hide console.error for this test
  271. sinon.stub(console, 'error');
  272. // Confirm but has API failure
  273. wrapper
  274. .find(
  275. 'Field[name="require2FA"] ModalDialog .modal-footer Button[priority="primary"]'
  276. )
  277. .simulate('click');
  278. await tick();
  279. wrapper.update();
  280. expect(wrapper.find('Switch[name="require2FA"]').prop('isActive')).toBe(false);
  281. // eslint-disable-next-line no-console
  282. console.error.restore();
  283. });
  284. });