]> source.dussan.org Git - sonarqube.git/blob
21cd232a2626f18d4b279449c5fde704e07905c7
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
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';
36
37 let handler: GitlabProvisioningServiceMock;
38 let system: SystemServiceMock;
39 let settingsHandler: SettingsServiceMock;
40 let computeEngineHandler: ComputeEngineServiceMock;
41
42 beforeEach(() => {
43   handler = new GitlabProvisioningServiceMock();
44   system = new SystemServiceMock();
45   settingsHandler = new SettingsServiceMock();
46   computeEngineHandler = new ComputeEngineServiceMock();
47 });
48
49 afterEach(() => {
50   handler.reset();
51   settingsHandler.reset();
52   system.reset();
53   computeEngineHandler.reset();
54 });
55
56 const glContainer = byRole('tabpanel', { name: 'gitlab GitLab' });
57
58 const ui = {
59   noGitlabConfiguration: glContainer.byText('settings.authentication.gitlab.form.not_configured'),
60   createConfigButton: glContainer.byRole('button', {
61     name: 'settings.authentication.form.create',
62   }),
63   editConfigButton: glContainer.byRole('button', {
64     name: 'settings.authentication.form.edit',
65   }),
66   deleteConfigButton: glContainer.byRole('button', {
67     name: 'settings.authentication.form.delete',
68   }),
69   enableConfigButton: glContainer.byRole('button', {
70     name: 'settings.authentication.form.enable',
71   }),
72   disableConfigButton: glContainer.byRole('button', {
73     name: 'settings.authentication.form.disable',
74   }),
75   createDialog: byRole('dialog', {
76     name: 'settings.authentication.gitlab.form.create',
77   }),
78   editDialog: byRole('dialog', {
79     name: 'settings.authentication.gitlab.form.edit',
80   }),
81   applicationId: byRole('textbox', {
82     name: 'property.applicationId.name',
83   }),
84   url: byRole('textbox', { name: 'property.url.name' }),
85   secret: byRole('textbox', {
86     name: 'property.secret.name',
87   }),
88   synchronizeGroups: byRole('switch', {
89     name: 'property.synchronizeGroups.name',
90   }),
91   saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
92   jitProvisioningRadioButton: glContainer.byRole('radio', {
93     name: /settings.authentication.gitlab.provisioning_at_login/,
94   }),
95   autoProvisioningRadioButton: glContainer.byRole('radio', {
96     name: /settings.authentication.gitlab.form.provisioning_with_gitlab/,
97   }),
98   jitAllowUsersToSignUpToggle: byRole('switch', { name: 'property.allowUsersToSignUp.name' }),
99   autoProvisioningToken: byRole('textbox', {
100     name: 'property.provisioningToken.name',
101   }),
102   autoProvisioningUpdateTokenButton: byRole('button', {
103     name: 'settings.almintegration.form.secret.update_field',
104   }),
105   groups: byRole('textbox', {
106     name: 'property.allowedGroups.name',
107   }),
108   deleteGroupButton: byRole('button', { name: /delete_value/ }),
109   removeProvisioniongGroup: byRole('button', {
110     name: /settings.definition.delete_value.property.allowedGroups.name./,
111   }),
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',
116   }),
117   confirmJitProvisioningDialog: byRole('dialog', {
118     name: 'settings.authentication.gitlab.confirm.JIT',
119   }),
120   confirmProvisioningChange: byRole('button', {
121     name: 'settings.authentication.gitlab.provisioning_change.confirm_changes',
122   }),
123   syncSummary: glContainer.byText(/Test summary/),
124   syncWarning: glContainer.byText(/Warning/),
125   gitlabProvisioningPending: glContainer
126     .byRole('list')
127     .byRole('status')
128     .byText(/synchronization_pending/),
129   gitlabProvisioningInProgress: glContainer
130     .byRole('list')
131     .byRole('status')
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/,
137   }),
138   testConfiguration: glContainer.byRole('button', {
139     name: 'settings.authentication.configuration.test',
140   }),
141 };
142
143 it('should create a Gitlab configuration and disable it with proper validation', async () => {
144   handler.setGitlabConfigurations([]);
145   renderAuthentication();
146   const user = userEvent.setup();
147
148   expect(await ui.noGitlabConfiguration.find()).toBeInTheDocument();
149   expect(ui.createConfigButton.get()).toBeInTheDocument();
150
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());
160
161   expect(await ui.editConfigButton.find()).toBeInTheDocument();
162   expect(ui.noGitlabConfiguration.query()).not.toBeInTheDocument();
163   expect(glContainer.get()).toHaveTextContent('https://company.ui.com');
164
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();
169 });
170
171 it('should edit a configuration with proper validation and delete it', async () => {
172   const user = userEvent.setup();
173   renderAuthentication();
174
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();
180
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();
187
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();
193
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());
200
201   expect(glContainer.get()).not.toHaveTextContent('URL');
202   expect(glContainer.get()).toHaveTextContent('www.internet.com');
203
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();
211 });
212
213 it('should be able to save just-in-time with no organizations', async () => {
214   const user = userEvent.setup();
215   renderAuthentication([Feature.GitlabProvisioning]);
216
217   expect(await ui.jitProvisioningRadioButton.find()).toBeChecked();
218
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();
223 });
224
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,
230       enabled: true,
231       provisioningType: ProvisioningType.auto,
232       allowedGroups: ['D12'],
233       isProvisioningTokenSet: true,
234     }),
235   ]);
236   renderAuthentication([Feature.GitlabProvisioning]);
237
238   expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
239
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();
244 });
245
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,
251       enabled: true,
252       provisioningType: ProvisioningType.jit,
253       allowedGroups: [],
254       isProvisioningTokenSet: false,
255     }),
256   ]);
257   renderAuthentication([Feature.GitlabProvisioning]);
258
259   expect(await ui.editConfigButton.find()).toBeInTheDocument();
260   expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
261
262   await user.click(ui.autoProvisioningRadioButton.get());
263   expect(await ui.autoProvisioningRadioButton.find()).toBeEnabled();
264   expect(ui.saveProvisioning.get()).toBeDisabled();
265
266   await user.type(ui.autoProvisioningToken.get(), 'JRR Tolkien');
267   expect(await ui.saveProvisioning.find()).toBeDisabled();
268
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();
273
274   await user.type(ui.groups.get(), 'Public Enemy');
275   expect(await ui.saveProvisioning.find()).toBeEnabled();
276 });
277
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,
282       enabled: true,
283       provisioningType: ProvisioningType.jit,
284       allowedGroups: ['D12'],
285       isProvisioningTokenSet: true,
286     }),
287   ]);
288   const user = userEvent.setup();
289   renderAuthentication([Feature.GitlabProvisioning]);
290
291   expect(await ui.editConfigButton.find()).toBeInTheDocument();
292   expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
293
294   user.click(ui.autoProvisioningRadioButton.get());
295   expect(await ui.autoProvisioningRadioButton.find()).toBeEnabled();
296   expect(await ui.saveProvisioning.find()).toBeEnabled();
297
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');
302
303   expect(ui.saveProvisioning.get()).toBeEnabled();
304 });
305
306 it('should change from auto provisioning to JIT with proper validation', async () => {
307   handler.setGitlabConfigurations([
308     mockGitlabConfiguration({
309       allowUsersToSignUp: false,
310       enabled: true,
311       provisioningType: ProvisioningType.auto,
312       allowedGroups: ['D12'],
313       isProvisioningTokenSet: true,
314     }),
315   ]);
316   const user = userEvent.setup();
317   renderAuthentication([Feature.GitlabProvisioning]);
318
319   expect(await ui.editConfigButton.find()).toBeInTheDocument();
320
321   expect(ui.jitProvisioningRadioButton.get()).not.toBeChecked();
322   expect(ui.autoProvisioningRadioButton.get()).toBeChecked();
323
324   expect(ui.autoProvisioningToken.query()).not.toBeInTheDocument();
325   expect(ui.autoProvisioningUpdateTokenButton.get()).toBeInTheDocument();
326
327   await user.click(ui.jitProvisioningRadioButton.get());
328   expect(await ui.jitProvisioningRadioButton.find()).toBeChecked();
329
330   expect(await ui.saveProvisioning.find()).toBeEnabled();
331
332   expect(ui.jitAllowUsersToSignUpToggle.get()).toBeInTheDocument();
333
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();
338
339   expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
340   expect(await ui.saveProvisioning.find()).toBeDisabled();
341 });
342
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,
347       enabled: true,
348       provisioningType: ProvisioningType.jit,
349     }),
350   ]);
351   const user = userEvent.setup();
352   renderAuthentication([Feature.GitlabProvisioning]);
353
354   expect(await ui.editConfigButton.find()).toBeInTheDocument();
355
356   expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
357   expect(ui.autoProvisioningRadioButton.get()).not.toBeChecked();
358
359   expect(ui.jitAllowUsersToSignUpToggle.get()).not.toBeChecked();
360
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());
367
368   await user.click(ui.saveProvisioning.get());
369
370   expect(ui.jitProvisioningRadioButton.get()).toBeChecked();
371   expect(ui.jitAllowUsersToSignUpToggle.get()).toBeChecked();
372   expect(await ui.saveProvisioning.find()).toBeDisabled();
373 });
374
375 it('should be able to edit token for Auto provisioning with proper validation', async () => {
376   handler.setGitlabConfigurations([
377     mockGitlabConfiguration({
378       allowUsersToSignUp: false,
379       enabled: true,
380       provisioningType: ProvisioningType.auto,
381       allowedGroups: ['Cypress Hill', 'Public Enemy'],
382       isProvisioningTokenSet: true,
383     }),
384   ]);
385   const user = userEvent.setup();
386   renderAuthentication([Feature.GitlabProvisioning]);
387
388   expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
389   expect(ui.autoProvisioningUpdateTokenButton.get()).toBeInTheDocument();
390
391   expect(ui.saveProvisioning.get()).toBeDisabled();
392
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();
398 });
399
400 it('should be able to reset Auto Provisioning changes', async () => {
401   handler.setGitlabConfigurations([
402     mockGitlabConfiguration({
403       allowUsersToSignUp: false,
404       enabled: true,
405       provisioningType: ProvisioningType.auto,
406       allowedGroups: ['Cypress Hill', 'Public Enemy'],
407       isProvisioningTokenSet: true,
408     }),
409   ]);
410   const user = userEvent.setup();
411   renderAuthentication([Feature.GitlabProvisioning]);
412
413   expect(await ui.autoProvisioningRadioButton.find()).toBeChecked();
414
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();
420 });
421
422 describe('Gitlab Provisioning', () => {
423   beforeEach(() => {
424     jest.useFakeTimers({
425       advanceTimers: true,
426       now: new Date('2022-02-04T12:00:59Z'),
427     });
428     handler.setGitlabConfigurations([
429       mockGitlabConfiguration({
430         enabled: true,
431         provisioningType: ProvisioningType.auto,
432         allowedGroups: ['Test'],
433       }),
434     ]);
435   });
436
437   afterEach(() => {
438     jest.runOnlyPendingTimers();
439     jest.useRealTimers();
440   });
441
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,
448     });
449
450     renderAuthentication([Feature.GitlabProvisioning]);
451     expect(await ui.gitlabProvisioningSuccess.find()).toBeInTheDocument();
452     expect(ui.syncSummary.get()).toBeInTheDocument();
453   });
454
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,
460     });
461     computeEngineHandler.addTask({
462       status: TaskStatuses.Success,
463       executedAt: '2022-02-03T11:45:35+0200',
464       type: TaskTypes.GitlabProvisioning,
465     });
466     renderAuthentication([Feature.GitlabProvisioning]);
467     expect(await ui.gitlabProvisioningSuccess.find()).toBeInTheDocument();
468     expect(ui.gitlabProvisioningPending.get()).toBeInTheDocument();
469   });
470
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,
477     });
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();
482   });
483
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,
489     });
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,
495     });
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();
501   });
502
503   it('should show warning', async () => {
504     computeEngineHandler.addTask({
505       status: TaskStatuses.Success,
506       warnings: ['Warning'],
507       infoMessages: ['Test summary'],
508       type: TaskTypes.GitlabProvisioning,
509     });
510     renderAuthentication([Feature.GitlabProvisioning]);
511
512     expect(await ui.syncWarning.find()).toBeInTheDocument();
513     expect(ui.syncSummary.get()).toBeInTheDocument();
514   });
515
516   it('should show configuration validity', async () => {
517     const user = userEvent.setup();
518     renderAuthentication([Feature.GitlabProvisioning]);
519
520     expect(await ui.gitlabConfigurationStatus.find()).toHaveTextContent(
521       'settings.authentication.gitlab.configuration.valid.AUTO_PROVISIONING',
522     );
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',
528     );
529     handler.setGitlabConfigurations([
530       mockGitlabConfiguration({ ...handler.gitlabConfigurations[0], errorMessage: 'ERROR' }),
531     ]);
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();
536   });
537 });
538
539 function renderAuthentication(features: Feature[] = []) {
540   renderComponent(
541     <AvailableFeaturesContext.Provider value={features}>
542       <Authentication definitions={definitions} />
543     </AvailableFeaturesContext.Provider>,
544     `?tab=${AlmKeys.GitLab}`,
545   );
546 }