/* * SonarQube * Copyright (C) 2009-2023 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { act, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import selectEvent from 'react-select-event'; import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock'; import { searchProjects, searchUsers } from '../../../../api/quality-gates'; import { renderAppRoutes, RenderContext } from '../../../../helpers/testReactTestingUtils'; import { Feature } from '../../../../types/features'; import routes from '../../routes'; jest.mock('../../../../api/quality-gates'); let handler: QualityGatesServiceMock; beforeAll(() => { handler = new QualityGatesServiceMock(); }); afterEach(() => handler.reset()); it('should open the default quality gates', async () => { renderQualityGateApp(); const defaultQualityGate = handler.getDefaultQualityGate(); expect( await screen.findByRole('link', { current: 'page', name: `${defaultQualityGate.name} default` }) ).toBeInTheDocument(); }); it('should list all quality gates', async () => { renderQualityGateApp(); expect( await screen.findByRole('link', { name: `${handler.getDefaultQualityGate().name} default`, }) ).toBeInTheDocument(); expect( screen.getByRole('link', { name: `${handler.getBuiltInQualityGate().name} quality_gates.built_in`, }) ).toBeInTheDocument(); }); it('should be able to create a quality gate then delete it', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); let createButton = await screen.findByRole('button', { name: 'create' }); // Using keyboard await user.click(createButton); await act(async () => { await user.click(screen.getByRole('textbox', { name: /name.*/ })); await user.keyboard('testone{Enter}'); }); expect(await screen.findByRole('link', { name: 'testone' })).toBeInTheDocument(); // Using modal button createButton = await screen.findByRole('button', { name: 'create' }); await user.click(createButton); const saveButton = screen.getByRole('button', { name: 'save' }); expect(saveButton).toBeDisabled(); const nameInput = screen.getByRole('textbox', { name: /name.*/ }); await act(async () => { await user.click(nameInput); await user.keyboard('testtwo'); await user.click(saveButton); }); const newQG = await screen.findByRole('link', { name: 'testtwo' }); expect(newQG).toBeInTheDocument(); // Delete the quality gate await user.click(newQG); const deleteButton = await screen.findByRole('button', { name: 'delete' }); await user.click(deleteButton); const popup = screen.getByRole('dialog'); const dialogDeleteButton = within(popup).getByRole('button', { name: 'delete' }); await user.click(dialogDeleteButton); await waitFor(() => { expect(screen.queryByRole('link', { name: 'testtwo' })).not.toBeInTheDocument(); }); }); it('should be able to copy a quality gate which is CAYC compliant', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('Sonar way'); await user.click(notDefaultQualityGate); const copyButton = await screen.findByRole('button', { name: 'copy' }); await user.click(copyButton); const nameInput = screen.getByRole('textbox', { name: /name.*/ }); expect(nameInput).toBeInTheDocument(); await act(async () => { await user.click(nameInput); await user.keyboard(' bis{Enter}'); }); expect(await screen.findByRole('link', { name: /.* bis/ })).toBeInTheDocument(); }); it('should not be able to copy a quality gate which is not CAYC compliant', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily'); await user.click(notDefaultQualityGate); const copyButton = await screen.findByRole('button', { name: 'copy' }); expect(copyButton).toBeDisabled(); }); it('should be able to rename a quality gate', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const renameButton = await screen.findByRole('button', { name: 'rename' }); await user.click(renameButton); const nameInput = screen.getByRole('textbox', { name: /name.*/ }); expect(nameInput).toBeInTheDocument(); await user.click(nameInput); await user.keyboard('{Control>}a{/Control}New Name{Enter}'); expect(await screen.findByRole('link', { name: /New Name.*/ })).toBeInTheDocument(); }); it('should not be able to set as default a quality gate which is not CAYC compliant', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily'); await user.click(notDefaultQualityGate); const setAsDefaultButton = screen.getByRole('button', { name: 'set_as_default' }); expect(setAsDefaultButton).toBeDisabled(); }); it('should be able to set as default a quality gate which is CAYC compliant', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('Sonar way'); await user.click(notDefaultQualityGate); const setAsDefaultButton = screen.getByRole('button', { name: 'set_as_default' }); await user.click(setAsDefaultButton); expect(screen.getAllByRole('link')[2]).toHaveTextContent('default'); }); it('should be able to add a condition', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); await user.click(await screen.findByText('SonarSource way - CFamily')); // On new code await user.click(await screen.findByText('quality_gates.add_condition')); let dialog = within(screen.getByRole('dialog')); await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.new_code' })); await selectEvent.select(dialog.getByRole('textbox'), ['Issues']); await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' })); await user.keyboard('12{Enter}'); const newConditions = within(await screen.findByTestId('quality-gates__conditions-new')); expect(await newConditions.findByRole('cell', { name: 'Issues' })).toBeInTheDocument(); expect(await newConditions.findByRole('cell', { name: '12' })).toBeInTheDocument(); // On overall code await user.click(await screen.findByText('quality_gates.add_condition')); dialog = within(screen.getByRole('dialog')); await selectEvent.select(dialog.getByRole('textbox'), ['Info Issues']); await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' })); await user.click(dialog.getByLabelText('quality_gates.conditions.operator')); await user.click(dialog.getByText('quality_gates.operator.LT')); await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' })); await user.keyboard('42{Enter}'); const overallConditions = within(await screen.findByTestId('quality-gates__conditions-overall')); expect(await overallConditions.findByRole('cell', { name: 'Info Issues' })).toBeInTheDocument(); expect(await overallConditions.findByRole('cell', { name: '42' })).toBeInTheDocument(); // Select a rating await user.click(await screen.findByText('quality_gates.add_condition')); dialog = within(screen.getByRole('dialog')); await user.click(dialog.getByRole('radio', { name: 'quality_gates.conditions.overall_code' })); await selectEvent.select(dialog.getByRole('textbox'), ['Maintainability Rating']); await user.click(dialog.getByLabelText('quality_gates.conditions.value')); await user.click(dialog.getByText('B')); await user.click(dialog.getByRole('button', { name: 'quality_gates.add_condition' })); expect( await overallConditions.findByRole('cell', { name: 'Maintainability Rating' }) ).toBeInTheDocument(); expect(await overallConditions.findByRole('cell', { name: 'B' })).toBeInTheDocument(); }); it('should be able to edit a condition', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const newConditions = within(await screen.findByTestId('quality-gates__conditions-new')); await user.click( newConditions.getByLabelText('quality_gates.condition.edit.Coverage on New Code') ); const dialog = within(screen.getByRole('dialog')); await user.click(dialog.getByRole('textbox', { name: 'quality_gates.conditions.value' })); await user.keyboard('{Backspace}{Backspace}23{Enter}'); expect(await newConditions.findByText('Coverage')).toBeInTheDocument(); expect(await newConditions.findByText('23.0%')).toBeInTheDocument(); }); it('should be able to handle duplicate or deprecated condition', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); await user.click( // make it a regexp to ignore badges: await screen.findByRole('link', { name: new RegExp(handler.getCorruptedQualityGateName()) }) ); expect(await screen.findByText('quality_gates.duplicated_conditions')).toBeInTheDocument(); expect( await screen.findByRole('cell', { name: 'Complexity / Function deprecated' }) ).toBeInTheDocument(); }); it('should be able to handle delete condition', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); await user.click(await screen.findByText('Non Cayc QG')); const newConditions = within(await screen.findByTestId('quality-gates__conditions-new')); await user.click( newConditions.getByLabelText('quality_gates.condition.delete.Coverage on New Code') ); const dialog = within(screen.getByRole('dialog')); await user.click(dialog.getByRole('button', { name: 'delete' })); await waitFor(() => { expect(newConditions.queryByRole('cell', { name: 'Coverage' })).not.toBeInTheDocument(); }); }); it('should explain condition on branch', async () => { renderQualityGateApp({ featureList: [Feature.BranchSupport] }); expect( await screen.findByText('quality_gates.conditions.new_code.description') ).toBeInTheDocument(); expect( await screen.findByText('quality_gates.conditions.overall_code.description') ).toBeInTheDocument(); }); it('should show warning banner when CAYC condition is not properly set and should be able to update them', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const qualityGate = await screen.findByText('SonarSource way - CFamily'); await user.click(qualityGate); expect(screen.getByText('quality_gates.cayc_missing.banner.title')).toBeInTheDocument(); expect(screen.getByText('quality_gates.cayc_missing.banner.description')).toBeInTheDocument(); expect( screen.getByRole('button', { name: 'quality_gates.cayc_condition.review_update' }) ).toBeInTheDocument(); await user.click( screen.getByRole('button', { name: 'quality_gates.cayc_condition.review_update' }) ); expect( screen.getByRole('dialog', { name: 'quality_gates.cayc.review_update_modal.header.SonarSource way - CFamily', }) ).toBeInTheDocument(); expect( screen.getByText('quality_gates.cayc.review_update_modal.description1') ).toBeInTheDocument(); expect( screen.getByText('quality_gates.cayc.review_update_modal.description2') ).toBeInTheDocument(); expect( screen.getByRole('button', { name: 'quality_gates.cayc.review_update_modal.confirm_text' }) ).toBeInTheDocument(); await user.click( screen.getByRole('button', { name: 'quality_gates.cayc.review_update_modal.confirm_text' }) ); const conditionsWrapper = within(await screen.findByTestId('quality-gates__conditions-new')); expect(conditionsWrapper.getByText('Maintainability Rating')).toBeInTheDocument(); expect(conditionsWrapper.getByText('Reliability Rating')).toBeInTheDocument(); expect(conditionsWrapper.getByText('Security Hotspots Reviewed')).toBeInTheDocument(); expect(conditionsWrapper.getByText('Security Rating')).toBeInTheDocument(); expect(conditionsWrapper.getAllByText('Coverage')).toHaveLength(2); // This quality gate has duplicate condition expect(conditionsWrapper.getByText('Duplicated Lines (%)')).toBeInTheDocument(); const overallConditionsWrapper = within( await screen.findByTestId('quality-gates__conditions-overall') ); expect(overallConditionsWrapper.getByText('Complexity / Function')).toBeInTheDocument(); }); it('should show success banner when quality gate is CAYC compliant', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const qualityGate = await screen.findByText('SonarSource way'); await user.click(qualityGate); expect(screen.getByText('quality_gates.cayc.banner.title')).toBeInTheDocument(); expect( screen.getByText('quality_gates.cayc.banner.description1', { exact: false }) ).toBeInTheDocument(); expect( screen.queryByText('quality_gates.cayc_condition.missing_warning.title') ).not.toBeInTheDocument(); expect( screen.queryByRole('button', { name: 'quality_gates.cayc_condition.review_update' }) ).not.toBeInTheDocument(); const conditionsWrapper = within(await screen.findByTestId('quality-gates__conditions-new')); expect(await conditionsWrapper.findByText('Maintainability Rating')).toBeInTheDocument(); expect(await conditionsWrapper.findByText('Reliability Rating')).toBeInTheDocument(); expect(await conditionsWrapper.findByText('Security Hotspots Reviewed')).toBeInTheDocument(); expect(await conditionsWrapper.findByText('Security Rating')).toBeInTheDocument(); expect(await conditionsWrapper.findByText('Coverage')).toBeInTheDocument(); expect(await conditionsWrapper.findByText('Duplicated Lines (%)')).toBeInTheDocument(); }); it('should show info banner when quality gate is CAYC over-compliant', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const qualityGate = await screen.findByText('Over Compliant CAYC QG'); await user.click(qualityGate); expect(screen.getByText('quality_gates.cayc.banner.title')).toBeInTheDocument(); expect( screen.getByText('quality_gates.cayc.banner.description1', { exact: false }) ).toBeInTheDocument(); expect(screen.getByText('quality_gates.cayc_over_compliant.banner.title')).toBeInTheDocument(); expect( screen.queryByText('quality_gates.cayc_condition.missing_warning.title') ).not.toBeInTheDocument(); expect( screen.queryByRole('button', { name: 'quality_gates.cayc_condition.review_update' }) ).not.toBeInTheDocument(); expect(screen.getByText('quality_gates.cayc.unlock_edit')).toBeInTheDocument(); }); it('should unlock editing option for CAYC conditions', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const qualityGate = await screen.findByText('SonarSource way'); await user.click(qualityGate); expect(screen.getByText('quality_gates.cayc.unlock_edit')).toBeInTheDocument(); expect( screen.queryByRole('button', { name: 'quality_gates.condition.edit.Security Rating on New Code', }) ).not.toBeInTheDocument(); expect( screen.queryByRole('button', { name: 'quality_gates.condition.delete.Security Rating on New Code', }) ).not.toBeInTheDocument(); await user.click(screen.getByText('quality_gates.cayc.unlock_edit')); expect( screen.getByRole('button', { name: 'quality_gates.condition.edit.Security Rating on New Code' }) ).toBeInTheDocument(); expect( screen.getByRole('button', { name: 'quality_gates.condition.delete.Security Rating on New Code', }) ).toBeInTheDocument(); }); describe('The Project section', () => { it('should render list of projects correctly in different tabs', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily'); await user.click(notDefaultQualityGate); // by default it shows "selected" values expect(screen.getAllByRole('checkbox')).toHaveLength(2); // change tabs to show deselected projects await user.click(screen.getByRole('button', { name: 'quality_gates.projects.without' })); expect(screen.getAllByRole('checkbox')).toHaveLength(2); // change tabs to show all projects await user.click(screen.getByRole('button', { name: 'quality_gates.projects.all' })); expect(screen.getAllByRole('checkbox')).toHaveLength(4); }); it('should handle select and deselect correctly', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily'); await user.click(notDefaultQualityGate); const checkedProjects = screen.getAllByRole('checkbox')[0]; expect(screen.getAllByRole('checkbox')).toHaveLength(2); await user.click(checkedProjects); const reloadButton = screen.getByRole('button', { name: 'reload' }); expect(reloadButton).toBeInTheDocument(); await user.click(reloadButton); // FP // eslint-disable-next-line jest-dom/prefer-in-document expect(screen.getAllByRole('checkbox')).toHaveLength(1); // change tabs to show deselected projects await user.click(screen.getByRole('button', { name: 'quality_gates.projects.without' })); const uncheckedProjects = screen.getAllByRole('checkbox')[0]; expect(screen.getAllByRole('checkbox')).toHaveLength(3); await user.click(uncheckedProjects); expect(reloadButton).toBeInTheDocument(); await user.click(reloadButton); expect(screen.getAllByRole('checkbox')).toHaveLength(2); }); it('should handle the search of projects', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily'); await user.click(notDefaultQualityGate); const searchInput = screen.getByRole('searchbox', { name: 'search_verb' }); expect(searchInput).toBeInTheDocument(); await user.click(searchInput); await user.keyboard('test2{Enter}'); // FP // eslint-disable-next-line jest-dom/prefer-in-document expect(screen.getAllByRole('checkbox')).toHaveLength(1); }); it('should display show more button if there are multiple pages of data', async () => { (searchProjects as jest.Mock).mockResolvedValueOnce({ paging: { pageIndex: 2, pageSize: 3, total: 55 }, results: [], }); const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const notDefaultQualityGate = await screen.findByText('SonarSource way - CFamily'); await user.click(notDefaultQualityGate); expect(screen.getByRole('button', { name: 'show_more' })).toBeInTheDocument(); }); }); describe('The Permissions section', () => { it('should not show button to grant permission when user is not admin', async () => { renderQualityGateApp(); // await just to make sure we've loaded the page expect( await screen.findByRole('link', { name: `${handler.getDefaultQualityGate().name} default`, }) ).toBeInTheDocument(); expect(screen.queryByText('quality_gates.permissions')).not.toBeInTheDocument(); }); it('should show button to grant permission when user is admin', async () => { handler.setIsAdmin(true); renderQualityGateApp(); const grantPermissionButton = await screen.findByRole('button', { name: 'quality_gates.permissions.grant', }); expect(screen.getByText('quality_gates.permissions')).toBeInTheDocument(); expect(grantPermissionButton).toBeInTheDocument(); }); it('should assign permission to a user and delete it later', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); expect(screen.queryByText('userlogin')).not.toBeInTheDocument(); // Granting permission to a user const grantPermissionButton = await screen.findByRole('button', { name: 'quality_gates.permissions.grant', }); await user.click(grantPermissionButton); const popup = screen.getByRole('dialog'); const searchUserInput = within(popup).getByRole('textbox'); expect(searchUserInput).toBeInTheDocument(); const addUserButton = screen.getByRole('button', { name: 'add_verb', }); expect(addUserButton).toBeDisabled(); await user.click(searchUserInput); expect(screen.getAllByTestId('qg-add-permission-option')).toHaveLength(2); await user.click(screen.getByText('userlogin')); expect(addUserButton).toBeEnabled(); await user.click(addUserButton); expect(screen.getByText('userlogin')).toBeInTheDocument(); // Cancel granting permission await user.click(grantPermissionButton); await user.click(searchUserInput); await user.keyboard('test{Enter}'); const cancelButton = screen.getByRole('button', { name: 'cancel', }); await user.click(cancelButton); const permissionList = within(await screen.findByTestId('quality-gate-permissions')); expect(permissionList.getByRole('listitem')).toBeInTheDocument(); // Delete the user permission const deleteButton = screen.getByTestId('permission-delete-button'); await user.click(deleteButton); const deletePopup = screen.getByRole('dialog'); const dialogDeleteButton = within(deletePopup).getByRole('button', { name: 'remove' }); await user.click(dialogDeleteButton); expect(permissionList.queryByRole('listitem')).not.toBeInTheDocument(); }); it('should assign permission to a group and delete it later', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); expect(screen.queryByText('userlogin')).not.toBeInTheDocument(); // Granting permission to a group const grantPermissionButton = await screen.findByRole('button', { name: 'quality_gates.permissions.grant', }); await user.click(grantPermissionButton); const popup = screen.getByRole('dialog'); const searchUserInput = within(popup).getByRole('textbox'); const addUserButton = screen.getByRole('button', { name: 'add_verb', }); await user.click(searchUserInput); expect(screen.getAllByTestId('qg-add-permission-option')).toHaveLength(2); await user.click(screen.getAllByTestId('qg-add-permission-option')[1]); await user.click(addUserButton); expect(screen.getByText('Foo')).toBeInTheDocument(); // Delete the group permission const deleteButton = screen.getByTestId('permission-delete-button'); await user.click(deleteButton); const deletePopup = screen.getByRole('dialog'); const dialogDeleteButton = within(deletePopup).getByRole('button', { name: 'remove' }); await user.click(dialogDeleteButton); const permissionList = within(await screen.findByTestId('quality-gate-permissions')); expect(permissionList.queryByRole('listitem')).not.toBeInTheDocument(); }); it('should handle searchUser service failure', async () => { (searchUsers as jest.Mock).mockRejectedValue('error'); const user = userEvent.setup(); handler.setIsAdmin(true); renderQualityGateApp(); const grantPermissionButton = await screen.findByRole('button', { name: 'quality_gates.permissions.grant', }); await user.click(grantPermissionButton); const popup = screen.getByRole('dialog'); const searchUserInput = within(popup).getByRole('textbox'); await user.click(searchUserInput); expect(screen.getByText('no_results')).toBeInTheDocument(); }); }); function renderQualityGateApp(context?: RenderContext) { renderAppRoutes('quality_gates', routes, context); }