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 userEvent from '@testing-library/user-event';
21 import React from 'react';
22 import { byRole, byText } from '~sonar-aligned/helpers/testSelector';
23 import ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
24 import GitlabProvisioningServiceMock from '../../../../../api/mocks/GitlabProvisioningServiceMock';
25 import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
26 import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
27 import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
28 import { mockGitlabConfiguration } from '../../../../../helpers/mocks/alm-integrations';
29 import { definitions } from '../../../../../helpers/mocks/definitions-list';
30 import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
31 import { AlmKeys } from '../../../../../types/alm-settings';
32 import { Feature } from '../../../../../types/features';
33 import { ProvisioningType } from '../../../../../types/provisioning';
34 import { TaskStatuses, TaskTypes } from '../../../../../types/tasks';
35 import Authentication from '../Authentication';
37 let handler: GitlabProvisioningServiceMock;
38 let system: SystemServiceMock;
39 let settingsHandler: SettingsServiceMock;
40 let computeEngineHandler: ComputeEngineServiceMock;
43 handler = new GitlabProvisioningServiceMock();
44 system = new SystemServiceMock();
45 settingsHandler = new SettingsServiceMock();
46 computeEngineHandler = new ComputeEngineServiceMock();
51 settingsHandler.reset();
53 computeEngineHandler.reset();
56 const glContainer = byRole('tabpanel', { name: 'gitlab GitLab' });
59 noGitlabConfiguration: glContainer.byText('settings.authentication.gitlab.form.not_configured'),
60 createConfigButton: glContainer.byRole('button', {
61 name: 'settings.authentication.form.create',
63 editConfigButton: glContainer.byRole('button', {
64 name: 'settings.authentication.form.edit',
66 deleteConfigButton: glContainer.byRole('button', {
67 name: 'settings.authentication.form.delete',
69 enableConfigButton: glContainer.byRole('button', {
70 name: 'settings.authentication.form.enable',
72 disableConfigButton: glContainer.byRole('button', {
73 name: 'settings.authentication.form.disable',
75 createDialog: byRole('dialog', {
76 name: 'settings.authentication.gitlab.form.create',
78 editDialog: byRole('dialog', {
79 name: 'settings.authentication.gitlab.form.edit',
81 applicationId: byRole('textbox', {
82 name: 'property.applicationId.name',
84 url: byRole('textbox', { name: 'property.url.name' }),
85 secret: byRole('textbox', {
86 name: 'property.secret.name',
88 synchronizeGroups: byRole('switch', {
89 name: 'property.synchronizeGroups.name',
91 saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
92 jitProvisioningRadioButton: glContainer.byRole('radio', {
93 name: /settings.authentication.gitlab.provisioning_at_login/,
95 autoProvisioningRadioButton: glContainer.byRole('radio', {
96 name: /settings.authentication.gitlab.form.provisioning_with_gitlab/,
98 jitAllowUsersToSignUpToggle: byRole('switch', { name: 'property.allowUsersToSignUp.name' }),
99 autoProvisioningToken: byRole('textbox', {
100 name: 'property.provisioningToken.name',
102 autoProvisioningUpdateTokenButton: byRole('button', {
103 name: 'settings.almintegration.form.secret.update_field',
105 groups: byRole('textbox', {
106 name: 'property.allowedGroups.name',
108 deleteGroupButton: byRole('button', { name: /delete_value/ }),
109 removeProvisioniongGroup: byRole('button', {
110 name: /settings.definition.delete_value.property.allowedGroups.name./,
112 saveProvisioning: glContainer.byRole('button', { name: 'save' }),
113 cancelProvisioningChanges: glContainer.byRole('button', { name: 'cancel' }),
114 confirmAutoProvisioningDialog: byRole('dialog', {
115 name: 'settings.authentication.gitlab.confirm.AUTO_PROVISIONING',
117 confirmJitProvisioningDialog: byRole('dialog', {
118 name: 'settings.authentication.gitlab.confirm.JIT',
120 confirmInsecureProvisioningDialog: byRole('dialog', {
121 name: 'settings.authentication.gitlab.confirm.insecure',
123 confirmProvisioningChange: byRole('button', {
124 name: 'settings.authentication.gitlab.provisioning_change.confirm_changes',
126 syncSummary: glContainer.byText(/Test summary/),
127 syncWarning: glContainer.byText(/Warning/),
128 gitlabProvisioningPending: glContainer
131 .byText(/synchronization_pending/),
132 gitlabProvisioningInProgress: glContainer
135 .byText(/synchronization_in_progress/),
136 gitlabProvisioningSuccess: glContainer.byText(/synchronization_successful/),
137 gitlabProvisioningAlert: glContainer.byText(/synchronization_failed/),
138 gitlabConfigurationStatus: glContainer.byRole('status', {
139 name: /settings.authentication.gitlab.configuration/,
141 gitlabMissingSecretErrorMessage: byText(
142 'settings.authentication.gitlab.form.secret.required_for_url_change',
144 testConfiguration: glContainer.byRole('button', {
145 name: 'settings.authentication.configuration.test',
149 it('should create a Gitlab configuration and disable it with proper validation', async () => {
150 handler.setGitlabConfigurations([]);
151 renderAuthentication();
152 const user = userEvent.setup();
154 expect(await ui.noGitlabConfiguration.find()).toBeInTheDocument();
155 expect(ui.createConfigButton.get()).toBeInTheDocument();
157 await user.click(ui.createConfigButton.get());
158 expect(await ui.createDialog.find()).toBeInTheDocument();
159 await user.type(ui.applicationId.get(), '123');
160 expect(ui.saveConfigButton.get()).toBeDisabled();
161 await user.type(ui.url.get(), 'https://company.ui.com');
162 await user.type(ui.secret.get(), '123');
163 expect(ui.saveConfigButton.get()).toBeEnabled();
164 await user.click(ui.synchronizeGroups.get());
165 await user.click(ui.saveConfigButton.get());
167 expect(await ui.editConfigButton.find()).toBeInTheDocument();
168 expect(ui.noGitlabConfiguration.query()).not.toBeInTheDocument();
169 expect(glContainer.get()).toHaveTextContent('https://company.ui.com');
171 expect(ui.disableConfigButton.get()).toBeInTheDocument();
172 await user.click(ui.disableConfigButton.get());
173 expect(ui.enableConfigButton.get()).toBeInTheDocument();
174 expect(ui.disableConfigButton.query()).not.toBeInTheDocument();
177 it('should edit a configuration with proper validation and delete it', async () => {
178 const user = userEvent.setup();
179 renderAuthentication();
181 expect(await ui.editConfigButton.find()).toBeInTheDocument();
182 expect(glContainer.get()).toHaveTextContent('URL');
183 expect(ui.disableConfigButton.get()).toBeInTheDocument();
184 expect(ui.deleteConfigButton.get()).toBeInTheDocument();
185 expect(ui.deleteConfigButton.get()).toBeDisabled();
187 await user.click(ui.editConfigButton.get());
188 expect(await ui.editDialog.find()).toBeInTheDocument();
189 expect(ui.url.get()).toHaveValue('URL');
190 expect(ui.applicationId.get()).toBeInTheDocument();
191 expect(ui.secret.query()).not.toBeInTheDocument();
192 expect(ui.synchronizeGroups.get()).toBeChecked();
194 expect(ui.applicationId.get()).toBeInTheDocument();
195 await user.clear(ui.applicationId.get());
196 expect(ui.saveConfigButton.get()).toBeDisabled();
197 await user.type(ui.applicationId.get(), '456');
198 expect(ui.saveConfigButton.get()).toBeEnabled();
200 expect(ui.url.get()).toBeInTheDocument();
201 await user.clear(ui.url.get());
202 expect(ui.saveConfigButton.get()).toBeDisabled();
203 await user.type(ui.url.get(), 'www.internet.com');
204 expect(ui.saveConfigButton.get()).toBeDisabled();
205 expect(ui.gitlabMissingSecretErrorMessage.get()).toBeInTheDocument();
206 await user.click(ui.autoProvisioningUpdateTokenButton.get());
207 await user.type(ui.secret.get(), '123');
208 expect(ui.gitlabMissingSecretErrorMessage.query()).not.toBeInTheDocument();
209 expect(ui.saveConfigButton.get()).toBeEnabled();
210 await user.click(ui.saveConfigButton.get());
212 expect(glContainer.get()).not.toHaveTextContent('URL');
213 expect(glContainer.get()).toHaveTextContent('www.internet.com');
215 expect(ui.disableConfigButton.get()).toBeInTheDocument();
216 await user.click(ui.disableConfigButton.get());
217 expect(await ui.enableConfigButton.find()).toBeInTheDocument();
218 expect(ui.deleteConfigButton.get()).toBeEnabled();
219 await user.click(ui.deleteConfigButton.get());
220 expect(await ui.noGitlabConfiguration.find()).toBeInTheDocument();
221 expect(ui.editConfigButton.query()).not.toBeInTheDocument();
224 it('should be able to save just-in-time with no organizations', async () => {
225 const user = userEvent.setup();
226 renderAuthentication([Feature.GitlabProvisioning]);
228 expect(await ui.jitProvisioningRadioButton.find()).toBeChecked();
230 expect(ui.groups.get()).toHaveValue('Cypress Hill');
231 expect(await ui.saveProvisioning.find()).toBeDisabled();
232 await user.click(ui.deleteGroupButton.get());
233 expect(await ui.saveProvisioning.find()).toBeEnabled();
236 it('should not be able to save Auto provisioning with no organizations', async () => {
237 const user = userEvent.setup();
238 handler.setGitlabConfigurations([
239 mockGitlabConfiguration({
240 allowUsersToSignUp: false,
242 provisioningType: ProvisioningType.auto,
243 allowedGroups: ['D12'],
244 isProvisioningTokenSet: true,
247 renderAuthentication([Feature.GitlabProvisioning]);
249 expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
251 expect(ui.groups.get()).toHaveValue('D12');
252 expect(ui.saveProvisioning.get()).toBeDisabled();
253 await user.click(ui.deleteGroupButton.get());
254 expect(await ui.saveProvisioning.find()).toBeDisabled();
257 it('should change from just-in-time to Auto Provisioning if auto was never set before', async () => {
258 const user = userEvent.setup();
259 handler.setGitlabConfigurations([
260 mockGitlabConfiguration({
261 allowUsersToSignUp: false,
263 provisioningType: ProvisioningType.jit,
265 isProvisioningTokenSet: false,
268 renderAuthentication([Feature.GitlabProvisioning]);
270 expect(await ui.editConfigButton.find()).toBeInTheDocument();
271 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
273 await user.click(ui.autoProvisioningRadioButton.get());
274 expect(await ui.autoProvisioningRadioButton.find()).toBeEnabled();
275 expect(ui.saveProvisioning.get()).toBeDisabled();
277 await user.type(ui.autoProvisioningToken.get(), 'JRR Tolkien');
278 expect(await ui.saveProvisioning.find()).toBeDisabled();
280 await user.type(ui.groups.get(), 'Run DMC');
281 expect(await ui.saveProvisioning.find()).toBeEnabled();
282 await user.click(ui.deleteGroupButton.get());
283 expect(await ui.saveProvisioning.find()).toBeDisabled();
285 await user.type(ui.groups.get(), 'Public Enemy');
286 expect(await ui.saveProvisioning.find()).toBeEnabled();
289 it('should change from just-in-time to Auto Provisioning if auto was set before', async () => {
290 handler.setGitlabConfigurations([
291 mockGitlabConfiguration({
292 allowUsersToSignUp: false,
294 provisioningType: ProvisioningType.jit,
295 allowedGroups: ['D12'],
296 isProvisioningTokenSet: true,
299 const user = userEvent.setup();
300 renderAuthentication([Feature.GitlabProvisioning]);
302 expect(await ui.editConfigButton.find()).toBeInTheDocument();
303 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
305 user.click(ui.autoProvisioningRadioButton.get());
306 expect(await ui.autoProvisioningRadioButton.find()).toBeEnabled();
307 expect(await ui.saveProvisioning.find()).toBeEnabled();
309 expect(ui.groups.get()).toHaveValue('D12');
310 await user.click(ui.deleteGroupButton.get());
311 expect(await ui.saveProvisioning.find()).toBeDisabled();
312 await user.type(ui.groups.get(), 'Wu Tang Clan');
314 expect(ui.saveProvisioning.get()).toBeEnabled();
317 it('should change from auto provisioning to JIT with proper validation', async () => {
318 handler.setGitlabConfigurations([
319 mockGitlabConfiguration({
320 allowUsersToSignUp: false,
322 provisioningType: ProvisioningType.auto,
323 allowedGroups: ['D12'],
324 isProvisioningTokenSet: true,
327 const user = userEvent.setup();
328 renderAuthentication([Feature.GitlabProvisioning]);
330 expect(await ui.editConfigButton.find()).toBeInTheDocument();
332 expect(ui.jitProvisioningRadioButton.get()).not.toBeChecked();
333 expect(ui.autoProvisioningRadioButton.get()).toBeChecked();
335 expect(ui.autoProvisioningToken.query()).not.toBeInTheDocument();
336 expect(ui.autoProvisioningUpdateTokenButton.get()).toBeInTheDocument();
338 await user.click(ui.jitProvisioningRadioButton.get());
339 expect(await ui.jitProvisioningRadioButton.find()).toBeChecked();
341 expect(await ui.saveProvisioning.find()).toBeEnabled();
343 await user.click(ui.jitAllowUsersToSignUpToggle.get());
344 await user.click(ui.deleteGroupButton.get());
346 await user.click(ui.saveProvisioning.get());
348 ui.confirmJitProvisioningDialog
349 .byText('settings.authentication.gitlab.provisioning_change.insecure_config')
351 ).toBeInTheDocument();
352 await user.click(ui.confirmProvisioningChange.get());
353 expect(ui.confirmJitProvisioningDialog.query()).not.toBeInTheDocument();
355 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
356 expect(await ui.saveProvisioning.find()).toBeDisabled();
359 it('should show configuration warning with jit provisioning and no groups', async () => {
360 handler.setGitlabConfigurations([
361 mockGitlabConfiguration({
362 allowUsersToSignUp: false,
364 provisioningType: ProvisioningType.jit,
366 isProvisioningTokenSet: true,
369 const user = userEvent.setup();
370 renderAuthentication([Feature.GitlabProvisioning]);
372 expect(await ui.editConfigButton.find()).toBeInTheDocument();
374 await user.click(ui.jitAllowUsersToSignUpToggle.get());
375 await user.click(ui.saveProvisioning.get());
378 ui.confirmInsecureProvisioningDialog
379 .byText('settings.authentication.gitlab.provisioning_change.insecure_config')
381 ).toBeInTheDocument();
383 await user.click(ui.confirmProvisioningChange.get());
384 expect(ui.confirmJitProvisioningDialog.query()).not.toBeInTheDocument();
386 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
387 expect(await ui.saveProvisioning.find()).toBeDisabled();
390 it('should be able to allow user to sign up for JIT with proper validation', async () => {
391 handler.setGitlabConfigurations([
392 mockGitlabConfiguration({
393 allowUsersToSignUp: false,
395 provisioningType: ProvisioningType.jit,
398 const user = userEvent.setup();
399 renderAuthentication([Feature.GitlabProvisioning]);
401 expect(await ui.editConfigButton.find()).toBeInTheDocument();
403 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
404 expect(ui.autoProvisioningRadioButton.get()).not.toBeChecked();
406 expect(ui.jitAllowUsersToSignUpToggle.get()).not.toBeChecked();
408 expect(ui.saveProvisioning.get()).toBeDisabled();
409 await user.click(ui.jitAllowUsersToSignUpToggle.get());
410 expect(ui.saveProvisioning.get()).toBeEnabled();
411 await user.click(ui.jitAllowUsersToSignUpToggle.get());
412 expect(ui.saveProvisioning.get()).toBeDisabled();
413 await user.click(ui.jitAllowUsersToSignUpToggle.get());
415 await user.click(ui.saveProvisioning.get());
417 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
418 expect(ui.jitAllowUsersToSignUpToggle.get()).toBeChecked();
419 expect(await ui.saveProvisioning.find()).toBeDisabled();
422 it('should be able to edit token for Auto provisioning with proper validation', async () => {
423 handler.setGitlabConfigurations([
424 mockGitlabConfiguration({
425 allowUsersToSignUp: false,
427 provisioningType: ProvisioningType.auto,
428 allowedGroups: ['Cypress Hill', 'Public Enemy'],
429 isProvisioningTokenSet: true,
432 const user = userEvent.setup();
433 renderAuthentication([Feature.GitlabProvisioning]);
435 expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
436 expect(ui.autoProvisioningUpdateTokenButton.get()).toBeInTheDocument();
438 expect(ui.saveProvisioning.get()).toBeDisabled();
440 // Changing the Provisioning token should enable save
441 await user.click(ui.autoProvisioningUpdateTokenButton.get());
442 expect(ui.saveProvisioning.get()).toBeDisabled();
443 await user.click(ui.cancelProvisioningChanges.get());
444 expect(ui.saveProvisioning.get()).toBeDisabled();
447 it('should be able to reset Auto Provisioning changes', async () => {
448 handler.setGitlabConfigurations([
449 mockGitlabConfiguration({
450 allowUsersToSignUp: false,
452 provisioningType: ProvisioningType.auto,
453 allowedGroups: ['Cypress Hill', 'Public Enemy'],
454 isProvisioningTokenSet: true,
457 const user = userEvent.setup();
458 renderAuthentication([Feature.GitlabProvisioning]);
460 expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
462 await user.click(ui.autoProvisioningUpdateTokenButton.get());
463 await user.type(ui.autoProvisioningToken.get(), 'ToToken!');
464 expect(ui.saveProvisioning.get()).toBeEnabled();
465 await user.click(ui.cancelProvisioningChanges.get());
466 expect(ui.saveProvisioning.get()).toBeDisabled();
469 describe('Gitlab Provisioning', () => {
473 now: new Date('2022-02-04T12:00:59Z'),
475 handler.setGitlabConfigurations([
476 mockGitlabConfiguration({
478 provisioningType: ProvisioningType.auto,
479 allowedGroups: ['Test'],
485 jest.runOnlyPendingTimers();
486 jest.useRealTimers();
489 it('should display a success status when the synchronisation is a success', async () => {
490 computeEngineHandler.addTask({
491 status: TaskStatuses.Success,
492 executedAt: '2022-02-03T11:45:35+0200',
493 infoMessages: ['Test summary'],
494 type: TaskTypes.GitlabProvisioning,
497 renderAuthentication([Feature.GitlabProvisioning]);
498 expect(await ui.gitlabProvisioningSuccess.find()).toBeInTheDocument();
499 expect(ui.syncSummary.get()).toBeInTheDocument();
502 it('should display a success status even when another task is pending', async () => {
503 computeEngineHandler.addTask({
504 status: TaskStatuses.Pending,
505 executedAt: '2022-02-03T11:55:35+0200',
506 type: TaskTypes.GitlabProvisioning,
508 computeEngineHandler.addTask({
509 status: TaskStatuses.Success,
510 executedAt: '2022-02-03T11:45:35+0200',
511 type: TaskTypes.GitlabProvisioning,
513 renderAuthentication([Feature.GitlabProvisioning]);
514 expect(await ui.gitlabProvisioningSuccess.find()).toBeInTheDocument();
515 expect(ui.gitlabProvisioningPending.get()).toBeInTheDocument();
518 it('should display an error alert when the synchronisation failed', async () => {
519 computeEngineHandler.addTask({
520 status: TaskStatuses.Failed,
521 executedAt: '2022-02-03T11:45:35+0200',
522 errorMessage: "T'es mauvais Jacques",
523 type: TaskTypes.GitlabProvisioning,
525 renderAuthentication([Feature.GitlabProvisioning]);
526 expect(await ui.gitlabProvisioningAlert.find()).toBeInTheDocument();
527 expect(glContainer.get()).toHaveTextContent("T'es mauvais Jacques");
528 expect(ui.gitlabProvisioningSuccess.query()).not.toBeInTheDocument();
531 it('should display an error alert even when another task is in progress', async () => {
532 computeEngineHandler.addTask({
533 status: TaskStatuses.InProgress,
534 executedAt: '2022-02-03T11:55:35+0200',
535 type: TaskTypes.GitlabProvisioning,
537 computeEngineHandler.addTask({
538 status: TaskStatuses.Failed,
539 executedAt: '2022-02-03T11:45:35+0200',
540 errorMessage: "T'es mauvais Jacques",
541 type: TaskTypes.GitlabProvisioning,
543 renderAuthentication([Feature.GitlabProvisioning]);
544 expect(await ui.gitlabProvisioningAlert.find()).toBeInTheDocument();
545 expect(glContainer.get()).toHaveTextContent("T'es mauvais Jacques");
546 expect(ui.gitlabProvisioningSuccess.query()).not.toBeInTheDocument();
547 expect(ui.gitlabProvisioningInProgress.get()).toBeInTheDocument();
550 it('should show warning', async () => {
551 computeEngineHandler.addTask({
552 status: TaskStatuses.Success,
553 warnings: ['Warning'],
554 infoMessages: ['Test summary'],
555 type: TaskTypes.GitlabProvisioning,
557 renderAuthentication([Feature.GitlabProvisioning]);
559 expect(await ui.syncWarning.find()).toBeInTheDocument();
560 expect(ui.syncSummary.get()).toBeInTheDocument();
563 it('should show configuration validity', async () => {
564 const user = userEvent.setup();
565 renderAuthentication([Feature.GitlabProvisioning]);
567 expect(await ui.gitlabConfigurationStatus.find()).toHaveTextContent(
568 'settings.authentication.gitlab.configuration.valid.AUTO_PROVISIONING',
570 await user.click(ui.jitProvisioningRadioButton.get());
571 await user.click(ui.saveProvisioning.get());
572 await user.click(ui.confirmProvisioningChange.get());
573 expect(ui.gitlabConfigurationStatus.get()).toHaveTextContent(
574 'settings.authentication.gitlab.configuration.valid.JIT',
576 handler.setGitlabConfigurations([
577 mockGitlabConfiguration({ ...handler.gitlabConfigurations[0], errorMessage: 'ERROR' }),
579 await user.click(ui.testConfiguration.get());
580 expect(glContainer.get()).toHaveTextContent('ERROR');
581 await user.click(ui.disableConfigButton.get());
582 expect(ui.gitlabConfigurationStatus.query()).not.toBeInTheDocument();
586 function renderAuthentication(features: Feature[] = []) {
588 <AvailableFeaturesContext.Provider value={features}>
589 <Authentication definitions={definitions} />
590 </AvailableFeaturesContext.Provider>,
591 `?tab=${AlmKeys.GitLab}`,