]> source.dussan.org Git - sonarqube.git/blob
e3c2cbfceebbff4e10c2b31b4f28033ae3c7457d
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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
21 import { act, screen, waitFor } from '@testing-library/react';
22 import userEvent from '@testing-library/user-event';
23 import AlmSettingsServiceMock from '../../../../../api/mocks/AlmSettingsServiceMock';
24 import AuthenticationServiceMock from '../../../../../api/mocks/AuthenticationServiceMock';
25 import PermissionsServiceMock from '../../../../../api/mocks/PermissionsServiceMock';
26 import { mockComponent } from '../../../../../helpers/mocks/component';
27 import { mockPermissionGroup, mockPermissionUser } from '../../../../../helpers/mocks/permissions';
28 import {
29   PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE,
30   PERMISSIONS_ORDER_FOR_VIEW,
31 } from '../../../../../helpers/permissions';
32 import {
33   RenderContext,
34   renderAppWithComponentContext,
35 } from '../../../../../helpers/testReactTestingUtils';
36 import { AlmKeys } from '../../../../../types/alm-settings';
37 import {
38   ComponentContextShape,
39   ComponentQualifier,
40   Visibility,
41 } from '../../../../../types/component';
42 import { Feature } from '../../../../../types/features';
43 import { Permissions } from '../../../../../types/permissions';
44 import { Component, PermissionGroup, PermissionUser } from '../../../../../types/types';
45 import { projectPermissionsRoutes } from '../../../routes';
46 import { getPageObject } from '../../../test-utils';
47
48 let serviceMock: PermissionsServiceMock;
49 let authHandler: AuthenticationServiceMock;
50 let almHandler: AlmSettingsServiceMock;
51 beforeAll(() => {
52   serviceMock = new PermissionsServiceMock();
53   authHandler = new AuthenticationServiceMock();
54   almHandler = new AlmSettingsServiceMock();
55 });
56
57 afterEach(() => {
58   serviceMock.reset();
59   authHandler.reset();
60   almHandler.reset();
61 });
62
63 describe('rendering', () => {
64   it.each([
65     [ComponentQualifier.Project, 'roles.page.description2', PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE],
66     [ComponentQualifier.Portfolio, 'roles.page.description_portfolio', PERMISSIONS_ORDER_FOR_VIEW],
67     [
68       ComponentQualifier.Application,
69       'roles.page.description_application',
70       PERMISSIONS_ORDER_FOR_VIEW,
71     ],
72   ])('should render correctly for %s', async (qualifier, description, permissions) => {
73     const user = userEvent.setup();
74     const ui = getPageObject(user);
75     renderPermissionsProjectApp({ qualifier, visibility: Visibility.Private });
76     await ui.appLoaded();
77
78     expect(screen.getByText(description)).toBeInTheDocument();
79     permissions.forEach((permission) => {
80       expect(ui.projectPermissionCheckbox('johndoe', permission).get()).toBeInTheDocument();
81     });
82   });
83 });
84
85 describe('filtering', () => {
86   it('should allow to filter permission holders', async () => {
87     const user = userEvent.setup();
88     const ui = getPageObject(user);
89     renderPermissionsProjectApp();
90     await ui.appLoaded();
91
92     expect(screen.getByText('sonar-users')).toBeInTheDocument();
93     expect(screen.getByText('johndoe')).toBeInTheDocument();
94
95     await ui.showOnlyUsers();
96     expect(screen.queryByText('sonar-users')).not.toBeInTheDocument();
97     expect(screen.getByText('johndoe')).toBeInTheDocument();
98
99     await ui.showOnlyGroups();
100     expect(screen.getByText('sonar-users')).toBeInTheDocument();
101     expect(screen.queryByText('johndoe')).not.toBeInTheDocument();
102
103     await ui.showAll();
104     expect(screen.getByText('sonar-users')).toBeInTheDocument();
105     expect(screen.getByText('johndoe')).toBeInTheDocument();
106
107     await ui.searchFor('sonar-adm');
108     expect(screen.getByText('sonar-admins')).toBeInTheDocument();
109     expect(screen.queryByText('sonar-users')).not.toBeInTheDocument();
110     expect(screen.queryByText('johndoe')).not.toBeInTheDocument();
111
112     await ui.clearSearch();
113     expect(screen.getByText('sonar-users')).toBeInTheDocument();
114     expect(screen.getByText('johndoe')).toBeInTheDocument();
115   });
116
117   it('should allow to show only permission holders with a specific permission', async () => {
118     const user = userEvent.setup();
119     const ui = getPageObject(user);
120     renderPermissionsProjectApp();
121     await ui.appLoaded();
122
123     expect(screen.getAllByRole('row').length).toBe(11);
124     await ui.toggleFilterByPermission(Permissions.Admin);
125     expect(screen.getAllByRole('row').length).toBe(3);
126     await ui.toggleFilterByPermission(Permissions.Admin);
127     expect(screen.getAllByRole('row').length).toBe(11);
128   });
129 });
130
131 describe('assigning/revoking permissions', () => {
132   it('should allow to apply a permission template', async () => {
133     const user = userEvent.setup();
134     const ui = getPageObject(user);
135     renderPermissionsProjectApp();
136     await ui.appLoaded();
137
138     await ui.openTemplateModal();
139     expect(ui.confirmApplyTemplateBtn.get()).toBeDisabled();
140     await ui.chooseTemplate('Permission Template 2');
141     expect(ui.templateSuccessfullyApplied.get()).toBeInTheDocument();
142     await ui.closeTemplateModal();
143     expect(ui.templateSuccessfullyApplied.query()).not.toBeInTheDocument();
144   });
145
146   it('should allow to turn a public project private (and vice-versa)', async () => {
147     const user = userEvent.setup();
148     const ui = getPageObject(user);
149     renderPermissionsProjectApp();
150     await ui.appLoaded();
151
152     expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
153     expect(
154       ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).query(),
155     ).not.toBeInTheDocument();
156     await act(async () => {
157       await ui.turnProjectPrivate();
158     });
159     expect(ui.visibilityRadio(Visibility.Private).get()).toBeChecked();
160     expect(
161       ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get(),
162     ).toBeInTheDocument();
163
164     await ui.turnProjectPublic();
165     expect(ui.makePublicDisclaimer.get()).toBeInTheDocument();
166     await act(async () => {
167       await ui.confirmTurnProjectPublic();
168     });
169     expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
170   });
171
172   it('should add and remove permissions to/from a group', async () => {
173     const user = userEvent.setup();
174     const ui = getPageObject(user);
175     renderPermissionsProjectApp();
176     await ui.appLoaded();
177
178     expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Admin).get()).not.toBeChecked();
179
180     await ui.toggleProjectPermission('sonar-users', Permissions.Admin);
181     await ui.appLoaded();
182     expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Admin).get()).toBeChecked();
183
184     await ui.toggleProjectPermission('sonar-users', Permissions.Admin);
185     await ui.appLoaded();
186     expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Admin).get()).not.toBeChecked();
187   });
188
189   it('should add and remove permissions to/from a user', async () => {
190     const user = userEvent.setup();
191     const ui = getPageObject(user);
192     renderPermissionsProjectApp();
193     await ui.appLoaded();
194
195     expect(ui.projectPermissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
196
197     await ui.toggleProjectPermission('johndoe', Permissions.Scan);
198     await ui.appLoaded();
199     expect(ui.projectPermissionCheckbox('johndoe', Permissions.Scan).get()).toBeChecked();
200
201     await ui.toggleProjectPermission('johndoe', Permissions.Scan);
202     await ui.appLoaded();
203     expect(ui.projectPermissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
204   });
205
206   it('should handle errors correctly', async () => {
207     serviceMock.setIsAllowedToChangePermissions(false);
208     const user = userEvent.setup();
209     const ui = getPageObject(user);
210     renderPermissionsProjectApp();
211     await ui.appLoaded();
212
213     expect(ui.projectPermissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
214     await ui.toggleProjectPermission('johndoe', Permissions.Scan);
215     await ui.appLoaded();
216     expect(ui.projectPermissionCheckbox('johndoe', Permissions.Scan).get()).not.toBeChecked();
217   });
218 });
219
220 it('should correctly handle pagination', async () => {
221   const groups: PermissionGroup[] = [];
222   const users: PermissionUser[] = [];
223   Array.from(Array(20).keys()).forEach((i) => {
224     groups.push(mockPermissionGroup({ name: `Group ${i}` }));
225     users.push(mockPermissionUser({ login: `user-${i}` }));
226   });
227   serviceMock.setGroups(groups);
228   serviceMock.setUsers(users);
229
230   const user = userEvent.setup();
231   const ui = getPageObject(user);
232   renderPermissionsProjectApp();
233   await ui.appLoaded();
234
235   expect(screen.getAllByRole('row').length).toBe(11);
236   await ui.clickLoadMore();
237   expect(screen.getAllByRole('row').length).toBe(21);
238 });
239
240 it('should not allow to change visibility for GH Project with auto-provisioning', async () => {
241   const user = userEvent.setup();
242   const ui = getPageObject(user);
243   authHandler.githubProvisioningStatus = true;
244   almHandler.handleSetProjectBinding(AlmKeys.GitHub, {
245     almSetting: 'test',
246     repository: 'test',
247     monorepo: false,
248     project: 'my-project',
249   });
250   renderPermissionsProjectApp({}, { featureList: [Feature.GithubProvisioning] });
251   await ui.appLoaded();
252
253   expect(ui.visibilityRadio(Visibility.Public).get()).toBeDisabled();
254   expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
255   expect(ui.visibilityRadio(Visibility.Private).get()).toBeDisabled();
256   await act(async () => {
257     await ui.turnProjectPrivate();
258   });
259   expect(ui.visibilityRadio(Visibility.Private).get()).not.toBeChecked();
260 });
261
262 it('should allow to change visibility for non-GH Project', async () => {
263   const user = userEvent.setup();
264   const ui = getPageObject(user);
265   authHandler.githubProvisioningStatus = true;
266   almHandler.handleSetProjectBinding(AlmKeys.Azure, {
267     almSetting: 'test',
268     repository: 'test',
269     monorepo: false,
270     project: 'my-project',
271   });
272   renderPermissionsProjectApp({}, { featureList: [Feature.GithubProvisioning] });
273   await ui.appLoaded();
274
275   expect(ui.visibilityRadio(Visibility.Public).get()).not.toHaveClass('disabled');
276   expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
277   expect(ui.visibilityRadio(Visibility.Private).get()).not.toHaveClass('disabled');
278   await act(async () => {
279     await ui.turnProjectPrivate();
280   });
281   expect(ui.visibilityRadio(Visibility.Private).get()).toBeChecked();
282 });
283
284 it('should allow to change visibility for GH Project with disabled auto-provisioning', async () => {
285   const user = userEvent.setup();
286   const ui = getPageObject(user);
287   authHandler.githubProvisioningStatus = false;
288   almHandler.handleSetProjectBinding(AlmKeys.GitHub, {
289     almSetting: 'test',
290     repository: 'test',
291     monorepo: false,
292     project: 'my-project',
293   });
294   renderPermissionsProjectApp({}, { featureList: [Feature.GithubProvisioning] });
295   await ui.appLoaded();
296
297   expect(ui.visibilityRadio(Visibility.Public).get()).not.toHaveClass('disabled');
298   expect(ui.visibilityRadio(Visibility.Public).get()).toBeChecked();
299   expect(ui.visibilityRadio(Visibility.Private).get()).not.toHaveClass('disabled');
300   await act(async () => {
301     await ui.turnProjectPrivate();
302   });
303   expect(ui.visibilityRadio(Visibility.Private).get()).toBeChecked();
304 });
305
306 it('should have disabled permissions for GH Project', async () => {
307   const user = userEvent.setup();
308   const ui = getPageObject(user);
309   authHandler.githubProvisioningStatus = true;
310   almHandler.handleSetProjectBinding(AlmKeys.GitHub, {
311     almSetting: 'test',
312     repository: 'test',
313     monorepo: false,
314     project: 'my-project',
315   });
316   renderPermissionsProjectApp(
317     {},
318     { featureList: [Feature.GithubProvisioning] },
319     {
320       component: mockComponent({ visibility: Visibility.Private }),
321     },
322   );
323   await ui.appLoaded();
324
325   expect(ui.pageTitle.get()).toBeInTheDocument();
326   await waitFor(() =>
327     expect(ui.pageTitle.get()).toHaveAccessibleName(/project_permission.github_managed/),
328   );
329   expect(ui.pageTitle.byRole('img').get()).toBeInTheDocument();
330   expect(ui.githubExplanations.get()).toBeInTheDocument();
331
332   expect(ui.projectPermissionCheckbox('John', Permissions.Admin).get()).toBeChecked();
333   expect(ui.projectPermissionCheckbox('John', Permissions.Admin).get()).toHaveAttribute(
334     'aria-disabled',
335     'true',
336   );
337   expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).toBeChecked();
338   expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).toHaveAttribute(
339     'aria-disabled',
340     'false',
341   );
342   await ui.toggleProjectPermission('Alexa', Permissions.IssueAdmin);
343   expect(ui.confirmRemovePermissionDialog.get()).toBeInTheDocument();
344   expect(ui.confirmRemovePermissionDialog.get()).toHaveTextContent(
345     `${Permissions.IssueAdmin}Alexa`,
346   );
347   await act(() =>
348     user.click(ui.confirmRemovePermissionDialog.byRole('button', { name: 'confirm' }).get()),
349   );
350   expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).not.toBeChecked();
351
352   expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).toBeChecked();
353   expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).toHaveAttribute(
354     'aria-disabled',
355     'false',
356   );
357   await ui.toggleProjectPermission('sonar-users', Permissions.Browse);
358   expect(ui.confirmRemovePermissionDialog.get()).toBeInTheDocument();
359   expect(ui.confirmRemovePermissionDialog.get()).toHaveTextContent(
360     `${Permissions.Browse}sonar-users`,
361   );
362   await act(() =>
363     user.click(ui.confirmRemovePermissionDialog.byRole('button', { name: 'confirm' }).get()),
364   );
365   expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).not.toBeChecked();
366   expect(ui.projectPermissionCheckbox('sonar-admins', Permissions.Admin).get()).toBeChecked();
367   expect(ui.projectPermissionCheckbox('sonar-admins', Permissions.Admin).get()).toHaveAttribute(
368     'aria-disabled',
369     'true',
370   );
371
372   const johnRow = screen.getAllByRole('row')[4];
373   expect(johnRow).toHaveTextContent('John');
374   expect(ui.githubLogo.get(johnRow)).toBeInTheDocument();
375   const alexaRow = screen.getAllByRole('row')[5];
376   expect(alexaRow).toHaveTextContent('Alexa');
377   expect(ui.githubLogo.query(alexaRow)).not.toBeInTheDocument();
378   const usersGroupRow = screen.getAllByRole('row')[1];
379   expect(usersGroupRow).toHaveTextContent('sonar-users');
380   expect(ui.githubLogo.query(usersGroupRow)).not.toBeInTheDocument();
381   const adminsGroupRow = screen.getAllByRole('row')[2];
382   expect(adminsGroupRow).toHaveTextContent('sonar-admins');
383   expect(ui.githubLogo.query(adminsGroupRow)).toBeInTheDocument();
384
385   expect(ui.applyTemplateBtn.query()).not.toBeInTheDocument();
386
387   // not possible to grant permissions at all
388   expect(
389     screen
390       .getAllByRole('checkbox', { checked: false })
391       .every((item) => item.getAttribute('aria-disabled') === 'true'),
392   ).toBe(true);
393 });
394
395 it('should allow to change permissions for GH Project without auto-provisioning', async () => {
396   const user = userEvent.setup();
397   const ui = getPageObject(user);
398   authHandler.githubProvisioningStatus = false;
399   almHandler.handleSetProjectBinding(AlmKeys.GitHub, {
400     almSetting: 'test',
401     repository: 'test',
402     monorepo: false,
403     project: 'my-project',
404   });
405   renderPermissionsProjectApp(
406     { visibility: Visibility.Private },
407     { featureList: [Feature.GithubProvisioning] },
408   );
409   await ui.appLoaded();
410
411   expect(ui.pageTitle.get()).toBeInTheDocument();
412   expect(ui.pageTitle.byRole('img').query()).not.toBeInTheDocument();
413
414   expect(ui.applyTemplateBtn.get()).toBeInTheDocument();
415
416   // no restrictions
417   expect(
418     screen.getAllByRole('checkbox').every((item) => item.getAttribute('aria-disabled') !== 'true'),
419   ).toBe(true);
420 });
421
422 it('should allow to change permissions for non-GH Project', async () => {
423   const user = userEvent.setup();
424   const ui = getPageObject(user);
425   authHandler.githubProvisioningStatus = true;
426   renderPermissionsProjectApp({}, { featureList: [Feature.GithubProvisioning] });
427   await ui.appLoaded();
428
429   expect(ui.pageTitle.get()).toBeInTheDocument();
430   expect(ui.nonGHProjectWarning.get()).toBeInTheDocument();
431   expect(ui.pageTitle.byRole('img').query()).not.toBeInTheDocument();
432
433   expect(ui.applyTemplateBtn.get()).toBeInTheDocument();
434
435   // no restrictions
436   expect(
437     screen.getAllByRole('checkbox').every((item) => item.getAttribute('aria-disabled') !== 'true'),
438   ).toBe(true);
439 });
440
441 function renderPermissionsProjectApp(
442   override: Partial<Component> = {},
443   contextOverride: Partial<RenderContext> = {},
444   componentContextOverride: Partial<ComponentContextShape> = {},
445 ) {
446   return renderAppWithComponentContext(
447     'project_roles?id=my-project',
448     projectPermissionsRoutes,
449     contextOverride,
450     {
451       component: mockComponent({
452         visibility: Visibility.Public,
453         configuration: {
454           canUpdateProjectVisibilityToPrivate: true,
455           canApplyPermissionTemplate: true,
456         },
457         ...override,
458       }),
459       ...componentContextOverride,
460     },
461   );
462 }