From ab5820b3aa5810100109170997ad71de73a4352f Mon Sep 17 00:00:00 2001 From: Zipeng WU Date: Tue, 22 Aug 2023 17:49:26 +0200 Subject: [PATCH] SONAR-20220 Show warnings for non compliant Quality Gate only when user can edit it --- .../js/api/mocks/QualityGatesServiceMock.ts | 71 ++++++-- .../src/main/js/api/quality-gates.ts | 11 +- .../apps/overview/branches/BranchOverview.tsx | 20 ++- .../branches/BranchOverviewRenderer.tsx | 5 +- .../overview/branches/QualityGatePanel.tsx | 6 +- .../branches/__tests__/BranchOverview-it.tsx | 153 ++++++++++++------ .../quality-gates/components/Conditions.tsx | 6 +- .../apps/quality-gates/components/Details.tsx | 1 + .../components/DetailsHeader.tsx | 5 +- .../js/apps/quality-gates/components/List.tsx | 17 +- .../components/__tests__/QualityGate-it.tsx | 33 +++- 11 files changed, 230 insertions(+), 98 deletions(-) diff --git a/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts index 3259ef0f239..74bc38d4a1f 100644 --- a/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts @@ -19,10 +19,19 @@ */ import { cloneDeep, flatten, omit, remove } from 'lodash'; import { Project } from '../../apps/quality-gates/components/Projects'; -import { mockQualityGate } from '../../helpers/mocks/quality-gates'; +import { + mockQualityGate, + mockQualityGateApplicationStatus, + mockQualityGateProjectStatus, +} from '../../helpers/mocks/quality-gates'; import { mockUserBase } from '../../helpers/mocks/users'; import { mockCondition, mockGroup } from '../../helpers/testMocks'; import { MetricKey } from '../../types/metrics'; +import { + QualityGateApplicationStatus, + QualityGateProjectStatus, + SearchPermissionsParameters, +} from '../../types/quality-gates'; import { CaycStatus, Condition, QualityGate } from '../../types/types'; import { addGroup, @@ -36,7 +45,9 @@ import { dissociateGateWithProject, fetchQualityGate, fetchQualityGates, + getApplicationQualityGate, getGateForProject, + getQualityGateProjectStatus, renameQualityGate, searchGroups, searchProjects, @@ -45,6 +56,8 @@ import { updateCondition, } from '../quality-gates'; +jest.mock('../quality-gates'); + export class QualityGatesServiceMock { isAdmin = false; readOnlyList: QualityGate[]; @@ -52,6 +65,8 @@ export class QualityGatesServiceMock { projects: Project[]; getGateForProjectGateName: string; throwOnGetGateForProject: boolean; + qualityGateProjectStatus: QualityGateProjectStatus; + applicationQualityGate: QualityGateApplicationStatus; constructor(list?: QualityGate[]) { this.readOnlyList = list || [ @@ -198,22 +213,27 @@ export class QualityGatesServiceMock { this.getGateForProjectGateName = 'SonarSource way'; this.throwOnGetGateForProject = false; - (fetchQualityGate as jest.Mock).mockImplementation(this.showHandler); - (fetchQualityGates as jest.Mock).mockImplementation(this.listHandler); - (createQualityGate as jest.Mock).mockImplementation(this.createHandler); - (deleteQualityGate as jest.Mock).mockImplementation(this.destroyHandler); - (copyQualityGate as jest.Mock).mockImplementation(this.copyHandler); + jest.mocked(fetchQualityGate).mockImplementation(this.showHandler); + jest.mocked(fetchQualityGates).mockImplementation(this.listHandler); + jest.mocked(createQualityGate).mockImplementation(this.createHandler); + jest.mocked(deleteQualityGate).mockImplementation(this.destroyHandler); + jest.mocked(copyQualityGate).mockImplementation(this.copyHandler); (renameQualityGate as jest.Mock).mockImplementation(this.renameHandler); - (createCondition as jest.Mock).mockImplementation(this.createConditionHandler); - (updateCondition as jest.Mock).mockImplementation(this.updateConditionHandler); - (deleteCondition as jest.Mock).mockImplementation(this.deleteConditionHandler); - (searchProjects as jest.Mock).mockImplementation(this.searchProjectsHandler); - (searchUsers as jest.Mock).mockImplementation(this.searchUsersHandler); - (searchGroups as jest.Mock).mockImplementation(this.searchGroupsHandler); - (associateGateWithProject as jest.Mock).mockImplementation(this.selectHandler); - (dissociateGateWithProject as jest.Mock).mockImplementation(this.deSelectHandler); - (setQualityGateAsDefault as jest.Mock).mockImplementation(this.setDefaultHandler); + jest.mocked(createCondition).mockImplementation(this.createConditionHandler); + jest.mocked(updateCondition).mockImplementation(this.updateConditionHandler); + jest.mocked(deleteCondition).mockImplementation(this.deleteConditionHandler); + jest.mocked(searchProjects).mockImplementation(this.searchProjectsHandler); + jest.mocked(searchUsers).mockImplementation(this.searchUsersHandler); + jest.mocked(searchGroups).mockImplementation(this.searchGroupsHandler); + jest.mocked(associateGateWithProject).mockImplementation(this.selectHandler); + jest.mocked(dissociateGateWithProject).mockImplementation(this.deSelectHandler); + jest.mocked(setQualityGateAsDefault).mockImplementation(this.setDefaultHandler); (getGateForProject as jest.Mock).mockImplementation(this.projectGateHandler); + jest.mocked(getQualityGateProjectStatus).mockImplementation(this.handleQualityGetProjectStatus); + jest.mocked(getApplicationQualityGate).mockImplementation(this.handleGetApplicationQualityGate); + + this.qualityGateProjectStatus = mockQualityGateProjectStatus({}); + this.applicationQualityGate = mockQualityGateApplicationStatus({}); // To be implemented. (addUser as jest.Mock).mockResolvedValue({}); @@ -416,6 +436,7 @@ export class QualityGatesServiceMock { selected, query, }: { + gateName: string; selected: string; query: string | undefined; }) => { @@ -437,7 +458,7 @@ export class QualityGatesServiceMock { return this.reply(response); }; - searchUsersHandler = ({ selected }: { selected: string }) => { + searchUsersHandler = ({ selected }: SearchPermissionsParameters) => { if (selected === 'selected') { return this.reply({ users: [] }); } @@ -445,7 +466,7 @@ export class QualityGatesServiceMock { return this.reply({ users: [mockUserBase()] }); }; - searchGroupsHandler = ({ selected }: { selected: string }) => { + searchGroupsHandler = ({ selected }: SearchPermissionsParameters) => { if (selected === 'selected') { return this.reply({ groups: [] }); } @@ -477,6 +498,22 @@ export class QualityGatesServiceMock { return this.reply(this.list.find((qg) => qg.name === this.getGateForProjectGateName)); }; + handleGetApplicationQualityGate = () => { + return this.reply(this.applicationQualityGate); + }; + + setApplicationQualityGateStatus = (status: QualityGateApplicationStatus) => { + this.applicationQualityGate = mockQualityGateApplicationStatus(status); + }; + + handleQualityGetProjectStatus = () => { + return this.reply(this.qualityGateProjectStatus); + }; + + setQualityGateProjectStatus = (status: QualityGateProjectStatus) => { + this.qualityGateProjectStatus = mockQualityGateProjectStatus(status); + }; + reply(response: T): Promise { return Promise.resolve(cloneDeep(response)); } diff --git a/server/sonar-web/src/main/js/api/quality-gates.ts b/server/sonar-web/src/main/js/api/quality-gates.ts index 2a3c7a4bf69..4f63afdbb61 100644 --- a/server/sonar-web/src/main/js/api/quality-gates.ts +++ b/server/sonar-web/src/main/js/api/quality-gates.ts @@ -81,13 +81,12 @@ export function deleteCondition(data: { id: string }): Promise { return post('/api/qualitygates/delete_condition', data); } -export function getGateForProject(data: { project: string }): Promise { +export function getGateForProject(data: { project: string }): Promise { return getJSON('/api/qualitygates/get_by_project', data).then( - ({ qualityGate }) => - qualityGate && { - ...qualityGate, - isDefault: qualityGate.default, - }, + ({ qualityGate }) => ({ + ...qualityGate, + isDefault: qualityGate.default, + }), throwGlobalError ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx index 8c0cb2c306a..820159a466a 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx @@ -22,7 +22,12 @@ import * as React from 'react'; import { getApplicationDetails, getApplicationLeak } from '../../../api/application'; import { getMeasuresWithPeriodAndMetrics } from '../../../api/measures'; import { getProjectActivity } from '../../../api/projectActivity'; -import { getApplicationQualityGate, getQualityGateProjectStatus } from '../../../api/quality-gates'; +import { + fetchQualityGate, + getApplicationQualityGate, + getGateForProject, + getQualityGateProjectStatus, +} from '../../../api/quality-gates'; import { getAllTimeMachineData } from '../../../api/time-machine'; import { getActivityGraph, @@ -47,7 +52,7 @@ import { ComponentQualifier } from '../../../types/component'; import { MetricKey } from '../../../types/metrics'; import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity'; import { QualityGateStatus, QualityGateStatusCondition } from '../../../types/quality-gates'; -import { Component, MeasureEnhanced, Metric, Period } from '../../../types/types'; +import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types'; import '../styles.css'; import { HISTORY_METRICS_LIST, METRICS } from '../utils'; import BranchOverviewRenderer from './BranchOverviewRenderer'; @@ -70,6 +75,7 @@ interface State { metrics?: Metric[]; period?: Period; qgStatuses?: QualityGateStatus[]; + qualityGate?: QualityGate; } export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph'; @@ -111,6 +117,7 @@ export default class BranchOverview extends React.PureComponent { this.loadApplicationStatus(); } else { this.loadProjectStatus(); + this.loadProjectQualityGate(); } }; @@ -268,6 +275,13 @@ export default class BranchOverview extends React.PureComponent { ); }; + loadProjectQualityGate = async () => { + const { component } = this.props; + const qualityGate = await getGateForProject({ project: component.key }); + const qgDetails = await fetchQualityGate({ name: qualityGate.name }); + this.setState({ qualityGate: qgDetails }); + }; + loadMeasuresAndMeta = ( componentKey: string, branchLike?: BranchLike, @@ -409,6 +423,7 @@ export default class BranchOverview extends React.PureComponent { metrics, period, qgStatuses, + qualityGate, } = this.state; const projectIsEmpty = @@ -436,6 +451,7 @@ export default class BranchOverview extends React.PureComponent { period={period} projectIsEmpty={projectIsEmpty} qgStatuses={qgStatuses} + qualityGate={qualityGate} /> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx index 9a7654eccea..7e41afbe80c 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx @@ -26,7 +26,7 @@ import { Branch } from '../../../types/branch-like'; import { ComponentQualifier } from '../../../types/component'; import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity'; import { QualityGateStatus } from '../../../types/quality-gates'; -import { Component, MeasureEnhanced, Metric, Period } from '../../../types/types'; +import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types'; import ActivityPanel from './ActivityPanel'; import FirstAnalysisNextStepsNotif from './FirstAnalysisNextStepsNotif'; import MeasuresPanel from './MeasuresPanel'; @@ -50,6 +50,7 @@ export interface BranchOverviewRendererProps { period?: Period; projectIsEmpty?: boolean; qgStatuses?: QualityGateStatus[]; + qualityGate?: QualityGate; } export default function BranchOverviewRenderer(props: BranchOverviewRendererProps) { @@ -70,6 +71,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp period, projectIsEmpty, qgStatuses, + qualityGate, } = props; const leakPeriod = component.qualifier === ComponentQualifier.Application ? appLeak : period; @@ -95,6 +97,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp component={component} loading={loadingStatus} qgStatuses={qgStatuses} + qualityGate={qualityGate} /> diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx index a06e88baa3f..0e708a4be2b 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx @@ -23,7 +23,7 @@ import { flatMap } from 'lodash'; import * as React from 'react'; import { ComponentQualifier, isApplication } from '../../../types/component'; import { QualityGateStatus } from '../../../types/quality-gates'; -import { CaycStatus, Component } from '../../../types/types'; +import { CaycStatus, Component, QualityGate } from '../../../types/types'; import IgnoredConditionWarning from '../components/IgnoredConditionWarning'; import QualityGateStatusHeader from '../components/QualityGateStatusHeader'; import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView'; @@ -37,10 +37,11 @@ export interface QualityGatePanelProps { component: Pick; loading?: boolean; qgStatuses?: QualityGateStatus[]; + qualityGate?: QualityGate; } export function QualityGatePanel(props: QualityGatePanelProps) { - const { component, loading, qgStatuses = [] } = props; + const { component, loading, qgStatuses = [], qualityGate } = props; if (qgStatuses === undefined) { return null; @@ -112,6 +113,7 @@ export function QualityGatePanel(props: QualityGatePanelProps) { {qgStatuses.length === 1 && qgStatuses[0].caycStatus === CaycStatus.NonCompliant && + qualityGate?.actions?.manageConditions && !isApp && ( diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx index ab51129a308..0448c68e0ac 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx @@ -23,11 +23,9 @@ import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { getMeasuresWithPeriodAndMetrics } from '../../../../api/measures'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; +import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock'; import { getProjectActivity } from '../../../../api/projectActivity'; -import { - getApplicationQualityGate, - getQualityGateProjectStatus, -} from '../../../../api/quality-gates'; +import { getQualityGateProjectStatus } from '../../../../api/quality-gates'; import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider'; import { getActivityGraph, saveActivityGraph } from '../../../../components/activity-graph/utils'; import { isDiffMetric } from '../../../../helpers/measures'; @@ -84,47 +82,6 @@ jest.mock('../../../../api/measures', () => { }; }); -jest.mock('../../../../api/quality-gates', () => { - const { mockQualityGateProjectStatus, mockQualityGateApplicationStatus } = jest.requireActual( - '../../../../helpers/mocks/quality-gates' - ); - const { MetricKey } = jest.requireActual('../../../../types/metrics'); - return { - getQualityGateProjectStatus: jest.fn().mockResolvedValue( - mockQualityGateProjectStatus({ - status: 'ERROR', - conditions: [ - { - actualValue: '2', - comparator: 'GT', - errorThreshold: '1', - metricKey: MetricKey.new_reliability_rating, - periodIndex: 1, - status: 'ERROR', - }, - { - actualValue: '5', - comparator: 'GT', - errorThreshold: '2.0', - metricKey: MetricKey.bugs, - periodIndex: 0, - status: 'ERROR', - }, - { - actualValue: '2', - comparator: 'GT', - errorThreshold: '1.0', - metricKey: 'unknown_metric', - periodIndex: 0, - status: 'ERROR', - }, - ], - }) - ), - getApplicationQualityGate: jest.fn().mockResolvedValue(mockQualityGateApplicationStatus()), - }; -}); - jest.mock('../../../../api/time-machine', () => { const { MetricKey } = jest.requireActual('../../../../types/metrics'); return { @@ -195,19 +152,58 @@ jest.mock('../../../../components/activity-graph/utils', () => { }); const almHandler = new AlmSettingsServiceMock(); - -beforeEach(jest.clearAllMocks); +let qualityGatesMock: QualityGatesServiceMock; + +beforeAll(() => { + qualityGatesMock = new QualityGatesServiceMock(); + qualityGatesMock.setQualityGateProjectStatus( + mockQualityGateProjectStatus({ + status: 'ERROR', + conditions: [ + { + actualValue: '2', + comparator: 'GT', + errorThreshold: '1', + metricKey: MetricKey.new_reliability_rating, + periodIndex: 1, + status: 'ERROR', + }, + { + actualValue: '5', + comparator: 'GT', + errorThreshold: '2.0', + metricKey: MetricKey.bugs, + periodIndex: 0, + status: 'ERROR', + }, + { + actualValue: '2', + comparator: 'GT', + errorThreshold: '1.0', + metricKey: 'unknown_metric', + periodIndex: 0, + status: 'ERROR', + }, + ], + }) + ); + qualityGatesMock.setApplicationQualityGateStatus(mockQualityGateApplicationStatus()); +}); afterEach(() => { + jest.clearAllMocks(); + qualityGatesMock.reset(); almHandler.reset(); }); describe('project overview', () => { it('should show a successful QG', async () => { const user = userEvent.setup(); - jest - .mocked(getQualityGateProjectStatus) - .mockResolvedValueOnce(mockQualityGateProjectStatus({ status: 'OK' })); + qualityGatesMock.setQualityGateProjectStatus( + mockQualityGateProjectStatus({ + status: 'OK', + }) + ); renderBranchOverview(); // QG panel @@ -236,10 +232,61 @@ describe('project overview', () => { renderBranchOverview(); expect(await screen.findByText('metric.level.OK')).toBeInTheDocument(); - expect(screen.getByText('overview.quality_gate.conditions.cayc.warning')).toBeInTheDocument(); + expect( + screen.queryByText('overview.quality_gate.conditions.cayc.warning') + ).not.toBeInTheDocument(); + }); + + it('should show a successful non-compliant QG as admin', async () => { + jest + .mocked(getQualityGateProjectStatus) + .mockResolvedValueOnce( + mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.NonCompliant }) + ); + qualityGatesMock.setIsAdmin(true); + qualityGatesMock.setGetGateForProjectName('Non Cayc QG'); + + renderBranchOverview(); + + await screen.findByText('metric.level.OK'); + expect( + await screen.findByText('overview.quality_gate.conditions.cayc.warning') + ).toBeInTheDocument(); }); it('should show a failed QG', async () => { + qualityGatesMock.setQualityGateProjectStatus( + mockQualityGateProjectStatus({ + status: 'ERROR', + conditions: [ + { + actualValue: '2', + comparator: 'GT', + errorThreshold: '1', + metricKey: MetricKey.new_reliability_rating, + periodIndex: 1, + status: 'ERROR', + }, + { + actualValue: '5', + comparator: 'GT', + errorThreshold: '2.0', + metricKey: MetricKey.bugs, + periodIndex: 0, + status: 'ERROR', + }, + { + actualValue: '2', + comparator: 'GT', + errorThreshold: '1.0', + metricKey: 'unknown_metric', + periodIndex: 0, + status: 'ERROR', + }, + ], + }) + ); + renderBranchOverview(); expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(); @@ -312,7 +359,7 @@ describe('application overview', () => { }, ], }); - jest.mocked(getApplicationQualityGate).mockResolvedValueOnce(appStatus); + qualityGatesMock.setApplicationQualityGateStatus(appStatus); renderBranchOverview({ component }); expect( @@ -380,7 +427,7 @@ it('should correctly handle graph type storage', async () => { }); function renderBranchOverview(props: Partial = {}) { - renderComponent( + return renderComponent( { )} - {qualityGate.caycStatus === CaycStatus.NonCompliant && ( - + {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && ( +

{translate('quality_gates.cayc_missing.banner.title')}

diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx index cf246948bbb..ebbfcd3de91 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx @@ -63,6 +63,7 @@ export default class Details extends React.PureComponent { fetchDetails = () => { const { qualityGateName } = this.props; + this.setState({ loading: true }); return fetchQualityGate({ name: qualityGateName }).then( (qualityGate) => { diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx index cca136bfc21..20ff6b28536 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import { setQualityGateAsDefault } from '../../../api/quality-gates'; -import { Button } from '../../../components/controls/buttons'; import ModalButton from '../../../components/controls/ModalButton'; import Tooltip from '../../../components/controls/Tooltip'; +import { Button } from '../../../components/controls/buttons'; import AlertWarnIcon from '../../../components/icons/AlertWarnIcon'; import { translate } from '../../../helpers/l10n'; import { CaycStatus, QualityGate } from '../../../types/types'; @@ -64,6 +64,7 @@ export default class DetailsHeader extends React.PureComponent { render() { const { qualityGate } = this.props; const actions = qualityGate.actions || ({} as any); + const canEdit = Boolean(actions?.manageConditions); return (
@@ -72,7 +73,7 @@ export default class DetailsHeader extends React.PureComponent {

{qualityGate.name}

{qualityGate.isBuiltIn && } - {qualityGate.caycStatus === CaycStatus.NonCompliant && ( + {qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && ( } mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}> } /> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx index 979aff85c9c..4cd78cc3a26 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx @@ -49,14 +49,15 @@ export default function List({ qualityGates, currentQualityGate }: Props) { )} {qualityGate.isBuiltIn && } - {qualityGate.caycStatus === CaycStatus.NonCompliant && ( - - - - )} + {qualityGate.caycStatus === CaycStatus.NonCompliant && + qualityGate.actions?.manageConditions && ( + + + + )} ))}
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx index cea9b441406..c786418b010 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx @@ -22,12 +22,10 @@ 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 { RenderContext, renderAppRoutes } from '../../../../helpers/testReactTestingUtils'; import { Feature } from '../../../../types/features'; import routes from '../../routes'; -jest.mock('../../../../api/quality-gates'); - let handler: QualityGatesServiceMock; beforeAll(() => { @@ -346,6 +344,33 @@ it('should show warning banner when CAYC condition is not properly set and shoul expect(overallConditionsWrapper.getByText('Complexity / Function')).toBeInTheDocument(); }); +it('should not warn user when quality gate is not CAYC compliant and user has no permission to edit it', async () => { + const user = userEvent.setup(); + renderQualityGateApp(); + + const nonCompliantQualityGate = await screen.findByRole('link', { name: 'Non Cayc QG' }); + + await user.click(nonCompliantQualityGate); + + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + expect(screen.queryByText('quality_gates.cayc.tooltip.message')).not.toBeInTheDocument(); +}); + +it('should warn user when quality gate is not CAYC compliant and user has permission to edit it', async () => { + const user = userEvent.setup(); + handler.setIsAdmin(true); + renderQualityGateApp(); + + const nonCompliantQualityGate = await screen.findByRole('link', { name: /Non Cayc QG/ }); + + await user.click(nonCompliantQualityGate); + + expect(await screen.findByRole('alert')).toHaveTextContent( + /quality_gates.cayc_missing.banner.title/ + ); + expect(screen.getAllByText('quality_gates.cayc.tooltip.message').length).toBeGreaterThan(0); +}); + it('should show success banner when quality gate is CAYC compliant', async () => { const user = userEvent.setup(); handler.setIsAdmin(true); @@ -626,5 +651,5 @@ describe('The Permissions section', () => { }); function renderQualityGateApp(context?: RenderContext) { - renderAppRoutes('quality_gates', routes, context); + return renderAppRoutes('quality_gates', routes, context); } -- 2.39.5