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 { 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 let handler: AuthenticationServiceMock;
38 let system: SystemServiceMock;
39 let settingsHandler: SettingsServiceMock;
40 let computeEngineHandler: ComputeEngineServiceMock;
43 handler = new AuthenticationServiceMock();
44 system = new SystemServiceMock();
45 settingsHandler = new SettingsServiceMock();
46 computeEngineHandler = new ComputeEngineServiceMock();
49 key: 'sonar.auth.saml.signature.enabled',
53 key: 'sonar.auth.saml.enabled',
57 key: 'sonar.auth.saml.applicationId',
61 key: 'sonar.auth.saml.providerName',
64 ].forEach((setting: any) => settingsHandler.set(setting.key, setting.value));
69 settingsHandler.reset();
71 computeEngineHandler.reset();
75 saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
76 customMessageInformation: byText('settings.authentication.custom_message_information'),
77 enabledToggle: byRole('switch'),
78 testButton: byText('settings.authentication.saml.form.test'),
79 textbox1: byRole('textbox', { name: 'test1' }),
80 textbox2: byRole('textbox', { name: 'test2' }),
82 noSamlConfiguration: byText('settings.authentication.saml.form.not_configured'),
83 createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
84 providerName: byRole('textbox', { name: 'property.sonar.auth.saml.providerName.name' }),
85 providerId: byRole('textbox', { name: 'property.sonar.auth.saml.providerId.name' }),
86 providerCertificate: byRole('textbox', {
87 name: 'property.sonar.auth.saml.certificate.secured.name',
89 loginUrl: byRole('textbox', { name: 'property.sonar.auth.saml.loginUrl.name' }),
90 userLoginAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.login.name' }),
91 userNameAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.name.name' }),
92 saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
93 confirmProvisioningButton: byRole('button', {
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) => {
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');
118 createConfiguration: async (user: UserEvent) => {
121 await user.click((await saml.createConfigButton.findAll())[0]);
122 await saml.fillForm(user);
123 await user.click(saml.saveConfigButton.get());
127 tab: byRole('tab', { name: 'github GitHub' }),
128 noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
129 createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
130 clientId: byRole('textbox', { name: 'property.sonar.auth.github.clientId.secured.name' }),
131 appId: byRole('textbox', { name: 'property.sonar.auth.github.appId.name' }),
132 privateKey: byRole('textbox', { name: 'property.sonar.auth.github.privateKey.secured.name' }),
133 clientSecret: byRole('textbox', {
134 name: 'property.sonar.auth.github.clientSecret.secured.name',
136 githubApiUrl: byRole('textbox', { name: 'property.sonar.auth.github.apiUrl.name' }),
137 githubWebUrl: byRole('textbox', { name: 'property.sonar.auth.github.webUrl.name' }),
138 allowUserToSignUp: byRole('switch', {
139 name: 'sonar.auth.github.allowUsersToSignUp',
141 organizations: byRole('textbox', { name: 'property.sonar.auth.github.organizations.name' }),
142 saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
143 confirmProvisioningButton: byRole('button', {
144 name: 'settings.authentication.github.provisioning_change.confirm_changes',
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 emptyRoleError: byRole('dialog', {
167 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
168 }).byText('settings.authentication.github.configuration.roles_mapping.empty_custom_role'),
169 deleteCustomRoleCustom2: byRole('button', {
170 name: 'settings.authentication.github.configuration.roles_mapping.dialog.delete_custom_role.custom2',
172 getMappingRowByRole: (text: string) =>
173 ui.github.mappingRow.getAll().find((row) => within(row).queryByText(text) !== null),
174 mappingCheckbox: byRole('checkbox'),
175 mappingDialogClose: byRole('dialog', {
176 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
177 }).byRole('button', {
180 deleteOrg: (org: string) =>
182 name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
184 enableFirstMessage: byText('settings.authentication.github.enable_first'),
185 jitProvisioningButton: byRole('radio', {
186 name: 'settings.authentication.form.provisioning_at_login',
188 githubProvisioningButton: byRole('radio', {
189 name: 'settings.authentication.github.form.provisioning_with_github',
191 githubProvisioningPending: byText(/synchronization_pending/),
192 githubProvisioningInProgress: byText(/synchronization_in_progress/),
193 githubProvisioningSuccess: byText(/synchronization_successful/),
194 githubProvisioningAlert: byText(/synchronization_failed/),
195 configurationValidityLoading: byRole('status', {
196 name: /github.configuration.validation.loading/,
198 configurationValiditySuccess: byRole('status', {
199 name: /github.configuration.validation.valid/,
201 configurationValidityError: byRole('status', {
202 name: /github.configuration.validation.invalid/,
204 syncWarning: byText(/Warning/),
205 syncSummary: byText(/Test summary/),
206 configurationValidityWarning: byRole('status', {
207 name: /github.configuration.validation.valid.short/,
209 checkConfigButton: byRole('button', {
210 name: 'settings.authentication.github.configuration.validation.test',
212 viewConfigValidityDetailsButton: byRole('button', {
213 name: 'settings.authentication.github.configuration.validation.details',
215 configDetailsDialog: byRole('dialog', {
216 name: 'settings.authentication.github.configuration.validation.details.title',
218 continueAutoButton: byRole('button', {
219 name: 'settings.authentication.github.confirm_auto_provisioning.continue',
221 switchJitButton: byRole('button', {
222 name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
224 consentDialog: byRole('dialog', {
225 name: 'settings.authentication.github.confirm_auto_provisioning.header',
227 getConfigDetailsTitle: () => within(ui.github.configDetailsDialog.get()).getByRole('heading'),
228 getOrgs: () => within(ui.github.configDetailsDialog.get()).getAllByRole('listitem'),
229 fillForm: async (user: UserEvent) => {
230 const { github } = ui;
232 await user.type(await github.clientId.find(), 'Awsome GITHUB config');
233 await user.type(github.clientSecret.get(), 'Client shut');
234 await user.type(github.appId.get(), 'App id');
235 await user.type(github.privateKey.get(), 'Private Key');
236 await user.type(github.githubApiUrl.get(), 'API Url');
237 await user.type(github.githubWebUrl.get(), 'WEb Url');
238 await user.type(github.organizations.get(), 'organization1');
240 createConfiguration: async (user: UserEvent) => {
241 const { github } = ui;
243 await user.click((await github.createConfigButton.findAll())[1]);
244 await github.fillForm(user);
246 await user.click(github.saveConfigButton.get());
248 enableConfiguration: async (user: UserEvent) => {
249 const { github } = ui;
250 await user.click(await github.tab.find());
251 await github.createConfiguration(user);
252 await user.click(await github.enableConfigButton.find());
254 enableProvisioning: async (user: UserEvent) => {
255 const { github } = ui;
256 await user.click(await github.tab.find());
258 await github.createConfiguration(user);
260 await user.click(await github.enableConfigButton.find());
261 await user.click(await github.githubProvisioningButton.find());
262 await user.click(github.saveGithubProvisioning.get());
263 await user.click(github.confirmProvisioningButton.get());
268 it('should render tabs and allow navigation', async () => {
269 const user = userEvent.setup();
270 renderAuthentication();
272 expect(screen.getAllByRole('tab')).toHaveLength(4);
274 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
276 await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
278 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
279 expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
285 it('should not display the login message feature info box', () => {
286 renderAuthentication();
288 expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
291 it('should display the login message feature info box', () => {
292 renderAuthentication([Feature.LoginMessage]);
294 expect(ui.customMessageInformation.get()).toBeInTheDocument();
297 describe('SAML tab', () => {
300 it('should render an empty SAML configuration', async () => {
301 renderAuthentication();
302 expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
305 it('should be able to create a configuration', async () => {
306 const user = userEvent.setup();
307 renderAuthentication();
309 await user.click((await saml.createConfigButton.findAll())[0]);
311 expect(saml.saveConfigButton.get()).toBeDisabled();
312 await saml.fillForm(user);
313 expect(saml.saveConfigButton.get()).toBeEnabled();
315 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 user.click(github.saveConfigButton.get());
396 expect(await github.editConfigButton.find()).toBeInTheDocument();
399 it('should be able to edit configuration', async () => {
400 const { github } = ui;
401 const user = userEvent.setup();
402 renderAuthentication();
403 await user.click(await github.tab.find());
405 await github.createConfiguration(user);
407 await user.click(github.editConfigButton.get());
408 await user.click(github.deleteOrg('organization1').get());
410 await user.click(github.saveConfigButton.get());
412 await user.click(await github.editConfigButton.find());
414 expect(github.organizations.get()).toHaveValue('');
417 it('should be able to enable/disable configuration', async () => {
418 const { github } = ui;
419 const user = userEvent.setup();
420 renderAuthentication();
421 await user.click(await github.tab.find());
423 await github.createConfiguration(user);
425 await user.click(await github.enableConfigButton.find());
427 expect(await github.disableConfigButton.find()).toBeInTheDocument();
428 await user.click(github.disableConfigButton.get());
429 await waitFor(() => expect(github.disableConfigButton.query()).not.toBeInTheDocument());
431 expect(await github.enableConfigButton.find()).toBeInTheDocument();
434 it('should not allow edtion below Enterprise to select Github provisioning', async () => {
435 const { github } = ui;
436 const user = userEvent.setup();
438 renderAuthentication();
439 await user.click(await github.tab.find());
441 await github.createConfiguration(user);
442 await user.click(await github.enableConfigButton.find());
444 expect(await github.jitProvisioningButton.find()).toBeChecked();
445 expect(github.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
448 it('should be able to choose provisioning', async () => {
449 const { github } = ui;
450 const user = userEvent.setup();
452 renderAuthentication([Feature.GithubProvisioning]);
453 await user.click(await github.tab.find());
455 await github.createConfiguration(user);
457 expect(await github.enableFirstMessage.find()).toBeInTheDocument();
458 await user.click(await github.enableConfigButton.find());
460 expect(await github.jitProvisioningButton.find()).toBeChecked();
462 expect(github.saveGithubProvisioning.get()).toBeDisabled();
463 await user.click(github.allowUserToSignUp.get());
465 expect(github.saveGithubProvisioning.get()).toBeEnabled();
466 await user.click(github.saveGithubProvisioning.get());
468 await waitFor(() => expect(github.saveGithubProvisioning.query()).toBeDisabled());
470 await user.click(github.githubProvisioningButton.get());
472 expect(github.saveGithubProvisioning.get()).toBeEnabled();
473 await user.click(github.saveGithubProvisioning.get());
474 await user.click(github.confirmProvisioningButton.get());
476 expect(await github.githubProvisioningButton.find()).toBeChecked();
477 expect(github.disableConfigButton.get()).toBeDisabled();
478 expect(github.saveGithubProvisioning.get()).toBeDisabled();
481 describe('Github Provisioning', () => {
487 now: new Date('2022-02-04T12:00:59Z'),
489 user = userEvent.setup();
493 jest.runOnlyPendingTimers();
494 jest.useRealTimers();
497 it('should display a success status when the synchronisation is a success', async () => {
498 handler.addProvisioningTask({
499 status: TaskStatuses.Success,
500 executedAt: '2022-02-03T11:45:35+0200',
503 renderAuthentication([Feature.GithubProvisioning]);
504 await github.enableProvisioning(user);
505 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
506 expect(github.syncSummary.get()).toBeInTheDocument();
509 it('should display a success status even when another task is pending', async () => {
510 handler.addProvisioningTask({
511 status: TaskStatuses.Pending,
512 executedAt: '2022-02-03T11:55:35+0200',
514 handler.addProvisioningTask({
515 status: TaskStatuses.Success,
516 executedAt: '2022-02-03T11:45:35+0200',
518 renderAuthentication([Feature.GithubProvisioning]);
519 await github.enableProvisioning(user);
520 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
521 expect(github.githubProvisioningPending.get()).toBeInTheDocument();
524 it('should display an error alert when the synchronisation failed', async () => {
525 handler.addProvisioningTask({
526 status: TaskStatuses.Failed,
527 executedAt: '2022-02-03T11:45:35+0200',
528 errorMessage: "T'es mauvais Jacques",
530 renderAuthentication([Feature.GithubProvisioning]);
531 await github.enableProvisioning(user);
532 expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
533 expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
534 expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
537 it('should display an error alert even when another task is in progress', async () => {
538 handler.addProvisioningTask({
539 status: TaskStatuses.InProgress,
540 executedAt: '2022-02-03T11:55:35+0200',
542 handler.addProvisioningTask({
543 status: TaskStatuses.Failed,
544 executedAt: '2022-02-03T11:45:35+0200',
545 errorMessage: "T'es mauvais Jacques",
547 renderAuthentication([Feature.GithubProvisioning]);
548 await github.enableProvisioning(user);
549 expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
550 expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
551 expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
552 expect(github.githubProvisioningInProgress.get()).toBeInTheDocument();
555 it('should display that config is valid for both provisioning with 1 org', async () => {
556 renderAuthentication([Feature.GithubProvisioning]);
557 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);
584 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
585 expect(github.configurationValiditySuccess.get()).toHaveTextContent('2');
587 await user.click(github.viewConfigValidityDetailsButton.get());
588 expect(github.getConfigDetailsTitle()).toHaveTextContent(
589 'settings.authentication.github.configuration.validation.details.valid_label',
591 expect(github.getOrgs()[0]).toHaveTextContent(
592 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
594 expect(github.getOrgs()[1]).toHaveTextContent(
595 'settings.authentication.github.configuration.validation.details.valid_labelorg2',
599 it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
600 const errorMessage = 'Installation suspended';
601 handler.setConfigurationValidity({
604 organization: 'org1',
606 status: GitHubProvisioningStatus.Failed,
610 status: GitHubProvisioningStatus.Failed,
617 renderAuthentication([Feature.GithubProvisioning]);
618 await github.enableConfiguration(user);
622 await waitFor(() => expect(github.configurationValidityWarning.get()).toBeInTheDocument());
623 expect(github.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
625 await user.click(github.viewConfigValidityDetailsButton.get());
626 expect(github.getConfigDetailsTitle()).toHaveTextContent(
627 'settings.authentication.github.configuration.validation.details.valid_label',
629 expect(github.getOrgs()[0]).toHaveTextContent(
630 'settings.authentication.github.configuration.validation.details.invalid_labelorg1 - Installation suspended',
633 await user.click(github.configDetailsDialog.byRole('button', { name: 'close' }).get());
635 await user.click(github.githubProvisioningButton.get());
636 await waitFor(() => expect(github.configurationValidityError.get()).toBeInTheDocument());
637 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
640 it('should display that config is valid but some organizations were not found', async () => {
641 handler.setConfigurationValidity({
644 organization: 'org1',
645 autoProvisioning: { status: GitHubProvisioningStatus.Success },
646 jit: { status: GitHubProvisioningStatus.Success },
651 renderAuthentication([Feature.GithubProvisioning]);
652 await github.enableConfiguration(user);
656 await waitFor(() => expect(github.configurationValiditySuccess.get()).toBeInTheDocument());
657 expect(github.configurationValiditySuccess.get()).toHaveTextContent('1');
659 await user.click(github.viewConfigValidityDetailsButton.get());
660 expect(github.getConfigDetailsTitle()).toHaveTextContent(
661 'settings.authentication.github.configuration.validation.details.valid_label',
663 expect(github.getOrgs()[0]).toHaveTextContent(
664 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
666 expect(github.getOrgs()[1]).toHaveTextContent(
667 'settings.authentication.github.configuration.validation.details.org_not_found.organization1',
671 it('should display that config is invalid', async () => {
672 const errorMessage = 'Test error';
673 handler.setConfigurationValidity({
676 status: GitHubProvisioningStatus.Failed,
680 status: GitHubProvisioningStatus.Failed,
685 renderAuthentication([Feature.GithubProvisioning]);
686 await github.enableConfiguration(user);
690 await waitFor(() => expect(github.configurationValidityError.query()).toBeInTheDocument());
691 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
693 await user.click(github.viewConfigValidityDetailsButton.get());
694 expect(github.getConfigDetailsTitle()).toHaveTextContent(
695 'settings.authentication.github.configuration.validation.details.invalid_label',
697 expect(github.configDetailsDialog.get()).toHaveTextContent(errorMessage);
700 it('should display that config is valid for jit, but not for auto', async () => {
701 const errorMessage = 'Test error';
702 handler.setConfigurationValidity({
705 status: GitHubProvisioningStatus.Success,
708 status: GitHubProvisioningStatus.Failed,
713 renderAuthentication([Feature.GithubProvisioning]);
714 await github.enableConfiguration(user);
718 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
719 expect(github.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
721 await user.click(github.viewConfigValidityDetailsButton.get());
722 expect(github.getConfigDetailsTitle()).toHaveTextContent(
723 'settings.authentication.github.configuration.validation.details.valid_label',
725 await user.click(github.configDetailsDialog.byRole('button', { name: 'close' }).get());
727 await user.click(github.githubProvisioningButton.get());
729 expect(github.configurationValidityError.get()).toBeInTheDocument();
730 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
732 await user.click(github.viewConfigValidityDetailsButton.get());
733 expect(github.getConfigDetailsTitle()).toHaveTextContent(
734 'settings.authentication.github.configuration.validation.details.invalid_label',
738 it('should display that config is invalid because of orgs', async () => {
739 const errorMessage = 'Test error';
740 handler.setConfigurationValidity({
743 organization: 'org1',
744 autoProvisioning: { status: GitHubProvisioningStatus.Success },
745 jit: { status: GitHubProvisioningStatus.Success },
748 organization: 'org2',
749 jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
750 autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
754 renderAuthentication([Feature.GithubProvisioning]);
755 await github.enableConfiguration(user);
759 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
761 await user.click(github.viewConfigValidityDetailsButton.get());
763 expect(github.getOrgs()[0]).toHaveTextContent(
764 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
766 expect(github.getOrgs()[1]).toHaveTextContent(
767 'settings.authentication.github.configuration.validation.details.invalid_labelorg2 - Test error',
770 await user.click(github.configDetailsDialog.byRole('button', { name: 'close' }).get());
772 await user.click(github.githubProvisioningButton.get());
774 expect(github.configurationValidityError.get()).toBeInTheDocument();
775 expect(github.configurationValidityError.get()).toHaveTextContent(
776 `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`,
778 await user.click(github.viewConfigValidityDetailsButton.get());
779 expect(github.getOrgs()[1]).toHaveTextContent(
780 `settings.authentication.github.configuration.validation.details.invalid_labelorg2 - ${errorMessage}`,
784 it('should update provisioning validity after clicking Test Configuration', async () => {
785 const errorMessage = 'Test error';
786 handler.setConfigurationValidity({
789 status: GitHubProvisioningStatus.Failed,
793 status: GitHubProvisioningStatus.Failed,
798 renderAuthentication([Feature.GithubProvisioning]);
799 await github.enableConfiguration(user);
800 handler.setConfigurationValidity({
803 status: GitHubProvisioningStatus.Success,
806 status: GitHubProvisioningStatus.Success,
813 expect(await github.configurationValidityError.find()).toBeInTheDocument();
815 await user.click(github.checkConfigButton.get());
817 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
818 expect(github.configurationValidityError.query()).not.toBeInTheDocument();
821 it('should show warning', async () => {
822 handler.addProvisioningTask({
823 status: TaskStatuses.Success,
824 warnings: ['Warning'],
826 renderAuthentication([Feature.GithubProvisioning]);
827 await github.enableProvisioning(user);
829 expect(await github.syncWarning.find()).toBeInTheDocument();
830 expect(github.syncSummary.get()).toBeInTheDocument();
833 it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
834 const user = userEvent.setup();
835 settingsHandler.presetGithubAutoProvisioning();
836 handler.enableGithubProvisioning();
837 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
838 renderAuthentication([Feature.GithubProvisioning]);
840 await user.click(await github.tab.find());
842 expect(await github.consentDialog.find()).toBeInTheDocument();
843 await user.click(github.continueAutoButton.get());
845 expect(await github.githubProvisioningButton.find()).toBeChecked();
846 expect(github.consentDialog.query()).not.toBeInTheDocument();
849 it('should display a modal if user was already using auto and switch to JIT', async () => {
850 const user = userEvent.setup();
851 settingsHandler.presetGithubAutoProvisioning();
852 handler.enableGithubProvisioning();
853 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
854 renderAuthentication([Feature.GithubProvisioning]);
856 await user.click(await github.tab.find());
858 expect(await github.consentDialog.find()).toBeInTheDocument();
859 await user.click(github.switchJitButton.get());
861 expect(await github.jitProvisioningButton.find()).toBeChecked();
862 expect(github.consentDialog.query()).not.toBeInTheDocument();
865 it('should sort mapping rows', async () => {
866 const user = userEvent.setup();
867 settingsHandler.presetGithubAutoProvisioning();
868 handler.enableGithubProvisioning();
869 renderAuthentication([Feature.GithubProvisioning]);
870 await user.click(await github.tab.find());
872 expect(await github.editMappingButton.find()).toBeInTheDocument();
873 await user.click(github.editMappingButton.get());
875 const rows = (await github.mappingRow.findAll()).filter(
876 (row) => within(row).queryAllByRole('checkbox').length > 0,
879 expect(rows).toHaveLength(5);
881 expect(rows[0]).toHaveTextContent('read');
882 expect(rows[1]).toHaveTextContent('triage');
883 expect(rows[2]).toHaveTextContent('write');
884 expect(rows[3]).toHaveTextContent('maintain');
885 expect(rows[4]).toHaveTextContent('admin');
888 it('should apply new mapping and new provisioning type at the same time', async () => {
889 const user = userEvent.setup();
890 renderAuthentication([Feature.GithubProvisioning]);
891 await user.click(await github.tab.find());
893 await github.createConfiguration(user);
894 await user.click(await github.enableConfigButton.find());
896 expect(await github.jitProvisioningButton.find()).toBeChecked();
897 expect(github.editMappingButton.query()).not.toBeInTheDocument();
898 await user.click(github.githubProvisioningButton.get());
899 expect(await github.editMappingButton.find()).toBeInTheDocument();
900 await user.click(github.editMappingButton.get());
902 expect(await github.mappingRow.findAll()).toHaveLength(7);
904 let readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'));
905 let adminCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('admin'));
907 expect(readCheckboxes[0]).toBeChecked();
908 expect(readCheckboxes[5]).not.toBeChecked();
909 expect(adminCheckboxes[5]).toBeChecked();
911 await user.click(readCheckboxes[0]);
912 await user.click(readCheckboxes[5]);
913 await user.click(adminCheckboxes[5]);
914 await user.click(github.mappingDialogClose.get());
916 await user.click(github.saveGithubProvisioning.get());
917 await user.click(github.confirmProvisioningButton.get());
919 // Clean local mapping state
920 await user.click(github.jitProvisioningButton.get());
921 await user.click(github.githubProvisioningButton.get());
923 await user.click(github.editMappingButton.get());
924 readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'));
925 adminCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('admin'));
927 expect(readCheckboxes[0]).not.toBeChecked();
928 expect(readCheckboxes[5]).toBeChecked();
929 expect(adminCheckboxes[5]).not.toBeChecked();
930 await user.click(github.mappingDialogClose.get());
933 it('should apply new mapping on auto-provisioning', async () => {
934 const user = userEvent.setup();
935 settingsHandler.presetGithubAutoProvisioning();
936 handler.enableGithubProvisioning();
937 renderAuthentication([Feature.GithubProvisioning]);
938 await user.click(await github.tab.find());
940 expect(await github.saveGithubProvisioning.find()).toBeDisabled();
941 await user.click(github.editMappingButton.get());
943 expect(await github.mappingRow.findAll()).toHaveLength(7);
945 let readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'))[0];
947 expect(readCheckboxes).toBeChecked();
949 await user.click(readCheckboxes);
950 await user.click(github.mappingDialogClose.get());
952 expect(await github.saveGithubProvisioning.find()).toBeEnabled();
954 await user.click(github.saveGithubProvisioning.get());
956 // Clean local mapping state
957 await user.click(github.jitProvisioningButton.get());
958 await user.click(github.githubProvisioningButton.get());
960 await user.click(github.editMappingButton.get());
961 readCheckboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('read'))[0];
963 expect(readCheckboxes).not.toBeChecked();
964 await user.click(github.mappingDialogClose.get());
967 it('should add/remove/update custom roles', async () => {
968 const user = userEvent.setup();
969 settingsHandler.presetGithubAutoProvisioning();
970 handler.enableGithubProvisioning();
971 handler.addGitHubCustomRole('custom1', ['user', 'codeViewer', 'scan']);
972 handler.addGitHubCustomRole('custom2', ['user', 'codeViewer', 'issueAdmin', 'scan']);
973 renderAuthentication([Feature.GithubProvisioning]);
974 await user.click(await github.tab.find());
976 expect(await github.saveGithubProvisioning.find()).toBeDisabled();
977 await user.click(github.editMappingButton.get());
979 const rows = (await github.mappingRow.findAll()).filter(
980 (row) => within(row).queryAllByRole('checkbox').length > 0,
983 expect(rows).toHaveLength(7);
985 let custom1Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom1'));
987 expect(custom1Checkboxes[0]).toBeChecked();
988 expect(custom1Checkboxes[1]).toBeChecked();
989 expect(custom1Checkboxes[2]).not.toBeChecked();
990 expect(custom1Checkboxes[3]).not.toBeChecked();
991 expect(custom1Checkboxes[4]).not.toBeChecked();
992 expect(custom1Checkboxes[5]).toBeChecked();
994 await user.click(custom1Checkboxes[1]);
995 await user.click(custom1Checkboxes[2]);
997 await user.click(github.deleteCustomRoleCustom2.get());
999 expect(github.customRoleInput.get()).toHaveValue('');
1000 await user.type(github.customRoleInput.get(), 'read');
1001 await user.click(github.customRoleAddBtn.get());
1002 expect(await github.roleExistsError.find()).toBeInTheDocument();
1003 expect(github.customRoleAddBtn.get()).toBeDisabled();
1004 await user.clear(github.customRoleInput.get());
1005 expect(github.roleExistsError.query()).not.toBeInTheDocument();
1006 await user.type(github.customRoleInput.get(), 'custom1');
1007 await user.click(github.customRoleAddBtn.get());
1008 expect(await github.roleExistsError.find()).toBeInTheDocument();
1009 expect(github.customRoleAddBtn.get()).toBeDisabled();
1010 await user.clear(github.customRoleInput.get());
1011 await user.type(github.customRoleInput.get(), 'custom3');
1012 expect(github.roleExistsError.query()).not.toBeInTheDocument();
1013 expect(github.customRoleAddBtn.get()).toBeEnabled();
1014 await user.click(github.customRoleAddBtn.get());
1016 let custom3Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom3'));
1017 expect(custom3Checkboxes[0]).toBeChecked();
1018 expect(custom3Checkboxes[1]).not.toBeChecked();
1019 expect(custom3Checkboxes[2]).not.toBeChecked();
1020 expect(custom3Checkboxes[3]).not.toBeChecked();
1021 expect(custom3Checkboxes[4]).not.toBeChecked();
1022 expect(custom3Checkboxes[5]).not.toBeChecked();
1023 await user.click(custom3Checkboxes[0]);
1024 expect(await github.emptyRoleError.find()).toBeInTheDocument();
1025 expect(github.mappingDialogClose.get()).toBeDisabled();
1026 await user.click(custom3Checkboxes[1]);
1027 expect(github.emptyRoleError.query()).not.toBeInTheDocument();
1028 expect(github.mappingDialogClose.get()).toBeEnabled();
1029 await user.click(github.mappingDialogClose.get());
1031 expect(await github.saveGithubProvisioning.find()).toBeEnabled();
1032 await user.click(github.saveGithubProvisioning.get());
1034 // Clean local mapping state
1035 await user.click(github.jitProvisioningButton.get());
1036 await user.click(github.githubProvisioningButton.get());
1038 await user.click(github.editMappingButton.get());
1041 (await github.mappingRow.findAll()).filter(
1042 (row) => within(row).queryAllByRole('checkbox').length > 0,
1045 custom1Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom1'));
1046 custom3Checkboxes = github.mappingCheckbox.getAll(github.getMappingRowByRole('custom3'));
1047 expect(github.getMappingRowByRole('custom2')).toBeUndefined();
1048 expect(custom1Checkboxes[0]).toBeChecked();
1049 expect(custom1Checkboxes[1]).not.toBeChecked();
1050 expect(custom1Checkboxes[2]).toBeChecked();
1051 expect(custom1Checkboxes[3]).not.toBeChecked();
1052 expect(custom1Checkboxes[4]).not.toBeChecked();
1053 expect(custom1Checkboxes[5]).toBeChecked();
1054 expect(custom3Checkboxes[0]).not.toBeChecked();
1055 expect(custom3Checkboxes[1]).toBeChecked();
1056 expect(custom3Checkboxes[2]).not.toBeChecked();
1057 expect(custom3Checkboxes[3]).not.toBeChecked();
1058 expect(custom3Checkboxes[4]).not.toBeChecked();
1059 expect(custom3Checkboxes[5]).not.toBeChecked();
1060 await user.click(github.mappingDialogClose.get());
1065 const appLoaded = async () => {
1066 await waitFor(async () => {
1067 expect(await screen.findByText('loading')).not.toBeInTheDocument();
1071 function renderAuthentication(features: Feature[] = []) {
1073 <AvailableFeaturesContext.Provider value={features}>
1074 <Authentication definitions={definitions} />
1075 </AvailableFeaturesContext.Provider>,