3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 import { act, screen, waitFor, within } from '@testing-library/react';
21 import userEvent from '@testing-library/user-event';
22 import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
23 import React from 'react';
24 import AuthenticationServiceMock from '../../../../../api/mocks/AuthenticationServiceMock';
25 import ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
26 import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
27 import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
28 import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
29 import { definitions } from '../../../../../helpers/mocks/definitions-list';
30 import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
31 import { byRole, byText } from '../../../../../helpers/testSelector';
32 import { Feature } from '../../../../../types/features';
33 import { GitHubProvisioningStatus } from '../../../../../types/provisioning';
34 import { TaskStatuses } from '../../../../../types/tasks';
35 import Authentication from '../Authentication';
37 jest.mock('../../../../../api/system');
39 let handler: AuthenticationServiceMock;
40 let system: SystemServiceMock;
41 let settingsHandler: SettingsServiceMock;
42 let computeEngineHandler: ComputeEngineServiceMock;
45 handler = new AuthenticationServiceMock();
46 system = new SystemServiceMock();
47 settingsHandler = new SettingsServiceMock();
48 computeEngineHandler = new ComputeEngineServiceMock();
51 key: 'sonar.auth.saml.signature.enabled',
55 key: 'sonar.auth.saml.enabled',
59 key: 'sonar.auth.saml.applicationId',
63 key: 'sonar.auth.saml.providerName',
66 ].forEach((setting: any) => settingsHandler.set(setting.key, setting.value));
71 settingsHandler.reset();
73 computeEngineHandler.reset();
77 saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
78 customMessageInformation: byText('settings.authentication.custom_message_information'),
79 enabledToggle: byRole('switch'),
80 testButton: byText('settings.authentication.saml.form.test'),
81 textbox1: byRole('textbox', { name: 'test1' }),
82 textbox2: byRole('textbox', { name: 'test2' }),
84 noSamlConfiguration: byText('settings.authentication.saml.form.not_configured'),
85 createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
86 providerName: byRole('textbox', { name: 'property.sonar.auth.saml.providerName.name' }),
87 providerId: byRole('textbox', { name: 'property.sonar.auth.saml.providerId.name' }),
88 providerCertificate: byRole('textbox', {
89 name: 'property.sonar.auth.saml.certificate.secured.name',
91 loginUrl: byRole('textbox', { name: 'property.sonar.auth.saml.loginUrl.name' }),
92 userLoginAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.login.name' }),
93 userNameAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.name.name' }),
94 saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
95 confirmProvisioningButton: byRole('button', { name: 'yes' }),
96 saveScim: byRole('button', { name: 'save' }),
97 enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
98 disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
99 editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
100 enableFirstMessage: byText('settings.authentication.saml.enable_first'),
101 jitProvisioningButton: byRole('radio', {
102 name: 'settings.authentication.saml.form.provisioning_at_login',
104 scimProvisioningButton: byRole('radio', {
105 name: 'settings.authentication.saml.form.provisioning_with_scim',
107 fillForm: async (user: UserEvent) => {
109 await act(async () => {
110 await user.clear(saml.providerName.get());
111 await user.type(saml.providerName.get(), 'Awsome SAML config');
112 await user.type(saml.providerId.get(), 'okta-1234');
113 await user.type(saml.loginUrl.get(), 'http://test.org');
114 await user.type(saml.providerCertificate.get(), '-secret-');
115 await user.type(saml.userLoginAttribute.get(), 'login');
116 await user.type(saml.userNameAttribute.get(), 'name');
119 createConfiguration: async (user: UserEvent) => {
121 await act(async () => {
122 await user.click((await saml.createConfigButton.findAll())[0]);
124 await saml.fillForm(user);
125 await act(async () => {
126 await user.click(saml.saveConfigButton.get());
131 tab: byRole('tab', { name: 'github GitHub' }),
132 noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
133 createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
134 clientId: byRole('textbox', { name: 'property.sonar.auth.github.clientId.secured.name' }),
135 clientSecret: byRole('textbox', {
136 name: 'property.sonar.auth.github.clientSecret.secured.name',
138 githubApiUrl: byRole('textbox', { name: 'property.sonar.auth.github.apiUrl.name' }),
139 githubWebUrl: byRole('textbox', { name: 'property.sonar.auth.github.webUrl.name' }),
140 allowUserToSignUp: byRole('switch', {
141 name: 'sonar.auth.github.allowUsersToSignUp',
143 organizations: byRole('textbox', { name: 'property.sonar.auth.github.organizations.name' }),
144 saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
145 confirmProvisioningButton: byRole('button', { name: 'yes' }),
146 saveGithubProvisioning: byRole('button', { name: 'save' }),
147 groupAttribute: byRole('textbox', { name: 'property.sonar.auth.github.group.name.name' }),
148 enableConfigButton: byRole('button', { name: 'settings.authentication.form.enable' }),
149 disableConfigButton: byRole('button', { name: 'settings.authentication.form.disable' }),
150 editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
151 editMappingButton: byRole('button', {
152 name: 'settings.authentication.github.configuration.roles_mapping.button_label',
154 mappingRow: byRole('dialog', {
155 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
157 mappingCheckbox: byRole('checkbox'),
158 mappingDialogClose: byRole('dialog', {
159 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
160 }).byRole('button', {
163 deleteOrg: (org: string) =>
165 name: `settings.definition.delete_value.property.sonar.auth.github.organizations.name.${org}`,
167 enableFirstMessage: byText('settings.authentication.github.enable_first'),
168 jitProvisioningButton: byRole('radio', {
169 name: 'settings.authentication.form.provisioning_at_login',
171 githubProvisioningButton: byRole('radio', {
172 name: 'settings.authentication.github.form.provisioning_with_github',
174 githubProvisioningPending: byText(/synchronization_pending/),
175 githubProvisioningInProgress: byText(/synchronization_in_progress/),
176 githubProvisioningSuccess: byText(/synchronization_successful/),
177 githubProvisioningAlert: byText(/synchronization_failed/),
178 configurationValidityLoading: byRole('status', {
179 name: /github.configuration.validation.loading/,
181 configurationValiditySuccess: byRole('status', {
182 name: /github.configuration.validation.valid/,
184 configurationValidityError: byRole('status', {
185 name: /github.configuration.validation.invalid/,
187 syncWarning: byText(/Warning/),
188 syncSummary: byText(/Test summary/),
189 configurationValidityWarning: byRole('status', {
190 name: /github.configuration.validation.valid.short/,
192 checkConfigButton: byRole('button', {
193 name: 'settings.authentication.github.configuration.validation.test',
195 viewConfigValidityDetailsButton: byRole('button', {
196 name: 'settings.authentication.github.configuration.validation.details',
198 configDetailsDialog: byRole('dialog', {
199 name: 'settings.authentication.github.configuration.validation.details.title',
201 continueAutoButton: byRole('button', {
202 name: 'settings.authentication.github.confirm_auto_provisioning.continue',
204 switchJitButton: byRole('button', {
205 name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
207 consentDialog: byRole('dialog', {
208 name: 'settings.authentication.github.confirm_auto_provisioning.header',
210 getConfigDetailsTitle: () => within(ui.github.configDetailsDialog.get()).getByRole('heading'),
211 getOrgs: () => within(ui.github.configDetailsDialog.get()).getAllByRole('listitem'),
212 fillForm: async (user: UserEvent) => {
213 const { github } = ui;
214 await act(async () => {
215 await user.type(await github.clientId.find(), 'Awsome GITHUB config');
216 await user.type(github.clientSecret.get(), 'Client shut');
217 await user.type(github.githubApiUrl.get(), 'API Url');
218 await user.type(github.githubWebUrl.get(), 'WEb Url');
219 await user.type(github.organizations.get(), 'organization1');
222 createConfiguration: async (user: UserEvent) => {
223 const { github } = ui;
224 await act(async () => {
225 await user.click((await github.createConfigButton.findAll())[1]);
227 await github.fillForm(user);
228 await act(async () => {
229 await user.click(github.saveConfigButton.get());
232 enableConfiguration: async (user: UserEvent) => {
233 const { github } = ui;
234 await act(async () => user.click(await github.tab.find()));
235 await github.createConfiguration(user);
236 await act(async () => user.click(await github.enableConfigButton.find()));
238 enableProvisioning: async (user: UserEvent) => {
239 const { github } = ui;
240 await act(async () => user.click(await github.tab.find()));
242 await github.createConfiguration(user);
244 await act(async () => user.click(await github.enableConfigButton.find()));
245 await user.click(await github.githubProvisioningButton.find());
246 await user.click(github.saveGithubProvisioning.get());
247 await act(() => user.click(github.confirmProvisioningButton.get()));
252 it('should render tabs and allow navigation', async () => {
253 const user = userEvent.setup();
254 renderAuthentication();
256 expect(screen.getAllByRole('tab')).toHaveLength(4);
258 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
260 await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
262 expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
263 expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
269 it('should not display the login message feature info box', () => {
270 renderAuthentication();
272 expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
275 it('should display the login message feature info box', () => {
276 renderAuthentication([Feature.LoginMessage]);
278 expect(ui.customMessageInformation.get()).toBeInTheDocument();
281 describe('SAML tab', () => {
284 it('should render an empty SAML configuration', async () => {
285 renderAuthentication();
286 expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
289 it('should be able to create a configuration', async () => {
290 const user = userEvent.setup();
291 renderAuthentication();
293 await user.click((await saml.createConfigButton.findAll())[0]);
295 expect(saml.saveConfigButton.get()).toBeDisabled();
296 await saml.fillForm(user);
297 expect(saml.saveConfigButton.get()).toBeEnabled();
299 await act(async () => {
300 await user.click(saml.saveConfigButton.get());
303 expect(await saml.editConfigButton.find()).toBeInTheDocument();
306 it('should be able to enable/disable configuration', async () => {
308 const user = userEvent.setup();
309 renderAuthentication();
311 await saml.createConfiguration(user);
312 await user.click(await saml.enableConfigButton.find());
314 expect(await saml.disableConfigButton.find()).toBeInTheDocument();
315 await user.click(saml.disableConfigButton.get());
316 await waitFor(() => expect(saml.disableConfigButton.query()).not.toBeInTheDocument());
318 expect(await saml.enableConfigButton.find()).toBeInTheDocument();
321 it('should be able to choose provisioning', async () => {
323 const user = userEvent.setup();
325 renderAuthentication([Feature.Scim]);
327 await saml.createConfiguration(user);
329 expect(await saml.enableFirstMessage.find()).toBeInTheDocument();
330 await user.click(await saml.enableConfigButton.find());
332 expect(await saml.jitProvisioningButton.find()).toBeChecked();
333 expect(saml.saveScim.get()).toBeDisabled();
335 await user.click(saml.scimProvisioningButton.get());
336 expect(saml.saveScim.get()).toBeEnabled();
337 await user.click(saml.saveScim.get());
338 await user.click(saml.confirmProvisioningButton.get());
340 expect(await saml.scimProvisioningButton.find()).toBeChecked();
341 expect(await saml.saveScim.find()).toBeDisabled();
344 it('should not allow editions below Enterprise to select SCIM provisioning', async () => {
346 const user = userEvent.setup();
348 renderAuthentication();
350 await saml.createConfiguration(user);
351 await user.click(await saml.enableConfigButton.find());
353 expect(await saml.jitProvisioningButton.find()).toBeChecked();
354 expect(saml.scimProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
358 describe('Github tab', () => {
359 const { github } = ui;
361 it('should render an empty Github configuration', async () => {
362 renderAuthentication();
363 const user = userEvent.setup();
364 await user.click(await github.tab.find());
365 expect(await github.noGithubConfiguration.find()).toBeInTheDocument();
368 it('should be able to create a configuration', async () => {
369 const user = userEvent.setup();
370 renderAuthentication();
372 await user.click(await github.tab.find());
373 await user.click((await github.createConfigButton.findAll())[1]);
375 expect(github.saveConfigButton.get()).toBeDisabled();
377 await github.fillForm(user);
378 expect(github.saveConfigButton.get()).toBeEnabled();
380 await act(async () => {
381 await user.click(github.saveConfigButton.get());
384 expect(await github.editConfigButton.find()).toBeInTheDocument();
387 it('should be able to edit configuration', async () => {
388 const { github } = ui;
389 const user = userEvent.setup();
390 renderAuthentication();
391 await user.click(await github.tab.find());
393 await github.createConfiguration(user);
395 await user.click(github.editConfigButton.get());
396 await user.click(github.deleteOrg('organization1').get());
398 await user.click(github.saveConfigButton.get());
400 await user.click(await github.editConfigButton.find());
402 expect(github.organizations.get()).toHaveValue('');
405 it('should be able to enable/disable configuration', async () => {
406 const { github } = ui;
407 const user = userEvent.setup();
408 renderAuthentication();
409 await user.click(await github.tab.find());
411 await github.createConfiguration(user);
413 await user.click(await github.enableConfigButton.find());
415 expect(await github.disableConfigButton.find()).toBeInTheDocument();
416 await user.click(github.disableConfigButton.get());
417 await waitFor(() => expect(github.disableConfigButton.query()).not.toBeInTheDocument());
419 expect(await github.enableConfigButton.find()).toBeInTheDocument();
422 it('should not allow edtion below Enterprise to select Github provisioning', async () => {
423 const { github } = ui;
424 const user = userEvent.setup();
426 renderAuthentication();
427 await user.click(await github.tab.find());
429 await github.createConfiguration(user);
430 await user.click(await github.enableConfigButton.find());
432 expect(await github.jitProvisioningButton.find()).toBeChecked();
433 expect(github.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
436 it('should be able to choose provisioning', async () => {
437 const { github } = ui;
438 const user = userEvent.setup();
440 renderAuthentication([Feature.GithubProvisioning]);
441 await user.click(await github.tab.find());
443 await github.createConfiguration(user);
445 expect(await github.enableFirstMessage.find()).toBeInTheDocument();
446 await user.click(await github.enableConfigButton.find());
448 expect(await github.jitProvisioningButton.find()).toBeChecked();
450 expect(github.saveGithubProvisioning.get()).toBeDisabled();
451 await user.click(github.allowUserToSignUp.get());
453 expect(github.saveGithubProvisioning.get()).toBeEnabled();
454 await user.click(github.saveGithubProvisioning.get());
456 await waitFor(() => expect(github.saveGithubProvisioning.query()).toBeDisabled());
458 await user.click(github.githubProvisioningButton.get());
460 expect(github.saveGithubProvisioning.get()).toBeEnabled();
461 await user.click(github.saveGithubProvisioning.get());
462 await user.click(github.confirmProvisioningButton.get());
464 expect(await github.githubProvisioningButton.find()).toBeChecked();
465 expect(github.disableConfigButton.get()).toBeDisabled();
466 expect(github.saveGithubProvisioning.get()).toBeDisabled();
469 describe('Github Provisioning', () => {
475 now: new Date('2022-02-04T12:00:59Z'),
477 user = userEvent.setup();
481 jest.runOnlyPendingTimers();
482 jest.useRealTimers();
485 it('should display a success status when the synchronisation is a success', async () => {
486 handler.addProvisioningTask({
487 status: TaskStatuses.Success,
488 executedAt: '2022-02-03T11:45:35+0200',
491 renderAuthentication([Feature.GithubProvisioning]);
492 await github.enableProvisioning(user);
493 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
494 expect(github.syncSummary.get()).toBeInTheDocument();
497 it('should display a success status even when another task is pending', async () => {
498 handler.addProvisioningTask({
499 status: TaskStatuses.Pending,
500 executedAt: '2022-02-03T11:55:35+0200',
502 handler.addProvisioningTask({
503 status: TaskStatuses.Success,
504 executedAt: '2022-02-03T11:45:35+0200',
506 renderAuthentication([Feature.GithubProvisioning]);
507 await github.enableProvisioning(user);
508 expect(github.githubProvisioningSuccess.get()).toBeInTheDocument();
509 expect(github.githubProvisioningPending.get()).toBeInTheDocument();
512 it('should display an error alert when the synchronisation failed', async () => {
513 handler.addProvisioningTask({
514 status: TaskStatuses.Failed,
515 executedAt: '2022-02-03T11:45:35+0200',
516 errorMessage: "T'es mauvais Jacques",
518 renderAuthentication([Feature.GithubProvisioning]);
519 await github.enableProvisioning(user);
520 expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
521 expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
522 expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
525 it('should display an error alert even when another task is in progress', async () => {
526 handler.addProvisioningTask({
527 status: TaskStatuses.InProgress,
528 executedAt: '2022-02-03T11:55:35+0200',
530 handler.addProvisioningTask({
531 status: TaskStatuses.Failed,
532 executedAt: '2022-02-03T11:45:35+0200',
533 errorMessage: "T'es mauvais Jacques",
535 renderAuthentication([Feature.GithubProvisioning]);
536 await github.enableProvisioning(user);
537 expect(github.githubProvisioningAlert.get()).toBeInTheDocument();
538 expect(github.githubProvisioningButton.get()).toHaveTextContent("T'es mauvais Jacques");
539 expect(github.githubProvisioningSuccess.query()).not.toBeInTheDocument();
540 expect(github.githubProvisioningInProgress.get()).toBeInTheDocument();
543 it('should display that config is valid for both provisioning with 1 org', async () => {
544 renderAuthentication([Feature.GithubProvisioning]);
545 await github.enableConfiguration(user);
547 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
550 it('should display that config is valid for both provisioning with multiple orgs', async () => {
551 handler.setConfigurationValidity({
554 organization: 'org1',
555 autoProvisioning: { status: GitHubProvisioningStatus.Success },
556 jit: { status: GitHubProvisioningStatus.Success },
559 organization: 'org2',
560 autoProvisioning: { status: GitHubProvisioningStatus.Success },
561 jit: { status: GitHubProvisioningStatus.Success },
565 renderAuthentication([Feature.GithubProvisioning]);
566 await github.enableConfiguration(user);
568 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
569 expect(github.configurationValiditySuccess.get()).toHaveTextContent('2');
571 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
572 expect(github.getConfigDetailsTitle()).toHaveTextContent(
573 'settings.authentication.github.configuration.validation.details.valid_label',
575 expect(github.getOrgs()[0]).toHaveTextContent(
576 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
578 expect(github.getOrgs()[1]).toHaveTextContent(
579 'settings.authentication.github.configuration.validation.details.valid_labelorg2',
583 it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
584 const errorMessage = 'Installation suspended';
585 handler.setConfigurationValidity({
588 organization: 'org1',
590 status: GitHubProvisioningStatus.Failed,
594 status: GitHubProvisioningStatus.Failed,
601 renderAuthentication([Feature.GithubProvisioning]);
602 await github.enableConfiguration(user);
604 await waitFor(() => expect(github.configurationValidityWarning.get()).toBeInTheDocument());
605 expect(github.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
607 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
608 expect(github.getConfigDetailsTitle()).toHaveTextContent(
609 'settings.authentication.github.configuration.validation.details.valid_label',
611 expect(github.getOrgs()[0]).toHaveTextContent(
612 'settings.authentication.github.configuration.validation.details.invalid_labelorg1 - Installation suspended',
616 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
619 await user.click(github.githubProvisioningButton.get());
620 await waitFor(() => expect(github.configurationValidityError.get()).toBeInTheDocument());
621 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
624 it('should display that config is valid but some organizations were not found', async () => {
625 handler.setConfigurationValidity({
628 organization: 'org1',
629 autoProvisioning: { status: GitHubProvisioningStatus.Success },
630 jit: { status: GitHubProvisioningStatus.Success },
635 renderAuthentication([Feature.GithubProvisioning]);
636 await github.enableConfiguration(user);
638 await waitFor(() => expect(github.configurationValiditySuccess.get()).toBeInTheDocument());
639 expect(github.configurationValiditySuccess.get()).toHaveTextContent('1');
641 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
642 expect(github.getConfigDetailsTitle()).toHaveTextContent(
643 'settings.authentication.github.configuration.validation.details.valid_label',
645 expect(github.getOrgs()[0]).toHaveTextContent(
646 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
648 expect(github.getOrgs()[1]).toHaveTextContent(
649 'settings.authentication.github.configuration.validation.details.org_not_found.organization1',
653 it('should display that config is invalid', async () => {
654 const errorMessage = 'Test error';
655 handler.setConfigurationValidity({
658 status: GitHubProvisioningStatus.Failed,
662 status: GitHubProvisioningStatus.Failed,
667 renderAuthentication([Feature.GithubProvisioning]);
668 await github.enableConfiguration(user);
670 await waitFor(() => expect(github.configurationValidityError.query()).toBeInTheDocument());
671 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
673 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
674 expect(github.getConfigDetailsTitle()).toHaveTextContent(
675 'settings.authentication.github.configuration.validation.details.invalid_label',
677 expect(github.configDetailsDialog.get()).toHaveTextContent(errorMessage);
680 it('should display that config is valid for jit, but not for auto', async () => {
681 const errorMessage = 'Test error';
682 handler.setConfigurationValidity({
685 status: GitHubProvisioningStatus.Success,
688 status: GitHubProvisioningStatus.Failed,
693 renderAuthentication([Feature.GithubProvisioning]);
694 await github.enableConfiguration(user);
696 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
697 expect(github.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
699 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
700 expect(github.getConfigDetailsTitle()).toHaveTextContent(
701 'settings.authentication.github.configuration.validation.details.valid_label',
704 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
707 await act(() => user.click(github.githubProvisioningButton.get()));
709 expect(github.configurationValidityError.get()).toBeInTheDocument();
710 expect(github.configurationValidityError.get()).toHaveTextContent(errorMessage);
712 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
713 expect(github.getConfigDetailsTitle()).toHaveTextContent(
714 'settings.authentication.github.configuration.validation.details.invalid_label',
718 it('should display that config is invalid because of orgs', async () => {
719 const errorMessage = 'Test error';
720 handler.setConfigurationValidity({
723 organization: 'org1',
724 autoProvisioning: { status: GitHubProvisioningStatus.Success },
725 jit: { status: GitHubProvisioningStatus.Success },
728 organization: 'org2',
729 jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
730 autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
734 renderAuthentication([Feature.GithubProvisioning]);
735 await github.enableConfiguration(user);
737 await waitFor(() => expect(github.configurationValiditySuccess.query()).toBeInTheDocument());
739 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
741 expect(github.getOrgs()[0]).toHaveTextContent(
742 'settings.authentication.github.configuration.validation.details.valid_labelorg1',
744 expect(github.getOrgs()[1]).toHaveTextContent(
745 'settings.authentication.github.configuration.validation.details.invalid_labelorg2 - Test error',
749 user.click(within(github.configDetailsDialog.get()).getByRole('button', { name: 'close' })),
752 await act(() => user.click(github.githubProvisioningButton.get()));
754 expect(github.configurationValidityError.get()).toBeInTheDocument();
755 expect(github.configurationValidityError.get()).toHaveTextContent(
756 `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`,
758 await act(() => user.click(github.viewConfigValidityDetailsButton.get()));
759 expect(github.getOrgs()[1]).toHaveTextContent(
760 `settings.authentication.github.configuration.validation.details.invalid_labelorg2 - ${errorMessage}`,
764 it('should update provisioning validity after clicking Test Configuration', async () => {
765 const errorMessage = 'Test error';
766 handler.setConfigurationValidity({
769 status: GitHubProvisioningStatus.Failed,
773 status: GitHubProvisioningStatus.Failed,
778 renderAuthentication([Feature.GithubProvisioning]);
779 await github.enableConfiguration(user);
780 handler.setConfigurationValidity({
783 status: GitHubProvisioningStatus.Success,
786 status: GitHubProvisioningStatus.Success,
791 expect(await github.configurationValidityError.find()).toBeInTheDocument();
793 await act(() => user.click(github.checkConfigButton.get()));
795 expect(github.configurationValiditySuccess.get()).toBeInTheDocument();
796 expect(github.configurationValidityError.query()).not.toBeInTheDocument();
799 it('should show warning', async () => {
800 handler.addProvisioningTask({
801 status: TaskStatuses.Success,
802 warnings: ['Warning'],
804 renderAuthentication([Feature.GithubProvisioning]);
805 await github.enableProvisioning(user);
807 expect(await github.syncWarning.find()).toBeInTheDocument();
808 expect(github.syncSummary.get()).toBeInTheDocument();
811 it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
812 const user = userEvent.setup();
813 settingsHandler.presetGithubAutoProvisioning();
814 handler.enableGithubProvisioning();
815 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
816 renderAuthentication([Feature.GithubProvisioning]);
818 await user.click(await github.tab.find());
820 expect(await github.consentDialog.find()).toBeInTheDocument();
821 await user.click(github.continueAutoButton.get());
823 expect(await github.githubProvisioningButton.find()).toBeChecked();
824 expect(github.consentDialog.query()).not.toBeInTheDocument();
827 it('should display a modal if user was already using auto and switch to JIT', async () => {
828 const user = userEvent.setup();
829 settingsHandler.presetGithubAutoProvisioning();
830 handler.enableGithubProvisioning();
831 settingsHandler.set('sonar.auth.github.userConsentForPermissionProvisioningRequired', '');
832 renderAuthentication([Feature.GithubProvisioning]);
834 await user.click(await github.tab.find());
836 expect(await github.consentDialog.find()).toBeInTheDocument();
837 await user.click(github.switchJitButton.get());
839 expect(await github.jitProvisioningButton.find()).toBeChecked();
840 expect(github.consentDialog.query()).not.toBeInTheDocument();
843 it('should sort mapping rows', async () => {
844 const user = userEvent.setup();
845 settingsHandler.presetGithubAutoProvisioning();
846 handler.enableGithubProvisioning();
847 renderAuthentication([Feature.GithubProvisioning]);
848 await user.click(await github.tab.find());
850 expect(await github.editMappingButton.find()).toBeInTheDocument();
851 await user.click(github.editMappingButton.get());
853 expect(await github.mappingRow.findAll()).toHaveLength(6);
854 expect(github.mappingRow.getAt(1)).toHaveTextContent('read');
855 expect(github.mappingRow.getAt(2)).toHaveTextContent('triage');
856 expect(github.mappingRow.getAt(3)).toHaveTextContent('write');
857 expect(github.mappingRow.getAt(4)).toHaveTextContent('maintain');
858 expect(github.mappingRow.getAt(5)).toHaveTextContent('admin');
861 it('should apply new mapping and new provisioning type at the same time', async () => {
862 const user = userEvent.setup();
863 renderAuthentication([Feature.GithubProvisioning]);
864 await user.click(await github.tab.find());
866 await github.createConfiguration(user);
867 await user.click(await github.enableConfigButton.find());
869 expect(await github.jitProvisioningButton.find()).toBeChecked();
870 expect(github.editMappingButton.query()).not.toBeInTheDocument();
871 await user.click(github.githubProvisioningButton.get());
872 expect(await github.editMappingButton.find()).toBeInTheDocument();
873 await user.click(github.editMappingButton.get());
875 expect(await github.mappingRow.findAll()).toHaveLength(6);
877 let rowOneCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(1));
878 let rowFiveCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(5));
880 expect(rowOneCheckboxes[0]).toBeChecked();
881 expect(rowOneCheckboxes[5]).not.toBeChecked();
882 expect(rowFiveCheckboxes[5]).toBeChecked();
884 await user.click(rowOneCheckboxes[0]);
885 await user.click(rowOneCheckboxes[5]);
886 await user.click(rowFiveCheckboxes[5]);
887 await user.click(github.mappingDialogClose.get());
889 await user.click(github.saveGithubProvisioning.get());
890 await act(() => user.click(github.confirmProvisioningButton.get()));
892 // Clean local mapping state
893 await user.click(github.jitProvisioningButton.get());
894 await user.click(github.githubProvisioningButton.get());
896 await user.click(github.editMappingButton.get());
897 rowOneCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(1));
898 rowFiveCheckboxes = github.mappingCheckbox.getAll(github.mappingRow.getAt(5));
900 expect(rowOneCheckboxes[0]).not.toBeChecked();
901 expect(rowOneCheckboxes[5]).toBeChecked();
902 expect(rowFiveCheckboxes[5]).not.toBeChecked();
903 await user.click(github.mappingDialogClose.get());
906 it('should apply new mapping on auto-provisioning', async () => {
907 const user = userEvent.setup();
908 settingsHandler.presetGithubAutoProvisioning();
909 handler.enableGithubProvisioning();
910 renderAuthentication([Feature.GithubProvisioning]);
911 await user.click(await github.tab.find());
913 expect(await github.saveGithubProvisioning.find()).toBeDisabled();
914 await user.click(github.editMappingButton.get());
916 expect(await github.mappingRow.findAll()).toHaveLength(6);
918 let rowOneCheckbox = github.mappingCheckbox.getAll(github.mappingRow.getAt(1))[0];
920 expect(rowOneCheckbox).toBeChecked();
922 await user.click(rowOneCheckbox);
923 await user.click(github.mappingDialogClose.get());
925 expect(await github.saveGithubProvisioning.find()).toBeEnabled();
927 await act(() => user.click(github.saveGithubProvisioning.get()));
929 // Clean local mapping state
930 await user.click(github.jitProvisioningButton.get());
931 await user.click(github.githubProvisioningButton.get());
933 await user.click(github.editMappingButton.get());
934 rowOneCheckbox = github.mappingCheckbox.getAll(github.mappingRow.getAt(1))[0];
936 expect(rowOneCheckbox).not.toBeChecked();
937 await user.click(github.mappingDialogClose.get());
942 function renderAuthentication(features: Feature[] = []) {
944 <AvailableFeaturesContext.Provider value={features}>
945 <Authentication definitions={definitions} />
946 </AvailableFeaturesContext.Provider>,