diff options
author | Zipeng WU <zipeng.wu@sonarsource.com> | 2024-09-19 12:57:46 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-09-25 20:02:53 +0000 |
commit | ff50b95a68ef12f4f46b118bb7b36f7c5cf4f0b7 (patch) | |
tree | a2626ec90712c1fb7b7c9f6124f7c655f02bdcf0 /server/sonar-webserver-webapi | |
parent | 3126d7e9dbd2a36e29a293694e64bd335cfee55d (diff) | |
download | sonarqube-ff50b95a68ef12f4f46b118bb7b36f7c5cf4f0b7.tar.gz sonarqube-ff50b95a68ef12f4f46b118bb7b36f7c5cf4f0b7.zip |
SONAR-23064 Enhance api/qualitygates/search based on ai-assured flag
Diffstat (limited to 'server/sonar-webserver-webapi')
7 files changed, 189 insertions, 54 deletions
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 new file mode 100644 index 00000000000..4225bca5012 --- /dev/null +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifierTest.java @@ -0,0 +1,64 @@ +/* + * 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; + +import java.util.Optional; +import java.util.stream.Stream; +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.project.ProjectDto; + +import static org.assertj.core.api.Assertions.assertThat; +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 AiCodeAssuranceVerifier underTest; + + public static Stream<Arguments> provideParams() { + return Stream.of( + Arguments.of(Edition.COMMUNITY, true, false), + Arguments.of(Edition.COMMUNITY, false, false), + Arguments.of(Edition.DEVELOPER, true, true), + Arguments.of(Edition.DEVELOPER, false, false), + Arguments.of(Edition.ENTERPRISE, true, true), + Arguments.of(Edition.ENTERPRISE, false, false), + Arguments.of(Edition.DATACENTER, true, true), + Arguments.of(Edition.DATACENTER, false, false) + ); + } + + @ParameterizedTest + @MethodSource("provideParams") + void isAiCodeAssured(Edition edition, boolean aiCodeAssuredOnProject, boolean expected) { + when(platformEditionProvider.get()).thenReturn(Optional.of(edition)); + underTest = new AiCodeAssuranceVerifier(platformEditionProvider); + + when(projectDto.getAiCodeAssurance()).thenReturn(aiCodeAssuredOnProject); + + assertThat(underTest.isAiCodeAssured(projectDto.getAiCodeAssurance())).isEqualTo(expected); + assertThat(underTest.isAiCodeAssured(projectDto)).isEqualTo(expected); + } +}
\ No newline at end of file diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java index cfa55b434e5..4552039a188 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java @@ -54,6 +54,7 @@ import org.sonar.db.measure.LiveMeasureDto; import org.sonar.db.metric.MetricDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.property.PropertyDto; +import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier; import org.sonar.server.component.ws.SearchProjectsAction.RequestBuilder; import org.sonar.server.component.ws.SearchProjectsAction.SearchProjectsRequest; import org.sonar.server.es.EsTester; @@ -74,6 +75,7 @@ import static java.util.Optional.ofNullable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY; @@ -193,11 +195,6 @@ public class SearchProjectsActionIT { }; } - @DataProvider - public static List<Edition> editions_supporting_ai_code_assurance() { - return List.of(Edition.DEVELOPER, Edition.ENTERPRISE, Edition.DATACENTER); - } - private final DbClient dbClient = db.getDbClient(); private final DbSession dbSession = db.getSession(); @@ -208,7 +205,9 @@ public class SearchProjectsActionIT { System2.INSTANCE); private final ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client()); - private final WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, index, userSession, editionProviderMock)); + private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier = mock(AiCodeAssuranceVerifier.class); + + private final WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, index, userSession, editionProviderMock, aiCodeAssuranceVerifier)); private final RequestBuilder request = SearchProjectsRequest.builder(); @@ -1392,44 +1391,20 @@ public class SearchProjectsActionIT { } @Test - public void ai_code_assured_is_always_false_for_community_edition() { - when(editionProviderMock.get()).thenReturn(Optional.of(Edition.COMMUNITY)); + @DataProvider({"true", "false"}) + public void return_ai_code_assured(Boolean aiCodeAssured) { + when(aiCodeAssuranceVerifier.isAiCodeAssured(any())).thenReturn(aiCodeAssured); userSession.logIn(); - ProjectDto aiAssuredProject = db.components().insertPublicProject(componentDto -> componentDto.setName("proj_A"), + ProjectDto project = db.components().insertPublicProject(componentDto -> componentDto.setName("proj_A"), projectDto -> projectDto.setAiCodeAssurance(true)).getProjectDto(); - authorizationIndexerTester.allowOnlyAnyone(aiAssuredProject); - ProjectDto notAiAssuredProject = db.components().insertPrivateProject(componentDto -> componentDto.setName("proj_B"), - projectDto -> projectDto.setAiCodeAssurance(false)).getProjectDto(); - authorizationIndexerTester.allowOnlyAnyone(notAiAssuredProject); - index(); - - SearchProjectsWsResponse result = call(request); - - assertThat(result.getComponentsList()).extracting(Component::getKey, Component::getIsAiCodeAssured) - .containsExactly( - tuple(aiAssuredProject.getKey(), false), - tuple(notAiAssuredProject.getKey(), false)); - } - - @Test - @UseDataProvider("editions_supporting_ai_code_assurance") - public void return_ai_code_assured(Edition edition) { - when(editionProviderMock.get()).thenReturn(Optional.of(edition)); - userSession.logIn(); - ProjectDto aiAssuredProject = db.components().insertPublicProject(componentDto -> componentDto.setName("proj_A"), - projectDto -> projectDto.setAiCodeAssurance(true)).getProjectDto(); - authorizationIndexerTester.allowOnlyAnyone(aiAssuredProject); - ProjectDto notAiAssuredProject = db.components().insertPrivateProject(componentDto -> componentDto.setName("proj_B"), - projectDto -> projectDto.setAiCodeAssurance(false)).getProjectDto(); - authorizationIndexerTester.allowOnlyAnyone(notAiAssuredProject); + authorizationIndexerTester.allowOnlyAnyone(project); index(); SearchProjectsWsResponse result = call(request); assertThat(result.getComponentsList()).extracting(Component::getKey, Component::getIsAiCodeAssured) .containsExactly( - tuple(aiAssuredProject.getKey(), true), - tuple(notAiAssuredProject.getKey(), false)); + tuple(project.getKey(), aiCodeAssured)); } @Test diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java index cf8d8ee4a67..a28db92b482 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SearchActionIT.java @@ -28,6 +28,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.db.user.UserDto; +import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier; import org.sonar.server.component.TestComponentFinder; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.tester.UserSessionRule; @@ -39,6 +40,8 @@ import static java.lang.String.valueOf; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.sonar.api.server.ws.WebService.SelectionMode.ALL; import static org.sonar.api.server.ws.WebService.SelectionMode.DESELECTED; import static org.sonar.api.server.ws.WebService.SelectionMode.SELECTED; @@ -58,8 +61,9 @@ public class SearchActionIT { public DbTester db = DbTester.create(); private final DbClient dbClient = db.getDbClient(); + private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier = mock(AiCodeAssuranceVerifier.class); private final SearchAction underTest = new SearchAction(dbClient, userSession, - new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db))); + new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), aiCodeAssuranceVerifier); private final WsActionTester ws = new WsActionTester(underTest); @Test @@ -283,6 +287,30 @@ public class SearchActionIT { .hasMessageContaining("No quality gate has been found for name unknown"); } + + @Test + public void return_disabled_property_in_projects() { + QualityGateDto qualityGate = db.qualityGates().insertQualityGate(); + ProjectDto project1 = db.components().insertPublicProject(componentDto -> componentDto.setName("proj1"), + projectDto -> projectDto.setAiCodeAssurance(true)).getProjectDto(); + ProjectDto project2 = db.components().insertPublicProject(componentDto -> componentDto.setName("proj2"), + projectDto -> projectDto.setAiCodeAssurance(false)).getProjectDto(); + + when(aiCodeAssuranceVerifier.isAiCodeAssured(project1.getAiCodeAssurance())).thenReturn(true); + when(aiCodeAssuranceVerifier.isAiCodeAssured(project2.getAiCodeAssurance())).thenReturn(false); + + SearchResponse response = ws.newRequest() + .setParam(PARAM_GATE_NAME, valueOf(qualityGate.getName())) + .setParam(PARAM_SELECTED, ALL.value()) + .executeProtobuf(SearchResponse.class); + + assertThat(response.getResultsList()) + .extracting(Result::getName, Result::getKey, Result::getIsAiCodeAssured) + .containsExactlyInAnyOrder( + tuple(project1.getName(), project1.getKey(), true), + tuple(project2.getName(), project2.getKey(), false)); + } + @Test public void definition() { WebService.Action action = ws.getDef(); 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 new file mode 100644 index 00000000000..437c6e6dc20 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/AiCodeAssuranceVerifier.java @@ -0,0 +1,47 @@ +/* + * 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; + +import org.sonar.core.platform.EditionProvider; +import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.db.project.ProjectDto; + +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. + * This is to support correctly downgraded instances. + */ +public class AiCodeAssuranceVerifier { + private final boolean isSupported; + + public AiCodeAssuranceVerifier(PlatformEditionProvider editionProvider) { + this.isSupported = editionProvider.get().map(edition -> !edition.equals(COMMUNITY)).orElse(false); + } + + + public boolean isAiCodeAssured(ProjectDto projectDto) { + return isAiCodeAssured(projectDto.getAiCodeAssurance()); + } + + public boolean isAiCodeAssured(boolean projectAiCodeAssurance) { + return isSupported && projectAiCodeAssurance; + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/package-info.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/package-info.java new file mode 100644 index 00000000000..59166002a35 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/ai/code/assurance/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.ai.code.assurance; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java index a0427f9ff86..374d3e96dff 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java @@ -56,6 +56,7 @@ import org.sonar.db.component.SnapshotDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.property.PropertyDto; import org.sonar.db.property.PropertyQuery; +import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier; import org.sonar.db.user.TokenType; import org.sonar.server.component.ws.FilterParser.Criterion; import org.sonar.server.component.ws.SearchProjectsAction.SearchResults.SearchResultsBuilder; @@ -111,13 +112,15 @@ public class SearchProjectsAction implements ComponentsWsAction { private final ProjectMeasuresIndex index; private final UserSession userSession; private final PlatformEditionProvider editionProvider; + private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier; public SearchProjectsAction(DbClient dbClient, ProjectMeasuresIndex index, UserSession userSession, - PlatformEditionProvider editionProvider) { + PlatformEditionProvider editionProvider, AiCodeAssuranceVerifier aiCodeAssuranceVerifier) { this.dbClient = dbClient; this.index = index; this.userSession = userSession; this.editionProvider = editionProvider; + this.aiCodeAssuranceVerifier = aiCodeAssuranceVerifier; } @Override @@ -368,8 +371,7 @@ public class SearchProjectsAction implements ComponentsWsAction { } private SearchProjectsWsResponse buildResponse(SearchProjectsRequest request, SearchResults searchResults) { - Function<ProjectDto, Component> dbToWsComponent = new DbToWsComponent(request, searchResults, userSession.isLoggedIn(), - editionProvider.get().orElse(null)); + Function<ProjectDto, Component> dbToWsComponent = new DbToWsComponent(request, searchResults, userSession.isLoggedIn()); return Stream.of(SearchProjectsWsResponse.newBuilder()) .map(response -> response.setPaging(Common.Paging.newBuilder() @@ -462,23 +464,21 @@ public class SearchProjectsAction implements ComponentsWsAction { } } - private static class DbToWsComponent implements Function<ProjectDto, Component> { + private class DbToWsComponent implements Function<ProjectDto, Component> { private final SearchProjectsRequest request; private final Component.Builder wsComponent; private final Set<String> favoriteProjectUuids; private final boolean isUserLoggedIn; private final Map<String, SnapshotDto> analysisByProjectUuid; private final Map<String, Long> applicationsLeakPeriod; - private final Edition edition; - private DbToWsComponent(SearchProjectsRequest request, SearchResults searchResults, boolean isUserLoggedIn, @Nullable Edition edition) { + private DbToWsComponent(SearchProjectsRequest request, SearchResults searchResults, boolean isUserLoggedIn) { this.request = request; this.analysisByProjectUuid = searchResults.analysisByProjectUuid; this.applicationsLeakPeriod = searchResults.applicationsLeakPeriods; this.wsComponent = Component.newBuilder(); this.favoriteProjectUuids = searchResults.favoriteProjectUuids; this.isUserLoggedIn = isUserLoggedIn; - this.edition = edition; } @Override @@ -489,7 +489,7 @@ public class SearchProjectsAction implements ComponentsWsAction { .setName(dbProject.getName()) .setQualifier(dbProject.getQualifier()) .setVisibility(Visibility.getLabel(dbProject.isPrivate())) - .setIsAiCodeAssured(isAiCodeAssured(dbProject)); + .setIsAiCodeAssured(aiCodeAssuranceVerifier.isAiCodeAssured(dbProject)); wsComponent.getTagsBuilder().addAllTags(dbProject.getTags()); SnapshotDto snapshotDto = analysisByProjectUuid.get(dbProject.getUuid()); @@ -513,13 +513,7 @@ public class SearchProjectsAction implements ComponentsWsAction { return wsComponent.build(); } - /** - * Make sure that for {@link Edition#COMMUNITY} we'll always get false, no matter of the value in database. - * This is to support correctly downgraded instances. - */ - private boolean isAiCodeAssured(ProjectDto dbProject) { - return edition != null && !edition.equals(Edition.COMMUNITY) && dbProject.getAiCodeAssurance(); - } + } public static class SearchResults { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java index 3126f83f0ab..150bbda8bfb 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java @@ -33,6 +33,7 @@ import org.sonar.db.DbSession; import org.sonar.db.qualitygate.ProjectQgateAssociationDto; import org.sonar.db.qualitygate.ProjectQgateAssociationQuery; import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Qualitygates; @@ -51,11 +52,13 @@ public class SearchAction implements QualityGatesWsAction { private final DbClient dbClient; private final UserSession userSession; private final QualityGatesWsSupport wsSupport; + private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier; - public SearchAction(DbClient dbClient, UserSession userSession, QualityGatesWsSupport wsSupport) { + public SearchAction(DbClient dbClient, UserSession userSession, QualityGatesWsSupport wsSupport, AiCodeAssuranceVerifier aiCodeAssuranceVerifier) { this.dbClient = dbClient; this.userSession = userSession; this.wsSupport = wsSupport; + this.aiCodeAssuranceVerifier = aiCodeAssuranceVerifier; } @Override @@ -134,7 +137,8 @@ public class SearchAction implements QualityGatesWsAction { createResponse.addResultsBuilder() .setName(project.getName()) .setKey(project.getKey()) - .setSelected(project.getGateUuid() != null); + .setSelected(project.getGateUuid() != null) + .setIsAiCodeAssured(aiCodeAssuranceVerifier.isAiCodeAssured(project.getAiCodeAssurance())); } writeProtobuf(createResponse.build(), request, response); |