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.byText(/synchronization_pending/),
147 githubProvisioningInProgress: ghContainer.byText(/synchronization_in_progress/),
148 githubProvisioningSuccess: ghContainer.byText(/synchronization_successful/),
149 githubProvisioningAlert: ghContainer.byText(/synchronization_failed/),
150 configurationValidityLoading: ghContainer.byRole('status', {
151 name: /github.configuration.validation.loading/,
153 configurationValiditySuccess: ghContainer.byRole('status', {
154 name: /github.configuration.validation.valid/,
156 configurationValidityError: ghContainer.byRole('status', {
157 name: /github.configuration.validation.invalid/,
159 syncWarning: ghContainer.byText(/Warning/),
160 syncSummary: ghContainer.byText(/Test summary/),
161 configurationValidityWarning: ghContainer.byRole('status', {
162 name: /github.configuration.validation.valid.short/,
164 checkConfigButton: ghContainer.byRole('button', {
165 name: 'settings.authentication.configuration.test',
167 viewConfigValidityDetailsButton: ghContainer.byRole('button', {
168 name: 'settings.authentication.github.configuration.validation.details',
170 configDetailsDialog: byRole('dialog', {
171 name: 'settings.authentication.github.configuration.validation.details.title',
173 continueAutoButton: byRole('button', {
174 name: 'settings.authentication.github.confirm_auto_provisioning.continue',
176 switchJitButton: byRole('button', {
177 name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
179 consentDialog: byRole('dialog', {
180 name: 'settings.authentication.github.confirm_auto_provisioning.header',
182 getConfigDetailsTitle: () => ui.configDetailsDialog.byRole('heading').get(),
183 getOrgs: () => ui.configDetailsDialog.byRole('listitem').getAll(),
184 getIconForOrg: (text: string, org: HTMLElement) => byLabelText(text).get(org),
185 fillForm: async (user: UserEvent) => {
186 await user.type(await ui.clientId.find(), 'Awsome GITHUB config');
187 await user.type(ui.clientSecret.get(), 'Client shut');
188 await user.type(ui.appId.get(), 'App id');
189 await user.type(ui.privateKey.get(), 'Private Key');
190 await user.type(ui.githubApiUrl.get(), 'API Url');
191 await user.type(ui.githubWebUrl.get(), 'WEb Url');
192 await user.type(ui.organizations.get(), 'organization1');
194 createConfiguration: async (user: UserEvent) => {
195 await user.click(await ui.createConfigButton.find());
196 await ui.fillForm(user);
198 await user.click(ui.saveConfigButton.get());
200 enableConfiguration: async (user: UserEvent) => {
201 await user.click(await ui.tab.find());
202 await ui.createConfiguration(user);
203 await user.click(await ui.enableConfigButton.find());
205 enableProvisioning: async (user: UserEvent) => {
206 await user.click(await ui.tab.find());
208 await ui.createConfiguration(user);
210 await user.click(await ui.enableConfigButton.find());
211 await user.click(await ui.githubProvisioningButton.find());
212 await user.click(ui.saveGithubProvisioning.get());
213 await user.click(ui.confirmProvisioningButton.get());
217 describe('Github tab', () => {
218 it('should render an empty Github configuration', async () => {
219 renderAuthentication();
220 const user = userEvent.setup();
221 await user.click(await ui.tab.find());
222 expect(await ui.noGithubConfiguration.find()).toBeInTheDocument();
225 it('should be able to create a configuration', async () => {
226 const user = userEvent.setup();
227 renderAuthentication();
229 await user.click(await ui.tab.find());
230 await user.click(await ui.createConfigButton.find());
232 expect(ui.saveConfigButton.get()).toBeDisabled();
234 await ui.fillForm(user);
235 expect(ui.saveConfigButton.get()).toBeEnabled();
237 await user.click(ui.saveConfigButton.get());
239 expect(await ui.editConfigButton.find()).toBeInTheDocument();
242 it('should be able to edit configuration', async () => {
243 const user = userEvent.setup();
244 renderAuthentication();
245 await user.click(await ui.tab.find());
247 await ui.createConfiguration(user);
249 await user.click(ui.editConfigButton.get());
250 await user.click(ui.deleteOrg('organization1').get());
252 await user.click(ui.saveConfigButton.get());
254 await user.click(await ui.editConfigButton.find());
256 expect(ui.organizations.get()).toHaveValue('');
259 it('should be able to enable/disable configuration', async () => {
260 const user = userEvent.setup();
261 renderAuthentication();
262 await user.click(await ui.tab.find());
264 await ui.createConfiguration(user);
266 await user.click(await ui.enableConfigButton.find());
268 expect(await ui.disableConfigButton.find()).toBeInTheDocument();
269 await user.click(ui.disableConfigButton.get());
270 await waitFor(() => expect(ui.disableConfigButton.query()).not.toBeInTheDocument());
272 expect(await ui.enableConfigButton.find()).toBeInTheDocument();
275 it('should not allow edtion below Enterprise to select Github provisioning', async () => {
276 const user = userEvent.setup();
278 renderAuthentication();
279 await user.click(await ui.tab.find());
281 await ui.createConfiguration(user);
282 await user.click(await ui.enableConfigButton.find());
284 expect(await ui.jitProvisioningButton.find()).toBeChecked();
285 expect(ui.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
288 it('should be able to choose provisioning', async () => {
289 const user = userEvent.setup();
291 renderAuthentication([Feature.GithubProvisioning]);
292 await user.click(await ui.tab.find());
294 await ui.createConfiguration(user);
296 expect(await ui.enableFirstMessage.find()).toBeInTheDocument();
297 await user.click(await ui.enableConfigButton.find());
299 expect(await ui.jitProvisioningButton.find()).toBeChecked();
301 expect(ui.saveGithubProvisioning.get()).toBeDisabled();
302 await user.click(ui.allowUsersToSignUp.get());
304 expect(ui.saveGithubProvisioning.get()).toBeEnabled();
305 await user.click(ui.saveGithubProvisioning.get());
307 await waitFor(() => expect(ui.saveGithubProvisioning.query()).toBeDisabled());
309 await user.click(ui.githubProvisioningButton.get());
311 expect(ui.saveGithubProvisioning.get()).toBeEnabled();
312 await user.click(ui.saveGithubProvisioning.get());
313 await user.click(ui.confirmProvisioningButton.get());
315 expect(await ui.githubProvisioningButton.find()).toBeChecked();
316 expect(ui.disableConfigButton.get()).toBeDisabled();
317 expect(ui.saveGithubProvisioning.get()).toBeDisabled();
320 describe('Github Provisioning', () => {
326 now: new Date('2022-02-04T12:00:59Z'),
328 user = userEvent.setup();
332 jest.runOnlyPendingTimers();
333 jest.useRealTimers();
336 it('should display a success status when the synchronisation is a success', async () => {
337 handler.addProvisioningTask({
338 status: TaskStatuses.Success,
339 executedAt: '2022-02-03T11:45:35+0200',
342 renderAuthentication([Feature.GithubProvisioning]);
343 await ui.enableProvisioning(user);
344 expect(ui.githubProvisioningSuccess.get()).toBeInTheDocument();
345 expect(ui.syncSummary.get()).toBeInTheDocument();
348 it('should display a success status even when another task is pending', async () => {
349 handler.addProvisioningTask({
350 status: TaskStatuses.Pending,
351 executedAt: '2022-02-03T11:55:35+0200',
353 handler.addProvisioningTask({
354 status: TaskStatuses.Success,
355 executedAt: '2022-02-03T11:45:35+0200',
357 renderAuthentication([Feature.GithubProvisioning]);
358 await ui.enableProvisioning(user);
359 expect(ui.githubProvisioningSuccess.get()).toBeInTheDocument();
360 expect(ui.githubProvisioningPending.get()).toBeInTheDocument();
363 it('should display an error alert when the synchronisation failed', async () => {
364 handler.addProvisioningTask({
365 status: TaskStatuses.Failed,
366 executedAt: '2022-02-03T11:45:35+0200',
367 errorMessage: "T'es mauvais Jacques",
369 renderAuthentication([Feature.GithubProvisioning]);
370 await ui.enableProvisioning(user);
371 expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
372 expect(ui.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
373 expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
376 it('should display an error alert even when another task is in progress', async () => {
377 handler.addProvisioningTask({
378 status: TaskStatuses.InProgress,
379 executedAt: '2022-02-03T11:55:35+0200',
381 handler.addProvisioningTask({
382 status: TaskStatuses.Failed,
383 executedAt: '2022-02-03T11:45:35+0200',
384 errorMessage: "T'es mauvais Jacques",
386 renderAuthentication([Feature.GithubProvisioning]);
387 await ui.enableProvisioning(user);
388 expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
389 expect(ui.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
390 expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
391 expect(ui.githubProvisioningInProgress.get()).toBeInTheDocument();
394 it('should display that config is valid for both provisioning with 1 org', async () => {
395 renderAuthentication([Feature.GithubProvisioning]);
396 await ui.enableConfiguration(user);
400 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
403 it('should display that config is valid for both provisioning with multiple orgs', async () => {
404 handler.setConfigurationValidity({
407 organization: 'org1',
408 autoProvisioning: { status: GitHubProvisioningStatus.Success },
409 jit: { status: GitHubProvisioningStatus.Success },
412 organization: 'org2',
413 autoProvisioning: { status: GitHubProvisioningStatus.Success },
414 jit: { status: GitHubProvisioningStatus.Success },
418 renderAuthentication([Feature.GithubProvisioning]);
419 await ui.enableConfiguration(user);
423 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
424 expect(ui.configurationValiditySuccess.get()).toHaveTextContent('2');
426 await user.click(ui.viewConfigValidityDetailsButton.get());
427 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
428 expect(ui.getOrgs()).toHaveLength(3);
431 'settings.authentication.github.configuration.validation.details.valid_label',
434 ).toBeInTheDocument();
437 'settings.authentication.github.configuration.validation.details.valid_label',
440 ).toBeInTheDocument();
443 it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
444 const errorMessage = 'Installation suspended';
445 handler.setConfigurationValidity({
448 organization: 'org1',
450 status: GitHubProvisioningStatus.Failed,
454 status: GitHubProvisioningStatus.Failed,
461 renderAuthentication([Feature.GithubProvisioning]);
462 await ui.enableConfiguration(user);
466 await waitFor(() => expect(ui.configurationValidityWarning.get()).toBeInTheDocument());
467 expect(ui.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
469 await user.click(ui.viewConfigValidityDetailsButton.get());
470 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
472 ui.configDetailsDialog
473 .byText('settings.authentication.github.configuration.validation.valid.short')
475 ).toBeInTheDocument();
476 expect(ui.getOrgs()[0]).toHaveTextContent('org1 - Installation suspended');
479 'settings.authentication.github.configuration.validation.details.invalid_label',
482 ).toBeInTheDocument();
484 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
486 await user.click(ui.githubProvisioningButton.get());
487 await waitFor(() => expect(ui.configurationValidityError.get()).toBeInTheDocument());
488 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
491 it('should display that config is valid but some organizations were not found', async () => {
492 handler.setConfigurationValidity({
495 organization: 'org1',
496 autoProvisioning: { status: GitHubProvisioningStatus.Success },
497 jit: { status: GitHubProvisioningStatus.Success },
502 renderAuthentication([Feature.GithubProvisioning]);
503 await ui.enableConfiguration(user);
507 await waitFor(() => expect(ui.configurationValiditySuccess.get()).toBeInTheDocument());
508 expect(ui.configurationValiditySuccess.get()).toHaveTextContent('1');
510 await user.click(ui.viewConfigValidityDetailsButton.get());
511 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
513 ui.configDetailsDialog
514 .byText('settings.authentication.github.configuration.validation.valid.short')
516 ).toBeInTheDocument();
517 expect(ui.getOrgs()[0]).toHaveTextContent('org1');
520 'settings.authentication.github.configuration.validation.details.valid_label',
523 ).toBeInTheDocument();
524 expect(ui.getOrgs()[1]).toHaveTextContent(
525 'settings.authentication.github.configuration.validation.details.org_not_found.organization1',
529 it('should display that config is invalid', async () => {
530 const errorMessage = 'Test error';
531 handler.setConfigurationValidity({
534 status: GitHubProvisioningStatus.Failed,
538 status: GitHubProvisioningStatus.Failed,
543 renderAuthentication([Feature.GithubProvisioning]);
544 await ui.enableConfiguration(user);
548 await waitFor(() => expect(ui.configurationValidityError.query()).toBeInTheDocument());
549 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
551 await user.click(ui.viewConfigValidityDetailsButton.get());
552 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
554 ui.configDetailsDialog
555 .byText(/settings.authentication.github.configuration.validation.invalid/)
557 ).toBeInTheDocument();
558 expect(ui.configDetailsDialog.get()).toHaveTextContent(errorMessage);
561 it('should display that config is valid for jit, but not for auto', async () => {
562 const errorMessage = 'Test error';
563 handler.setConfigurationValidity({
566 status: GitHubProvisioningStatus.Success,
569 status: GitHubProvisioningStatus.Failed,
574 renderAuthentication([Feature.GithubProvisioning]);
575 await ui.enableConfiguration(user);
579 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
580 expect(ui.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
582 await user.click(ui.viewConfigValidityDetailsButton.get());
583 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
585 ui.configDetailsDialog
586 .byText('settings.authentication.github.configuration.validation.valid.short')
588 ).toBeInTheDocument();
589 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
591 await user.click(ui.githubProvisioningButton.get());
593 expect(ui.configurationValidityError.get()).toBeInTheDocument();
594 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
596 await user.click(ui.viewConfigValidityDetailsButton.get());
598 ui.configDetailsDialog
599 .byText(/settings.authentication.github.configuration.validation.invalid/)
601 ).toBeInTheDocument();
604 it('should display that config is invalid because of orgs', async () => {
605 const errorMessage = 'Test error';
606 handler.setConfigurationValidity({
609 organization: 'org1',
610 autoProvisioning: { status: GitHubProvisioningStatus.Success },
611 jit: { status: GitHubProvisioningStatus.Success },
614 organization: 'org2',
615 jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
616 autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
620 renderAuthentication([Feature.GithubProvisioning]);
621 await ui.enableConfiguration(user);
625 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
627 await user.click(ui.viewConfigValidityDetailsButton.get());
629 expect(ui.getOrgs()[0]).toHaveTextContent('org1');
632 'settings.authentication.github.configuration.validation.details.valid_label',
635 ).toBeInTheDocument();
636 expect(ui.getOrgs()[1]).toHaveTextContent('org2 - Test error');
639 'settings.authentication.github.configuration.validation.details.invalid_label',
642 ).toBeInTheDocument();
644 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
646 await user.click(ui.githubProvisioningButton.get());
648 expect(ui.configurationValidityError.get()).toBeInTheDocument();
649 expect(ui.configurationValidityError.get()).toHaveTextContent(
650 `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`,
652 await user.click(ui.viewConfigValidityDetailsButton.get());
655 ui.configDetailsDialog
657 'settings.authentication.github.configuration.validation.details.invalid_label',
661 expect(ui.getOrgs()[1]).toHaveTextContent(`org2 - ${errorMessage}`);
664 it('should update provisioning validity after clicking Test Configuration', async () => {
665 const errorMessage = 'Test error';
666 handler.setConfigurationValidity({
669 status: GitHubProvisioningStatus.Failed,
673 status: GitHubProvisioningStatus.Failed,
678 renderAuthentication([Feature.GithubProvisioning]);
679 await ui.enableConfiguration(user);
680 handler.setConfigurationValidity({
683 status: GitHubProvisioningStatus.Success,
686 status: GitHubProvisioningStatus.Success,
693 expect(await ui.configurationValidityError.find()).toBeInTheDocument();
695 await user.click(ui.checkConfigButton.get());
697 expect(ui.configurationValiditySuccess.get()).toBeInTheDocument();
698 expect(ui.configurationValidityError.query()).not.toBeInTheDocument();
701 it('should show warning', async () => {
702 handler.addProvisioningTask({
703 status: TaskStatuses.Success,
704 warnings: ['Warning'],
706 renderAuthentication([Feature.GithubProvisioning]);
707 await ui.enableProvisioning(user);
709 expect(await ui.syncWarning.find()).toBeInTheDocument();
710 expect(ui.syncSummary.get()).toBeInTheDocument();
713 it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
714 const user = userEvent.setup();
715 settingsHandler.presetGithubAutoProvisioning();
716 handler.enableGithubProvisioning();
717 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
718 renderAuthentication([Feature.GithubProvisioning]);
720 await user.click(await ui.tab.find());
722 expect(await ui.consentDialog.find()).toBeInTheDocument();
723 await user.click(ui.continueAutoButton.get());
725 expect(await ui.githubProvisioningButton.find()).toBeChecked();
726 expect(ui.consentDialog.query()).not.toBeInTheDocument();
729 it('should display a modal if user was already using auto and switch to JIT', async () => {
730 const user = userEvent.setup();
731 settingsHandler.presetGithubAutoProvisioning();
732 handler.enableGithubProvisioning();
733 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
734 renderAuthentication([Feature.GithubProvisioning]);
736 await user.click(await ui.tab.find());
738 expect(await ui.consentDialog.find()).toBeInTheDocument();
739 await user.click(ui.switchJitButton.get());
741 expect(await ui.jitProvisioningButton.find()).toBeChecked();
742 expect(ui.consentDialog.query()).not.toBeInTheDocument();
745 it('should sort mapping rows', async () => {
746 const user = userEvent.setup();
747 settingsHandler.presetGithubAutoProvisioning();
748 handler.enableGithubProvisioning();
749 renderAuthentication([Feature.GithubProvisioning]);
750 await user.click(await ui.tab.find());
752 expect(await ui.editMappingButton.find()).toBeInTheDocument();
753 await user.click(ui.editMappingButton.get());
755 const rows = (await ui.mappingRow.findAll()).filter(
756 (row) => within(row).queryAllByRole('checkbox').length > 0,
759 expect(rows).toHaveLength(5);
761 expect(rows[0]).toHaveTextContent('read');
762 expect(rows[1]).toHaveTextContent('triage');
763 expect(rows[2]).toHaveTextContent('write');
764 expect(rows[3]).toHaveTextContent('maintain');
765 expect(rows[4]).toHaveTextContent('admin');
768 it('should apply new mapping and new provisioning type at the same time', async () => {
769 const user = userEvent.setup();
770 renderAuthentication([Feature.GithubProvisioning]);
771 await user.click(await ui.tab.find());
773 await ui.createConfiguration(user);
774 await user.click(await ui.enableConfigButton.find());
776 expect(await ui.jitProvisioningButton.find()).toBeChecked();
777 expect(ui.editMappingButton.query()).not.toBeInTheDocument();
778 await user.click(ui.githubProvisioningButton.get());
779 expect(await ui.editMappingButton.find()).toBeInTheDocument();
780 await user.click(ui.editMappingButton.get());
782 expect(await ui.mappingRow.findAll()).toHaveLength(7);
784 let readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'));
785 let adminCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('admin'));
787 expect(readCheckboxes[0]).toBeChecked();
788 expect(readCheckboxes[5]).not.toBeChecked();
789 expect(adminCheckboxes[5]).toBeChecked();
791 await user.click(readCheckboxes[0]);
792 await user.click(readCheckboxes[5]);
793 await user.click(adminCheckboxes[5]);
794 await user.click(ui.mappingDialogClose.get());
796 await user.click(ui.saveGithubProvisioning.get());
797 await user.click(ui.confirmProvisioningButton.get());
799 // Clean local mapping state
800 await user.click(ui.jitProvisioningButton.get());
801 await user.click(ui.githubProvisioningButton.get());
803 await user.click(ui.editMappingButton.get());
804 readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'));
805 adminCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('admin'));
807 expect(readCheckboxes[0]).not.toBeChecked();
808 expect(readCheckboxes[5]).toBeChecked();
809 expect(adminCheckboxes[5]).not.toBeChecked();
810 await user.click(ui.mappingDialogClose.get());
813 it('should apply new mapping on auto-provisioning', async () => {
814 const user = userEvent.setup();
815 settingsHandler.presetGithubAutoProvisioning();
816 handler.enableGithubProvisioning();
817 renderAuthentication([Feature.GithubProvisioning]);
818 await user.click(await ui.tab.find());
820 expect(await ui.saveGithubProvisioning.find()).toBeDisabled();
821 await user.click(ui.editMappingButton.get());
823 expect(await ui.mappingRow.findAll()).toHaveLength(7);
825 let readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'))[0];
827 expect(readCheckboxes).toBeChecked();
829 await user.click(readCheckboxes);
830 await user.click(ui.mappingDialogClose.get());
832 expect(await ui.saveGithubProvisioning.find()).toBeEnabled();
834 await user.click(ui.saveGithubProvisioning.get());
836 // Clean local mapping state
837 await user.click(ui.jitProvisioningButton.get());
838 await user.click(ui.githubProvisioningButton.get());
840 await user.click(ui.editMappingButton.get());
841 readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'))[0];
843 expect(readCheckboxes).not.toBeChecked();
844 await user.click(ui.mappingDialogClose.get());
847 it('should add/remove/update custom roles', async () => {
848 const user = userEvent.setup();
849 settingsHandler.presetGithubAutoProvisioning();
850 handler.enableGithubProvisioning();
851 handler.addGitHubCustomRole('custom1', ['user', 'codeViewer', 'scan']);
852 handler.addGitHubCustomRole('custom2', ['user', 'codeViewer', 'issueAdmin', 'scan']);
853 renderAuthentication([Feature.GithubProvisioning]);
854 await user.click(await ui.tab.find());
856 expect(await ui.saveGithubProvisioning.find()).toBeDisabled();
857 await user.click(ui.editMappingButton.get());
859 const rows = (await ui.mappingRow.findAll()).filter(
860 (row) => within(row).queryAllByRole('checkbox').length > 0,
863 expect(rows).toHaveLength(7);
865 let custom1Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom1'));
867 expect(custom1Checkboxes[0]).toBeChecked();
868 expect(custom1Checkboxes[1]).toBeChecked();
869 expect(custom1Checkboxes[2]).not.toBeChecked();
870 expect(custom1Checkboxes[3]).not.toBeChecked();
871 expect(custom1Checkboxes[4]).not.toBeChecked();
872 expect(custom1Checkboxes[5]).toBeChecked();
874 await user.click(custom1Checkboxes[1]);
875 await user.click(custom1Checkboxes[2]);
877 await user.click(ui.deleteCustomRoleCustom2.get());
879 expect(ui.customRoleInput.get()).toHaveValue('');
880 await user.type(ui.customRoleInput.get(), 'read');
881 await user.click(ui.customRoleAddBtn.get());
882 expect(await ui.roleExistsError.find()).toBeInTheDocument();
883 expect(ui.customRoleAddBtn.get()).toBeDisabled();
884 await user.clear(ui.customRoleInput.get());
885 expect(ui.roleExistsError.query()).not.toBeInTheDocument();
886 await user.type(ui.customRoleInput.get(), 'custom1');
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 await user.type(ui.customRoleInput.get(), 'custom3');
892 expect(ui.roleExistsError.query()).not.toBeInTheDocument();
893 expect(ui.customRoleAddBtn.get()).toBeEnabled();
894 await user.click(ui.customRoleAddBtn.get());
896 let custom3Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom3'));
897 expect(custom3Checkboxes[0]).toBeChecked();
898 expect(custom3Checkboxes[1]).not.toBeChecked();
899 expect(custom3Checkboxes[2]).not.toBeChecked();
900 expect(custom3Checkboxes[3]).not.toBeChecked();
901 expect(custom3Checkboxes[4]).not.toBeChecked();
902 expect(custom3Checkboxes[5]).not.toBeChecked();
903 await user.click(custom3Checkboxes[0]);
904 expect(await ui.emptyRoleError.find()).toBeInTheDocument();
905 expect(ui.mappingDialogClose.get()).toBeDisabled();
906 await user.click(custom3Checkboxes[1]);
907 expect(ui.emptyRoleError.query()).not.toBeInTheDocument();
908 expect(ui.mappingDialogClose.get()).toBeEnabled();
909 await user.click(ui.mappingDialogClose.get());
911 expect(await ui.saveGithubProvisioning.find()).toBeEnabled();
912 await user.click(ui.saveGithubProvisioning.get());
914 // Clean local mapping state
915 await user.click(ui.jitProvisioningButton.get());
916 await user.click(ui.githubProvisioningButton.get());
918 await user.click(ui.editMappingButton.get());
921 (await ui.mappingRow.findAll()).filter(
922 (row) => within(row).queryAllByRole('checkbox').length > 0,
925 custom1Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom1'));
926 custom3Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom3'));
927 expect(ui.getMappingRowByRole('custom2')).toBeUndefined();
928 expect(custom1Checkboxes[0]).toBeChecked();
929 expect(custom1Checkboxes[1]).not.toBeChecked();
930 expect(custom1Checkboxes[2]).toBeChecked();
931 expect(custom1Checkboxes[3]).not.toBeChecked();
932 expect(custom1Checkboxes[4]).not.toBeChecked();
933 expect(custom1Checkboxes[5]).toBeChecked();
934 expect(custom3Checkboxes[0]).not.toBeChecked();
935 expect(custom3Checkboxes[1]).toBeChecked();
936 expect(custom3Checkboxes[2]).not.toBeChecked();
937 expect(custom3Checkboxes[3]).not.toBeChecked();
938 expect(custom3Checkboxes[4]).not.toBeChecked();
939 expect(custom3Checkboxes[5]).not.toBeChecked();
940 await user.click(ui.mappingDialogClose.get());
945 const appLoaded = async () => {
946 await waitFor(async () => {
947 expect(await screen.findByText('loading')).not.toBeInTheDocument();
951 function renderAuthentication(features: Feature[] = []) {
953 <AvailableFeaturesContext.Provider value={features}>
954 <Authentication definitions={definitions} />
955 </AvailableFeaturesContext.Provider>,
956 `?tab=${AlmKeys.GitHub}`,