From 2c496d7199d58ce76f1ec513220bde1127c68358 Mon Sep 17 00:00:00 2001 From: Anita Stanisz <106669481+anita-stanisz-sonarsource@users.noreply.github.com> Date: Mon, 25 Nov 2024 14:43:25 +0100 Subject: SONAR-23619 Update get_ai_code_assurance endpoint (#12342) --- .../legacy/TelemetryDataLoaderImplIT.java | 6 +- .../telemetry/legacy/TelemetryDataLoaderImpl.java | 2 +- .../assurance/AiCodeAssuranceVerifierTest.java | 130 +++++++++++++++++++-- .../server/ai/code/assurance/AiCodeAssurance.java | 26 +++++ .../ai/code/assurance/AiCodeAssuranceVerifier.java | 35 +++++- 5 files changed, 178 insertions(+), 21 deletions(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssurance.java diff --git a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java index 93b375711a4..6580a0fd8b4 100644 --- a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java +++ b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java @@ -184,7 +184,7 @@ class TelemetryDataLoaderImplIT { MetricDto nclocDistrib = db.measures().insertMetric(m -> m.setKey(NCLOC_LANGUAGE_DISTRIBUTION_KEY)); ProjectData projectData1 = db.components().insertPrivateProject(ComponentDbTester.defaults(), projectDto -> projectDto.setContainsAiCode(true)); - when(aiCodeAssuranceVerifier.isAiCodeAssured(projectData1.getProjectDto().getContainsAiCode())).thenReturn(true); + when(aiCodeAssuranceVerifier.isAiCodeAssured(projectData1.getProjectDto())).thenReturn(true); ComponentDto mainBranch1 = projectData1.getMainBranchComponent(); var branch1 = db.components().insertProjectBranch(mainBranch1, branchDto -> branchDto.setKey("reference")); @@ -203,7 +203,7 @@ class TelemetryDataLoaderImplIT { db.measures().insertMeasure(branch2, m -> m.addValue(technicalDebtDto.getKey(), 7d)); ProjectData projectData2 = db.components().insertPrivateProject(ComponentDbTester.defaults(), projectDto -> projectDto.setContainsAiCode(false)); - when(aiCodeAssuranceVerifier.isAiCodeAssured(projectData2.getProjectDto().getContainsAiCode())).thenReturn(false); + when(aiCodeAssuranceVerifier.isAiCodeAssured(projectData2.getProjectDto())).thenReturn(false); ComponentDto mainBranch2 = projectData2.getMainBranchComponent(); db.measures().insertMeasure(mainBranch2, m -> m.addValue(lines.getKey(), 200d)); @@ -544,7 +544,7 @@ class TelemetryDataLoaderImplIT { }, projectDto -> projectDto.setContainsAiCode(expected)).getProjectDto(); - when(aiCodeAssuranceVerifier.isAiCodeAssured(project1.getContainsAiCode())).thenReturn(expected); + when(aiCodeAssuranceVerifier.isAiCodeAssured(project1)).thenReturn(expected); TelemetryData data = communityUnderTest.load(); diff --git a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java index 7d346f0d094..2d4e54bfe0d 100644 --- a/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java +++ b/server/sonar-telemetry/src/main/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImpl.java @@ -334,7 +334,7 @@ public class TelemetryDataLoaderImpl implements TelemetryDataLoader { .setExternalSecurityReportExportedAt(securityReportExportedAtByProjectUuid.get(projectUuid)) .setCreationMethod(project.getCreationMethod()) .setMonorepo(resolveMonorepo(almAndUrlAndMonorepoByProject, projectUuid)) - .setIsAiCodeAssured(aiCodeAssuranceVerifier.isAiCodeAssured(project.getContainsAiCode())) + .setIsAiCodeAssured(aiCodeAssuranceVerifier.isAiCodeAssured(project)) .build(); projectStatistics.add(stats); } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java index 697ac4dd2a1..3479bb80164 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java @@ -21,23 +21,140 @@ package org.sonar.server.ai.code.assurance; import java.util.Optional; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.sonar.core.platform.EditionProvider.Edition; import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.DbClient; import org.sonar.db.project.ProjectDto; +import org.sonar.db.qualitygate.QualityGateDao; +import org.sonar.db.qualitygate.QualityGateDto; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class AiCodeAssuranceVerifierTest { private final ProjectDto projectDto = mock(ProjectDto.class); private final PlatformEditionProvider platformEditionProvider = mock(PlatformEditionProvider.class); + private final DbClient dbClient = mock(DbClient.class); + private final QualityGateDao qualityGateDao = mock(QualityGateDao.class); private AiCodeAssuranceVerifier underTest; - public static Stream provideParams() { + @ParameterizedTest + @MethodSource("provideParams") + void isAiCodeAssured(Edition edition, boolean containsAiCode, boolean expected) { + when(platformEditionProvider.get()).thenReturn(Optional.of(edition)); + underTest = new AiCodeAssuranceVerifier(platformEditionProvider, dbClient); + + when(projectDto.getContainsAiCode()).thenReturn(containsAiCode); + + assertThat(underTest.isAiCodeAssured(projectDto.getContainsAiCode())).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("isAiCodeAssuredForProject") + void isAiCodeAssuredForProject(boolean containsAiCode, boolean aiCodeSupportedQg, boolean expected) { + when(platformEditionProvider.get()).thenReturn(Optional.of(Edition.DEVELOPER)); + underTest = new AiCodeAssuranceVerifier(platformEditionProvider, dbClient); + mockProjectAndQualityGate(containsAiCode, aiCodeSupportedQg); + + when(projectDto.getContainsAiCode()).thenReturn(containsAiCode); + + assertThat(underTest.isAiCodeAssured(projectDto)).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("paramsForGetAiCodeAssurance") + void getAiCodeAssurance(Edition edition, boolean containsAiCode, boolean aiCodeSupportedQg, AiCodeAssurance expected) { + when(platformEditionProvider.get()).thenReturn(Optional.of(edition)); + underTest = new AiCodeAssuranceVerifier(platformEditionProvider, dbClient); + mockProjectAndQualityGate(containsAiCode, aiCodeSupportedQg); + + assertThat(underTest.getAiCodeAssurance(projectDto)).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("paramsForDefaultQualityGate") + void getAiCodeAssurance_fallback_on_default_qg_when_no_qg_defined_and_contains_ai_code(boolean aiCodeSupportedQg, + AiCodeAssurance expected) { + when(platformEditionProvider.get()).thenReturn(Optional.of(Edition.DEVELOPER)); + underTest = new AiCodeAssuranceVerifier(platformEditionProvider, dbClient); + when(projectDto.getContainsAiCode()).thenReturn(true); + mockDefaultQg(aiCodeSupportedQg); + + assertThat(underTest.getAiCodeAssurance(projectDto)).isEqualTo(expected); + } + + @Test + void getAiCodeAssurance_no_exception_when_no_default_qg_and_no_qg_defined_and_contains_ai_code() { + when(platformEditionProvider.get()).thenReturn(Optional.of(Edition.DEVELOPER)); + underTest = new AiCodeAssuranceVerifier(platformEditionProvider, dbClient); + when(projectDto.getContainsAiCode()).thenReturn(true); + when(dbClient.qualityGateDao()).thenReturn(qualityGateDao); + when(qualityGateDao.selectByProjectUuid(any(), any())).thenReturn(null); + when(qualityGateDao.selectDefault(any())).thenReturn(null); + + assertThat(underTest.getAiCodeAssurance(projectDto)).isEqualTo(AiCodeAssurance.CONTAINS_AI_CODE); + } + + private void mockDefaultQg(boolean aiCodeSupportedQg) { + when(dbClient.qualityGateDao()).thenReturn(qualityGateDao); + when(qualityGateDao.selectByProjectUuid(any(), any())).thenReturn(null); + QualityGateDto defaultQualityGate = mock(QualityGateDto.class); + when(defaultQualityGate.isAiCodeSupported()).thenReturn(aiCodeSupportedQg); + when(qualityGateDao.selectDefault(any())).thenReturn(defaultQualityGate); + } + + private void mockProjectAndQualityGate(boolean containsAiCode, boolean aiCodeSupportedQg) { + when(projectDto.getContainsAiCode()).thenReturn(containsAiCode); + when(dbClient.qualityGateDao()).thenReturn(qualityGateDao); + QualityGateDto qualityGateDto = mock(QualityGateDto.class); + when(qualityGateDto.isAiCodeSupported()).thenReturn(aiCodeSupportedQg); + when(qualityGateDao.selectByProjectUuid(any(), any())).thenReturn(qualityGateDto); + } + + private static Stream paramsForDefaultQualityGate() { + return Stream.of( + Arguments.of(true, AiCodeAssurance.AI_CODE_ASSURED), + Arguments.of(false, AiCodeAssurance.CONTAINS_AI_CODE) + ); + } + + private static Stream paramsForGetAiCodeAssurance() { + return Stream.of( + Arguments.of(Edition.COMMUNITY, true, true, AiCodeAssurance.NONE), + Arguments.of(Edition.COMMUNITY, true, false, AiCodeAssurance.NONE), + Arguments.of(Edition.COMMUNITY, false, false, AiCodeAssurance.NONE), + Arguments.of(Edition.COMMUNITY, false, true, AiCodeAssurance.NONE), + Arguments.of(Edition.DEVELOPER, true, true, AiCodeAssurance.AI_CODE_ASSURED), + Arguments.of(Edition.DEVELOPER, true, false, AiCodeAssurance.CONTAINS_AI_CODE), + Arguments.of(Edition.DEVELOPER, false, false, AiCodeAssurance.NONE), + Arguments.of(Edition.DEVELOPER, false, true, AiCodeAssurance.NONE), + Arguments.of(Edition.ENTERPRISE, true, true, AiCodeAssurance.AI_CODE_ASSURED), + Arguments.of(Edition.ENTERPRISE, true, false, AiCodeAssurance.CONTAINS_AI_CODE), + Arguments.of(Edition.ENTERPRISE, false, false, AiCodeAssurance.NONE), + Arguments.of(Edition.ENTERPRISE, false, true, AiCodeAssurance.NONE), + Arguments.of(Edition.DATACENTER, true, true, AiCodeAssurance.AI_CODE_ASSURED), + Arguments.of(Edition.DATACENTER, true, false, AiCodeAssurance.CONTAINS_AI_CODE), + Arguments.of(Edition.DATACENTER, false, false, AiCodeAssurance.NONE), + Arguments.of(Edition.DATACENTER, false, true, AiCodeAssurance.NONE) + ); + } + + private static Stream isAiCodeAssuredForProject() { + return Stream.of( + Arguments.of(true, true, true), + Arguments.of(true, false, false), + Arguments.of(false, false, false), + Arguments.of(false, true, false) + ); + } + + private static Stream provideParams() { return Stream.of( Arguments.of(Edition.COMMUNITY, true, false), Arguments.of(Edition.COMMUNITY, false, false), @@ -50,15 +167,4 @@ class AiCodeAssuranceVerifierTest { ); } - @ParameterizedTest - @MethodSource("provideParams") - void isAiCodeAssured(Edition edition, boolean aiCodeAssuredOnProject, boolean expected) { - when(platformEditionProvider.get()).thenReturn(Optional.of(edition)); - underTest = new AiCodeAssuranceVerifier(platformEditionProvider); - - when(projectDto.getContainsAiCode()).thenReturn(aiCodeAssuredOnProject); - - assertThat(underTest.isAiCodeAssured(projectDto.getContainsAiCode())).isEqualTo(expected); - assertThat(underTest.isAiCodeAssured(projectDto)).isEqualTo(expected); - } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssurance.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssurance.java new file mode 100644 index 00000000000..110fbbcdc9a --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssurance.java @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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. + */ +package org.sonar.server.ai.code.assurance; + +public enum AiCodeAssurance { + CONTAINS_AI_CODE, + AI_CODE_ASSURED, + NONE +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java index 7e1d40192cd..7e8a64fb6a0 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java @@ -21,27 +21,52 @@ package org.sonar.server.ai.code.assurance; import org.sonar.core.platform.EditionProvider; import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; import org.sonar.db.project.ProjectDto; +import org.sonar.db.qualitygate.QualityGateDto; import static org.sonar.core.platform.EditionProvider.Edition.COMMUNITY; /** - * Make sure that for {@link EditionProvider.Edition#COMMUNITY} we'll always get false, no matter of the value in database. + * Make sure that for {@link EditionProvider.Edition#COMMUNITY} we'll always get false or {@link AiCodeAssurance#NONE}, no matter of the + * value in database. * This is to support correctly downgraded instances. */ public class AiCodeAssuranceVerifier { private final boolean isSupported; + private final DbClient dbClient; - public AiCodeAssuranceVerifier(PlatformEditionProvider editionProvider) { + public AiCodeAssuranceVerifier(PlatformEditionProvider editionProvider, DbClient dbClient) { + this.dbClient = dbClient; this.isSupported = editionProvider.get().map(edition -> !edition.equals(COMMUNITY)).orElse(false); } + public AiCodeAssurance getAiCodeAssurance(ProjectDto projectDto) { + if (!isSupported) { + return AiCodeAssurance.NONE; + } + if (!projectDto.getContainsAiCode()) { + return AiCodeAssurance.NONE; + } + try (DbSession dbSession = dbClient.openSession(false)) { + QualityGateDto qualityGate = dbClient.qualityGateDao().selectByProjectUuid(dbSession, projectDto.getUuid()); + if (qualityGate == null) { + qualityGate = dbClient.qualityGateDao().selectDefault(dbSession); + } + if (qualityGate != null && qualityGate.isAiCodeSupported()) { + return AiCodeAssurance.AI_CODE_ASSURED; + } + return AiCodeAssurance.CONTAINS_AI_CODE; + } + } public boolean isAiCodeAssured(ProjectDto projectDto) { - return isAiCodeAssured(projectDto.getContainsAiCode()); + return AiCodeAssurance.AI_CODE_ASSURED.equals(getAiCodeAssurance(projectDto)); } - public boolean isAiCodeAssured(boolean projectAiCodeAssurance) { - return isSupported && projectAiCodeAssurance; + @Deprecated(forRemoval = true) + public boolean isAiCodeAssured(boolean containsAiCode) { + return isSupported && containsAiCode; } } -- cgit v1.2.3