3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
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';
37 jest.mock('../../../../../api/system');
39 let handler: AuthenticationServiceMock;
40 let system: SystemServiceMock;
41 let settingsHandler: SettingsServiceMock;
42 let computeEngineHandler: ComputeEngineServiceMock;
45 handler = new AuthenticationServiceMock();
46 system = new SystemServiceMock();
47 settingsHandler = new SettingsServiceMock();
48 computeEngineHandler = new ComputeEngineServiceMock();
51 key: 'sonar.auth.saml.signature.enabled',
55 key: 'sonar.auth.saml.enabled',
59 key: 'sonar.auth.saml.applicationId',
63 key: 'sonar.auth.saml.providerName',
66 ].forEach((setting: any) => settingsHandler.set(setting.key, setting.value));
71 settingsHandler.reset();
73 computeEngineHandler.reset();
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' }),
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',
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',
104 scimProvisioningButton: byRole('radio', {
105 name: 'settings.authentication.saml.form.provisioning_with_scim',
107 fillForm: async (user: UserEvent) => {
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');
119 createConfiguration: async (user: UserEvent) => {
121 await act(async () => {
122 await user.click((await saml.createConfigButton.findAll())[0]);
124 await saml.fillForm(user);
125 await act(async () => {
126 await user.click(saml.saveConfigButton.get());
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',
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',
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',
154 mappingRow: byRole('dialog', {
155 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
157 customRoleInput: byRole('textbox', {
158 name: 'settings.authentication.github.configuration.roles_mapping.dialog.add_custom_role',
160 customRoleAddBtn: byRole('dialog', {
161 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
162 }).byRole('button', { name: 'add_verb' }),
163 roleExistsError: byRole('dialog', {
164 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
165 }).byText('settings.authentication.github.configuration.roles_mapping.role_exists'),
166 deleteCustomRoleCustom2: byRole('button', {
167 name: 'settings.authentication.github.configuration.roles_mapping.dialog.delete_custom_role.custom2',
169 getMappingRowByRole: (text: string) =>
170 ui.github.mappingRow.getAll().find((row) => within(row).queryByText(text) !== null),
171 mappingCheckbox: byRole('checkbox'),
172 mappingDialogClose: byRole('dialog', {
173 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
174 }).byRole('button', {
177 deleteOrg: (org: string) =>
179 name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
181 enableFirstMessage: byText('settings.authentication.github.enable_first'),
182 jitProvisioningButton: byRole('radio', {
183 name: 'settings.authentication.form.provisioning_at_login',
185 githubProvisioningButton: byRole('radio', {
186 name: 'settings.authentication.github.form.provisioning_with_github',
188 githubProvisioningPending: byText(/synchronization_pending/),
189 githubProvisioningInProgress: byText(/synchronization_in_progress/),
190 githubProvisioningSuccess: byText(/synchronization_successful/),
191 githubProvisioningAlert: byText(/synchronization_failed/),
192 configurationValidityLoading: byRole('status', {
193 name: /github.configuration.validation.loading/,
195 configurationValiditySuccess: byRole('status', {
196 name: /github.configuration.validation.valid/,
198 configurationValidityError: byRole('status', {
199 name: /github.configuration.validation.invalid/,
201 syncWarning: byText(/Warning/),
202 syncSummary: byText(/Test summary/),
203 configurationValidityWarning: byRole('status', {
204 name: /github.configuration.validation.valid.short/,
206 checkConfigButton: byRole('button', {
207 name: 'settings.authentication.github.configuration.validation.test',
209 viewConfigValidityDetailsButton: byRole('button', {
210 name: 'settings.authentication.github.configuration.validation.details',
212 configDetailsDialog: byRole('dialog', {
213 name: 'settings.authentication.github.configuration.validation.details.title',
215 continueAutoButton: byRole('button', {
216 name: 'settings.authentication.github.confirm_auto_provisioning.continue',
218 switchJitButton: byRole('button', {
219 name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
221 consentDialog: byRole('dialog', {
222 name: 'settings.authentication.github.confirm_auto_provisioning.header',
224 getConfigDetailsTitle: () => within(ui.github.configDetailsDialog.get()).getByRole('heading'),
225 getOrgs: () => within(ui.github.configDetailsDialog.get()).getAllByRole('listitem'),
226 fillForm: async (user: UserEvent) => {
227 const { github } = ui;
228 await act(async () => {
229 await user.type(await github.clientId.find(), 'Awsome GITHUB config');
230 await user.type(github.clientSecret.get(), 'Client shut');
231 await user.type(github.githubApiUrl.get(), 'API Url');
232 await user.type(github.githubWebUrl.get(), 'WEb Url');
233 await user.type(github.organizations.get(), 'organization1');
236 createConfiguration: async (user: UserEvent) => {
237 const { github } = ui;
238 await act(async () => {
239 await user.click((await github.createConfigButton.findAll())[1]);
241 await github.fillForm(user);
242 await act(async () => {
243 await user.click(github.saveConfigButton.get());
246 enableConfiguration: async (user: UserEvent) => {
247 const { github } = ui;
248 await act(async () => user.click(await github.tab.find()));
249 await github.createConfiguration(user);
250 await act(async () => user.click(await github.enableConfigButton.find()));
252 enableProvisioning: async (user: UserEvent) => {
253 const { github } = ui;
254 await act(async () => user.click(await github.tab.find()));
256 await github.createConfiguration(user);
258 await act(async () => user.click(await github.enableConfigButton.find()));
259 await user.click(await github.githubProvisioningButton.find());
260 await user.click(github.saveGithubProvisioning.get());
261 await act(() => user.click(github.confirmProvisioningButton.get()));
266 it('should render tabs and allow navigation', async () => {
267 const user = userEvent.setup();
268 renderAuthentication();
270 expect(screen.getAllByRole('tab')).toHaveLength(4);
272 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
274 await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
276 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
277 expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
283 it('should not display the login message feature info box', () => {
284 renderAuthentication();
286 expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
289 it('should display the login message feature info box', () => {
290 renderAuthentication([Feature.LoginMessage]);
292 expect(ui.customMessageInformation.get()).toBeInTheDocument();
295 describe('SAML tab', () => {
298 it('should render an empty SAML configuration', async () => {
299 renderAuthentication();
300 expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
303 it('should be able to create a configuration', async () => {
304 const user = userEvent.setup();
305 renderAuthentication();
307 await user.click((await saml.createConfigButton.findAll())[0]);
309 expect(saml.saveConfigButton.get()).toBeDisabled();
310 await saml.fillForm(user);
311 expect(saml.saveConfigButton.get()).toBeEnabled();
313 await act(async () => {
314 await user.click(saml.saveConfigButton.get());
317 expect(await saml.editConfigButton.find()).toBeInTheDocument();
320 it('should be able to enable/disable configuration', async () => {
322 const user = userEvent.setup();
323 renderAuthentication();
325 await saml.createConfiguration(user);
326 await user.click(await saml.enableConfigButton.find());
328 expect(await saml.disableConfigButton.find()).toBeInTheDocument();
329 await user.click(saml.disableConfigButton.get());
330 await waitFor(() => expect(saml.disableConfigButton.query()).not.toBeInTheDocument());
332 expect(await saml.enableConfigButton.find()).toBeInTheDocument();
335 it('should be able to choose provisioning', async () => {
337 const user = userEvent.setup();
339 renderAuthentication([Feature.Scim]);
341 await saml.createConfiguration(user);
343 expect(await saml.enableFirstMessage.find()).toBeInTheDocument();
344 await user.click(await saml.enableConfigButton.find());
346 expect(await saml.jitProvisioningButton.find()).toBeChecked();
347 expect(saml.saveScim.get()).toBeDisabled();
349 await user.click(saml.scimProvisioningButton.get());
350 expect(saml.saveScim.get()).toBeEnabled();
351 await user.click(saml.saveScim.get());
352 await user.click(saml.confirmProvisioningButton.get());
354 expect(await saml.scimProvisioningButton.find()).toBeChecked();
355 expect(await saml.saveScim.find()).toBeDisabled();
358 it('should not allow editions below Enterprise to select SCIM provisioning', async () => {
360 const user = userEvent.setup();
362 renderAuthentication();
364 await saml.createConfiguration(user);
365 await user.click(await saml.enableConfigButton.find());
367 expect(await saml.jitProvisioningButton.find()).toBeChecked();
368 expect(saml.scimProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
372 describe('Github tab', () => {
373 const { github } = ui;
375 it('should render an empty Github configuration', async () => {
376 renderAuthentication();
377 const user = userEvent.setup();
378 await user.click(await github.tab.find());
379 expect(await github.noGithubConfiguration.find()).toBeInTheDocument();
382 it('should be able to create a configuration', async () => {
383 const user = userEvent.setup();
384 renderAuthentication();
386 await user.click(await github.tab.find());
387 await user.click((await github.createConfigButton.findAll())[1]);
389 expect(github.saveConfigButton.get()).toBeDisabled();
391 await github.fillForm(user);
392 expect(github.saveConfigButton.get()).toBeEnabled();
394 await act(async () => {
395 await user.click(github.saveConfigButton.get());
398 expect(await github.editConfigButton.find()).toBeInTheDocument();
401 it('should be able to edit configuration', async () => {
402 const { github } = ui;
403 const user = userEvent.setup();
404 renderAuthentication();
405 await user.click(await github.tab.find());
407 await github.createConfiguration(user);
409 await user.click(github.editConfigButton.get());
410 await user.click(github.deleteOrg('organization1').get());
412 await user.click(github.saveConfigButton.get());
414 await user.click(await github.editConfigButton.find());
416 expect(github.organizations.get()).toHaveValue('');
419 it('should be able to enable/disable configuration', async () => {
420 const { github } = ui;
421 const user = userEvent.setup();
422 renderAuthentication();
423 await user.click(await github.tab.find());
425 await github.createConfiguration(user);
427 await user.click(await github.enableConfigButton.find());
429 expect(await github.disableConfigButton.find()).toBeInTheDocument();
430 await user.click(github.disableConfigButton.get());
431 await waitFor(() => expect(github.disableConfigButton.query()).not.toBeInTheDocument());
433 expect(await github.enableConfigButton.find()).toBeInTheDocument();
436 it('should not allow edtion below Enterprise to select Github provisioning', async () => {
437 const { github } = ui;
438 const user = userEvent.setup();
440 renderAuthentication();
441 await user.click(await github.tab.find());
443 await github.createConfiguration(user);
444 await user.click(await github.enableConfigButton.find());
446 expect(await github.jitProvisioningButton.find()).toBeChecked();
447 expect(github.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
450 it('should be able to choose provisioning', async () => {
451 const { github } = ui;
452 const user = userEvent.setup();
454 renderAuthentication([Feature.GithubProvisioning]);
455 await user.click(await github.tab.find());
457 await github.createConfiguration(user);
459 expect(await github.enableFirstMessage.find()).toBeInTheDocument();
460 await user.click(await github.enableConfigButton.find());
462 expect(await github.jitProvisioningButton.find()).toBeChecked();
464 expect(github.saveGithubProvisioning.get()).toBeDisabled();
465 await user.click(github.allowUserToSignUp.get());
467 expect(github.saveGithubProvisioning.get()).toBeEnabled();
468 await user.click(github.saveGithubProvisioning.get());
470 await waitFor(() => expect(github.saveGithubProvisioning.query()).toBeDisabled());
472 await user.click(github.githubProvisioningButton.get());
474 expect(github.saveGithubProvisioning.get()).toBeEnabled();
475 await user.click(github.saveGithubProvisioning.get());
476 await user.click(github.confirmProvisioningButton.get());
478 expect(await github.githubProvisioningButton.find()).toBeChecked();
479 expect(github.disableConfigButton.get()).toBeDisabled();
480 expect(github.saveGithubProvisioning.get()).toBeDisabled();
483 describe('Github Provisioning', () => {
489 now: new Date('2022-02-04T12:00:59Z'),
491 user = userEvent.setup();
495 jest.runOnlyPendingTimers();
496 jest.useRealTimers();
499 it('should display a success status when the synchronisation is a success', async () => {
500 handler.addProvisioningTask({
501 status: TaskStatuses.Success,
502 executedAt: '2022-02-03T11:45:35+0200',
505 renderAuthentication([Feature.GithubProvisioning]);
506 await github.enableProvisioning(user);
507 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
508 expect(github.syncSummary.get()).toBeInTheDocument();
511 it('should display a success status even when another task is pending', async () => {
512 handler.addProvisioningTask({
513 status: TaskStatuses.Pending,
514 executedAt: '2022-02-03T11:55:35+0200',
516 handler.addProvisioningTask({
517 status: TaskStatuses.Success,
518 executedAt: '2022-02-03T11:45:35+0200',
520 renderAuthentication([Feature.GithubProvisioning]);
521 await github.enableProvisioning(user);
522 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
523 expect(github.githubProvisioningPending.get()).toBeInTheDocument();
526 it('should display an error alert when the synchronisation failed', async () => {
527 handler.addProvisioningTask({
528 status: TaskStatuses.Failed,
529 executedAt: '2022-02-03T11:45:35+0200',
530 errorMessage: "T'es mauvais Jacques",
532 renderAuthentication([Feature.GithubProvisioning]);
533 await github.enableProvisioning(user);
534 expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
535 expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
536 expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
539 it('should display an error alert even when another task is in progress', async () => {
540 handler.addProvisioningTask({
541 status: TaskStatuses.InProgress,
542 executedAt: '2022-02-03T11:55:35+0200',
544 handler.addProvisioningTask({
545 status: TaskStatuses.Failed,
546 executedAt: '2022-02-03T11:45:35+0200',
547 errorMessage: "T'es mauvais Jacques",
549 renderAuthentication([Feature.GithubProvisioning]);
550 await github.enableProvisioning(user);
551 expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
552 expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
553 expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
554 expect(github.githubProvisioningInProgress.get()).toBeInTheDocument();
557 it('should display that config is valid for both provisioning with 1 org', async () => {
558 renderAuthentication([Feature.GithubProvisioning]);
559 await github.enableConfiguration(user);
561 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
564 it('should display that config is valid for both provisioning with multiple orgs', async () => {
565 handler.setConfigurationValidity({
568 organization: 'org1',
569 autoProvisioning: { status: GitHubProvisioningStatus.Success },
570 jit: { status: GitHubProvisioningStatus.Success },
573 organization: 'org2',
574 autoProvisioning: { status: GitHubProvisioningStatus.Success },
575 jit: { status: GitHubProvisioningStatus.Success },
579 renderAuthentication([Feature.GithubProvisioning]);
580 await github.enableConfiguration(user);
582 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
583 expect(github.configurationValiditySuccess.get()).toHaveTextContent('2');
585 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
586 expect(github.getConfigDetailsTitle()).toHaveTextContent(
587 'settings.authentication.github.configuration.validation.details.valid_label',
589 expect(github.getOrgs()[0]).toHaveTextContent(
590 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
592 expect(github.getOrgs()[1]).toHaveTextContent(
593 'settings.authentication.github.configuration.validation.details.valid_labelorg2',
597 it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
598 const errorMessage = 'Installation suspended';
599 handler.setConfigurationValidity({
602 organization: 'org1',
604 status: GitHubProvisioningStatus.Failed,
608 status: GitHubProvisioningStatus.Failed,
615 renderAuthentication([Feature.GithubProvisioning]);
616 await github.enableConfiguration(user);
618 await waitFor(() => expect(github.configurationValidityWarning.get()).toBeInTheDocument());
619 expect(github.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
621 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
622 expect(github.getConfigDetailsTitle()).toHaveTextContent(
623 'settings.authentication.github.configuration.validation.details.valid_label',
625 expect(github.getOrgs()[0]).toHaveTextContent(
626 'settings.authentication.github.configuration.validation.details.invalid_labelorg1 - Installation suspended',
630 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
633 await user.click(github.githubProvisioningButton.get());
634 await waitFor(() => expect(github.configurationValidityError.get()).toBeInTheDocument());
635 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
638 it('should display that config is valid but some organizations were not found', async () => {
639 handler.setConfigurationValidity({
642 organization: 'org1',
643 autoProvisioning: { status: GitHubProvisioningStatus.Success },
644 jit: { status: GitHubProvisioningStatus.Success },
649 renderAuthentication([Feature.GithubProvisioning]);
650 await github.enableConfiguration(user);
652 await waitFor(() => expect(github.configurationValiditySuccess.get()).toBeInTheDocument());
653 expect(github.configurationValiditySuccess.get()).toHaveTextContent('1');
655 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
656 expect(github.getConfigDetailsTitle()).toHaveTextContent(
657 'settings.authentication.github.configuration.validation.details.valid_label',
659 expect(github.getOrgs()[0]).toHaveTextContent(
660 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
662 expect(github.getOrgs()[1]).toHaveTextContent(
663 'settings.authentication.github.configuration.validation.details.org_not_found.organization1',
667 it('should display that config is invalid', async () => {
668 const errorMessage = 'Test error';
669 handler.setConfigurationValidity({
672 status: GitHubProvisioningStatus.Failed,
676 status: GitHubProvisioningStatus.Failed,
681 renderAuthentication([Feature.GithubProvisioning]);
682 await github.enableConfiguration(user);
684 await waitFor(() => expect(github.configurationValidityError.query()).toBeInTheDocument());
685 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
687 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
688 expect(github.getConfigDetailsTitle()).toHaveTextContent(
689 'settings.authentication.github.configuration.validation.details.invalid_label',
691 expect(github.configDetailsDialog.get()).toHaveTextContent(errorMessage);
694 it('should display that config is valid for jit, but not for auto', async () => {
695 const errorMessage = 'Test error';
696 handler.setConfigurationValidity({
699 status: GitHubProvisioningStatus.Success,
702 status: GitHubProvisioningStatus.Failed,
707 renderAuthentication([Feature.GithubProvisioning]);
708 await github.enableConfiguration(user);
710 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
711 expect(github.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
713 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
714 expect(github.getConfigDetailsTitle()).toHaveTextContent(
715 'settings.authentication.github.configuration.validation.details.valid_label',
718 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
721 await act(() => user.click(github.githubProvisioningButton.get()));
723 expect(github.configurationValidityError.get()).toBeInTheDocument();
724 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
726 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
727 expect(github.getConfigDetailsTitle()).toHaveTextContent(
728 'settings.authentication.github.configuration.validation.details.invalid_label',
732 it('should display that config is invalid because of orgs', async () => {
733 const errorMessage = 'Test error';
734 handler.setConfigurationValidity({
737 organization: 'org1',
738 autoProvisioning: { status: GitHubProvisioningStatus.Success },
739 jit: { status: GitHubProvisioningStatus.Success },
742 organization: 'org2',
743 jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
744 autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
748 renderAuthentication([Feature.GithubProvisioning]);
749 await github.enableConfiguration(user);
751 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
753 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
755 expect(github.getOrgs()[0]).toHaveTextContent(
756 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
758 expect(github.getOrgs()[1]).toHaveTextContent(
759 'settings.authentication.github.configuration.validation.details.invalid_labelorg2 - Test error',
763 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
766 await act(() => user.click(github.githubProvisioningButton.get()));
768 expect(github.configurationValidityError.get()).toBeInTheDocument();
769 expect(github.configurationValidityError.get()).toHaveTextContent(
770 `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`,
772 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
773 expect(github.getOrgs()[1]).toHaveTextContent(
774 `settings.authentication.github.configuration.validation.details.invalid_labelorg2 - ${errorMessage}`,
778 it('should update provisioning validity after clicking Test Configuration', async () => {
779 const errorMessage = 'Test error';
780 handler.setConfigurationValidity({
783 status: GitHubProvisioningStatus.Failed,
787 status: GitHubProvisioningStatus.Failed,
792 renderAuthentication([Feature.GithubProvisioning]);
793 await github.enableConfiguration(user);
794 handler.setConfigurationValidity({
797 status: GitHubProvisioningStatus.Success,
800 status: GitHubProvisioningStatus.Success,
805 expect(await github.configurationValidityError.find()).toBeInTheDocument();
807 await act(() => user.click(github.checkConfigButton.get()));
809 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
810 expect(github.configurationValidityError.query()).not.toBeInTheDocument();
813 it('should show warning', async () => {
814 handler.addProvisioningTask({
815 status: TaskStatuses.Success,
816 warnings: ['Warning'],
818 renderAuthentication([Feature.GithubProvisioning]);
819 await github.enableProvisioning(user);
821 expect(await github.syncWarning.find()).toBeInTheDocument();
822 expect(github.syncSummary.get()).toBeInTheDocument();
825 it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
826 const user = userEvent.setup();
827 settingsHandler.presetGithubAutoProvisioning();
828 handler.enableGithubProvisioning();
829 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
830 renderAuthentication([Feature.GithubProvisioning]);
832 await user.click(await github.tab.find());
834 expect(await github.consentDialog.find()).toBeInTheDocument();
835 await user.click(github.continueAutoButton.get());
837 expect(await github.githubProvisioningButton.find()).toBeChecked();
838 expect(github.consentDialog.query()).not.toBeInTheDocument();
841 it('should display a modal if user was already using auto and switch to JIT', async () => {
842 const user = userEvent.setup();
843 settingsHandler.presetGithubAutoProvisioning();
844 handler.enableGithubProvisioning();
845 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
846 renderAuthentication([Feature.GithubProvisioning]);
848 await user.click(await github.tab.find());
850 expect(await github.consentDialog.find()).toBeInTheDocument();
851 await user.click(github.switchJitButton.get());
853 expect(await github.jitProvisioningButton.find()).toBeChecked();
854 expect(github.consentDialog.query()).not.toBeInTheDocument();
857 it('should sort mapping rows', async () => {
858 const user = userEvent.setup();
859 settingsHandler.presetGithubAutoProvisioning();
860 handler.enableGithubProvisioning();
861 renderAuthentication([Feature.GithubProvisioning]);
862 await user.click(await github.tab.find());
864 expect(await github.editMappingButton.find()).toBeInTheDocument();
865 await user.click(github.editMappingButton.get());
867 const rows = (await github.mappingRow.findAll()).filter(
868 (row) => within(row).queryAllByRole('checkbox').length > 0,
871 expect(rows).toHaveLength(5);
873 expect(rows[0]).toHaveTextContent('read');
874 expect(rows[1]).toHaveTextContent('triage');
875 expect(rows[2]).toHaveTextContent('write');
876 expect(rows[3]).toHaveTextContent('maintain');
877 expect(rows[4]).toHaveTextContent('admin');
880 it('should apply new mapping and new provisioning type at the same time', async () => {
881 const user = userEvent.setup();
882 renderAuthentication([Feature.GithubProvisioning]);
883 await user.click(await github.tab.find());
885 await github.createConfiguration(user);
886 await user.click(await github.enableConfigButton.find());
888 expect(await github.jitProvisioningButton.find()).toBeChecked();
889 expect(github.editMappingButton.query()).not.toBeInTheDocument();
890 await user.click(github.githubProvisioningButton.get());
891 expect(await github.editMappingButton.find()).toBeInTheDocument();
892 await user.click(github.editMappingButton.get());
894 expect(await github.mappingRow.findAll()).toHaveLength(7);
896 let readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'));
897 let adminCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('admin'));
899 expect(readCheckboxes[0]).toBeChecked();
900 expect(readCheckboxes[5]).not.toBeChecked();
901 expect(adminCheckboxes[5]).toBeChecked();
903 await user.click(readCheckboxes[0]);
904 await user.click(readCheckboxes[5]);
905 await user.click(adminCheckboxes[5]);
906 await user.click(github.mappingDialogClose.get());
908 await user.click(github.saveGithubProvisioning.get());
909 await act(() => user.click(github.confirmProvisioningButton.get()));
911 // Clean local mapping state
912 await user.click(github.jitProvisioningButton.get());
913 await user.click(github.githubProvisioningButton.get());
915 await user.click(github.editMappingButton.get());
916 readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'));
917 adminCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('admin'));
919 expect(readCheckboxes[0]).not.toBeChecked();
920 expect(readCheckboxes[5]).toBeChecked();
921 expect(adminCheckboxes[5]).not.toBeChecked();
922 await user.click(github.mappingDialogClose.get());
925 it('should apply new mapping on auto-provisioning', async () => {
926 const user = userEvent.setup();
927 settingsHandler.presetGithubAutoProvisioning();
928 handler.enableGithubProvisioning();
929 renderAuthentication([Feature.GithubProvisioning]);
930 await user.click(await github.tab.find());
932 expect(await github.saveGithubProvisioning.find()).toBeDisabled();
933 await user.click(github.editMappingButton.get());
935 expect(await github.mappingRow.findAll()).toHaveLength(7);
937 let readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'))[0];
939 expect(readCheckboxes).toBeChecked();
941 await user.click(readCheckboxes);
942 await user.click(github.mappingDialogClose.get());
944 expect(await github.saveGithubProvisioning.find()).toBeEnabled();
946 await act(() => user.click(github.saveGithubProvisioning.get()));
948 // Clean local mapping state
949 await user.click(github.jitProvisioningButton.get());
950 await user.click(github.githubProvisioningButton.get());
952 await user.click(github.editMappingButton.get());
953 readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'))[0];
955 expect(readCheckboxes).not.toBeChecked();
956 await user.click(github.mappingDialogClose.get());
959 it('should add/remove/update custom roles', async () => {
960 const user = userEvent.setup();
961 settingsHandler.presetGithubAutoProvisioning();
962 handler.enableGithubProvisioning();
963 handler.addGitHubCustomRole('custom1', ['user', 'codeViewer', 'scan']);
964 handler.addGitHubCustomRole('custom2', ['user', 'codeViewer', 'issueAdmin', 'scan']);
965 renderAuthentication([Feature.GithubProvisioning]);
966 await user.click(await github.tab.find());
968 expect(await github.saveGithubProvisioning.find()).toBeDisabled();
969 await user.click(github.editMappingButton.get());
971 const rows = (await github.mappingRow.findAll()).filter(
972 (row) => within(row).queryAllByRole('checkbox').length > 0,
975 expect(rows).toHaveLength(7);
977 let custom1Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom1'));
979 expect(custom1Checkboxes[0]).toBeChecked();
980 expect(custom1Checkboxes[1]).toBeChecked();
981 expect(custom1Checkboxes[2]).not.toBeChecked();
982 expect(custom1Checkboxes[3]).not.toBeChecked();
983 expect(custom1Checkboxes[4]).not.toBeChecked();
984 expect(custom1Checkboxes[5]).toBeChecked();
986 await user.click(custom1Checkboxes[1]);
987 await user.click(custom1Checkboxes[2]);
989 await user.click(github.deleteCustomRoleCustom2.get());
991 expect(github.customRoleInput.get()).toHaveValue('');
992 await user.type(github.customRoleInput.get(), 'read');
993 await user.click(github.customRoleAddBtn.get());
994 expect(await github.roleExistsError.find()).toBeInTheDocument();
995 expect(github.customRoleAddBtn.get()).toBeDisabled();
996 await user.clear(github.customRoleInput.get());
997 expect(github.roleExistsError.query()).not.toBeInTheDocument();
998 await user.type(github.customRoleInput.get(), 'custom1');
999 await user.click(github.customRoleAddBtn.get());
1000 expect(await github.roleExistsError.find()).toBeInTheDocument();
1001 expect(github.customRoleAddBtn.get()).toBeDisabled();
1002 await user.clear(github.customRoleInput.get());
1003 await user.type(github.customRoleInput.get(), 'custom3');
1004 expect(github.roleExistsError.query()).not.toBeInTheDocument();
1005 expect(github.customRoleAddBtn.get()).toBeEnabled();
1006 await user.click(github.customRoleAddBtn.get());
1008 let custom3Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom3'));
1009 expect(custom3Checkboxes[0]).not.toBeChecked();
1010 expect(custom3Checkboxes[1]).not.toBeChecked();
1011 expect(custom3Checkboxes[2]).not.toBeChecked();
1012 expect(custom3Checkboxes[3]).not.toBeChecked();
1013 expect(custom3Checkboxes[4]).not.toBeChecked();
1014 expect(custom3Checkboxes[5]).not.toBeChecked();
1015 await user.click(custom3Checkboxes[1]);
1016 await user.click(github.mappingDialogClose.get());
1018 expect(await github.saveGithubProvisioning.find()).toBeEnabled();
1019 await act(() => user.click(github.saveGithubProvisioning.get()));
1021 // Clean local mapping state
1022 await user.click(github.jitProvisioningButton.get());
1023 await user.click(github.githubProvisioningButton.get());
1025 await user.click(github.editMappingButton.get());
1028 (await github.mappingRow.findAll()).filter(
1029 (row) => within(row).queryAllByRole('checkbox').length > 0,
1032 custom1Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom1'));
1033 custom3Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom3'));
1034 expect(github.getMappingRowByRole('custom2')).toBeUndefined();
1035 expect(custom1Checkboxes[0]).toBeChecked();
1036 expect(custom1Checkboxes[1]).not.toBeChecked();
1037 expect(custom1Checkboxes[2]).toBeChecked();
1038 expect(custom1Checkboxes[3]).not.toBeChecked();
1039 expect(custom1Checkboxes[4]).not.toBeChecked();
1040 expect(custom1Checkboxes[5]).toBeChecked();
1041 expect(custom3Checkboxes[0]).not.toBeChecked();
1042 expect(custom3Checkboxes[1]).toBeChecked();
1043 expect(custom3Checkboxes[2]).not.toBeChecked();
1044 expect(custom3Checkboxes[3]).not.toBeChecked();
1045 expect(custom3Checkboxes[4]).not.toBeChecked();
1046 expect(custom3Checkboxes[5]).not.toBeChecked();
1047 await user.click(github.mappingDialogClose.get());
1052 function renderAuthentication(features: Feature[] = []) {
1054 <AvailableFeaturesContext.Provider value={features}>
1055 <Authentication definitions={definitions} />
1056 </AvailableFeaturesContext.Provider>,