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 { byLabelText, byRole, byText } from '~sonar-aligned/helpers/testSelector';
25 import ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
26 import DopTranslationServiceMock from '../../../../../api/mocks/DopTranslationServiceMock';
27 import GithubProvisioningServiceMock from '../../../../../api/mocks/GithubProvisioningServiceMock';
28 import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
29 import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
30 import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
31 import { definitions } from '../../../../../helpers/mocks/definitions-list';
32 import { mockGitHubConfiguration } from '../../../../../helpers/mocks/dop-translation';
33 import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
34 import { AlmKeys } from '../../../../../types/alm-settings';
35 import { Feature } from '../../../../../types/features';
36 import { GitHubProvisioningStatus, ProvisioningType } from '../../../../../types/provisioning';
37 import { TaskStatuses } from '../../../../../types/tasks';
38 import Authentication from '../Authentication';
40 let handler: GithubProvisioningServiceMock;
41 let system: SystemServiceMock;
42 let settingsHandler: SettingsServiceMock;
43 let computeEngineHandler: ComputeEngineServiceMock;
44 let dopTranslationHandler: DopTranslationServiceMock;
46 const mockedGitHubConfigurationResponse = mockGitHubConfiguration({
48 applicationId: 'Appid',
49 provisioningType: ProvisioningType.auto,
54 dopTranslationHandler = new DopTranslationServiceMock();
55 handler = new GithubProvisioningServiceMock(dopTranslationHandler);
56 system = new SystemServiceMock();
57 settingsHandler = new SettingsServiceMock();
58 computeEngineHandler = new ComputeEngineServiceMock();
63 settingsHandler.reset();
65 computeEngineHandler.reset();
66 dopTranslationHandler.reset();
69 const ghContainer = byRole('tabpanel', { name: 'github GitHub' });
72 saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
73 customMessageInformation: byText('settings.authentication.custom_message_information'),
74 enabledToggle: byRole('switch'),
75 testButton: byText('settings.authentication.saml.form.test'),
76 textbox1: byRole('textbox', { name: 'test1' }),
77 textbox2: byRole('textbox', { name: 'test2' }),
78 tab: byRole('tab', { name: 'github GitHub' }),
79 cancelDialogButton: byRole('dialog').byRole('button', { name: 'cancel' }),
80 noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
81 createConfigButton: ghContainer.byRole('button', {
82 name: 'settings.authentication.form.create',
84 clientId: byRole('textbox', {
85 name: 'property.clientId.name',
87 appId: byRole('textbox', { name: 'property.applicationId.name' }),
88 privateKey: byRole('textbox', {
89 name: 'property.privateKey.name',
91 clientSecret: byRole('textbox', {
92 name: 'property.clientSecret.name',
94 githubApiUrl: byRole('textbox', { name: 'property.apiUrl.name' }),
95 githubWebUrl: byRole('textbox', { name: 'property.webUrl.name' }),
96 allowUsersToSignUp: byRole('switch', {
97 name: 'property.allowUsersToSignUp.name',
99 projectVisibility: byRole('switch', {
100 name: 'property.projectVisibility.name',
102 organizations: byRole('textbox', {
103 name: 'property.allowedOrganizations.name',
105 saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
106 confirmProvisioningButton: byRole('button', {
107 name: 'settings.authentication.github.provisioning_change.confirm_changes',
109 saveGithubProvisioning: ghContainer.byRole('button', { name: 'save' }),
110 groupAttribute: byRole('textbox', {
111 name: 'property.sonar.auth.github.group.name.name',
113 enableConfigButton: ghContainer.byRole('button', {
114 name: 'settings.authentication.form.enable',
116 disableConfigButton: ghContainer.byRole('button', {
117 name: 'settings.authentication.form.disable',
119 editConfigButton: ghContainer.byRole('button', {
120 name: 'settings.authentication.form.edit',
122 editMappingButton: ghContainer.byRole('button', {
123 name: 'settings.authentication.github.configuration.roles_mapping.button_label',
125 mappingRow: byRole('dialog', {
126 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
128 customRoleInput: byRole('textbox', {
129 name: 'settings.authentication.github.configuration.roles_mapping.dialog.add_custom_role',
131 customRoleAddBtn: byRole('dialog', {
132 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
133 }).byRole('button', { name: 'add_verb' }),
134 roleExistsError: byRole('dialog', {
135 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
136 }).byText('settings.authentication.github.configuration.roles_mapping.role_exists'),
137 emptyRoleError: byRole('dialog', {
138 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
139 }).byText('settings.authentication.github.configuration.roles_mapping.empty_custom_role'),
140 deleteCustomRoleCustom2: byRole('button', {
141 name: 'settings.authentication.github.configuration.roles_mapping.dialog.delete_custom_role.custom2',
143 getMappingRowByRole: (text: string) =>
144 ui.mappingRow.getAll().find((row) => within(row).queryByText(text) !== null),
145 mappingCheckbox: byRole('checkbox'),
146 mappingDialogClose: byRole('dialog', {
147 name: 'settings.authentication.github.configuration.roles_mapping.dialog.title',
148 }).byRole('button', {
151 deleteOrg: (org: string) =>
153 name: `settings.definition.delete_value.property.allowedOrganizations.name.${org}`,
155 enableFirstMessage: ghContainer.byText('settings.authentication.github.enable_first'),
156 insecureConfigWarning: byRole('dialog').byText(
157 'settings.authentication.github.provisioning_change.insecure_config',
159 jitProvisioningButton: ghContainer.byRole('radio', {
160 name: /settings.authentication.form.provisioning_at_login/,
162 githubProvisioningButton: ghContainer.byRole('radio', {
163 name: /settings.authentication.github.form.provisioning_with_github/,
165 githubProvisioningPending: ghContainer
168 .byText(/synchronization_pending/),
169 githubProvisioningInProgress: ghContainer
172 .byText(/synchronization_in_progress/),
173 githubProvisioningSuccess: ghContainer.byText(/synchronization_successful/),
174 githubProvisioningAlert: ghContainer.byText(/synchronization_failed/),
175 configurationValidityLoading: ghContainer.byRole('status', {
176 name: /github.configuration.validation.loading/,
178 configurationValiditySuccess: ghContainer.byRole('status', {
179 name: /github.configuration.validation.valid/,
181 configurationValidityError: ghContainer.byRole('status', {
182 name: /github.configuration.validation.invalid/,
184 syncWarning: ghContainer.byText(/Warning/),
185 syncSummary: ghContainer.byText(/Test summary/),
186 configurationValidityWarning: ghContainer.byRole('status', {
187 name: /github.configuration.validation.valid.short/,
189 checkConfigButton: ghContainer.byRole('button', {
190 name: 'settings.authentication.configuration.test',
192 viewConfigValidityDetailsButton: ghContainer.byRole('button', {
193 name: 'settings.authentication.github.configuration.validation.details',
195 configDetailsDialog: byRole('dialog', {
196 name: 'settings.authentication.github.configuration.validation.details.title',
198 continueAutoButton: byRole('button', {
199 name: 'settings.authentication.github.confirm_auto_provisioning.continue',
201 switchJitButton: byRole('button', {
202 name: 'settings.authentication.github.confirm_auto_provisioning.switch_jit',
204 consentDialog: byRole('dialog', {
205 name: 'settings.authentication.github.confirm_auto_provisioning.header',
207 getConfigDetailsTitle: () => ui.configDetailsDialog.byRole('heading').get(),
208 getOrgs: () => ui.configDetailsDialog.byRole('listitem').getAll(),
209 getIconForOrg: (text: string, org: HTMLElement) => byLabelText(text).get(org),
210 fillForm: async (user: UserEvent) => {
211 await user.type(await ui.clientId.find(), 'Awsome GITHUB config');
212 await user.type(ui.clientSecret.get(), 'Client shut');
213 await user.type(ui.appId.get(), 'App id');
214 await user.type(ui.privateKey.get(), 'Private Key');
215 await user.clear(ui.githubApiUrl.get());
216 await user.type(ui.githubApiUrl.get(), 'API Url');
217 await user.clear(ui.githubWebUrl.get());
218 await user.type(ui.githubWebUrl.get(), 'WEb Url');
219 await user.type(ui.organizations.get(), 'organization1');
221 createConfiguration: async (user: UserEvent) => {
222 await user.click(await ui.createConfigButton.find());
223 await ui.fillForm(user);
225 await user.click(ui.saveConfigButton.get());
227 enableConfiguration: async (user: UserEvent) => {
228 await user.click(await ui.tab.find());
229 await ui.createConfiguration(user);
230 await user.click(await ui.enableConfigButton.find());
232 enableProvisioning: async (user: UserEvent) => {
233 await user.click(await ui.tab.find());
235 await ui.createConfiguration(user);
237 await user.click(await ui.enableConfigButton.find());
238 await user.click(await ui.githubProvisioningButton.find());
239 await user.click(ui.saveGithubProvisioning.get());
240 await user.click(ui.confirmProvisioningButton.get());
244 describe('Github tab', () => {
245 it('should render an empty Github configuration', async () => {
246 renderAuthentication();
247 const user = userEvent.setup();
248 await user.click(await ui.tab.find());
249 expect(await ui.noGithubConfiguration.find()).toBeInTheDocument();
252 it('should be able to create a configuration', async () => {
253 const user = userEvent.setup();
254 renderAuthentication();
256 await user.click(await ui.tab.find());
257 await user.click(await ui.createConfigButton.find());
259 expect(ui.saveConfigButton.get()).toBeDisabled();
261 await ui.fillForm(user);
262 expect(ui.saveConfigButton.get()).toBeEnabled();
264 await user.click(ui.saveConfigButton.get());
266 expect(await ui.editConfigButton.find()).toBeInTheDocument();
269 it('should be able to edit configuration', async () => {
270 const user = userEvent.setup();
271 renderAuthentication();
272 await user.click(await ui.tab.find());
274 await ui.createConfiguration(user);
276 await user.click(ui.editConfigButton.get());
277 await user.click(ui.deleteOrg('organization1').get());
279 await user.click(ui.saveConfigButton.get());
281 await user.click(await ui.editConfigButton.find());
283 expect(ui.organizations.get()).toHaveValue('');
286 it('should be able to enable/disable configuration', async () => {
287 const user = userEvent.setup();
288 renderAuthentication();
289 await user.click(await ui.tab.find());
291 await ui.createConfiguration(user);
293 await user.click(await ui.enableConfigButton.find());
295 expect(await ui.disableConfigButton.find()).toBeInTheDocument();
296 await user.click(ui.disableConfigButton.get());
297 await waitFor(() => expect(ui.disableConfigButton.query()).not.toBeInTheDocument());
299 expect(await ui.enableConfigButton.find()).toBeInTheDocument();
302 it('should not allow edtion below Enterprise to select Github provisioning', async () => {
303 const user = userEvent.setup();
305 renderAuthentication();
306 await user.click(await ui.tab.find());
308 await ui.createConfiguration(user);
309 await user.click(await ui.enableConfigButton.find());
311 expect(await ui.jitProvisioningButton.find()).toBeChecked();
312 expect(ui.githubProvisioningButton.get()).toHaveAttribute('aria-disabled', 'true');
315 it('should be able to choose provisioning', async () => {
316 const user = userEvent.setup();
318 renderAuthentication([Feature.GithubProvisioning]);
319 await user.click(await ui.tab.find());
321 await ui.createConfiguration(user);
323 expect(await ui.enableFirstMessage.find()).toBeInTheDocument();
324 await user.click(await ui.enableConfigButton.find());
326 expect(await ui.jitProvisioningButton.find()).toBeChecked();
328 expect(ui.saveGithubProvisioning.get()).toBeDisabled();
329 await user.click(ui.allowUsersToSignUp.get());
331 expect(ui.saveGithubProvisioning.get()).toBeEnabled();
332 await user.click(ui.saveGithubProvisioning.get());
334 await waitFor(() => expect(ui.saveGithubProvisioning.query()).toBeDisabled());
336 await user.click(ui.githubProvisioningButton.get());
338 expect(ui.saveGithubProvisioning.get()).toBeEnabled();
339 await user.click(ui.saveGithubProvisioning.get());
340 await user.click(ui.confirmProvisioningButton.get());
342 expect(await ui.githubProvisioningButton.find()).toBeChecked();
343 expect(ui.disableConfigButton.get()).toBeDisabled();
344 expect(ui.saveGithubProvisioning.get()).toBeDisabled();
347 describe('Github Provisioning', () => {
351 user = userEvent.setup();
354 it('should display a success status when the synchronisation is a success', async () => {
355 handler.addProvisioningTask({
356 status: TaskStatuses.Success,
357 executedAt: '2022-02-03T11:45:35+0200',
360 renderAuthentication([Feature.GithubProvisioning]);
361 await ui.enableProvisioning(user);
362 expect(ui.githubProvisioningSuccess.get()).toBeInTheDocument();
363 expect(ui.syncSummary.get()).toBeInTheDocument();
366 it('should display a success status even when another task is pending', async () => {
367 handler.addProvisioningTask({
368 status: TaskStatuses.Pending,
369 executedAt: '2022-02-03T11:55:35+0200',
371 handler.addProvisioningTask({
372 status: TaskStatuses.Success,
373 executedAt: '2022-02-03T11:45:35+0200',
375 renderAuthentication([Feature.GithubProvisioning]);
376 await ui.enableProvisioning(user);
377 expect(ui.githubProvisioningSuccess.get()).toBeInTheDocument();
378 expect(ui.githubProvisioningPending.get()).toBeInTheDocument();
381 it('should display an error alert when the synchronisation failed', async () => {
382 handler.addProvisioningTask({
383 status: TaskStatuses.Failed,
384 executedAt: '2022-02-03T11:45:35+0200',
385 errorMessage: "T'es mauvais Jacques",
387 renderAuthentication([Feature.GithubProvisioning]);
388 await ui.enableProvisioning(user);
389 expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
390 expect(ghContainer.get()).toHaveTextContent("T'es mauvais Jacques");
391 expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
394 it('should display an error alert even when another task is in progress', async () => {
395 handler.addProvisioningTask({
396 status: TaskStatuses.InProgress,
397 executedAt: '2022-02-03T11:55:35+0200',
399 handler.addProvisioningTask({
400 status: TaskStatuses.Failed,
401 executedAt: '2022-02-03T11:45:35+0200',
402 errorMessage: "T'es mauvais Jacques",
404 renderAuthentication([Feature.GithubProvisioning]);
405 await ui.enableProvisioning(user);
406 expect(ui.githubProvisioningAlert.get()).toBeInTheDocument();
407 expect(ghContainer.get()).toHaveTextContent("T'es mauvais Jacques");
408 expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
409 expect(ui.githubProvisioningInProgress.get()).toBeInTheDocument();
412 it('should display that config is valid for both provisioning with 1 org', async () => {
413 renderAuthentication([Feature.GithubProvisioning]);
414 await ui.enableConfiguration(user);
418 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
421 it('should display that config is valid for both provisioning with multiple orgs', async () => {
422 handler.setConfigurationValidity({
425 organization: 'org1',
426 autoProvisioning: { status: GitHubProvisioningStatus.Success },
427 jit: { status: GitHubProvisioningStatus.Success },
430 organization: 'org2',
431 autoProvisioning: { status: GitHubProvisioningStatus.Success },
432 jit: { status: GitHubProvisioningStatus.Success },
436 renderAuthentication([Feature.GithubProvisioning]);
437 await ui.enableConfiguration(user);
441 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
442 expect(ui.configurationValiditySuccess.get()).toHaveTextContent('2');
444 await user.click(ui.viewConfigValidityDetailsButton.get());
445 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
446 expect(ui.getOrgs()).toHaveLength(3);
449 'settings.authentication.github.configuration.validation.details.valid_label',
452 ).toBeInTheDocument();
455 'settings.authentication.github.configuration.validation.details.valid_label',
458 ).toBeInTheDocument();
461 it('should display that config is invalid for autoprovisioning if some apps are suspended but valid for jit', async () => {
462 const errorMessage = 'Installation suspended';
463 handler.setConfigurationValidity({
466 organization: 'org1',
468 status: GitHubProvisioningStatus.Failed,
472 status: GitHubProvisioningStatus.Failed,
479 renderAuthentication([Feature.GithubProvisioning]);
480 await ui.enableConfiguration(user);
484 await waitFor(() => expect(ui.configurationValidityWarning.get()).toBeInTheDocument());
485 expect(ui.configurationValidityWarning.get()).toHaveTextContent(errorMessage);
487 await user.click(ui.viewConfigValidityDetailsButton.get());
488 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
490 ui.configDetailsDialog
491 .byText('settings.authentication.github.configuration.validation.valid.short')
493 ).toBeInTheDocument();
494 expect(ui.getOrgs()[0]).toHaveTextContent('org1 - Installation suspended');
497 'settings.authentication.github.configuration.validation.details.invalid_label',
500 ).toBeInTheDocument();
502 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
504 await user.click(ui.githubProvisioningButton.get());
505 await waitFor(() => expect(ui.configurationValidityError.get()).toBeInTheDocument());
506 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
509 it('should display that config is valid but some organizations were not found', async () => {
510 handler.setConfigurationValidity({
513 organization: 'org1',
514 autoProvisioning: { status: GitHubProvisioningStatus.Success },
515 jit: { status: GitHubProvisioningStatus.Success },
520 renderAuthentication([Feature.GithubProvisioning]);
521 await ui.enableConfiguration(user);
525 await waitFor(() => expect(ui.configurationValiditySuccess.get()).toBeInTheDocument());
526 expect(ui.configurationValiditySuccess.get()).toHaveTextContent('1');
528 await user.click(ui.viewConfigValidityDetailsButton.get());
529 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
531 ui.configDetailsDialog
532 .byText('settings.authentication.github.configuration.validation.valid.short')
534 ).toBeInTheDocument();
535 expect(ui.getOrgs()[0]).toHaveTextContent('org1');
538 'settings.authentication.github.configuration.validation.details.valid_label',
541 ).toBeInTheDocument();
542 expect(ui.getOrgs()[1]).toHaveTextContent(
543 'settings.authentication.github.configuration.validation.details.org_not_found.organization1',
547 it('should display that config is invalid', async () => {
548 const errorMessage = 'Test error';
549 handler.setConfigurationValidity({
552 status: GitHubProvisioningStatus.Failed,
556 status: GitHubProvisioningStatus.Failed,
561 renderAuthentication([Feature.GithubProvisioning]);
562 await ui.enableConfiguration(user);
566 await waitFor(() => expect(ui.configurationValidityError.query()).toBeInTheDocument());
567 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
569 await user.click(ui.viewConfigValidityDetailsButton.get());
570 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
572 ui.configDetailsDialog
573 .byText(/settings.authentication.github.configuration.validation.invalid/)
575 ).toBeInTheDocument();
576 expect(ui.configDetailsDialog.get()).toHaveTextContent(errorMessage);
579 it('should display that config is valid for jit, but not for auto', async () => {
580 const errorMessage = 'Test error';
581 handler.setConfigurationValidity({
584 status: GitHubProvisioningStatus.Success,
587 status: GitHubProvisioningStatus.Failed,
592 renderAuthentication([Feature.GithubProvisioning]);
593 await ui.enableConfiguration(user);
597 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
598 expect(ui.configurationValiditySuccess.get()).not.toHaveTextContent(errorMessage);
600 await user.click(ui.viewConfigValidityDetailsButton.get());
601 expect(ui.getConfigDetailsTitle()).toBeInTheDocument();
603 ui.configDetailsDialog
604 .byText('settings.authentication.github.configuration.validation.valid.short')
606 ).toBeInTheDocument();
607 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
609 await user.click(ui.githubProvisioningButton.get());
611 expect(ui.configurationValidityError.get()).toBeInTheDocument();
612 expect(ui.configurationValidityError.get()).toHaveTextContent(errorMessage);
614 await user.click(ui.viewConfigValidityDetailsButton.get());
616 ui.configDetailsDialog
617 .byText(/settings.authentication.github.configuration.validation.invalid/)
619 ).toBeInTheDocument();
622 it('should display that config is invalid because of orgs', async () => {
623 const errorMessage = 'Test error';
624 handler.setConfigurationValidity({
627 organization: 'org1',
628 autoProvisioning: { status: GitHubProvisioningStatus.Success },
629 jit: { status: GitHubProvisioningStatus.Success },
632 organization: 'org2',
633 jit: { status: GitHubProvisioningStatus.Failed, errorMessage },
634 autoProvisioning: { status: GitHubProvisioningStatus.Failed, errorMessage },
638 renderAuthentication([Feature.GithubProvisioning]);
639 await ui.enableConfiguration(user);
643 await waitFor(() => expect(ui.configurationValiditySuccess.query()).toBeInTheDocument());
645 await user.click(ui.viewConfigValidityDetailsButton.get());
647 expect(ui.getOrgs()[0]).toHaveTextContent('org1');
650 'settings.authentication.github.configuration.validation.details.valid_label',
653 ).toBeInTheDocument();
654 expect(ui.getOrgs()[1]).toHaveTextContent('org2 - Test error');
657 'settings.authentication.github.configuration.validation.details.invalid_label',
660 ).toBeInTheDocument();
662 await user.click(ui.configDetailsDialog.byRole('button', { name: 'close' }).get());
664 await user.click(ui.githubProvisioningButton.get());
666 expect(ui.configurationValidityError.get()).toBeInTheDocument();
667 expect(ui.configurationValidityError.get()).toHaveTextContent(
668 `settings.authentication.github.configuration.validation.invalid_org.org2.${errorMessage}`,
670 await user.click(ui.viewConfigValidityDetailsButton.get());
673 ui.configDetailsDialog
675 'settings.authentication.github.configuration.validation.details.invalid_label',
679 expect(ui.getOrgs()[1]).toHaveTextContent(`org2 - ${errorMessage}`);
682 it('should update provisioning validity after clicking Test Configuration', async () => {
683 const errorMessage = 'Test error';
684 handler.setConfigurationValidity({
687 status: GitHubProvisioningStatus.Failed,
691 status: GitHubProvisioningStatus.Failed,
696 renderAuthentication([Feature.GithubProvisioning]);
697 await ui.enableConfiguration(user);
698 handler.setConfigurationValidity({
701 status: GitHubProvisioningStatus.Success,
704 status: GitHubProvisioningStatus.Success,
711 expect(await ui.configurationValidityError.find()).toBeInTheDocument();
713 await user.click(ui.checkConfigButton.get());
715 expect(ui.configurationValiditySuccess.get()).toBeInTheDocument();
716 expect(ui.configurationValidityError.query()).not.toBeInTheDocument();
719 it('should show warning', async () => {
720 handler.addProvisioningTask({
721 status: TaskStatuses.Success,
722 warnings: ['Warning'],
724 renderAuthentication([Feature.GithubProvisioning]);
725 await ui.enableProvisioning(user);
727 expect(await ui.syncWarning.find()).toBeInTheDocument();
728 expect(ui.syncSummary.get()).toBeInTheDocument();
731 it('should display a modal if user was already using auto and continue using auto provisioning', async () => {
732 const user = userEvent.setup();
733 dopTranslationHandler.gitHubConfigurations.push({
734 ...mockedGitHubConfigurationResponse,
735 userConsentRequiredAfterUpgrade: true,
737 renderAuthentication([Feature.GithubProvisioning]);
739 await user.click(await ui.tab.find());
741 expect(await ui.consentDialog.find()).toBeInTheDocument();
742 await user.click(ui.continueAutoButton.get());
744 expect(await ui.githubProvisioningButton.find()).toBeChecked();
745 expect(ui.consentDialog.query()).not.toBeInTheDocument();
748 it('should display a modal if user was already using auto and switch to JIT', async () => {
749 const user = userEvent.setup();
750 dopTranslationHandler.gitHubConfigurations.push({
751 ...mockedGitHubConfigurationResponse,
752 userConsentRequiredAfterUpgrade: true,
754 renderAuthentication([Feature.GithubProvisioning]);
756 await user.click(await ui.tab.find());
758 expect(await ui.consentDialog.find()).toBeInTheDocument();
759 await user.click(ui.switchJitButton.get());
761 expect(await ui.jitProvisioningButton.find()).toBeChecked();
762 expect(ui.consentDialog.query()).not.toBeInTheDocument();
765 it('should sort mapping rows', async () => {
766 const user = userEvent.setup();
767 dopTranslationHandler.gitHubConfigurations.push(mockedGitHubConfigurationResponse);
768 renderAuthentication([Feature.GithubProvisioning]);
769 await user.click(await ui.tab.find());
771 expect(await ui.editMappingButton.find()).toBeInTheDocument();
772 await user.click(ui.editMappingButton.get());
774 const rows = (await ui.mappingRow.findAll()).filter(
775 (row) => within(row).queryAllByRole('checkbox').length > 0,
778 expect(rows).toHaveLength(5);
780 expect(rows[0]).toHaveTextContent('read');
781 expect(rows[1]).toHaveTextContent('triage');
782 expect(rows[2]).toHaveTextContent('write');
783 expect(rows[3]).toHaveTextContent('maintain');
784 expect(rows[4]).toHaveTextContent('admin');
787 it('should apply new mapping and new provisioning type at the same time', async () => {
788 const user = userEvent.setup();
789 renderAuthentication([Feature.GithubProvisioning]);
790 await user.click(await ui.tab.find());
792 await ui.createConfiguration(user);
793 await user.click(await ui.enableConfigButton.find());
795 expect(await ui.jitProvisioningButton.find()).toBeChecked();
796 expect(ui.editMappingButton.query()).not.toBeInTheDocument();
797 await user.click(ui.githubProvisioningButton.get());
798 expect(await ui.editMappingButton.find()).toBeInTheDocument();
799 await user.click(ui.editMappingButton.get());
801 expect(await ui.mappingRow.findAll()).toHaveLength(7);
803 let readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'));
804 let adminCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('admin'));
806 expect(readCheckboxes[0]).toBeChecked();
807 expect(readCheckboxes[5]).not.toBeChecked();
808 expect(adminCheckboxes[5]).toBeChecked();
810 await user.click(readCheckboxes[0]);
811 await user.click(readCheckboxes[5]);
812 await user.click(adminCheckboxes[5]);
813 await user.click(ui.mappingDialogClose.get());
815 await user.click(ui.saveGithubProvisioning.get());
816 await user.click(ui.confirmProvisioningButton.get());
818 // Clean local mapping state
819 await user.click(ui.jitProvisioningButton.get());
820 await user.click(ui.githubProvisioningButton.get());
822 await user.click(ui.editMappingButton.get());
823 readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'));
824 adminCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('admin'));
826 expect(readCheckboxes[0]).not.toBeChecked();
827 expect(readCheckboxes[5]).toBeChecked();
828 expect(adminCheckboxes[5]).not.toBeChecked();
829 await user.click(ui.mappingDialogClose.get());
832 it('should apply new mapping on auto-provisioning', async () => {
833 const user = userEvent.setup();
834 dopTranslationHandler.gitHubConfigurations.push(mockedGitHubConfigurationResponse);
835 renderAuthentication([Feature.GithubProvisioning]);
836 await user.click(await ui.tab.find());
838 expect(await ui.saveGithubProvisioning.find()).toBeDisabled();
839 await user.click(ui.editMappingButton.get());
841 expect(await ui.mappingRow.findAll()).toHaveLength(7);
843 let readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'))[0];
845 expect(readCheckboxes).toBeChecked();
847 await user.click(readCheckboxes);
848 await user.click(ui.mappingDialogClose.get());
850 expect(await ui.saveGithubProvisioning.find()).toBeEnabled();
852 await user.click(ui.saveGithubProvisioning.get());
853 await user.click(ui.confirmProvisioningButton.get());
855 // Clean local mapping state
856 await user.click(ui.jitProvisioningButton.get());
857 await user.click(ui.githubProvisioningButton.get());
859 await user.click(ui.editMappingButton.get());
860 readCheckboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('read'))[0];
862 expect(readCheckboxes).not.toBeChecked();
863 await user.click(ui.mappingDialogClose.get());
866 it('should add/remove/update custom roles', async () => {
867 const user = userEvent.setup();
868 dopTranslationHandler.gitHubConfigurations.push(mockedGitHubConfigurationResponse);
869 handler.addGitHubCustomRole('custom1', ['user', 'codeViewer', 'scan']);
870 handler.addGitHubCustomRole('custom2', ['user', 'codeViewer', 'issueAdmin', 'scan']);
871 renderAuthentication([Feature.GithubProvisioning]);
872 await user.click(await ui.tab.find());
874 expect(await ui.saveGithubProvisioning.find()).toBeDisabled();
875 await user.click(ui.editMappingButton.get());
877 const rows = (await ui.mappingRow.findAll()).filter(
878 (row) => within(row).queryAllByRole('checkbox').length > 0,
881 expect(rows).toHaveLength(7);
883 let custom1Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom1'));
885 expect(custom1Checkboxes[0]).toBeChecked();
886 expect(custom1Checkboxes[1]).toBeChecked();
887 expect(custom1Checkboxes[2]).not.toBeChecked();
888 expect(custom1Checkboxes[3]).not.toBeChecked();
889 expect(custom1Checkboxes[4]).not.toBeChecked();
890 expect(custom1Checkboxes[5]).toBeChecked();
892 await user.click(custom1Checkboxes[1]);
893 await user.click(custom1Checkboxes[2]);
895 await user.click(ui.deleteCustomRoleCustom2.get());
897 expect(ui.customRoleInput.get()).toHaveValue('');
898 await user.type(ui.customRoleInput.get(), 'read');
899 await user.click(ui.customRoleAddBtn.get());
900 expect(await ui.roleExistsError.find()).toBeInTheDocument();
901 expect(ui.customRoleAddBtn.get()).toBeDisabled();
902 await user.clear(ui.customRoleInput.get());
903 expect(ui.roleExistsError.query()).not.toBeInTheDocument();
904 await user.type(ui.customRoleInput.get(), 'custom1');
905 await user.click(ui.customRoleAddBtn.get());
906 expect(await ui.roleExistsError.find()).toBeInTheDocument();
907 expect(ui.customRoleAddBtn.get()).toBeDisabled();
908 await user.clear(ui.customRoleInput.get());
909 await user.type(ui.customRoleInput.get(), 'custom3');
910 expect(ui.roleExistsError.query()).not.toBeInTheDocument();
911 expect(ui.customRoleAddBtn.get()).toBeEnabled();
912 await user.click(ui.customRoleAddBtn.get());
914 let custom3Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom3'));
915 expect(custom3Checkboxes[0]).toBeChecked();
916 expect(custom3Checkboxes[1]).not.toBeChecked();
917 expect(custom3Checkboxes[2]).not.toBeChecked();
918 expect(custom3Checkboxes[3]).not.toBeChecked();
919 expect(custom3Checkboxes[4]).not.toBeChecked();
920 expect(custom3Checkboxes[5]).not.toBeChecked();
921 await user.click(custom3Checkboxes[0]);
922 expect(await ui.emptyRoleError.find()).toBeInTheDocument();
923 expect(ui.mappingDialogClose.get()).toBeDisabled();
924 await user.click(custom3Checkboxes[1]);
925 expect(ui.emptyRoleError.query()).not.toBeInTheDocument();
926 expect(ui.mappingDialogClose.get()).toBeEnabled();
927 await user.click(ui.mappingDialogClose.get());
929 expect(await ui.saveGithubProvisioning.find()).toBeEnabled();
930 await user.click(ui.saveGithubProvisioning.get());
932 await user.click(ui.confirmProvisioningButton.get());
934 // Clean local mapping state
935 await user.click(ui.jitProvisioningButton.get());
936 await user.click(ui.githubProvisioningButton.get());
938 await user.click(ui.editMappingButton.get());
941 (await ui.mappingRow.findAll()).filter(
942 (row) => within(row).queryAllByRole('checkbox').length > 0,
945 custom1Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom1'));
946 custom3Checkboxes = ui.mappingCheckbox.getAll(ui.getMappingRowByRole('custom3'));
947 expect(ui.getMappingRowByRole('custom2')).toBeUndefined();
948 expect(custom1Checkboxes[0]).toBeChecked();
949 expect(custom1Checkboxes[1]).not.toBeChecked();
950 expect(custom1Checkboxes[2]).toBeChecked();
951 expect(custom1Checkboxes[3]).not.toBeChecked();
952 expect(custom1Checkboxes[4]).not.toBeChecked();
953 expect(custom1Checkboxes[5]).toBeChecked();
954 expect(custom3Checkboxes[0]).not.toBeChecked();
955 expect(custom3Checkboxes[1]).toBeChecked();
956 expect(custom3Checkboxes[2]).not.toBeChecked();
957 expect(custom3Checkboxes[3]).not.toBeChecked();
958 expect(custom3Checkboxes[4]).not.toBeChecked();
959 expect(custom3Checkboxes[5]).not.toBeChecked();
960 await user.click(ui.mappingDialogClose.get());
963 it('should should show insecure config warning', async () => {
964 const user = userEvent.setup();
965 dopTranslationHandler.gitHubConfigurations.push({
966 ...mockedGitHubConfigurationResponse,
967 provisioningType: ProvisioningType.jit,
969 renderAuthentication([Feature.GithubProvisioning]);
970 await user.click(await ui.tab.find());
972 expect(ui.allowUsersToSignUp.get()).toBeChecked();
973 await user.click(ui.allowUsersToSignUp.get());
974 await user.click(ui.saveGithubProvisioning.get());
976 expect(ui.insecureConfigWarning.query()).not.toBeInTheDocument();
978 await user.click(ui.allowUsersToSignUp.get());
979 await user.click(ui.saveGithubProvisioning.get());
981 expect(ui.insecureConfigWarning.get()).toBeInTheDocument();
982 await user.click(ui.confirmProvisioningButton.get());
984 await user.click(ui.githubProvisioningButton.get());
985 await user.click(ui.saveGithubProvisioning.get());
987 expect(ui.insecureConfigWarning.get()).toBeInTheDocument();
988 await user.click(ui.confirmProvisioningButton.get());
990 await user.click(ui.projectVisibility.get());
991 await user.click(ui.saveGithubProvisioning.get());
993 expect(ui.insecureConfigWarning.get()).toBeInTheDocument();
994 await user.click(ui.confirmProvisioningButton.get());
996 await user.click(ui.editConfigButton.get());
997 await user.click(ui.saveConfigButton.get());
999 expect(ui.insecureConfigWarning.get()).toBeInTheDocument();
1000 await user.click(ui.cancelDialogButton.get());
1001 await user.type(ui.organizations.get(), '123');
1002 await user.click(ui.saveConfigButton.get());
1003 expect(ui.insecureConfigWarning.query()).not.toBeInTheDocument();
1005 await user.click(ui.projectVisibility.get());
1006 await user.click(ui.saveGithubProvisioning.get());
1007 expect(ui.insecureConfigWarning.query()).not.toBeInTheDocument();
1009 await user.click(ui.jitProvisioningButton.get());
1010 await user.click(ui.saveGithubProvisioning.get());
1011 expect(ui.confirmProvisioningButton.get()).toBeInTheDocument();
1012 expect(ui.insecureConfigWarning.query()).not.toBeInTheDocument();
1013 await user.click(ui.confirmProvisioningButton.get());
1015 await user.click(ui.allowUsersToSignUp.get());
1016 await user.click(ui.saveGithubProvisioning.get());
1017 expect(ui.insecureConfigWarning.query()).not.toBeInTheDocument();
1022 const assertAppIsLoaded = () => {
1023 expect(screen.queryByText('loading')).not.toBeInTheDocument();
1026 function renderAuthentication(features: Feature[] = []) {
1028 <AvailableFeaturesContext.Provider value={features}>
1029 <Authentication definitions={definitions} />
1030 </AvailableFeaturesContext.Provider>,
1031 `?tab=${AlmKeys.GitHub}`,