3 * Copyright (C) 2009-2024 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 ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
25 import GithubProvisioningServiceMock from '../../../../../api/mocks/GithubProvisioningServiceMock';
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 { byLabelText, byRole, byText } from '../../../../../helpers/testSelector';
32 import { AlmKeys } from '../../../../../types/alm-settings';
33 import { Feature } from '../../../../../types/features';
34 import { GitHubProvisioningStatus } from '../../../../../types/provisioning';
35 import { TaskStatuses } from '../../../../../types/tasks';
36 import Authentication from '../Authentication';
38 let handler: GithubProvisioningServiceMock;
39 let system: SystemServiceMock;
40 let settingsHandler: SettingsServiceMock;
41 let computeEngineHandler: ComputeEngineServiceMock;
44 handler = new GithubProvisioningServiceMock();
45 system = new SystemServiceMock();
46 settingsHandler = new SettingsServiceMock();
47 computeEngineHandler = new ComputeEngineServiceMock();
52 settingsHandler.reset();
54 computeEngineHandler.reset();
57 const ghContainer = byRole('tabpanel', { name: 'github GitHub' });
60 saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
61 customMessageInformation: byText('settings.authentication.custom_message_information'),
62 enabledToggle: byRole('switch'),
63 testButton: byText('settings.authentication.saml.form.test'),
64 textbox1: byRole('textbox', { name: 'test1' }),
65 textbox2: byRole('textbox', { name: 'test2' }),
66 tab: byRole('tab', { name: 'github GitHub' }),
67 noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
68 createConfigButton: ghContainer.byRole('button', {
69 name: 'settings.authentication.form.create',
71 clientId: byRole('textbox', {
72 name: 'property.sonar.auth.github.clientId.secured.name',
74 appId: byRole('textbox', { name: 'property.sonar.auth.github.appId.name' }),
75 privateKey: byRole('textbox', {
76 name: 'property.sonar.auth.github.privateKey.secured.name',
78 clientSecret: byRole('textbox', {
79 name: 'property.sonar.auth.github.clientSecret.secured.name',
81 githubApiUrl: byRole('textbox', { name: 'property.sonar.auth.github.apiUrl.name' }),
82 githubWebUrl: byRole('textbox', { name: 'property.sonar.auth.github.webUrl.name' }),
83 allowUsersToSignUp: byRole('switch', {
84 name: 'property.sonar.auth.github.allowUsersToSignUp.name',
86 organizations: byRole('textbox', {
87 name: 'property.sonar.auth.github.organizations.name',
89 saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
90 confirmProvisioningButton: byRole('button', {
91 name: 'settings.authentication.github.provisioning_change.confirm_changes',
93 saveGithubProvisioning: ghContainer.byRole('button', { name: 'save' }),
94 groupAttribute: byRole('textbox', {
95 name: 'property.sonar.auth.github.group.name.name',
97 enableConfigButton: ghContainer.byRole('button', {
98 name: 'settings.authentication.form.enable',
100 disableConfigButton: ghContainer.byRole('button', {
101 name: 'settings.authentication.form.disable',
103 editConfigButton: ghContainer.byRole('button', {
104 name: 'settings.authentication.form.edit',
106 editMappingButton: ghContainer.byRole('button', {
107 name: 'settings.authentication.github.configuration.roles_mapping.button_label',
109 mappingRow: byRole('dialog', {
110 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
112 customRoleInput: byRole('textbox', {
113 name: 'settings.authentication.github.configuration.roles_mapping.dialog.add_custom_role',
115 customRoleAddBtn: byRole('dialog', {
116 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
117 }).byRole('button', { name: 'add_verb' }),
118 roleExistsError: byRole('dialog', {
119 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
120 }).byText('settings.authentication.github.configuration.roles_mapping.role_exists'),
121 emptyRoleError: byRole('dialog', {
122 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
123 }).byText('settings.authentication.github.configuration.roles_mapping.empty_custom_role'),
124 deleteCustomRoleCustom2: byRole('button', {
125 name: 'settings.authentication.github.configuration.roles_mapping.dialog.delete_custom_role.custom2',
127 getMappingRowByRole: (text: string) =>
128 ui.mappingRow.getAll().find((row) => within(row).queryByText(text) !== null),
129 mappingCheckbox: byRole('checkbox'),
130 mappingDialogClose: byRole('dialog', {
131 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
132 }).byRole('button', {
135 deleteOrg: (org: string) =>
137 name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
139 enableFirstMessage: ghContainer.byText('settings.authentication.github.enable_first'),
140 jitProvisioningButton: ghContainer.byRole('radio', {
141 name: /settings.authentication.form.provisioning_at_login/,
143 githubProvisioningButton: ghContainer.byRole('radio', {
144 name: /settings.authentication.github.form.provisioning_with_github/,
146 githubProvisioningPending: ghContainer
149 .byText(/synchronization_pending/),
150 githubProvisioningInProgress: ghContainer
153 .byText(/synchronization_in_progress/),
154 githubProvisioningSuccess: ghContainer.byText(/synchronization_successful/),
155 githubProvisioningAlert: ghContainer.byText(/synchronization_failed/),
156 configurationValidityLoading: ghContainer.byRole('status', {
157 name: /github.configuration.validation.loading/,
159 configurationValiditySuccess: ghContainer.byRole('status', {
160 name: /github.configuration.validation.valid/,
162 configurationValidityError: ghContainer.byRole('status', {
163 name: /github.configuration.validation.invalid/,
165 syncWarning: ghContainer.byText(/Warning/),
166 syncSummary: ghContainer.byText(/Test summary/),
167 configurationValidityWarning: ghContainer.byRole('status', {
168 name: /github.configuration.validation.valid.short/,
170 checkConfigButton: ghContainer.byRole('button', {
171 name: 'settings.authentication.configuration.test',
173 viewConfigValidityDetailsButton: ghContainer.byRole('button', {
174 name: 'settings.authentication.github.configuration.validation.details',
176 configDetailsDialog: byRole('dialog', {
177 name: 'settings.authentication.github.configuration.validation.details.title',
179 continueAutoButton: byRole('button', {
180 name: 'settings.authentication.github.confirm_auto_provisioning.continue',
182 switchJitButton: byRole('button', {
183 name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
185 consentDialog: byRole('dialog', {
186 name: 'settings.authentication.github.confirm_auto_provisioning.header',
188 getConfigDetailsTitle: () => ui.configDetailsDialog.byRole('heading').get(),
189 getOrgs: () => ui.configDetailsDialog.byRole('listitem').getAll(),
190 getIconForOrg: (text: string, org: HTMLElement) => byLabelText(text).get(org),
191 fillForm: async (user: UserEvent) => {
192 await user.type(await ui.clientId.find(), 'Awsome GITHUB config');
193 await user.type(ui.clientSecret.get(), 'Client shut');
194 await user.type(ui.appId.get(), 'App id');
195 await user.type(ui.privateKey.get(), 'Private Key');
196 await user.type(ui.githubApiUrl.get(), 'API Url');
197 await user.type(ui.githubWebUrl.get(), 'WEb Url');
198 await user.type(ui.organizations.get(), 'organization1');
200 createConfiguration: async (user: UserEvent) => {
201 await user.click(await ui.createConfigButton.find());
202 await ui.fillForm(user);
204 await user.click(ui.saveConfigButton.get());
206 enableConfiguration: async (user: UserEvent) => {
207 await user.click(await ui.tab.find());
208 await ui.createConfiguration(user);
209 await user.click(await ui.enableConfigButton.find());
211 enableProvisioning: async (user: UserEvent) => {
212 await user.click(await ui.tab.find());
214 await ui.createConfiguration(user);
216 await user.click(await ui.enableConfigButton.find());
217 await user.click(await ui.githubProvisioningButton.find());
218 await user.click(ui.saveGithubProvisioning.get());
219 await user.click(ui.confirmProvisioningButton.get());
223 describe('Github tab', () => {
224 it('should render an empty Github configuration', async () => {
225 renderAuthentication();
226 const user = userEvent.setup();
227 await user.click(await ui.tab.find());
228 expect(await ui.noGithubConfiguration.find()).toBeInTheDocument();
231 it('should be able to create a configuration', async () => {
232 const user = userEvent.setup();
233 renderAuthentication();
235 await user.click(await ui.tab.find());
236 await user.click(await ui.createConfigButton.find());
238 expect(ui.saveConfigButton.get()).toBeDisabled();
240 await ui.fillForm(user);
241 expect(ui.saveConfigButton.get()).toBeEnabled();
243 await user.click(ui.saveConfigButton.get());
245 expect(await ui.editConfigButton.find()).toBeInTheDocument();
248 it('should be able to edit configuration', async () => {
249 const user = userEvent.setup();
250 renderAuthentication();
251 await user.click(await ui.tab.find());
253 await ui.createConfiguration(user);
255 await user.click(ui.editConfigButton.get());
256 await user.click(ui.deleteOrg('organization1').get());
258 await user.click(ui.saveConfigButton.get());
260 await user.click(await ui.editConfigButton.find());
262 expect(ui.organizations.get()).toHaveValue('');
265 it('should be able to enable/disable configuration', async () => {
266 const user = userEvent.setup();
267 renderAuthentication();
268 await user.click(await ui.tab.find());
270 await ui.createConfiguration(user);
272 await user.click(await ui.enableConfigButton.find());
274 expect(await ui.disableConfigButton.find()).toBeInTheDocument();
275 await user.click(ui.disableConfigButton.get());
276 await waitFor(() => expect(ui.disableConfigButton.query()).not.toBeInTheDocument());
278 expect(await ui.enableConfigButton.find()).toBeInTheDocument();
281 it('should not allow edtion below Enterprise to select Github provisioning', async () => {
282 const user = userEvent.setup();
284 renderAuthentication();
285 await user.click(await ui.tab.find());
287 await ui.createConfiguration(user);
288 await user.click(await ui.enableConfigButton.find());
290 expect(await ui.jitProvisioningButton.find()).toBeChecked();
291 expect(ui.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
294 it('should be able to choose provisioning', async () => {
295 const user = userEvent.setup();
297 renderAuthentication([Feature.GithubProvisioning]);
298 await user.click(await ui.tab.find());
300 await ui.createConfiguration(user);
302 expect(await ui.enableFirstMessage.find()).toBeInTheDocument();
303 await user.click(await ui.enableConfigButton.find());
305 expect(await ui.jitProvisioningButton.find()).toBeChecked();
307 expect(ui.saveGithubProvisioning.get()).toBeDisabled();
308 await user.click(ui.allowUsersToSignUp.get());
310 expect(ui.saveGithubProvisioning.get()).toBeEnabled();
311 await user.click(ui.saveGithubProvisioning.get());
313 await waitFor(() => expect(ui.saveGithubProvisioning.query()).toBeDisabled());
315 await user.click(ui.githubProvisioningButton.get());
317 expect(ui.saveGithubProvisioning.get()).toBeEnabled();
318 await user.click(ui.saveGithubProvisioning.get());
319 await user.click(ui.confirmProvisioningButton.get());
321 expect(await ui.githubProvisioningButton.find()).toBeChecked();
322 expect(ui.disableConfigButton.get()).toBeDisabled();
323 expect(ui.saveGithubProvisioning.get()).toBeDisabled();
326 describe('Github Provisioning', () => {
332 now: new Date('2022-02-04T12:00:59Z'),
334 user = userEvent.setup();
338 jest.runOnlyPendingTimers();
339 jest.useRealTimers();
342 it('should display a success status when the synchronisation is a success', async () => {
343 handler.addProvisioningTask({
344 status: TaskStatuses.Success,
345 executedAt: '2022-02-03T11:45:35+0200',
348 renderAuthentication([Feature.GithubProvisioning]);
349 await ui.enableProvisioning(user);
350 expect(ui.githubProvisioningSuccess.get()).toBeInTheDocument();
351 expect(ui.syncSummary.get()).toBeInTheDocument();
354 it('should display a success status even when another task is pending', async () => {
355 handler.addProvisioningTask({
356 status: TaskStatuses.Pending,
357 executedAt: '2022-02-03T11:55:35+0200',
359 handler.addProvisioningTask({
360 status: TaskStatuses.Success,
361 executedAt: '2022-02-03T11:45:35+0200',
363 renderAuthentication([Feature.GithubProvisioning]);
364 await ui.enableProvisioning(user);
365 expect(ui.githubProvisioningSuccess.get()).toBeInTheDocument();
366 expect(ui.githubProvisioningPending.get()).toBeInTheDocument();
369 it('should display an error alert when the synchronisation failed', async () => {
370 handler.addProvisioningTask({
371 status: TaskStatuses.Failed,
372 executedAt: '2022-02-03T11:45:35+0200',
373 errorMessage: "T'es mauvais Jacques",
375 renderAuthentication([Feature.GithubProvisioning]);
376 await ui.enableProvisioning(user);
377 expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
378 expect(ghContainer.get()).toHaveTextContent("T'es mauvais Jacques");
379 expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
382 it('should display an error alert even when another task is in progress', async () => {
383 handler.addProvisioningTask({
384 status: TaskStatuses.InProgress,
385 executedAt: '2022-02-03T11:55:35+0200',
387 handler.addProvisioningTask({
388 status: TaskStatuses.Failed,
389 executedAt: '2022-02-03T11:45:35+0200',
390 errorMessage: "T'es mauvais Jacques",
392 renderAuthentication([Feature.GithubProvisioning]);
393 await ui.enableProvisioning(user);
394 expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
395 expect(ghContainer.get()).toHaveTextContent("T'es mauvais Jacques");
396 expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
397 expect(ui.githubProvisioningInProgress.get()).toBeInTheDocument();
400 it('should display that config is valid for both provisioning with 1 org', async () => {
401 renderAuthentication([Feature.GithubProvisioning]);
402 await ui.enableConfiguration(user);
406 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
409 it('should display that config is valid for both provisioning with multiple orgs', async () => {
410 handler.setConfigurationValidity({
413 organization: 'org1',
414 autoProvisioning: { status: GitHubProvisioningStatus.Success },
415 jit: { status: GitHubProvisioningStatus.Success },
418 organization: 'org2',
419 autoProvisioning: { status: GitHubProvisioningStatus.Success },
420 jit: { status: GitHubProvisioningStatus.Success },
424 renderAuthentication([Feature.GithubProvisioning]);
425 await ui.enableConfiguration(user);
429 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
430 expect(ui.configurationValiditySuccess.get()).toHaveTextContent('2');
432 await user.click(ui.viewConfigValidityDetailsButton.get());
433 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
434 expect(ui.getOrgs()).toHaveLength(3);
437 'settings.authentication.github.configuration.validation.details.valid_label',
440 ).toBeInTheDocument();
443 'settings.authentication.github.configuration.validation.details.valid_label',
446 ).toBeInTheDocument();
449 it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
450 const errorMessage = 'Installation suspended';
451 handler.setConfigurationValidity({
454 organization: 'org1',
456 status: GitHubProvisioningStatus.Failed,
460 status: GitHubProvisioningStatus.Failed,
467 renderAuthentication([Feature.GithubProvisioning]);
468 await ui.enableConfiguration(user);
472 await waitFor(() => expect(ui.configurationValidityWarning.get()).toBeInTheDocument());
473 expect(ui.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
475 await user.click(ui.viewConfigValidityDetailsButton.get());
476 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
478 ui.configDetailsDialog
479 .byText('settings.authentication.github.configuration.validation.valid.short')
481 ).toBeInTheDocument();
482 expect(ui.getOrgs()[0]).toHaveTextContent('org1 - Installation suspended');
485 'settings.authentication.github.configuration.validation.details.invalid_label',
488 ).toBeInTheDocument();
490 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
492 await user.click(ui.githubProvisioningButton.get());
493 await waitFor(() => expect(ui.configurationValidityError.get()).toBeInTheDocument());
494 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
497 it('should display that config is valid but some organizations were not found', async () => {
498 handler.setConfigurationValidity({
501 organization: 'org1',
502 autoProvisioning: { status: GitHubProvisioningStatus.Success },
503 jit: { status: GitHubProvisioningStatus.Success },
508 renderAuthentication([Feature.GithubProvisioning]);
509 await ui.enableConfiguration(user);
513 await waitFor(() => expect(ui.configurationValiditySuccess.get()).toBeInTheDocument());
514 expect(ui.configurationValiditySuccess.get()).toHaveTextContent('1');
516 await user.click(ui.viewConfigValidityDetailsButton.get());
517 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
519 ui.configDetailsDialog
520 .byText('settings.authentication.github.configuration.validation.valid.short')
522 ).toBeInTheDocument();
523 expect(ui.getOrgs()[0]).toHaveTextContent('org1');
526 'settings.authentication.github.configuration.validation.details.valid_label',
529 ).toBeInTheDocument();
530 expect(ui.getOrgs()[1]).toHaveTextContent(
531 'settings.authentication.github.configuration.validation.details.org_not_found.organization1',
535 it('should display that config is invalid', async () => {
536 const errorMessage = 'Test error';
537 handler.setConfigurationValidity({
540 status: GitHubProvisioningStatus.Failed,
544 status: GitHubProvisioningStatus.Failed,
549 renderAuthentication([Feature.GithubProvisioning]);
550 await ui.enableConfiguration(user);
554 await waitFor(() => expect(ui.configurationValidityError.query()).toBeInTheDocument());
555 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
557 await user.click(ui.viewConfigValidityDetailsButton.get());
558 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
560 ui.configDetailsDialog
561 .byText(/settings.authentication.github.configuration.validation.invalid/)
563 ).toBeInTheDocument();
564 expect(ui.configDetailsDialog.get()).toHaveTextContent(errorMessage);
567 it('should display that config is valid for jit, but not for auto', async () => {
568 const errorMessage = 'Test error';
569 handler.setConfigurationValidity({
572 status: GitHubProvisioningStatus.Success,
575 status: GitHubProvisioningStatus.Failed,
580 renderAuthentication([Feature.GithubProvisioning]);
581 await ui.enableConfiguration(user);
585 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
586 expect(ui.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
588 await user.click(ui.viewConfigValidityDetailsButton.get());
589 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
591 ui.configDetailsDialog
592 .byText('settings.authentication.github.configuration.validation.valid.short')
594 ).toBeInTheDocument();
595 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
597 await user.click(ui.githubProvisioningButton.get());
599 expect(ui.configurationValidityError.get()).toBeInTheDocument();
600 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
602 await user.click(ui.viewConfigValidityDetailsButton.get());
604 ui.configDetailsDialog
605 .byText(/settings.authentication.github.configuration.validation.invalid/)
607 ).toBeInTheDocument();
610 it('should display that config is invalid because of orgs', async () => {
611 const errorMessage = 'Test error';
612 handler.setConfigurationValidity({
615 organization: 'org1',
616 autoProvisioning: { status: GitHubProvisioningStatus.Success },
617 jit: { status: GitHubProvisioningStatus.Success },
620 organization: 'org2',
621 jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
622 autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
626 renderAuthentication([Feature.GithubProvisioning]);
627 await ui.enableConfiguration(user);
631 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
633 await user.click(ui.viewConfigValidityDetailsButton.get());
635 expect(ui.getOrgs()[0]).toHaveTextContent('org1');
638 'settings.authentication.github.configuration.validation.details.valid_label',
641 ).toBeInTheDocument();
642 expect(ui.getOrgs()[1]).toHaveTextContent('org2 - Test error');
645 'settings.authentication.github.configuration.validation.details.invalid_label',
648 ).toBeInTheDocument();
650 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
652 await user.click(ui.githubProvisioningButton.get());
654 expect(ui.configurationValidityError.get()).toBeInTheDocument();
655 expect(ui.configurationValidityError.get()).toHaveTextContent(
656 `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`,
658 await user.click(ui.viewConfigValidityDetailsButton.get());
661 ui.configDetailsDialog
663 'settings.authentication.github.configuration.validation.details.invalid_label',
667 expect(ui.getOrgs()[1]).toHaveTextContent(`org2 - ${errorMessage}`);
670 it('should update provisioning validity after clicking Test Configuration', async () => {
671 const errorMessage = 'Test error';
672 handler.setConfigurationValidity({
675 status: GitHubProvisioningStatus.Failed,
679 status: GitHubProvisioningStatus.Failed,
684 renderAuthentication([Feature.GithubProvisioning]);
685 await ui.enableConfiguration(user);
686 handler.setConfigurationValidity({
689 status: GitHubProvisioningStatus.Success,
692 status: GitHubProvisioningStatus.Success,
699 expect(await ui.configurationValidityError.find()).toBeInTheDocument();
701 await user.click(ui.checkConfigButton.get());
703 expect(ui.configurationValiditySuccess.get()).toBeInTheDocument();
704 expect(ui.configurationValidityError.query()).not.toBeInTheDocument();
707 it('should show warning', async () => {
708 handler.addProvisioningTask({
709 status: TaskStatuses.Success,
710 warnings: ['Warning'],
712 renderAuthentication([Feature.GithubProvisioning]);
713 await ui.enableProvisioning(user);
715 expect(await ui.syncWarning.find()).toBeInTheDocument();
716 expect(ui.syncSummary.get()).toBeInTheDocument();
719 it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
720 const user = userEvent.setup();
721 settingsHandler.presetGithubAutoProvisioning();
722 handler.enableGithubProvisioning();
723 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
724 renderAuthentication([Feature.GithubProvisioning]);
726 await user.click(await ui.tab.find());
728 expect(await ui.consentDialog.find()).toBeInTheDocument();
729 await user.click(ui.continueAutoButton.get());
731 expect(await ui.githubProvisioningButton.find()).toBeChecked();
732 expect(ui.consentDialog.query()).not.toBeInTheDocument();
735 it('should display a modal if user was already using auto and switch to JIT', async () => {
736 const user = userEvent.setup();
737 settingsHandler.presetGithubAutoProvisioning();
738 handler.enableGithubProvisioning();
739 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
740 renderAuthentication([Feature.GithubProvisioning]);
742 await user.click(await ui.tab.find());
744 expect(await ui.consentDialog.find()).toBeInTheDocument();
745 await user.click(ui.switchJitButton.get());
747 expect(await ui.jitProvisioningButton.find()).toBeChecked();
748 expect(ui.consentDialog.query()).not.toBeInTheDocument();
751 it('should sort mapping rows', async () => {
752 const user = userEvent.setup();
753 settingsHandler.presetGithubAutoProvisioning();
754 handler.enableGithubProvisioning();
755 renderAuthentication([Feature.GithubProvisioning]);
756 await user.click(await ui.tab.find());
758 expect(await ui.editMappingButton.find()).toBeInTheDocument();
759 await user.click(ui.editMappingButton.get());
761 const rows = (await ui.mappingRow.findAll()).filter(
762 (row) => within(row).queryAllByRole('checkbox').length > 0,
765 expect(rows).toHaveLength(5);
767 expect(rows[0]).toHaveTextContent('read');
768 expect(rows[1]).toHaveTextContent('triage');
769 expect(rows[2]).toHaveTextContent('write');
770 expect(rows[3]).toHaveTextContent('maintain');
771 expect(rows[4]).toHaveTextContent('admin');
774 it('should apply new mapping and new provisioning type at the same time', async () => {
775 const user = userEvent.setup();
776 renderAuthentication([Feature.GithubProvisioning]);
777 await user.click(await ui.tab.find());
779 await ui.createConfiguration(user);
780 await user.click(await ui.enableConfigButton.find());
782 expect(await ui.jitProvisioningButton.find()).toBeChecked();
783 expect(ui.editMappingButton.query()).not.toBeInTheDocument();
784 await user.click(ui.githubProvisioningButton.get());
785 expect(await ui.editMappingButton.find()).toBeInTheDocument();
786 await user.click(ui.editMappingButton.get());
788 expect(await ui.mappingRow.findAll()).toHaveLength(7);
790 let readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'));
791 let adminCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('admin'));
793 expect(readCheckboxes[0]).toBeChecked();
794 expect(readCheckboxes[5]).not.toBeChecked();
795 expect(adminCheckboxes[5]).toBeChecked();
797 await user.click(readCheckboxes[0]);
798 await user.click(readCheckboxes[5]);
799 await user.click(adminCheckboxes[5]);
800 await user.click(ui.mappingDialogClose.get());
802 await user.click(ui.saveGithubProvisioning.get());
803 await user.click(ui.confirmProvisioningButton.get());
805 // Clean local mapping state
806 await user.click(ui.jitProvisioningButton.get());
807 await user.click(ui.githubProvisioningButton.get());
809 await user.click(ui.editMappingButton.get());
810 readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'));
811 adminCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('admin'));
813 expect(readCheckboxes[0]).not.toBeChecked();
814 expect(readCheckboxes[5]).toBeChecked();
815 expect(adminCheckboxes[5]).not.toBeChecked();
816 await user.click(ui.mappingDialogClose.get());
819 it('should apply new mapping on auto-provisioning', async () => {
820 const user = userEvent.setup();
821 settingsHandler.presetGithubAutoProvisioning();
822 handler.enableGithubProvisioning();
823 renderAuthentication([Feature.GithubProvisioning]);
824 await user.click(await ui.tab.find());
826 expect(await ui.saveGithubProvisioning.find()).toBeDisabled();
827 await user.click(ui.editMappingButton.get());
829 expect(await ui.mappingRow.findAll()).toHaveLength(7);
831 let readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'))[0];
833 expect(readCheckboxes).toBeChecked();
835 await user.click(readCheckboxes);
836 await user.click(ui.mappingDialogClose.get());
838 expect(await ui.saveGithubProvisioning.find()).toBeEnabled();
840 await user.click(ui.saveGithubProvisioning.get());
842 // Clean local mapping state
843 await user.click(ui.jitProvisioningButton.get());
844 await user.click(ui.githubProvisioningButton.get());
846 await user.click(ui.editMappingButton.get());
847 readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'))[0];
849 expect(readCheckboxes).not.toBeChecked();
850 await user.click(ui.mappingDialogClose.get());
853 it('should add/remove/update custom roles', async () => {
854 const user = userEvent.setup();
855 settingsHandler.presetGithubAutoProvisioning();
856 handler.enableGithubProvisioning();
857 handler.addGitHubCustomRole('custom1', ['user', 'codeViewer', 'scan']);
858 handler.addGitHubCustomRole('custom2', ['user', 'codeViewer', 'issueAdmin', 'scan']);
859 renderAuthentication([Feature.GithubProvisioning]);
860 await user.click(await ui.tab.find());
862 expect(await ui.saveGithubProvisioning.find()).toBeDisabled();
863 await user.click(ui.editMappingButton.get());
865 const rows = (await ui.mappingRow.findAll()).filter(
866 (row) => within(row).queryAllByRole('checkbox').length > 0,
869 expect(rows).toHaveLength(7);
871 let custom1Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom1'));
873 expect(custom1Checkboxes[0]).toBeChecked();
874 expect(custom1Checkboxes[1]).toBeChecked();
875 expect(custom1Checkboxes[2]).not.toBeChecked();
876 expect(custom1Checkboxes[3]).not.toBeChecked();
877 expect(custom1Checkboxes[4]).not.toBeChecked();
878 expect(custom1Checkboxes[5]).toBeChecked();
880 await user.click(custom1Checkboxes[1]);
881 await user.click(custom1Checkboxes[2]);
883 await user.click(ui.deleteCustomRoleCustom2.get());
885 expect(ui.customRoleInput.get()).toHaveValue('');
886 await user.type(ui.customRoleInput.get(), 'read');
887 await user.click(ui.customRoleAddBtn.get());
888 expect(await ui.roleExistsError.find()).toBeInTheDocument();
889 expect(ui.customRoleAddBtn.get()).toBeDisabled();
890 await user.clear(ui.customRoleInput.get());
891 expect(ui.roleExistsError.query()).not.toBeInTheDocument();
892 await user.type(ui.customRoleInput.get(), 'custom1');
893 await user.click(ui.customRoleAddBtn.get());
894 expect(await ui.roleExistsError.find()).toBeInTheDocument();
895 expect(ui.customRoleAddBtn.get()).toBeDisabled();
896 await user.clear(ui.customRoleInput.get());
897 await user.type(ui.customRoleInput.get(), 'custom3');
898 expect(ui.roleExistsError.query()).not.toBeInTheDocument();
899 expect(ui.customRoleAddBtn.get()).toBeEnabled();
900 await user.click(ui.customRoleAddBtn.get());
902 let custom3Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom3'));
903 expect(custom3Checkboxes[0]).toBeChecked();
904 expect(custom3Checkboxes[1]).not.toBeChecked();
905 expect(custom3Checkboxes[2]).not.toBeChecked();
906 expect(custom3Checkboxes[3]).not.toBeChecked();
907 expect(custom3Checkboxes[4]).not.toBeChecked();
908 expect(custom3Checkboxes[5]).not.toBeChecked();
909 await user.click(custom3Checkboxes[0]);
910 expect(await ui.emptyRoleError.find()).toBeInTheDocument();
911 expect(ui.mappingDialogClose.get()).toBeDisabled();
912 await user.click(custom3Checkboxes[1]);
913 expect(ui.emptyRoleError.query()).not.toBeInTheDocument();
914 expect(ui.mappingDialogClose.get()).toBeEnabled();
915 await user.click(ui.mappingDialogClose.get());
917 expect(await ui.saveGithubProvisioning.find()).toBeEnabled();
918 await user.click(ui.saveGithubProvisioning.get());
920 // Clean local mapping state
921 await user.click(ui.jitProvisioningButton.get());
922 await user.click(ui.githubProvisioningButton.get());
924 await user.click(ui.editMappingButton.get());
927 (await ui.mappingRow.findAll()).filter(
928 (row) => within(row).queryAllByRole('checkbox').length > 0,
931 custom1Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom1'));
932 custom3Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom3'));
933 expect(ui.getMappingRowByRole('custom2')).toBeUndefined();
934 expect(custom1Checkboxes[0]).toBeChecked();
935 expect(custom1Checkboxes[1]).not.toBeChecked();
936 expect(custom1Checkboxes[2]).toBeChecked();
937 expect(custom1Checkboxes[3]).not.toBeChecked();
938 expect(custom1Checkboxes[4]).not.toBeChecked();
939 expect(custom1Checkboxes[5]).toBeChecked();
940 expect(custom3Checkboxes[0]).not.toBeChecked();
941 expect(custom3Checkboxes[1]).toBeChecked();
942 expect(custom3Checkboxes[2]).not.toBeChecked();
943 expect(custom3Checkboxes[3]).not.toBeChecked();
944 expect(custom3Checkboxes[4]).not.toBeChecked();
945 expect(custom3Checkboxes[5]).not.toBeChecked();
946 await user.click(ui.mappingDialogClose.get());
951 const appLoaded = async () => {
952 await waitFor(async () => {
953 expect(await screen.findByText('loading')).not.toBeInTheDocument();
957 function renderAuthentication(features: Feature[] = []) {
959 <AvailableFeaturesContext.Provider value={features}>
960 <Authentication definitions={definitions} />
961 </AvailableFeaturesContext.Provider>,
962 `?tab=${AlmKeys.GitHub}`,