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 ComputeEngineServiceMock from '../../../../../api/mocks/ComputeEngineServiceMock';
23 import GitlabProvisioningServiceMock from '../../../../../api/mocks/GitlabProvisioningServiceMock';
24 import SettingsServiceMock from '../../../../../api/mocks/SettingsServiceMock';
25 import SystemServiceMock from '../../../../../api/mocks/SystemServiceMock';
26 import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
27 import { mockGitlabConfiguration } from '../../../../../helpers/mocks/alm-integrations';
28 import { definitions } from '../../../../../helpers/mocks/definitions-list';
29 import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
30 import { byRole } from '../../../../../helpers/testSelector';
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 confirmProvisioningChange: byRole('button', {
121 name: 'settings.authentication.gitlab.provisioning_change.confirm_changes',
123 syncSummary: glContainer.byText(/Test summary/),
124 syncWarning: glContainer.byText(/Warning/),
125 gitlabProvisioningPending: glContainer
128 .byText(/synchronization_pending/),
129 gitlabProvisioningInProgress: glContainer
132 .byText(/synchronization_in_progress/),
133 gitlabProvisioningSuccess: glContainer.byText(/synchronization_successful/),
134 gitlabProvisioningAlert: glContainer.byText(/synchronization_failed/),
135 gitlabConfigurationStatus: glContainer.byRole('status', {
136 name: /settings.authentication.gitlab.configuration/,
138 testConfiguration: glContainer.byRole('button', {
139 name: 'settings.authentication.configuration.test',
143 it('should create a Gitlab configuration and disable it with proper validation', async () => {
144 handler.setGitlabConfigurations([]);
145 renderAuthentication();
146 const user = userEvent.setup();
148 expect(await ui.noGitlabConfiguration.find()).toBeInTheDocument();
149 expect(ui.createConfigButton.get()).toBeInTheDocument();
151 await user.click(ui.createConfigButton.get());
152 expect(await ui.createDialog.find()).toBeInTheDocument();
153 await user.type(ui.applicationId.get(), '123');
154 expect(ui.saveConfigButton.get()).toBeDisabled();
155 await user.type(ui.url.get(), 'https://company.ui.com');
156 await user.type(ui.secret.get(), '123');
157 expect(ui.saveConfigButton.get()).toBeEnabled();
158 await user.click(ui.synchronizeGroups.get());
159 await user.click(ui.saveConfigButton.get());
161 expect(await ui.editConfigButton.find()).toBeInTheDocument();
162 expect(ui.noGitlabConfiguration.query()).not.toBeInTheDocument();
163 expect(glContainer.get()).toHaveTextContent('https://company.ui.com');
165 expect(ui.disableConfigButton.get()).toBeInTheDocument();
166 await user.click(ui.disableConfigButton.get());
167 expect(ui.enableConfigButton.get()).toBeInTheDocument();
168 expect(ui.disableConfigButton.query()).not.toBeInTheDocument();
171 it('should edit a configuration with proper validation and delete it', async () => {
172 const user = userEvent.setup();
173 renderAuthentication();
175 expect(await ui.editConfigButton.find()).toBeInTheDocument();
176 expect(glContainer.get()).toHaveTextContent('URL');
177 expect(ui.disableConfigButton.get()).toBeInTheDocument();
178 expect(ui.deleteConfigButton.get()).toBeInTheDocument();
179 expect(ui.deleteConfigButton.get()).toBeDisabled();
181 await user.click(ui.editConfigButton.get());
182 expect(await ui.editDialog.find()).toBeInTheDocument();
183 expect(ui.url.get()).toHaveValue('URL');
184 expect(ui.applicationId.get()).toBeInTheDocument();
185 expect(ui.secret.query()).not.toBeInTheDocument();
186 expect(ui.synchronizeGroups.get()).toBeChecked();
188 expect(ui.applicationId.get()).toBeInTheDocument();
189 await user.clear(ui.applicationId.get());
190 expect(ui.saveConfigButton.get()).toBeDisabled();
191 await user.type(ui.applicationId.get(), '456');
192 expect(ui.saveConfigButton.get()).toBeEnabled();
194 expect(ui.url.get()).toBeInTheDocument();
195 await user.clear(ui.url.get());
196 expect(ui.saveConfigButton.get()).toBeDisabled();
197 await user.type(ui.url.get(), 'www.internet.com');
198 expect(ui.saveConfigButton.get()).toBeEnabled();
199 await user.click(ui.saveConfigButton.get());
201 expect(glContainer.get()).not.toHaveTextContent('URL');
202 expect(glContainer.get()).toHaveTextContent('www.internet.com');
204 expect(ui.disableConfigButton.get()).toBeInTheDocument();
205 await user.click(ui.disableConfigButton.get());
206 expect(await ui.enableConfigButton.find()).toBeInTheDocument();
207 expect(ui.deleteConfigButton.get()).toBeEnabled();
208 await user.click(ui.deleteConfigButton.get());
209 expect(await ui.noGitlabConfiguration.find()).toBeInTheDocument();
210 expect(ui.editConfigButton.query()).not.toBeInTheDocument();
213 it('should be able to save just-in-time with no organizations', async () => {
214 const user = userEvent.setup();
215 renderAuthentication([Feature.GitlabProvisioning]);
217 expect(await ui.jitProvisioningRadioButton.find()).toBeChecked();
219 expect(ui.groups.get()).toHaveValue('Cypress Hill');
220 expect(await ui.saveProvisioning.find()).toBeDisabled();
221 await user.click(ui.deleteGroupButton.get());
222 expect(await ui.saveProvisioning.find()).toBeEnabled();
225 it('should not be able to save Auto provisioning with no organizations', async () => {
226 const user = userEvent.setup();
227 handler.setGitlabConfigurations([
228 mockGitlabConfiguration({
229 allowUsersToSignUp: false,
231 provisioningType: ProvisioningType.auto,
232 allowedGroups: ['D12'],
233 isProvisioningTokenSet: true,
236 renderAuthentication([Feature.GitlabProvisioning]);
238 expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
240 expect(ui.groups.get()).toHaveValue('D12');
241 expect(ui.saveProvisioning.get()).toBeDisabled();
242 await user.click(ui.deleteGroupButton.get());
243 expect(await ui.saveProvisioning.find()).toBeDisabled();
246 it('should change from just-in-time to Auto Provisioning if auto was never set before', async () => {
247 const user = userEvent.setup();
248 handler.setGitlabConfigurations([
249 mockGitlabConfiguration({
250 allowUsersToSignUp: false,
252 provisioningType: ProvisioningType.jit,
254 isProvisioningTokenSet: false,
257 renderAuthentication([Feature.GitlabProvisioning]);
259 expect(await ui.editConfigButton.find()).toBeInTheDocument();
260 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
262 await user.click(ui.autoProvisioningRadioButton.get());
263 expect(await ui.autoProvisioningRadioButton.find()).toBeEnabled();
264 expect(ui.saveProvisioning.get()).toBeDisabled();
266 await user.type(ui.autoProvisioningToken.get(), 'JRR Tolkien');
267 expect(await ui.saveProvisioning.find()).toBeDisabled();
269 await user.type(ui.groups.get(), 'Run DMC');
270 expect(await ui.saveProvisioning.find()).toBeEnabled();
271 await user.click(ui.deleteGroupButton.get());
272 expect(await ui.saveProvisioning.find()).toBeDisabled();
274 await user.type(ui.groups.get(), 'Public Enemy');
275 expect(await ui.saveProvisioning.find()).toBeEnabled();
278 it('should change from just-in-time to Auto Provisioning if auto was set before', async () => {
279 handler.setGitlabConfigurations([
280 mockGitlabConfiguration({
281 allowUsersToSignUp: false,
283 provisioningType: ProvisioningType.jit,
284 allowedGroups: ['D12'],
285 isProvisioningTokenSet: true,
288 const user = userEvent.setup();
289 renderAuthentication([Feature.GitlabProvisioning]);
291 expect(await ui.editConfigButton.find()).toBeInTheDocument();
292 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
294 user.click(ui.autoProvisioningRadioButton.get());
295 expect(await ui.autoProvisioningRadioButton.find()).toBeEnabled();
296 expect(await ui.saveProvisioning.find()).toBeEnabled();
298 expect(ui.groups.get()).toHaveValue('D12');
299 await user.click(ui.deleteGroupButton.get());
300 expect(await ui.saveProvisioning.find()).toBeDisabled();
301 await user.type(ui.groups.get(), 'Wu Tang Clan');
303 expect(ui.saveProvisioning.get()).toBeEnabled();
306 it('should change from auto provisioning to JIT with proper validation', async () => {
307 handler.setGitlabConfigurations([
308 mockGitlabConfiguration({
309 allowUsersToSignUp: false,
311 provisioningType: ProvisioningType.auto,
312 allowedGroups: ['D12'],
313 isProvisioningTokenSet: true,
316 const user = userEvent.setup();
317 renderAuthentication([Feature.GitlabProvisioning]);
319 expect(await ui.editConfigButton.find()).toBeInTheDocument();
321 expect(ui.jitProvisioningRadioButton.get()).not.toBeChecked();
322 expect(ui.autoProvisioningRadioButton.get()).toBeChecked();
324 expect(ui.autoProvisioningToken.query()).not.toBeInTheDocument();
325 expect(ui.autoProvisioningUpdateTokenButton.get()).toBeInTheDocument();
327 await user.click(ui.jitProvisioningRadioButton.get());
328 expect(await ui.jitProvisioningRadioButton.find()).toBeChecked();
330 expect(await ui.saveProvisioning.find()).toBeEnabled();
332 expect(ui.jitAllowUsersToSignUpToggle.get()).toBeInTheDocument();
334 await user.click(ui.saveProvisioning.get());
335 expect(ui.confirmJitProvisioningDialog.get()).toBeInTheDocument();
336 await user.click(ui.confirmProvisioningChange.get());
337 expect(ui.confirmJitProvisioningDialog.query()).not.toBeInTheDocument();
339 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
340 expect(await ui.saveProvisioning.find()).toBeDisabled();
343 it('should be able to allow user to sign up for JIT with proper validation', async () => {
344 handler.setGitlabConfigurations([
345 mockGitlabConfiguration({
346 allowUsersToSignUp: false,
348 provisioningType: ProvisioningType.jit,
351 const user = userEvent.setup();
352 renderAuthentication([Feature.GitlabProvisioning]);
354 expect(await ui.editConfigButton.find()).toBeInTheDocument();
356 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
357 expect(ui.autoProvisioningRadioButton.get()).not.toBeChecked();
359 expect(ui.jitAllowUsersToSignUpToggle.get()).not.toBeChecked();
361 expect(ui.saveProvisioning.get()).toBeDisabled();
362 await user.click(ui.jitAllowUsersToSignUpToggle.get());
363 expect(ui.saveProvisioning.get()).toBeEnabled();
364 await user.click(ui.jitAllowUsersToSignUpToggle.get());
365 expect(ui.saveProvisioning.get()).toBeDisabled();
366 await user.click(ui.jitAllowUsersToSignUpToggle.get());
368 await user.click(ui.saveProvisioning.get());
370 expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
371 expect(ui.jitAllowUsersToSignUpToggle.get()).toBeChecked();
372 expect(await ui.saveProvisioning.find()).toBeDisabled();
375 it('should be able to edit token for Auto provisioning with proper validation', async () => {
376 handler.setGitlabConfigurations([
377 mockGitlabConfiguration({
378 allowUsersToSignUp: false,
380 provisioningType: ProvisioningType.auto,
381 allowedGroups: ['Cypress Hill', 'Public Enemy'],
382 isProvisioningTokenSet: true,
385 const user = userEvent.setup();
386 renderAuthentication([Feature.GitlabProvisioning]);
388 expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
389 expect(ui.autoProvisioningUpdateTokenButton.get()).toBeInTheDocument();
391 expect(ui.saveProvisioning.get()).toBeDisabled();
393 // Changing the Provisioning token should enable save
394 await user.click(ui.autoProvisioningUpdateTokenButton.get());
395 expect(ui.saveProvisioning.get()).toBeDisabled();
396 await user.click(ui.cancelProvisioningChanges.get());
397 expect(ui.saveProvisioning.get()).toBeDisabled();
400 it('should be able to reset Auto Provisioning changes', async () => {
401 handler.setGitlabConfigurations([
402 mockGitlabConfiguration({
403 allowUsersToSignUp: false,
405 provisioningType: ProvisioningType.auto,
406 allowedGroups: ['Cypress Hill', 'Public Enemy'],
407 isProvisioningTokenSet: true,
410 const user = userEvent.setup();
411 renderAuthentication([Feature.GitlabProvisioning]);
413 expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
415 await user.click(ui.autoProvisioningUpdateTokenButton.get());
416 await user.type(ui.autoProvisioningToken.get(), 'ToToken!');
417 expect(ui.saveProvisioning.get()).toBeEnabled();
418 await user.click(ui.cancelProvisioningChanges.get());
419 expect(ui.saveProvisioning.get()).toBeDisabled();
422 describe('Gitlab Provisioning', () => {
426 now: new Date('2022-02-04T12:00:59Z'),
428 handler.setGitlabConfigurations([
429 mockGitlabConfiguration({
431 provisioningType: ProvisioningType.auto,
432 allowedGroups: ['Test'],
438 jest.runOnlyPendingTimers();
439 jest.useRealTimers();
442 it('should display a success status when the synchronisation is a success', async () => {
443 computeEngineHandler.addTask({
444 status: TaskStatuses.Success,
445 executedAt: '2022-02-03T11:45:35+0200',
446 infoMessages: ['Test summary'],
447 type: TaskTypes.GitlabProvisioning,
450 renderAuthentication([Feature.GitlabProvisioning]);
451 expect(await ui.gitlabProvisioningSuccess.find()).toBeInTheDocument();
452 expect(ui.syncSummary.get()).toBeInTheDocument();
455 it('should display a success status even when another task is pending', async () => {
456 computeEngineHandler.addTask({
457 status: TaskStatuses.Pending,
458 executedAt: '2022-02-03T11:55:35+0200',
459 type: TaskTypes.GitlabProvisioning,
461 computeEngineHandler.addTask({
462 status: TaskStatuses.Success,
463 executedAt: '2022-02-03T11:45:35+0200',
464 type: TaskTypes.GitlabProvisioning,
466 renderAuthentication([Feature.GitlabProvisioning]);
467 expect(await ui.gitlabProvisioningSuccess.find()).toBeInTheDocument();
468 expect(ui.gitlabProvisioningPending.get()).toBeInTheDocument();
471 it('should display an error alert when the synchronisation failed', async () => {
472 computeEngineHandler.addTask({
473 status: TaskStatuses.Failed,
474 executedAt: '2022-02-03T11:45:35+0200',
475 errorMessage: "T'es mauvais Jacques",
476 type: TaskTypes.GitlabProvisioning,
478 renderAuthentication([Feature.GitlabProvisioning]);
479 expect(await ui.gitlabProvisioningAlert.find()).toBeInTheDocument();
480 expect(glContainer.get()).toHaveTextContent("T'es mauvais Jacques");
481 expect(ui.gitlabProvisioningSuccess.query()).not.toBeInTheDocument();
484 it('should display an error alert even when another task is in progress', async () => {
485 computeEngineHandler.addTask({
486 status: TaskStatuses.InProgress,
487 executedAt: '2022-02-03T11:55:35+0200',
488 type: TaskTypes.GitlabProvisioning,
490 computeEngineHandler.addTask({
491 status: TaskStatuses.Failed,
492 executedAt: '2022-02-03T11:45:35+0200',
493 errorMessage: "T'es mauvais Jacques",
494 type: TaskTypes.GitlabProvisioning,
496 renderAuthentication([Feature.GitlabProvisioning]);
497 expect(await ui.gitlabProvisioningAlert.find()).toBeInTheDocument();
498 expect(glContainer.get()).toHaveTextContent("T'es mauvais Jacques");
499 expect(ui.gitlabProvisioningSuccess.query()).not.toBeInTheDocument();
500 expect(ui.gitlabProvisioningInProgress.get()).toBeInTheDocument();
503 it('should show warning', async () => {
504 computeEngineHandler.addTask({
505 status: TaskStatuses.Success,
506 warnings: ['Warning'],
507 infoMessages: ['Test summary'],
508 type: TaskTypes.GitlabProvisioning,
510 renderAuthentication([Feature.GitlabProvisioning]);
512 expect(await ui.syncWarning.find()).toBeInTheDocument();
513 expect(ui.syncSummary.get()).toBeInTheDocument();
516 it('should show configuration validity', async () => {
517 const user = userEvent.setup();
518 renderAuthentication([Feature.GitlabProvisioning]);
520 expect(await ui.gitlabConfigurationStatus.find()).toHaveTextContent(
521 'settings.authentication.gitlab.configuration.valid.AUTO_PROVISIONING',
523 await user.click(ui.jitProvisioningRadioButton.get());
524 await user.click(ui.saveProvisioning.get());
525 await user.click(ui.confirmProvisioningChange.get());
526 expect(ui.gitlabConfigurationStatus.get()).toHaveTextContent(
527 'settings.authentication.gitlab.configuration.valid.JIT',
529 handler.setGitlabConfigurations([
530 mockGitlabConfiguration({ ...handler.gitlabConfigurations[0], errorMessage: 'ERROR' }),
532 await user.click(ui.testConfiguration.get());
533 expect(glContainer.get()).toHaveTextContent('ERROR');
534 await user.click(ui.disableConfigButton.get());
535 expect(ui.gitlabConfigurationStatus.query()).not.toBeInTheDocument();
539 function renderAuthentication(features: Feature[] = []) {
541 <AvailableFeaturesContext.Provider value={features}>
542 <Authentication definitions={definitions} />
543 </AvailableFeaturesContext.Provider>,
544 `?tab=${AlmKeys.GitLab}`,