]> source.dussan.org Git - sonarqube.git/blob
ef05268b16769c0fe38fd5fc4bb3e825b5876bf0
[sonarqube.git] /
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
37 jest.mock('../../../../../api/system');
38
39 let handler: AuthenticationServiceMock;
40 let system: SystemServiceMock;
41 let settingsHandler: SettingsServiceMock;
42 let computeEngineHandler: ComputeEngineServiceMock;
43
44 beforeEach(() => {
45   handler = new AuthenticationServiceMock();
46   system = new SystemServiceMock();
47   settingsHandler = new SettingsServiceMock();
48   computeEngineHandler = new ComputeEngineServiceMock();
49   [
50     {
51       key: 'sonar.auth.saml.signature.enabled',
52       value: 'false',
53     },
54     {
55       key: 'sonar.auth.saml.enabled',
56       value: 'false',
57     },
58     {
59       key: 'sonar.auth.saml.applicationId',
60       value: 'sonarqube',
61     },
62     {
63       key: 'sonar.auth.saml.providerName',
64       value: 'SAML',
65     },
66   ].forEach((setting: any) => settingsHandler.set(setting.key, setting.value));
67 });
68
69 afterEach(() => {
70   handler.reset();
71   settingsHandler.reset();
72   system.reset();
73   computeEngineHandler.reset();
74 });
75
76 const ui = {
77   saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
78   customMessageInformation: byText('settings.authentication.custom_message_information'),
79   enabledToggle: byRole('switch'),
80   testButton: byText('settings.authentication.saml.form.test'),
81   textbox1: byRole('textbox', { name: 'test1' }),
82   textbox2: byRole('textbox', { name: 'test2' }),
83   saml: {
84     noSamlConfiguration: byText('settings.authentication.saml.form.not_configured'),
85     createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
86     providerName: byRole('textbox', { name: 'property.sonar.auth.saml.providerName.name' }),
87     providerId: byRole('textbox', { name: 'property.sonar.auth.saml.providerId.name' }),
88     providerCertificate: byRole('textbox', {
89       name: 'property.sonar.auth.saml.certificate.secured.name',
90     }),
91     loginUrl: byRole('textbox', { name: 'property.sonar.auth.saml.loginUrl.name' }),
92     userLoginAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.login.name' }),
93     userNameAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.name.name' }),
94     saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
95     confirmProvisioningButton: byRole('button', { name: 'yes' }),
96     saveScim: byRole('button', { name: 'save' }),
97     enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
98     disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
99     editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
100     enableFirstMessage: byText('settings.authentication.saml.enable_first'),
101     jitProvisioningButton: byRole('radio', {
102       name: 'settings.authentication.saml.form.provisioning_at_login',
103     }),
104     scimProvisioningButton: byRole('radio', {
105       name: 'settings.authentication.saml.form.provisioning_with_scim',
106     }),
107     fillForm: async (user: UserEvent) => {
108       const { saml } = ui;
109       await act(async () => {
110         await user.clear(saml.providerName.get());
111         await user.type(saml.providerName.get(), 'Awsome SAML config');
112         await user.type(saml.providerId.get(), 'okta-1234');
113         await user.type(saml.loginUrl.get(), 'http://test.org');
114         await user.type(saml.providerCertificate.get(), '-secret-');
115         await user.type(saml.userLoginAttribute.get(), 'login');
116         await user.type(saml.userNameAttribute.get(), 'name');
117       });
118     },
119     createConfiguration: async (user: UserEvent) => {
120       const { saml } = ui;
121       await act(async () => {
122         await user.click((await saml.createConfigButton.findAll())[0]);
123       });
124       await saml.fillForm(user);
125       await act(async () => {
126         await user.click(saml.saveConfigButton.get());
127       });
128     },
129   },
130   github: {
131     tab: byRole('tab', { name: 'github GitHub' }),
132     noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
133     createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
134     clientId: byRole('textbox', { name: 'property.sonar.auth.github.clientId.secured.name' }),
135     clientSecret: byRole('textbox', {
136       name: 'property.sonar.auth.github.clientSecret.secured.name',
137     }),
138     githubApiUrl: byRole('textbox', { name: 'property.sonar.auth.github.apiUrl.name' }),
139     githubWebUrl: byRole('textbox', { name: 'property.sonar.auth.github.webUrl.name' }),
140     allowUserToSignUp: byRole('switch', {
141       name: 'sonar.auth.github.allowUsersToSignUp',
142     }),
143     organizations: byRole('textbox', { name: 'property.sonar.auth.github.organizations.name' }),
144     saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
145     confirmProvisioningButton: byRole('button', { name: 'yes' }),
146     saveGithubProvisioning: byRole('button', { name: 'save' }),
147     groupAttribute: byRole('textbox', { name: 'property.sonar.auth.github.group.name.name' }),
148     enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
149     disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
150     editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
151     editMappingButton: byRole('button', {
152       name: 'settings.authentication.github.configuration.roles_mapping.button_label',
153     }),
154     mappingRow: byRole('dialog', {
155       name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
156     }).byRole('row'),
157     mappingCheckbox: byRole('checkbox'),
158     mappingDialogClose: byRole('dialog', {
159       name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
160     }).byRole('button', {
161       name: 'close',
162     }),
163     deleteOrg: (org: string) =>
164       byRole('button', {
165         name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
166       }),
167     enableFirstMessage: byText('settings.authentication.github.enable_first'),
168     jitProvisioningButton: byRole('radio', {
169       name: 'settings.authentication.form.provisioning_at_login',
170     }),
171     githubProvisioningButton: byRole('radio', {
172       name: 'settings.authentication.github.form.provisioning_with_github',
173     }),
174     githubProvisioningPending: byText(/synchronization_pending/),
175     githubProvisioningInProgress: byText(/synchronization_in_progress/),
176     githubProvisioningSuccess: byText(/synchronization_successful/),
177     githubProvisioningAlert: byText(/synchronization_failed/),
178     configurationValidityLoading: byRole('status', {
179       name: /github.configuration.validation.loading/,
180     }),
181     configurationValiditySuccess: byRole('status', {
182       name: /github.configuration.validation.valid/,
183     }),
184     configurationValidityError: byRole('status', {
185       name: /github.configuration.validation.invalid/,
186     }),
187     syncWarning: byText(/Warning/),
188     syncSummary: byText(/Test summary/),
189     configurationValidityWarning: byRole('status', {
190       name: /github.configuration.validation.valid.short/,
191     }),
192     checkConfigButton: byRole('button', {
193       name: 'settings.authentication.github.configuration.validation.test',
194     }),
195     viewConfigValidityDetailsButton: byRole('button', {
196       name: 'settings.authentication.github.configuration.validation.details',
197     }),
198     configDetailsDialog: byRole('dialog', {
199       name: 'settings.authentication.github.configuration.validation.details.title',
200     }),
201     continueAutoButton: byRole('button', {
202       name: 'settings.authentication.github.confirm_auto_provisioning.continue',
203     }),
204     switchJitButton: byRole('button', {
205       name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
206     }),
207     consentDialog: byRole('dialog', {
208       name: 'settings.authentication.github.confirm_auto_provisioning.header',
209     }),
210     getConfigDetailsTitle: () => within(ui.github.configDetailsDialog.get()).getByRole('heading'),
211     getOrgs: () => within(ui.github.configDetailsDialog.get()).getAllByRole('listitem'),
212     fillForm: async (user: UserEvent) => {
213       const { github } = ui;
214       await act(async () => {
215         await user.type(await github.clientId.find(), 'Awsome GITHUB config');
216         await user.type(github.clientSecret.get(), 'Client shut');
217         await user.type(github.githubApiUrl.get(), 'API Url');
218         await user.type(github.githubWebUrl.get(), 'WEb Url');
219         await user.type(github.organizations.get(), 'organization1');
220       });
221     },
222     createConfiguration: async (user: UserEvent) => {
223       const { github } = ui;
224       await act(async () => {
225         await user.click((await github.createConfigButton.findAll())[1]);
226       });
227       await github.fillForm(user);
228       await act(async () => {
229         await user.click(github.saveConfigButton.get());
230       });
231     },
232     enableConfiguration: async (user: UserEvent) => {
233       const { github } = ui;
234       await act(async () => user.click(await github.tab.find()));
235       await github.createConfiguration(user);
236       await act(async () => user.click(await github.enableConfigButton.find()));
237     },
238     enableProvisioning: async (user: UserEvent) => {
239       const { github } = ui;
240       await act(async () => user.click(await github.tab.find()));
241
242       await github.createConfiguration(user);
243
244       await act(async () => user.click(await github.enableConfigButton.find()));
245       await user.click(await github.githubProvisioningButton.find());
246       await user.click(github.saveGithubProvisioning.get());
247       await act(() => user.click(github.confirmProvisioningButton.get()));
248     },
249   },
250 };
251
252 it('should render tabs and allow navigation', async () => {
253   const user = userEvent.setup();
254   renderAuthentication();
255
256   expect(screen.getAllByRole('tab')).toHaveLength(4);
257
258   expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
259
260   await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
261
262   expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
263   expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
264     'aria-selected',
265     'true',
266   );
267 });
268
269 it('should not display the login message feature info box', () => {
270   renderAuthentication();
271
272   expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
273 });
274
275 it('should display the login message feature info box', () => {
276   renderAuthentication([Feature.LoginMessage]);
277
278   expect(ui.customMessageInformation.get()).toBeInTheDocument();
279 });
280
281 describe('SAML tab', () => {
282   const { saml } = ui;
283
284   it('should render an empty SAML configuration', async () => {
285     renderAuthentication();
286     expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
287   });
288
289   it('should be able to create a configuration', async () => {
290     const user = userEvent.setup();
291     renderAuthentication();
292
293     await user.click((await saml.createConfigButton.findAll())[0]);
294
295     expect(saml.saveConfigButton.get()).toBeDisabled();
296     await saml.fillForm(user);
297     expect(saml.saveConfigButton.get()).toBeEnabled();
298
299     await act(async () => {
300       await user.click(saml.saveConfigButton.get());
301     });
302
303     expect(await saml.editConfigButton.find()).toBeInTheDocument();
304   });
305
306   it('should be able to enable/disable configuration', async () => {
307     const { saml } = ui;
308     const user = userEvent.setup();
309     renderAuthentication();
310
311     await saml.createConfiguration(user);
312     await user.click(await saml.enableConfigButton.find());
313
314     expect(await saml.disableConfigButton.find()).toBeInTheDocument();
315     await user.click(saml.disableConfigButton.get());
316     await waitFor(() => expect(saml.disableConfigButton.query()).not.toBeInTheDocument());
317
318     expect(await saml.enableConfigButton.find()).toBeInTheDocument();
319   });
320
321   it('should be able to choose provisioning', async () => {
322     const { saml } = ui;
323     const user = userEvent.setup();
324
325     renderAuthentication([Feature.Scim]);
326
327     await saml.createConfiguration(user);
328
329     expect(await saml.enableFirstMessage.find()).toBeInTheDocument();
330     await user.click(await saml.enableConfigButton.find());
331
332     expect(await saml.jitProvisioningButton.find()).toBeChecked();
333     expect(saml.saveScim.get()).toBeDisabled();
334
335     await user.click(saml.scimProvisioningButton.get());
336     expect(saml.saveScim.get()).toBeEnabled();
337     await user.click(saml.saveScim.get());
338     await user.click(saml.confirmProvisioningButton.get());
339
340     expect(await saml.scimProvisioningButton.find()).toBeChecked();
341     expect(await saml.saveScim.find()).toBeDisabled();
342   });
343
344   it('should not allow editions below Enterprise to select SCIM provisioning', async () => {
345     const { saml } = ui;
346     const user = userEvent.setup();
347
348     renderAuthentication();
349
350     await saml.createConfiguration(user);
351     await user.click(await saml.enableConfigButton.find());
352
353     expect(await saml.jitProvisioningButton.find()).toBeChecked();
354     expect(saml.scimProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
355   });
356 });
357
358 describe('Github tab', () => {
359   const { github } = ui;
360
361   it('should render an empty Github configuration', async () => {
362     renderAuthentication();
363     const user = userEvent.setup();
364     await user.click(await github.tab.find());
365     expect(await github.noGithubConfiguration.find()).toBeInTheDocument();
366   });
367
368   it('should be able to create a configuration', async () => {
369     const user = userEvent.setup();
370     renderAuthentication();
371
372     await user.click(await github.tab.find());
373     await user.click((await github.createConfigButton.findAll())[1]);
374
375     expect(github.saveConfigButton.get()).toBeDisabled();
376
377     await github.fillForm(user);
378     expect(github.saveConfigButton.get()).toBeEnabled();
379
380     await act(async () => {
381       await user.click(github.saveConfigButton.get());
382     });
383
384     expect(await github.editConfigButton.find()).toBeInTheDocument();
385   });
386
387   it('should be able to edit configuration', async () => {
388     const { github } = ui;
389     const user = userEvent.setup();
390     renderAuthentication();
391     await user.click(await github.tab.find());
392
393     await github.createConfiguration(user);
394
395     await user.click(github.editConfigButton.get());
396     await user.click(github.deleteOrg('organization1').get());
397
398     await user.click(github.saveConfigButton.get());
399
400     await user.click(await github.editConfigButton.find());
401
402     expect(github.organizations.get()).toHaveValue('');
403   });
404
405   it('should be able to enable/disable configuration', async () => {
406     const { github } = ui;
407     const user = userEvent.setup();
408     renderAuthentication();
409     await user.click(await github.tab.find());
410
411     await github.createConfiguration(user);
412
413     await user.click(await github.enableConfigButton.find());
414
415     expect(await github.disableConfigButton.find()).toBeInTheDocument();
416     await user.click(github.disableConfigButton.get());
417     await waitFor(() => expect(github.disableConfigButton.query()).not.toBeInTheDocument());
418
419     expect(await github.enableConfigButton.find()).toBeInTheDocument();
420   });
421
422   it('should not allow edtion below Enterprise to select Github provisioning', async () => {
423     const { github } = ui;
424     const user = userEvent.setup();
425
426     renderAuthentication();
427     await user.click(await github.tab.find());
428
429     await github.createConfiguration(user);
430     await user.click(await github.enableConfigButton.find());
431
432     expect(await github.jitProvisioningButton.find()).toBeChecked();
433     expect(github.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
434   });
435
436   it('should be able to choose provisioning', async () => {
437     const { github } = ui;
438     const user = userEvent.setup();
439
440     renderAuthentication([Feature.GithubProvisioning]);
441     await user.click(await github.tab.find());
442
443     await github.createConfiguration(user);
444
445     expect(await github.enableFirstMessage.find()).toBeInTheDocument();
446     await user.click(await github.enableConfigButton.find());
447
448     expect(await github.jitProvisioningButton.find()).toBeChecked();
449
450     expect(github.saveGithubProvisioning.get()).toBeDisabled();
451     await user.click(github.allowUserToSignUp.get());
452
453     expect(github.saveGithubProvisioning.get()).toBeEnabled();
454     await user.click(github.saveGithubProvisioning.get());
455
456     await waitFor(() => expect(github.saveGithubProvisioning.query()).toBeDisabled());
457
458     await user.click(github.githubProvisioningButton.get());
459
460     expect(github.saveGithubProvisioning.get()).toBeEnabled();
461     await user.click(github.saveGithubProvisioning.get());
462     await user.click(github.confirmProvisioningButton.get());
463
464     expect(await github.githubProvisioningButton.find()).toBeChecked();
465     expect(github.disableConfigButton.get()).toBeDisabled();
466     expect(github.saveGithubProvisioning.get()).toBeDisabled();
467   });
468
469   describe('Github Provisioning', () => {
470     let user: UserEvent;
471
472     beforeEach(() => {
473       jest.useFakeTimers({
474         advanceTimers: true,
475         now: new Date('2022-02-04T12:00:59Z'),
476       });
477       user = userEvent.setup();
478     });
479
480     afterEach(() => {
481       jest.runOnlyPendingTimers();
482       jest.useRealTimers();
483     });
484
485     it('should display a success status when the synchronisation is a success', async () => {
486       handler.addProvisioningTask({
487         status: TaskStatuses.Success,
488         executedAt: '2022-02-03T11:45:35+0200',
489       });
490
491       renderAuthentication([Feature.GithubProvisioning]);
492       await github.enableProvisioning(user);
493       expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
494       expect(github.syncSummary.get()).toBeInTheDocument();
495     });
496
497     it('should display a success status even when another task is pending', async () => {
498       handler.addProvisioningTask({
499         status: TaskStatuses.Pending,
500         executedAt: '2022-02-03T11:55:35+0200',
501       });
502       handler.addProvisioningTask({
503         status: TaskStatuses.Success,
504         executedAt: '2022-02-03T11:45:35+0200',
505       });
506       renderAuthentication([Feature.GithubProvisioning]);
507       await github.enableProvisioning(user);
508       expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
509       expect(github.githubProvisioningPending.get()).toBeInTheDocument();
510     });
511
512     it('should display an error alert when the synchronisation failed', async () => {
513       handler.addProvisioningTask({
514         status: TaskStatuses.Failed,
515         executedAt: '2022-02-03T11:45:35+0200',
516         errorMessage: "T'es mauvais Jacques",
517       });
518       renderAuthentication([Feature.GithubProvisioning]);
519       await github.enableProvisioning(user);
520       expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
521       expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
522       expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
523     });
524
525     it('should display an error alert even when another task is in progress', async () => {
526       handler.addProvisioningTask({
527         status: TaskStatuses.InProgress,
528         executedAt: '2022-02-03T11:55:35+0200',
529       });
530       handler.addProvisioningTask({
531         status: TaskStatuses.Failed,
532         executedAt: '2022-02-03T11:45:35+0200',
533         errorMessage: "T'es mauvais Jacques",
534       });
535       renderAuthentication([Feature.GithubProvisioning]);
536       await github.enableProvisioning(user);
537       expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
538       expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
539       expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
540       expect(github.githubProvisioningInProgress.get()).toBeInTheDocument();
541     });
542
543     it('should display that config is valid for both provisioning with 1 org', async () => {
544       renderAuthentication([Feature.GithubProvisioning]);
545       await github.enableConfiguration(user);
546
547       await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
548     });
549
550     it('should display that config is valid for both provisioning with multiple orgs', async () => {
551       handler.setConfigurationValidity({
552         installations: [
553           {
554             organization: 'org1',
555             autoProvisioning: { status: GitHubProvisioningStatus.Success },
556             jit: { status: GitHubProvisioningStatus.Success },
557           },
558           {
559             organization: 'org2',
560             autoProvisioning: { status: GitHubProvisioningStatus.Success },
561             jit: { status: GitHubProvisioningStatus.Success },
562           },
563         ],
564       });
565       renderAuthentication([Feature.GithubProvisioning]);
566       await github.enableConfiguration(user);
567
568       await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
569       expect(github.configurationValiditySuccess.get()).toHaveTextContent('2');
570
571       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
572       expect(github.getConfigDetailsTitle()).toHaveTextContent(
573         'settings.authentication.github.configuration.validation.details.valid_label',
574       );
575       expect(github.getOrgs()[0]).toHaveTextContent(
576         'settings.authentication.github.configuration.validation.details.valid_labelorg1',
577       );
578       expect(github.getOrgs()[1]).toHaveTextContent(
579         'settings.authentication.github.configuration.validation.details.valid_labelorg2',
580       );
581     });
582
583     it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
584       const errorMessage = 'Installation suspended';
585       handler.setConfigurationValidity({
586         installations: [
587           {
588             organization: 'org1',
589             autoProvisioning: {
590               status: GitHubProvisioningStatus.Failed,
591               errorMessage,
592             },
593             jit: {
594               status: GitHubProvisioningStatus.Failed,
595               errorMessage,
596             },
597           },
598         ],
599       });
600
601       renderAuthentication([Feature.GithubProvisioning]);
602       await github.enableConfiguration(user);
603
604       await waitFor(() => expect(github.configurationValidityWarning.get()).toBeInTheDocument());
605       expect(github.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
606
607       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
608       expect(github.getConfigDetailsTitle()).toHaveTextContent(
609         'settings.authentication.github.configuration.validation.details.valid_label',
610       );
611       expect(github.getOrgs()[0]).toHaveTextContent(
612         'settings.authentication.github.configuration.validation.details.invalid_labelorg1 - Installation suspended',
613       );
614
615       await act(() =>
616         user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
617       );
618
619       await user.click(github.githubProvisioningButton.get());
620       await waitFor(() => expect(github.configurationValidityError.get()).toBeInTheDocument());
621       expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
622     });
623
624     it('should display that config is valid but some organizations were not found', async () => {
625       handler.setConfigurationValidity({
626         installations: [
627           {
628             organization: 'org1',
629             autoProvisioning: { status: GitHubProvisioningStatus.Success },
630             jit: { status: GitHubProvisioningStatus.Success },
631           },
632         ],
633       });
634
635       renderAuthentication([Feature.GithubProvisioning]);
636       await github.enableConfiguration(user);
637
638       await waitFor(() => expect(github.configurationValiditySuccess.get()).toBeInTheDocument());
639       expect(github.configurationValiditySuccess.get()).toHaveTextContent('1');
640
641       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
642       expect(github.getConfigDetailsTitle()).toHaveTextContent(
643         'settings.authentication.github.configuration.validation.details.valid_label',
644       );
645       expect(github.getOrgs()[0]).toHaveTextContent(
646         'settings.authentication.github.configuration.validation.details.valid_labelorg1',
647       );
648       expect(github.getOrgs()[1]).toHaveTextContent(
649         'settings.authentication.github.configuration.validation.details.org_not_found.organization1',
650       );
651     });
652
653     it('should display that config is invalid', async () => {
654       const errorMessage = 'Test error';
655       handler.setConfigurationValidity({
656         application: {
657           jit: {
658             status: GitHubProvisioningStatus.Failed,
659             errorMessage,
660           },
661           autoProvisioning: {
662             status: GitHubProvisioningStatus.Failed,
663             errorMessage,
664           },
665         },
666       });
667       renderAuthentication([Feature.GithubProvisioning]);
668       await github.enableConfiguration(user);
669
670       await waitFor(() => expect(github.configurationValidityError.query()).toBeInTheDocument());
671       expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
672
673       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
674       expect(github.getConfigDetailsTitle()).toHaveTextContent(
675         'settings.authentication.github.configuration.validation.details.invalid_label',
676       );
677       expect(github.configDetailsDialog.get()).toHaveTextContent(errorMessage);
678     });
679
680     it('should display that config is valid for jit, but not for auto', async () => {
681       const errorMessage = 'Test error';
682       handler.setConfigurationValidity({
683         application: {
684           jit: {
685             status: GitHubProvisioningStatus.Success,
686           },
687           autoProvisioning: {
688             status: GitHubProvisioningStatus.Failed,
689             errorMessage,
690           },
691         },
692       });
693       renderAuthentication([Feature.GithubProvisioning]);
694       await github.enableConfiguration(user);
695
696       await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
697       expect(github.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
698
699       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
700       expect(github.getConfigDetailsTitle()).toHaveTextContent(
701         'settings.authentication.github.configuration.validation.details.valid_label',
702       );
703       await act(() =>
704         user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
705       );
706
707       await act(() => user.click(github.githubProvisioningButton.get()));
708
709       expect(github.configurationValidityError.get()).toBeInTheDocument();
710       expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
711
712       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
713       expect(github.getConfigDetailsTitle()).toHaveTextContent(
714         'settings.authentication.github.configuration.validation.details.invalid_label',
715       );
716     });
717
718     it('should display that config is invalid because of orgs', async () => {
719       const errorMessage = 'Test error';
720       handler.setConfigurationValidity({
721         installations: [
722           {
723             organization: 'org1',
724             autoProvisioning: { status: GitHubProvisioningStatus.Success },
725             jit: { status: GitHubProvisioningStatus.Success },
726           },
727           {
728             organization: 'org2',
729             jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
730             autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
731           },
732         ],
733       });
734       renderAuthentication([Feature.GithubProvisioning]);
735       await github.enableConfiguration(user);
736
737       await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
738
739       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
740
741       expect(github.getOrgs()[0]).toHaveTextContent(
742         'settings.authentication.github.configuration.validation.details.valid_labelorg1',
743       );
744       expect(github.getOrgs()[1]).toHaveTextContent(
745         'settings.authentication.github.configuration.validation.details.invalid_labelorg2 - Test error',
746       );
747
748       await act(() =>
749         user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
750       );
751
752       await act(() => user.click(github.githubProvisioningButton.get()));
753
754       expect(github.configurationValidityError.get()).toBeInTheDocument();
755       expect(github.configurationValidityError.get()).toHaveTextContent(
756         `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`,
757       );
758       await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
759       expect(github.getOrgs()[1]).toHaveTextContent(
760         `settings.authentication.github.configuration.validation.details.invalid_labelorg2 - ${errorMessage}`,
761       );
762     });
763
764     it('should update provisioning validity after clicking Test Configuration', async () => {
765       const errorMessage = 'Test error';
766       handler.setConfigurationValidity({
767         application: {
768           jit: {
769             status: GitHubProvisioningStatus.Failed,
770             errorMessage,
771           },
772           autoProvisioning: {
773             status: GitHubProvisioningStatus.Failed,
774             errorMessage,
775           },
776         },
777       });
778       renderAuthentication([Feature.GithubProvisioning]);
779       await github.enableConfiguration(user);
780       handler.setConfigurationValidity({
781         application: {
782           jit: {
783             status: GitHubProvisioningStatus.Success,
784           },
785           autoProvisioning: {
786             status: GitHubProvisioningStatus.Success,
787           },
788         },
789       });
790
791       expect(await github.configurationValidityError.find()).toBeInTheDocument();
792
793       await act(() => user.click(github.checkConfigButton.get()));
794
795       expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
796       expect(github.configurationValidityError.query()).not.toBeInTheDocument();
797     });
798
799     it('should show warning', async () => {
800       handler.addProvisioningTask({
801         status: TaskStatuses.Success,
802         warnings: ['Warning'],
803       });
804       renderAuthentication([Feature.GithubProvisioning]);
805       await github.enableProvisioning(user);
806
807       expect(await github.syncWarning.find()).toBeInTheDocument();
808       expect(github.syncSummary.get()).toBeInTheDocument();
809     });
810
811     it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
812       const user = userEvent.setup();
813       settingsHandler.presetGithubAutoProvisioning();
814       handler.enableGithubProvisioning();
815       settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
816       renderAuthentication([Feature.GithubProvisioning]);
817
818       await user.click(await github.tab.find());
819
820       expect(await github.consentDialog.find()).toBeInTheDocument();
821       await user.click(github.continueAutoButton.get());
822
823       expect(await github.githubProvisioningButton.find()).toBeChecked();
824       expect(github.consentDialog.query()).not.toBeInTheDocument();
825     });
826
827     it('should display a modal if user was already using auto and switch to JIT', async () => {
828       const user = userEvent.setup();
829       settingsHandler.presetGithubAutoProvisioning();
830       handler.enableGithubProvisioning();
831       settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
832       renderAuthentication([Feature.GithubProvisioning]);
833
834       await user.click(await github.tab.find());
835
836       expect(await github.consentDialog.find()).toBeInTheDocument();
837       await user.click(github.switchJitButton.get());
838
839       expect(await github.jitProvisioningButton.find()).toBeChecked();
840       expect(github.consentDialog.query()).not.toBeInTheDocument();
841     });
842
843     it('should sort mapping rows', async () => {
844       const user = userEvent.setup();
845       settingsHandler.presetGithubAutoProvisioning();
846       handler.enableGithubProvisioning();
847       renderAuthentication([Feature.GithubProvisioning]);
848       await user.click(await github.tab.find());
849
850       expect(await github.editMappingButton.find()).toBeInTheDocument();
851       await user.click(github.editMappingButton.get());
852
853       expect(await github.mappingRow.findAll()).toHaveLength(6);
854       expect(github.mappingRow.getAt(1)).toHaveTextContent('read');
855       expect(github.mappingRow.getAt(2)).toHaveTextContent('triage');
856       expect(github.mappingRow.getAt(3)).toHaveTextContent('write');
857       expect(github.mappingRow.getAt(4)).toHaveTextContent('maintain');
858       expect(github.mappingRow.getAt(5)).toHaveTextContent('admin');
859     });
860
861     it('should apply new mapping and new provisioning type at the same time', async () => {
862       const user = userEvent.setup();
863       renderAuthentication([Feature.GithubProvisioning]);
864       await user.click(await github.tab.find());
865
866       await github.createConfiguration(user);
867       await user.click(await github.enableConfigButton.find());
868
869       expect(await github.jitProvisioningButton.find()).toBeChecked();
870       expect(github.editMappingButton.query()).not.toBeInTheDocument();
871       await user.click(github.githubProvisioningButton.get());
872       expect(await github.editMappingButton.find()).toBeInTheDocument();
873       await user.click(github.editMappingButton.get());
874
875       expect(await github.mappingRow.findAll()).toHaveLength(6);
876
877       let rowOneCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(1));
878       let rowFiveCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(5));
879
880       expect(rowOneCheckboxes[0]).toBeChecked();
881       expect(rowOneCheckboxes[5]).not.toBeChecked();
882       expect(rowFiveCheckboxes[5]).toBeChecked();
883
884       await user.click(rowOneCheckboxes[0]);
885       await user.click(rowOneCheckboxes[5]);
886       await user.click(rowFiveCheckboxes[5]);
887       await user.click(github.mappingDialogClose.get());
888
889       await user.click(github.saveGithubProvisioning.get());
890       await act(() => user.click(github.confirmProvisioningButton.get()));
891
892       // Clean local mapping state
893       await user.click(github.jitProvisioningButton.get());
894       await user.click(github.githubProvisioningButton.get());
895
896       await user.click(github.editMappingButton.get());
897       rowOneCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(1));
898       rowFiveCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(5));
899
900       expect(rowOneCheckboxes[0]).not.toBeChecked();
901       expect(rowOneCheckboxes[5]).toBeChecked();
902       expect(rowFiveCheckboxes[5]).not.toBeChecked();
903       await user.click(github.mappingDialogClose.get());
904     });
905
906     it('should apply new mapping on auto-provisioning', async () => {
907       const user = userEvent.setup();
908       settingsHandler.presetGithubAutoProvisioning();
909       handler.enableGithubProvisioning();
910       renderAuthentication([Feature.GithubProvisioning]);
911       await user.click(await github.tab.find());
912
913       expect(await github.saveGithubProvisioning.find()).toBeDisabled();
914       await user.click(github.editMappingButton.get());
915
916       expect(await github.mappingRow.findAll()).toHaveLength(6);
917
918       let rowOneCheckbox = github.mappingCheckbox.getAll(github.mappingRow.getAt(1))[0];
919
920       expect(rowOneCheckbox).toBeChecked();
921
922       await user.click(rowOneCheckbox);
923       await user.click(github.mappingDialogClose.get());
924
925       expect(await github.saveGithubProvisioning.find()).toBeEnabled();
926
927       await act(() => user.click(github.saveGithubProvisioning.get()));
928
929       // Clean local mapping state
930       await user.click(github.jitProvisioningButton.get());
931       await user.click(github.githubProvisioningButton.get());
932
933       await user.click(github.editMappingButton.get());
934       rowOneCheckbox = github.mappingCheckbox.getAll(github.mappingRow.getAt(1))[0];
935
936       expect(rowOneCheckbox).not.toBeChecked();
937       await user.click(github.mappingDialogClose.get());
938     });
939   });
940 });
941
942 function renderAuthentication(features: Feature[] = []) {
943   renderComponent(
944     <AvailableFeaturesContext.Provider value={features}>
945       <Authentication definitions={definitions} />
946     </AvailableFeaturesContext.Provider>,
947   );
948 }