]> source.dussan.org Git - sonarqube.git/blob
5b95545450d4a3e2eec8d130ff2b655a6ec6fef4
[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(10);
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(10);
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()).toBeDisabled();
334   expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).toBeChecked();
335   expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).toBeEnabled();
336   await ui.toggleProjectPermission('Alexa', Permissions.IssueAdmin);
337   expect(ui.confirmRemovePermissionDialog.get()).toBeInTheDocument();
338   expect(ui.confirmRemovePermissionDialog.get()).toHaveTextContent(
339     `${Permissions.IssueAdmin}Alexa`,
340   );
341   await act(() =>
342     user.click(ui.confirmRemovePermissionDialog.byRole('button', { name: 'confirm' }).get()),
343   );
344   expect(ui.projectPermissionCheckbox('Alexa', Permissions.IssueAdmin).get()).not.toBeChecked();
345
346   expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).toBeChecked();
347   expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).toBeEnabled();
348   await ui.toggleProjectPermission('sonar-users', Permissions.Browse);
349   expect(ui.confirmRemovePermissionDialog.get()).toBeInTheDocument();
350   expect(ui.confirmRemovePermissionDialog.get()).toHaveTextContent(
351     `${Permissions.Browse}sonar-users`,
352   );
353   await act(() =>
354     user.click(ui.confirmRemovePermissionDialog.byRole('button', { name: 'confirm' }).get()),
355   );
356   expect(ui.projectPermissionCheckbox('sonar-users', Permissions.Browse).get()).not.toBeChecked();
357   expect(ui.projectPermissionCheckbox('sonar-admins', Permissions.Admin).get()).toBeChecked();
358   expect(ui.projectPermissionCheckbox('sonar-admins', Permissions.Admin).get()).toHaveAttribute(
359     'disabled',
360   );
361
362   const johnRow = screen.getAllByRole('row')[4];
363   expect(johnRow).toHaveTextContent('John');
364   expect(ui.githubLogo.get(johnRow)).toBeInTheDocument();
365   const alexaRow = screen.getAllByRole('row')[5];
366   expect(alexaRow).toHaveTextContent('Alexa');
367   expect(ui.githubLogo.query(alexaRow)).not.toBeInTheDocument();
368   const usersGroupRow = screen.getAllByRole('row')[1];
369   expect(usersGroupRow).toHaveTextContent('sonar-users');
370   expect(ui.githubLogo.query(usersGroupRow)).not.toBeInTheDocument();
371   const adminsGroupRow = screen.getAllByRole('row')[2];
372   expect(adminsGroupRow).toHaveTextContent('sonar-admins');
373   expect(ui.githubLogo.query(adminsGroupRow)).toBeInTheDocument();
374
375   expect(ui.applyTemplateBtn.query()).not.toBeInTheDocument();
376
377   // not possible to grant permissions at all
378   expect(
379     screen
380       .getAllByRole('checkbox', { checked: false })
381       .every((item) => item.getAttributeNames().includes('disabled')),
382   ).toBe(true);
383 });
384
385 it('should allow to change permissions for GH Project without auto-provisioning', async () => {
386   const user = userEvent.setup();
387   const ui = getPageObject(user);
388   authHandler.githubProvisioningStatus = false;
389   almHandler.handleSetProjectBinding(AlmKeys.GitHub, {
390     almSetting: 'test',
391     repository: 'test',
392     monorepo: false,
393     project: 'my-project',
394   });
395   renderPermissionsProjectApp(
396     { visibility: Visibility.Private },
397     { featureList: [Feature.GithubProvisioning] },
398   );
399   await ui.appLoaded();
400
401   expect(ui.pageTitle.get()).toBeInTheDocument();
402   expect(ui.pageTitle.byRole('img').query()).not.toBeInTheDocument();
403
404   expect(ui.applyTemplateBtn.get()).toBeInTheDocument();
405
406   // no restrictions
407   expect(
408     screen.getAllByRole('checkbox').every((item) => item.getAttributeNames().includes('disabled')),
409   ).toBe(false);
410 });
411
412 it('should allow to change permissions for non-GH Project', async () => {
413   const user = userEvent.setup();
414   const ui = getPageObject(user);
415   authHandler.githubProvisioningStatus = true;
416   renderPermissionsProjectApp({}, { featureList: [Feature.GithubProvisioning] });
417   await ui.appLoaded();
418
419   expect(ui.pageTitle.get()).toBeInTheDocument();
420   expect(ui.nonGHProjectWarning.get()).toBeInTheDocument();
421   expect(ui.pageTitle.byRole('img').query()).not.toBeInTheDocument();
422
423   expect(ui.applyTemplateBtn.get()).toBeInTheDocument();
424
425   // no restrictions
426   expect(
427     screen.getAllByRole('checkbox').every((item) => item.getAttributeNames().includes('disabled')),
428   ).toBe(false);
429 });
430
431 function renderPermissionsProjectApp(
432   override: Partial<Component> = {},
433   contextOverride: Partial<RenderContext> = {},
434   componentContextOverride: Partial<ComponentContextShape> = {},
435 ) {
436   return renderAppWithComponentContext(
437     'project_roles?id=my-project',
438     projectPermissionsRoutes,
439     contextOverride,
440     {
441       component: mockComponent({
442         visibility: Visibility.Public,
443         configuration: {
444           canUpdateProjectVisibilityToPrivate: true,
445           canApplyPermissionTemplate: true,
446         },
447         ...override,
448       }),
449       ...componentContextOverride,
450     },
451   );
452 }