You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Authentication-it.tsx 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { act, screen, waitFor, within } from '@testing-library/react';
  21. import userEvent from '@testing-library/user-event';
  22. import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
  23. import React from 'react';
  24. import AuthenticationServiceMock from '../../../../../api/mocks/AuthenticationServiceMock';
  25. import ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
  26. import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
  27. import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
  28. import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
  29. import { definitions } from '../../../../../helpers/mocks/definitions-list';
  30. import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
  31. import { byRole, byText } from '../../../../../helpers/testSelector';
  32. import { Feature } from '../../../../../types/features';
  33. import { GitHubProvisioningStatus } from '../../../../../types/provisioning';
  34. import { TaskStatuses } from '../../../../../types/tasks';
  35. import Authentication from '../Authentication';
  36. jest.mock('../../../../../api/system');
  37. let handler: AuthenticationServiceMock;
  38. let system: SystemServiceMock;
  39. let settingsHandler: SettingsServiceMock;
  40. let computeEngineHandler: ComputeEngineServiceMock;
  41. beforeEach(() => {
  42. handler = new AuthenticationServiceMock();
  43. system = new SystemServiceMock();
  44. settingsHandler = new SettingsServiceMock();
  45. computeEngineHandler = new ComputeEngineServiceMock();
  46. [
  47. {
  48. key: 'sonar.auth.saml.signature.enabled',
  49. value: 'false',
  50. },
  51. {
  52. key: 'sonar.auth.saml.enabled',
  53. value: 'false',
  54. },
  55. {
  56. key: 'sonar.auth.saml.applicationId',
  57. value: 'sonarqube',
  58. },
  59. {
  60. key: 'sonar.auth.saml.providerName',
  61. value: 'SAML',
  62. },
  63. ].forEach((setting: any) => settingsHandler.set(setting.key, setting.value));
  64. });
  65. afterEach(() => {
  66. handler.reset();
  67. settingsHandler.reset();
  68. system.reset();
  69. computeEngineHandler.reset();
  70. });
  71. const ui = {
  72. saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
  73. customMessageInformation: byText('settings.authentication.custom_message_information'),
  74. enabledToggle: byRole('switch'),
  75. testButton: byText('settings.authentication.saml.form.test'),
  76. textbox1: byRole('textbox', { name: 'test1' }),
  77. textbox2: byRole('textbox', { name: 'test2' }),
  78. saml: {
  79. noSamlConfiguration: byText('settings.authentication.saml.form.not_configured'),
  80. createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
  81. providerName: byRole('textbox', { name: 'Provider Name' }),
  82. providerId: byRole('textbox', { name: 'Provider ID' }),
  83. providerCertificate: byRole('textbox', { name: 'Identity provider certificate' }),
  84. loginUrl: byRole('textbox', { name: 'SAML login url' }),
  85. userLoginAttribute: byRole('textbox', { name: 'SAML user login attribute' }),
  86. userNameAttribute: byRole('textbox', { name: 'SAML user name attribute' }),
  87. saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
  88. confirmProvisioningButton: byRole('button', { name: 'yes' }),
  89. saveScim: byRole('button', { name: 'save' }),
  90. enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
  91. disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
  92. editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
  93. enableFirstMessage: byText('settings.authentication.saml.enable_first'),
  94. jitProvisioningButton: byRole('radio', {
  95. name: 'settings.authentication.saml.form.provisioning_at_login',
  96. }),
  97. scimProvisioningButton: byRole('radio', {
  98. name: 'settings.authentication.saml.form.provisioning_with_scim',
  99. }),
  100. fillForm: async (user: UserEvent) => {
  101. const { saml } = ui;
  102. await act(async () => {
  103. await user.clear(saml.providerName.get());
  104. await user.type(saml.providerName.get(), 'Awsome SAML config');
  105. await user.type(saml.providerId.get(), 'okta-1234');
  106. await user.type(saml.loginUrl.get(), 'http://test.org');
  107. await user.type(saml.providerCertificate.get(), '-secret-');
  108. await user.type(saml.userLoginAttribute.get(), 'login');
  109. await user.type(saml.userNameAttribute.get(), 'name');
  110. });
  111. },
  112. createConfiguration: async (user: UserEvent) => {
  113. const { saml } = ui;
  114. await act(async () => {
  115. await user.click((await saml.createConfigButton.findAll())[0]);
  116. });
  117. await saml.fillForm(user);
  118. await act(async () => {
  119. await user.click(saml.saveConfigButton.get());
  120. });
  121. },
  122. },
  123. github: {
  124. tab: byRole('tab', { name: 'github GitHub' }),
  125. noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
  126. createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
  127. clientId: byRole('textbox', { name: 'Client ID' }),
  128. clientSecret: byRole('textbox', { name: 'Client Secret' }),
  129. githubAppId: byRole('textbox', { name: 'GitHub App ID' }), // not working
  130. privateKey: byRole('textarea', { name: 'Private Key' }), // not working
  131. githubApiUrl: byRole('textbox', { name: 'The API url for a GitHub instance.' }),
  132. githubWebUrl: byRole('textbox', { name: 'The WEB url for a GitHub instance.' }),
  133. allowUserToSignUp: byRole('switch', {
  134. name: 'sonar.auth.github.allowUsersToSignUp',
  135. }),
  136. syncGroupsAsTeams: byRole('switch', { name: 'sonar.auth.github.groupsSync' }),
  137. organizations: byRole('textbox', { name: 'Organizations' }),
  138. saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
  139. confirmProvisioningButton: byRole('button', { name: 'yes' }),
  140. saveGithubProvisioning: byRole('button', { name: 'save' }),
  141. groupAttribute: byRole('textbox', { name: 'property.sonar.auth.github.group.name.name' }),
  142. enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
  143. disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
  144. editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
  145. deleteOrg: (org: string) =>
  146. byRole('button', {
  147. name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
  148. }),
  149. enableFirstMessage: byText('settings.authentication.github.enable_first'),
  150. jitProvisioningButton: byRole('radio', {
  151. name: 'settings.authentication.form.provisioning_at_login',
  152. }),
  153. githubProvisioningButton: byRole('radio', {
  154. name: 'settings.authentication.github.form.provisioning_with_github',
  155. }),
  156. githubProvisioningPending: byText(/synchronization_pending/),
  157. githubProvisioningInProgress: byText(/synchronization_in_progress/),
  158. githubProvisioningSuccess: byText(/synchronization_successful/),
  159. githubProvisioningAlert: byText(/synchronization_failed/),
  160. configurationValidityLoading: byRole('status', {
  161. name: /github.configuration.validation.loading/,
  162. }),
  163. configurationValiditySuccess: byRole('status', {
  164. name: /github.configuration.validation.valid/,
  165. }),
  166. configurationValidityError: byRole('status', {
  167. name: /github.configuration.validation.invalid/,
  168. }),
  169. syncWarning: byText(/Warning/),
  170. syncSummary: byText(/Test summary/),
  171. configurationValidityWarning: byRole('status', {
  172. name: /github.configuration.validation.valid.short/,
  173. }),
  174. checkConfigButton: byRole('button', {
  175. name: 'settings.authentication.github.configuration.validation.test',
  176. }),
  177. viewConfigValidityDetailsButton: byRole('button', {
  178. name: 'settings.authentication.github.configuration.validation.details',
  179. }),
  180. configDetailsDialog: byRole('dialog', {
  181. name: 'settings.authentication.github.configuration.validation.details.title',
  182. }),
  183. continueAutoButton: byRole('button', {
  184. name: 'settings.authentication.github.confirm_auto_provisioning.continue',
  185. }),
  186. switchJitButton: byRole('button', {
  187. name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
  188. }),
  189. consentDialog: byRole('dialog', {
  190. name: 'settings.authentication.github.confirm_auto_provisioning.header',
  191. }),
  192. getConfigDetailsTitle: () => within(ui.github.configDetailsDialog.get()).getByRole('heading'),
  193. getOrgs: () => within(ui.github.configDetailsDialog.get()).getAllByRole('listitem'),
  194. fillForm: async (user: UserEvent) => {
  195. const { github } = ui;
  196. await act(async () => {
  197. await user.type(await github.clientId.find(), 'Awsome GITHUB config');
  198. await user.type(github.clientSecret.get(), 'Client shut');
  199. await user.type(github.githubApiUrl.get(), 'API Url');
  200. await user.type(github.githubWebUrl.get(), 'WEb Url');
  201. await user.type(github.organizations.get(), 'organization1');
  202. });
  203. },
  204. createConfiguration: async (user: UserEvent) => {
  205. const { github } = ui;
  206. await act(async () => {
  207. await user.click((await github.createConfigButton.findAll())[1]);
  208. });
  209. await github.fillForm(user);
  210. await act(async () => {
  211. await user.click(github.saveConfigButton.get());
  212. });
  213. },
  214. enableConfiguration: async (user: UserEvent) => {
  215. const { github } = ui;
  216. await act(async () => user.click(await github.tab.find()));
  217. await github.createConfiguration(user);
  218. await act(async () => user.click(await github.enableConfigButton.find()));
  219. },
  220. enableProvisioning: async (user: UserEvent) => {
  221. const { github } = ui;
  222. await act(async () => user.click(await github.tab.find()));
  223. await github.createConfiguration(user);
  224. await act(async () => user.click(await github.enableConfigButton.find()));
  225. await user.click(await github.githubProvisioningButton.find());
  226. await user.click(github.saveGithubProvisioning.get());
  227. await act(() => user.click(github.confirmProvisioningButton.get()));
  228. },
  229. },
  230. };
  231. it('should render tabs and allow navigation', async () => {
  232. const user = userEvent.setup();
  233. renderAuthentication();
  234. expect(screen.getAllByRole('tab')).toHaveLength(4);
  235. expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
  236. await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
  237. expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
  238. expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
  239. 'aria-selected',
  240. 'true'
  241. );
  242. });
  243. it('should not display the login message feature info box', () => {
  244. renderAuthentication();
  245. expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
  246. });
  247. it('should display the login message feature info box', () => {
  248. renderAuthentication([Feature.LoginMessage]);
  249. expect(ui.customMessageInformation.get()).toBeInTheDocument();
  250. });
  251. describe('SAML tab', () => {
  252. const { saml } = ui;
  253. it('should render an empty SAML configuration', async () => {
  254. renderAuthentication();
  255. expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
  256. });
  257. it('should be able to create a configuration', async () => {
  258. const user = userEvent.setup();
  259. renderAuthentication();
  260. await user.click((await saml.createConfigButton.findAll())[0]);
  261. expect(saml.saveConfigButton.get()).toBeDisabled();
  262. await saml.fillForm(user);
  263. expect(saml.saveConfigButton.get()).toBeEnabled();
  264. await act(async () => {
  265. await user.click(saml.saveConfigButton.get());
  266. });
  267. expect(await saml.editConfigButton.find()).toBeInTheDocument();
  268. });
  269. it('should be able to enable/disable configuration', async () => {
  270. const { saml } = ui;
  271. const user = userEvent.setup();
  272. renderAuthentication();
  273. await saml.createConfiguration(user);
  274. await user.click(await saml.enableConfigButton.find());
  275. expect(await saml.disableConfigButton.find()).toBeInTheDocument();
  276. await user.click(saml.disableConfigButton.get());
  277. await waitFor(() => expect(saml.disableConfigButton.query()).not.toBeInTheDocument());
  278. expect(await saml.enableConfigButton.find()).toBeInTheDocument();
  279. });
  280. it('should be able to choose provisioning', async () => {
  281. const { saml } = ui;
  282. const user = userEvent.setup();
  283. renderAuthentication([Feature.Scim]);
  284. await saml.createConfiguration(user);
  285. expect(await saml.enableFirstMessage.find()).toBeInTheDocument();
  286. await user.click(await saml.enableConfigButton.find());
  287. expect(await saml.jitProvisioningButton.find()).toBeChecked();
  288. expect(saml.saveScim.get()).toBeDisabled();
  289. await user.click(saml.scimProvisioningButton.get());
  290. expect(saml.saveScim.get()).toBeEnabled();
  291. await user.click(saml.saveScim.get());
  292. await user.click(saml.confirmProvisioningButton.get());
  293. expect(await saml.scimProvisioningButton.find()).toBeChecked();
  294. expect(await saml.saveScim.find()).toBeDisabled();
  295. });
  296. it('should not allow editions below Enterprise to select SCIM provisioning', async () => {
  297. const { saml } = ui;
  298. const user = userEvent.setup();
  299. renderAuthentication();
  300. await saml.createConfiguration(user);
  301. await user.click(await saml.enableConfigButton.find());
  302. expect(await saml.jitProvisioningButton.find()).toBeChecked();
  303. expect(saml.scimProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
  304. });
  305. });
  306. describe('Github tab', () => {
  307. const { github } = ui;
  308. it('should render an empty Github configuration', async () => {
  309. renderAuthentication();
  310. const user = userEvent.setup();
  311. await user.click(await github.tab.find());
  312. expect(await github.noGithubConfiguration.find()).toBeInTheDocument();
  313. });
  314. it('should be able to create a configuration', async () => {
  315. const user = userEvent.setup();
  316. renderAuthentication();
  317. await user.click(await github.tab.find());
  318. await user.click((await github.createConfigButton.findAll())[1]);
  319. expect(github.saveConfigButton.get()).toBeDisabled();
  320. await github.fillForm(user);
  321. expect(github.saveConfigButton.get()).toBeEnabled();
  322. await act(async () => {
  323. await user.click(github.saveConfigButton.get());
  324. });
  325. expect(await github.editConfigButton.find()).toBeInTheDocument();
  326. });
  327. it('should be able to edit configuration', async () => {
  328. const { github } = ui;
  329. const user = userEvent.setup();
  330. renderAuthentication();
  331. await user.click(await github.tab.find());
  332. await github.createConfiguration(user);
  333. await user.click(github.editConfigButton.get());
  334. await user.click(github.deleteOrg('organization1').get());
  335. await user.click(github.saveConfigButton.get());
  336. await user.click(await github.editConfigButton.find());
  337. expect(github.organizations.get()).toHaveValue('');
  338. });
  339. it('should be able to enable/disable configuration', async () => {
  340. const { github } = ui;
  341. const user = userEvent.setup();
  342. renderAuthentication();
  343. await user.click(await github.tab.find());
  344. await github.createConfiguration(user);
  345. await user.click(await github.enableConfigButton.find());
  346. expect(await github.disableConfigButton.find()).toBeInTheDocument();
  347. await user.click(github.disableConfigButton.get());
  348. await waitFor(() => expect(github.disableConfigButton.query()).not.toBeInTheDocument());
  349. expect(await github.enableConfigButton.find()).toBeInTheDocument();
  350. });
  351. it('should not allow edtion below Enterprise to select Github provisioning', async () => {
  352. const { github } = ui;
  353. const user = userEvent.setup();
  354. renderAuthentication();
  355. await user.click(await github.tab.find());
  356. await github.createConfiguration(user);
  357. await user.click(await github.enableConfigButton.find());
  358. expect(await github.jitProvisioningButton.find()).toBeChecked();
  359. expect(github.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
  360. });
  361. it('should be able to choose provisioning', async () => {
  362. const { github } = ui;
  363. const user = userEvent.setup();
  364. renderAuthentication([Feature.GithubProvisioning]);
  365. await user.click(await github.tab.find());
  366. await github.createConfiguration(user);
  367. expect(await github.enableFirstMessage.find()).toBeInTheDocument();
  368. await user.click(await github.enableConfigButton.find());
  369. expect(await github.jitProvisioningButton.find()).toBeChecked();
  370. expect(github.saveGithubProvisioning.get()).toBeDisabled();
  371. await user.click(github.allowUserToSignUp.get());
  372. await user.click(github.syncGroupsAsTeams.get());
  373. expect(github.saveGithubProvisioning.get()).toBeEnabled();
  374. await user.click(github.saveGithubProvisioning.get());
  375. await waitFor(() => expect(github.saveGithubProvisioning.query()).toBeDisabled());
  376. await user.click(github.githubProvisioningButton.get());
  377. expect(github.saveGithubProvisioning.get()).toBeEnabled();
  378. await user.click(github.saveGithubProvisioning.get());
  379. await user.click(github.confirmProvisioningButton.get());
  380. expect(await github.githubProvisioningButton.find()).toBeChecked();
  381. expect(github.disableConfigButton.get()).toBeDisabled();
  382. expect(github.saveGithubProvisioning.get()).toBeDisabled();
  383. });
  384. describe('Github Provisioning', () => {
  385. let user: UserEvent;
  386. beforeEach(() => {
  387. jest.useFakeTimers({
  388. advanceTimers: true,
  389. now: new Date('2022-02-04T12:00:59Z'),
  390. });
  391. user = userEvent.setup();
  392. });
  393. afterEach(() => {
  394. jest.runOnlyPendingTimers();
  395. jest.useRealTimers();
  396. });
  397. it('should display a success status when the synchronisation is a success', async () => {
  398. handler.addProvisioningTask({
  399. status: TaskStatuses.Success,
  400. executedAt: '2022-02-03T11:45:35+0200',
  401. });
  402. renderAuthentication([Feature.GithubProvisioning]);
  403. await github.enableProvisioning(user);
  404. expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
  405. expect(github.syncSummary.get()).toBeInTheDocument();
  406. });
  407. it('should display a success status even when another task is pending', async () => {
  408. handler.addProvisioningTask({
  409. status: TaskStatuses.Pending,
  410. executedAt: '2022-02-03T11:55:35+0200',
  411. });
  412. handler.addProvisioningTask({
  413. status: TaskStatuses.Success,
  414. executedAt: '2022-02-03T11:45:35+0200',
  415. });
  416. renderAuthentication([Feature.GithubProvisioning]);
  417. await github.enableProvisioning(user);
  418. expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
  419. expect(github.githubProvisioningPending.get()).toBeInTheDocument();
  420. });
  421. it('should display an error alert when the synchronisation failed', async () => {
  422. handler.addProvisioningTask({
  423. status: TaskStatuses.Failed,
  424. executedAt: '2022-02-03T11:45:35+0200',
  425. errorMessage: "T'es mauvais Jacques",
  426. });
  427. renderAuthentication([Feature.GithubProvisioning]);
  428. await github.enableProvisioning(user);
  429. expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
  430. expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
  431. expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
  432. });
  433. it('should display an error alert even when another task is in progress', async () => {
  434. handler.addProvisioningTask({
  435. status: TaskStatuses.InProgress,
  436. executedAt: '2022-02-03T11:55:35+0200',
  437. });
  438. handler.addProvisioningTask({
  439. status: TaskStatuses.Failed,
  440. executedAt: '2022-02-03T11:45:35+0200',
  441. errorMessage: "T'es mauvais Jacques",
  442. });
  443. renderAuthentication([Feature.GithubProvisioning]);
  444. await github.enableProvisioning(user);
  445. expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
  446. expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
  447. expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
  448. expect(github.githubProvisioningInProgress.get()).toBeInTheDocument();
  449. });
  450. it('should display that config is valid for both provisioning with 1 org', async () => {
  451. renderAuthentication([Feature.GithubProvisioning]);
  452. await github.enableConfiguration(user);
  453. await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
  454. });
  455. it('should display that config is valid for both provisioning with multiple orgs', async () => {
  456. handler.setConfigurationValidity({
  457. installations: [
  458. {
  459. organization: 'org1',
  460. autoProvisioning: { status: GitHubProvisioningStatus.Success },
  461. jit: { status: GitHubProvisioningStatus.Success },
  462. },
  463. {
  464. organization: 'org2',
  465. autoProvisioning: { status: GitHubProvisioningStatus.Success },
  466. jit: { status: GitHubProvisioningStatus.Success },
  467. },
  468. ],
  469. });
  470. renderAuthentication([Feature.GithubProvisioning]);
  471. await github.enableConfiguration(user);
  472. await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
  473. expect(github.configurationValiditySuccess.get()).toHaveTextContent('2');
  474. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  475. expect(github.getConfigDetailsTitle()).toHaveTextContent(
  476. 'settings.authentication.github.configuration.validation.details.valid_label'
  477. );
  478. expect(github.getOrgs()[0]).toHaveTextContent(
  479. 'settings.authentication.github.configuration.validation.details.valid_labelorg1'
  480. );
  481. expect(github.getOrgs()[1]).toHaveTextContent(
  482. 'settings.authentication.github.configuration.validation.details.valid_labelorg2'
  483. );
  484. });
  485. it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
  486. const errorMessage = 'Installation suspended';
  487. handler.setConfigurationValidity({
  488. installations: [
  489. {
  490. organization: 'org1',
  491. autoProvisioning: {
  492. status: GitHubProvisioningStatus.Failed,
  493. errorMessage,
  494. },
  495. jit: {
  496. status: GitHubProvisioningStatus.Failed,
  497. errorMessage,
  498. },
  499. },
  500. ],
  501. });
  502. renderAuthentication([Feature.GithubProvisioning]);
  503. await github.enableConfiguration(user);
  504. await waitFor(() => expect(github.configurationValidityWarning.get()).toBeInTheDocument());
  505. expect(github.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
  506. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  507. expect(github.getConfigDetailsTitle()).toHaveTextContent(
  508. 'settings.authentication.github.configuration.validation.details.valid_label'
  509. );
  510. expect(github.getOrgs()[0]).toHaveTextContent(
  511. 'settings.authentication.github.configuration.validation.details.invalid_labelorg1 - Installation suspended'
  512. );
  513. await act(() =>
  514. user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' }))
  515. );
  516. await user.click(github.githubProvisioningButton.get());
  517. await waitFor(() => expect(github.configurationValidityError.get()).toBeInTheDocument());
  518. expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
  519. });
  520. it('should display that config is valid but some organizations were not found', async () => {
  521. handler.setConfigurationValidity({
  522. installations: [
  523. {
  524. organization: 'org1',
  525. autoProvisioning: { status: GitHubProvisioningStatus.Success },
  526. jit: { status: GitHubProvisioningStatus.Success },
  527. },
  528. ],
  529. });
  530. renderAuthentication([Feature.GithubProvisioning]);
  531. await github.enableConfiguration(user);
  532. await waitFor(() => expect(github.configurationValiditySuccess.get()).toBeInTheDocument());
  533. expect(github.configurationValiditySuccess.get()).toHaveTextContent('1');
  534. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  535. expect(github.getConfigDetailsTitle()).toHaveTextContent(
  536. 'settings.authentication.github.configuration.validation.details.valid_label'
  537. );
  538. expect(github.getOrgs()[0]).toHaveTextContent(
  539. 'settings.authentication.github.configuration.validation.details.valid_labelorg1'
  540. );
  541. expect(github.getOrgs()[1]).toHaveTextContent(
  542. 'settings.authentication.github.configuration.validation.details.org_not_found.organization1'
  543. );
  544. });
  545. it('should display that config is invalid', async () => {
  546. const errorMessage = 'Test error';
  547. handler.setConfigurationValidity({
  548. application: {
  549. jit: {
  550. status: GitHubProvisioningStatus.Failed,
  551. errorMessage,
  552. },
  553. autoProvisioning: {
  554. status: GitHubProvisioningStatus.Failed,
  555. errorMessage,
  556. },
  557. },
  558. });
  559. renderAuthentication([Feature.GithubProvisioning]);
  560. await github.enableConfiguration(user);
  561. await waitFor(() => expect(github.configurationValidityError.query()).toBeInTheDocument());
  562. expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
  563. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  564. expect(github.getConfigDetailsTitle()).toHaveTextContent(
  565. 'settings.authentication.github.configuration.validation.details.invalid_label'
  566. );
  567. expect(github.configDetailsDialog.get()).toHaveTextContent(errorMessage);
  568. });
  569. it('should display that config is valid for jit, but not for auto', async () => {
  570. const errorMessage = 'Test error';
  571. handler.setConfigurationValidity({
  572. application: {
  573. jit: {
  574. status: GitHubProvisioningStatus.Success,
  575. },
  576. autoProvisioning: {
  577. status: GitHubProvisioningStatus.Failed,
  578. errorMessage,
  579. },
  580. },
  581. });
  582. renderAuthentication([Feature.GithubProvisioning]);
  583. await github.enableConfiguration(user);
  584. await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
  585. expect(github.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
  586. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  587. expect(github.getConfigDetailsTitle()).toHaveTextContent(
  588. 'settings.authentication.github.configuration.validation.details.valid_label'
  589. );
  590. await act(() =>
  591. user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' }))
  592. );
  593. await act(() => user.click(github.githubProvisioningButton.get()));
  594. expect(github.configurationValidityError.get()).toBeInTheDocument();
  595. expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
  596. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  597. expect(github.getConfigDetailsTitle()).toHaveTextContent(
  598. 'settings.authentication.github.configuration.validation.details.invalid_label'
  599. );
  600. });
  601. it('should display that config is invalid because of orgs', async () => {
  602. const errorMessage = 'Test error';
  603. handler.setConfigurationValidity({
  604. installations: [
  605. {
  606. organization: 'org1',
  607. autoProvisioning: { status: GitHubProvisioningStatus.Success },
  608. jit: { status: GitHubProvisioningStatus.Success },
  609. },
  610. {
  611. organization: 'org2',
  612. jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
  613. autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
  614. },
  615. ],
  616. });
  617. renderAuthentication([Feature.GithubProvisioning]);
  618. await github.enableConfiguration(user);
  619. await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
  620. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  621. expect(github.getOrgs()[0]).toHaveTextContent(
  622. 'settings.authentication.github.configuration.validation.details.valid_labelorg1'
  623. );
  624. expect(github.getOrgs()[1]).toHaveTextContent(
  625. 'settings.authentication.github.configuration.validation.details.invalid_labelorg2 - Test error'
  626. );
  627. await act(() =>
  628. user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' }))
  629. );
  630. await act(() => user.click(github.githubProvisioningButton.get()));
  631. expect(github.configurationValidityError.get()).toBeInTheDocument();
  632. expect(github.configurationValidityError.get()).toHaveTextContent(
  633. `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`
  634. );
  635. await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
  636. expect(github.getOrgs()[1]).toHaveTextContent(
  637. `settings.authentication.github.configuration.validation.details.invalid_labelorg2 - ${errorMessage}`
  638. );
  639. });
  640. it('should update provisioning validity after clicking Test Configuration', async () => {
  641. const errorMessage = 'Test error';
  642. handler.setConfigurationValidity({
  643. application: {
  644. jit: {
  645. status: GitHubProvisioningStatus.Failed,
  646. errorMessage,
  647. },
  648. autoProvisioning: {
  649. status: GitHubProvisioningStatus.Failed,
  650. errorMessage,
  651. },
  652. },
  653. });
  654. renderAuthentication([Feature.GithubProvisioning]);
  655. await github.enableConfiguration(user);
  656. handler.setConfigurationValidity({
  657. application: {
  658. jit: {
  659. status: GitHubProvisioningStatus.Success,
  660. },
  661. autoProvisioning: {
  662. status: GitHubProvisioningStatus.Success,
  663. },
  664. },
  665. });
  666. expect(await github.configurationValidityError.find()).toBeInTheDocument();
  667. await act(() => user.click(github.checkConfigButton.get()));
  668. expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
  669. expect(github.configurationValidityError.query()).not.toBeInTheDocument();
  670. });
  671. it('should show warning', async () => {
  672. handler.addProvisioningTask({
  673. status: TaskStatuses.Success,
  674. warnings: ['Warning'],
  675. });
  676. renderAuthentication([Feature.GithubProvisioning]);
  677. await github.enableProvisioning(user);
  678. expect(await github.syncWarning.find()).toBeInTheDocument();
  679. expect(github.syncSummary.get()).toBeInTheDocument();
  680. });
  681. it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
  682. const user = userEvent.setup();
  683. settingsHandler.presetGithubAutoProvisioning();
  684. handler.enableGithubProvisioning();
  685. settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
  686. renderAuthentication([Feature.GithubProvisioning]);
  687. await user.click(await github.tab.find());
  688. expect(await github.consentDialog.find()).toBeInTheDocument();
  689. await user.click(github.continueAutoButton.get());
  690. expect(await github.githubProvisioningButton.find()).toBeChecked();
  691. expect(github.consentDialog.query()).not.toBeInTheDocument();
  692. });
  693. it('should display a modal if user was already using auto and switch to JIT', async () => {
  694. const user = userEvent.setup();
  695. settingsHandler.presetGithubAutoProvisioning();
  696. handler.enableGithubProvisioning();
  697. settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
  698. renderAuthentication([Feature.GithubProvisioning]);
  699. await user.click(await github.tab.find());
  700. expect(await github.consentDialog.find()).toBeInTheDocument();
  701. await user.click(github.switchJitButton.get());
  702. expect(await github.jitProvisioningButton.find()).toBeChecked();
  703. expect(github.consentDialog.query()).not.toBeInTheDocument();
  704. });
  705. });
  706. });
  707. function renderAuthentication(features: Feature[] = []) {
  708. renderComponent(
  709. <AvailableFeaturesContext.Provider value={features}>
  710. <Authentication definitions={definitions} />
  711. </AvailableFeaturesContext.Provider>
  712. );
  713. }