"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("6.3", "the response returns the issue with all its details"),
new Change("6.5", "the database ids of the components are removed from the response"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."))
.setDescription("Assign/Unassign an issue. Requires authentication and Browse permission on project")
.setSince("3.6")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("6.5", "the database ids of the components are removed from the response"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."))
.setHandler(this)
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."),
new Change("6.5", "the database ids of the components are removed from the response"),
new Change("6.3", "the response returns the issue with all its details"),
"The transitions involving security hotspots require the permission 'Administer Security Hotspot'.")
.setSince("3.6")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("8.1", format("transitions '%s' and '%s' are no more supported", SET_AS_IN_REVIEW, OPEN_AS_VULNERABILITY)),
new Change("7.8", format("added '%s', %s, %s and %s transitions for security hotspots ", SET_AS_IN_REVIEW, RESOLVE_AS_REVIEWED, OPEN_AS_VULNERABILITY, RESET_AS_TO_REVIEW)),
new Change("7.3", "added transitions for security hotspots"),
"Requires authentication and the following permission: 'Browse' on the project of the specified issue.")
.setSince("3.6")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("6.3", "the response returns the issue with all its details"),
new Change("6.3", format("the 'key' parameter has been renamed %s", PARAM_COMMENT)),
new Change("6.5", "the database ids of the components are removed from the response"),
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6")
.setChangelog(
-
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
+ new Change("9.6", "New possible value for 'additionalFields' parameter: 'ruleDescriptionContextKey'"),
new Change("9.6", "Facet 'moduleUuids' is dropped."),
new Change("9.4", format("Parameter '%s' is deprecated, please use '%s' instead", PARAM_SINCE_LEAK_PERIOD, PARAM_IN_NEW_CODE_PERIOD)),
new Change("9.2", "Response field 'quickFixAvailable' added"),
.filter(FACETS_REQUIRING_PROJECT::contains)
.collect(toSet());
checkArgument(facetsRequiringProjectParameter.isEmpty() ||
- (!query.projectUuids().isEmpty()), "Facet(s) '%s' require to also filter by project",
+ (!query.projectUuids().isEmpty()), "Facet(s) '%s' require to also filter by project",
String.join(",", facetsRequiringProjectParameter));
// execute request
LANGUAGES("languages"),
RULES("rules"),
TRANSITIONS("transitions"),
- USERS("users");
+ USERS("users"),
+ RULE_DESCRIPTION_CONTEXT_KEY("ruleDescriptionContextKey");
public static final String ALL_ALIAS = "_all";
static final EnumSet<SearchAdditionalField> ALL_ADDITIONAL_FIELDS = EnumSet.allOf(SearchAdditionalField.class);
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
-import org.sonar.api.resources.Qualifiers;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.DateUtils;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
+import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
import static org.sonar.api.rule.RuleKey.EXTERNAL_RULE_REPO_PREFIX;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.server.issue.index.IssueIndex.FACET_ASSIGNED_TO_ME;
import static org.sonar.server.issue.index.IssueIndex.FACET_PROJECTS;
+import static org.sonar.server.issue.ws.SearchAdditionalField.ACTIONS;
+import static org.sonar.server.issue.ws.SearchAdditionalField.ALL_ADDITIONAL_FIELDS;
+import static org.sonar.server.issue.ws.SearchAdditionalField.COMMENTS;
+import static org.sonar.server.issue.ws.SearchAdditionalField.RULE_DESCRIPTION_CONTEXT_KEY;
+import static org.sonar.server.issue.ws.SearchAdditionalField.TRANSITIONS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
formatPaging(paging, response);
ofNullable(data.getEffortTotal()).ifPresent(response::setEffortTotal);
- response.addAllIssues(formatIssues(fields, data));
+ response.addAllIssues(createIssues(fields, data));
response.addAllComponents(formatComponents(data));
formatFacets(data, facets, response);
if (fields.contains(SearchAdditionalField.RULES)) {
Operation.Builder response = Operation.newBuilder();
if (data.getIssues().size() == 1) {
- Issue.Builder issueBuilder = Issue.newBuilder();
IssueDto dto = data.getIssues().get(0);
- formatIssue(issueBuilder, dto, data);
- formatIssueActions(data, issueBuilder, dto);
- formatIssueTransitions(data, issueBuilder, dto);
- formatIssueComments(data, issueBuilder, dto);
- response.setIssue(issueBuilder.build());
+ response.setIssue(createIssue(ALL_ADDITIONAL_FIELDS, data, dto));
}
response.addAllComponents(formatComponents(data));
response.addAllRules(formatRules(data).getRulesList());
.setTotal(paging.total());
}
- private List<Issues.Issue> formatIssues(Set<SearchAdditionalField> fields, SearchResponseData data) {
- List<Issues.Issue> result = new ArrayList<>();
+ private List<Issues.Issue> createIssues(Collection<SearchAdditionalField> fields, SearchResponseData data) {
+ return data.getIssues().stream()
+ .map(dto -> createIssue(fields, data, dto))
+ .collect(toList());
+ }
+
+ private Issue createIssue(Collection<SearchAdditionalField> fields, SearchResponseData data, IssueDto dto) {
Issue.Builder issueBuilder = Issue.newBuilder();
- data.getIssues().forEach(dto -> {
- issueBuilder.clear();
- formatIssue(issueBuilder, dto, data);
- if (fields.contains(SearchAdditionalField.ACTIONS)) {
- formatIssueActions(data, issueBuilder, dto);
- }
- if (fields.contains(SearchAdditionalField.TRANSITIONS)) {
- formatIssueTransitions(data, issueBuilder, dto);
- }
- if (fields.contains(SearchAdditionalField.COMMENTS)) {
- formatIssueComments(data, issueBuilder, dto);
- }
- result.add(issueBuilder.build());
- });
- return result;
+ addMandatoryFieldsToIssueBuilder(issueBuilder, dto, data);
+ addAdditionalFieldsToIssueBuilder(fields, data, dto, issueBuilder);
+ return issueBuilder.build();
}
- private void formatIssue(Issue.Builder issueBuilder, IssueDto dto, SearchResponseData data) {
+ private void addMandatoryFieldsToIssueBuilder(Issue.Builder issueBuilder, IssueDto dto, SearchResponseData data) {
issueBuilder.setKey(dto.getKey());
issueBuilder.setType(Common.RuleType.forNumber(dto.getType()));
ofNullable(dto.getIssueUpdateDate()).map(DateUtils::formatDateTime).ifPresent(issueBuilder::setUpdateDate);
ofNullable(dto.getIssueCloseDate()).map(DateUtils::formatDateTime).ifPresent(issueBuilder::setCloseDate);
- ofNullable(dto.isQuickFixAvailable())
+ Optional.of(dto.isQuickFixAvailable())
.ifPresentOrElse(issueBuilder::setQuickFixAvailable, () -> issueBuilder.setQuickFixAvailable(false));
- issueBuilder.setScope(Qualifiers.UNIT_TEST_FILE.equals(component.qualifier()) ? IssueScope.TEST.name() : IssueScope.MAIN.name());
+ issueBuilder.setScope(UNIT_TEST_FILE.equals(component.qualifier()) ? IssueScope.TEST.name() : IssueScope.MAIN.name());
+ }
+
+ private static void addAdditionalFieldsToIssueBuilder(Collection<SearchAdditionalField> fields, SearchResponseData data, IssueDto dto, Issue.Builder issueBuilder) {
+ if (fields.contains(ACTIONS)) {
+ issueBuilder.setActions(createIssueActions(data, dto));
+ }
+ if (fields.contains(TRANSITIONS)) {
+ issueBuilder.setTransitions(createIssueTransition(data, dto));
+ }
+ if (fields.contains(COMMENTS)) {
+ issueBuilder.setComments(createIssueComments(data, dto));
+ }
+ if (fields.contains(RULE_DESCRIPTION_CONTEXT_KEY)) {
+ dto.getOptionalRuleDescriptionContextKey().ifPresent(issueBuilder::setRuleDescriptionContextKey);
+ }
}
private static String engineNameFrom(RuleKey ruleKey) {
}
}
- private static void formatIssueTransitions(SearchResponseData data, Issue.Builder wsIssue, IssueDto dto) {
+ private static Transitions createIssueTransition(SearchResponseData data, IssueDto dto) {
Transitions.Builder wsTransitions = Transitions.newBuilder();
List<Transition> transitions = data.getTransitionsForIssueKey(dto.getKey());
if (transitions != null) {
wsTransitions.addTransitions(transition.key());
}
}
- wsIssue.setTransitions(wsTransitions);
+ return wsTransitions.build();
}
- private static void formatIssueActions(SearchResponseData data, Issue.Builder wsIssue, IssueDto dto) {
+ private static Actions createIssueActions(SearchResponseData data, IssueDto dto) {
Actions.Builder wsActions = Actions.newBuilder();
List<String> actions = data.getActionsForIssueKey(dto.getKey());
if (actions != null) {
wsActions.addAllActions(actions);
}
- wsIssue.setActions(wsActions);
+ return wsActions.build();
}
- private static void formatIssueComments(SearchResponseData data, Issue.Builder wsIssue, IssueDto dto) {
+ private static Comments createIssueComments(SearchResponseData data, IssueDto dto) {
Comments.Builder wsComments = Comments.newBuilder();
List<IssueChangeDto> comments = data.getCommentsForIssueKey(dto.getKey());
if (comments != null) {
wsComments.addComments(wsComment);
}
}
- wsIssue.setComments(wsComments);
+ return wsComments.build();
}
private Common.Rules.Builder formatRules(SearchResponseData data) {
"</ul>")
.setSince("3.6")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("6.5", "the database ids of the components are removed from the response"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."))
.setHandler(this)
.setDescription("Set tags on an issue. <br/>" +
"Requires authentication and Browse permission on project")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("6.5", "the database ids of the components are removed from the response"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."),
new Change("6.4", "response contains issue information instead of list of tags"))
"</ul>")
.setSince("5.5")
.setChangelog(
+ new Change("9.6", "Response field 'ruleDescriptionContextKey' added"),
new Change("6.5", "the database ids of the components are removed from the response"),
new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."))
.setHandler(this)
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
]
}
],
- "quickFixAvailable": false
+ "quickFixAvailable": false,
+ "ruleDescriptionContextKey": "spring"
}
],
"components": [
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
],
"creationDate": "2016-11-25T13:50:24+0100",
"updateDate": "2017-01-09T13:51:12+0100",
- "type": "CODE_SMELL"
+ "type": "CODE_SMELL",
+ "ruleDescriptionContextKey": "spring"
},
"components": [
{
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.StringUtils.EMPTY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.groups.Tuple.tuple;
.hasMessage("If both provided, the following parameters sinceLeakPeriod and inNewCodePeriod must match.");
}
+ @Test
+ public void search_when_additional_field_set_return_context_key() {
+ insertIssues(issue -> issue.setRuleDescriptionContextKey("spring"));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam("additionalFields", "ruleDescriptionContextKey")
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList()).isNotEmpty()
+ .extracting(Issue::getRuleDescriptionContextKey).containsExactly("spring");
+ }
+
+ @Test
+ public void search_when_no_additional_field_return_empty_context_key() {
+ insertIssues(issue -> issue.setRuleDescriptionContextKey("spring"));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList()).isNotEmpty()
+ .extracting(Issue::getRuleDescriptionContextKey).containsExactly(EMPTY);
+ }
+
+ @Test
+ public void search_when_additional_field_but_no_context_key_return_empty_context_key() {
+ insertIssues(issue -> issue.setRuleDescriptionContextKey(null));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam("additionalFields", "ruleDescriptionContextKey")
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList()).isNotEmpty()
+ .extracting(Issue::getRuleDescriptionContextKey).containsExactly(EMPTY);
+ }
+
+ @Test
+ public void search_when_additional_field_set_to_all_return_context_key() {
+ insertIssues(issue -> issue.setRuleDescriptionContextKey("spring"));
+ indexPermissionsAndIssues();
+
+ SearchWsResponse response = ws.newRequest()
+ .setParam("additionalFields", "_all")
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getIssuesList()).isNotEmpty()
+ .extracting(Issue::getRuleDescriptionContextKey).containsExactly("spring");
+ }
+
private RuleDto newIssueRule() {
RuleDto rule = newRule(XOO_X1, createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc"))
.setLanguage("xoo")
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.issue.ws;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.TextRangeResponseFormatter;
+import org.sonar.server.issue.workflow.Transition;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.Issues.Operation;
+
+import static java.lang.System.currentTimeMillis;
+import static java.util.stream.Collectors.toList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
+import static org.sonar.api.rule.RuleKey.EXTERNAL_RULE_REPO_PREFIX;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.db.component.ComponentDto.BRANCH_KEY_SEPARATOR;
+import static org.sonar.db.component.ComponentDto.PULL_REQUEST_SEPARATOR;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.issue.IssueTesting.newIssue;
+import static org.sonar.db.issue.IssueTesting.newIssuechangeDto;
+import static org.sonar.db.rule.RuleTesting.newRule;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.issue.index.IssueScope.MAIN;
+import static org.sonar.server.issue.index.IssueScope.TEST;
+
+@RunWith(MockitoJUnitRunner.class)
+public class SearchResponseFormatFormatOperationTest {
+
+ private SearchResponseFormat searchResponseFormat;
+
+ private final Durations durations = new Durations();
+ @Mock
+ private Languages languages;
+ @Mock
+ private TextRangeResponseFormatter textRangeResponseFormatter;
+ @Mock
+ private UserResponseFormatter userResponseFormatter;
+ @Mock
+ private Common.User user;
+
+ private SearchResponseData searchResponseData;
+ private IssueDto issueDto;
+ private ComponentDto componentDto;
+ private UserDto userDto;
+
+
+ @Before
+ public void setUp() {
+ searchResponseFormat = new SearchResponseFormat(durations, languages, textRangeResponseFormatter, userResponseFormatter);
+ searchResponseData = newSearchResponseData();
+ issueDto = searchResponseData.getIssues().get(0);
+ componentDto = searchResponseData.getComponents().iterator().next();
+ userDto = searchResponseData.getUsers().get(0);
+ when(userResponseFormatter.formatUser(any(Common.User.Builder.class), eq(userDto))).thenReturn(user);
+ }
+
+ @Test
+ public void formatOperation_should_add_components_to_response() {
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getComponentsList()).hasSize(1);
+ assertThat(result.getComponentsList().get(0).getKey()).isEqualTo(issueDto.getComponentKey());
+ }
+
+ @Test
+ public void formatOperation_should_add_rules_to_response() {
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getRulesList()).hasSize(1);
+ assertThat(result.getRulesList().get(0).getKey()).isEqualTo(issueDto.getRuleKey().toString());
+ }
+
+ @Test
+ public void formatOperation_should_add_users_to_response() {
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getUsersList()).hasSize(1);
+ assertThat(result.getUsers(0)).isSameAs(user);
+ }
+
+ @Test
+ public void formatOperation_should_add_issue_to_response() {
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertIssueEqualsIssueDto(result.getIssue(), issueDto);
+ }
+
+ private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) {
+ assertThat(issue.getKey()).isEqualTo(issueDto.getKey());
+ assertThat(issue.getType().getNumber()).isEqualTo(issueDto.getType());
+ assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey());
+ assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString());
+ assertThat(issue.getSeverity()).hasToString(issueDto.getSeverity());
+ assertThat(issue.getAssignee()).isEqualTo(userDto.getLogin());
+ assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
+ assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
+ assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
+ assertThat(new ArrayList<>(issue.getTagsList())).containsExactlyInAnyOrderElementsOf(issueDto.getTags());
+ assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
+ assertThat(issue.getHash()).isEqualTo(issueDto.getChecksum());
+ assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
+ assertThat(issue.getCreationDate()).isEqualTo(formatDateTime(issueDto.getIssueCreationDate()));
+ assertThat(issue.getUpdateDate()).isEqualTo(formatDateTime(issueDto.getIssueUpdateDate()));
+ assertThat(issue.getCloseDate()).isEqualTo(formatDateTime(issueDto.getIssueCloseDate()));
+ assertThat(issue.getQuickFixAvailable()).isEqualTo(issueDto.isQuickFixAvailable());
+ assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issueDto.getOptionalRuleDescriptionContextKey().orElse(null));
+ }
+
+ @Test
+ public void formatOperation_should_not_add_issue_when_several_issue() {
+ searchResponseData = new SearchResponseData(List.of(createIssue(), createIssue()));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue()).isEqualTo(Issue.getDefaultInstance());
+ }
+
+ private static IssueDto createIssue() {
+ RuleDto ruleDto = newRule();
+ String projectUuid = "project_uuid_" + randomAlphanumeric(5);
+ ComponentDto projectDto = newPrivateProjectDto();
+ projectDto.setProjectUuid(projectUuid);
+ return newIssue(ruleDto, projectUuid, "project_key_" + randomAlphanumeric(5), projectDto);
+ }
+
+ @Test
+ public void formatOperation_should_add_branch_on_issue() {
+ componentDto.setDbKey(randomAlphanumeric(5) + BRANCH_KEY_SEPARATOR + randomAlphanumeric(5));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getBranch()).isEqualTo(componentDto.getBranch());
+ }
+
+ @Test
+ public void formatOperation_should_add_pullrequest_on_issue() {
+ componentDto.setDbKey(randomAlphanumeric(5) + PULL_REQUEST_SEPARATOR + randomAlphanumeric(5));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getPullRequest()).isEqualTo(componentDto.getPullRequest());
+ }
+
+ @Test
+ public void formatOperation_should_add_project_on_issue() {
+ issueDto.setProjectUuid(componentDto.uuid());
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getProject()).isEqualTo(componentDto.getKey());
+ }
+
+ @Test
+ public void formatOperation_should_add_external_rule_engine_on_issue() {
+ issueDto.setExternal(true);
+ String expected = randomAlphanumeric(5);
+ issueDto.setRuleKey(EXTERNAL_RULE_REPO_PREFIX + expected, randomAlphanumeric(5));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getExternalRuleEngine()).isEqualTo(expected);
+ }
+
+ @Test
+ public void formatOperation_should_add_effort_and_debt_on_issue() {
+ long effort = 60L;
+ issueDto.setEffort(effort);
+ String expected = durations.encode(Duration.create(effort));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getEffort()).isEqualTo(expected);
+ assertThat(result.getIssue().getDebt()).isEqualTo(expected);
+ }
+
+ @Test
+ public void formatOperation_should_add_scope_test_on_issue_when_unit_test_file() {
+ componentDto.setQualifier(UNIT_TEST_FILE);
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getScope()).isEqualTo(TEST.name());
+ }
+
+ @Test
+ public void formatOperation_should_add_scope_main_on_issue_when_not_unit_test_file() {
+ componentDto.setQualifier(randomAlphanumeric(5));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getScope()).isEqualTo(MAIN.name());
+ }
+
+ @Test
+ public void formatOperation_should_add_actions_on_issues() {
+ Set<String> expectedActions = Set.of("actionA", "actionB");
+ searchResponseData.addActions(issueDto.getKey(), expectedActions);
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getActions().getActionsList()).containsExactlyInAnyOrderElementsOf(expectedActions);
+ }
+
+ @Test
+ public void formatOperation_should_add_transitions_on_issues() {
+ Set<String> expectedTransitions = Set.of("transitionone", "transitiontwo");
+ searchResponseData.addTransitions(issueDto.getKey(), createFakeTransitions(expectedTransitions));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getTransitions().getTransitionsList()).containsExactlyInAnyOrderElementsOf(expectedTransitions);
+ }
+
+ private static List<Transition> createFakeTransitions(Collection<String> transitions) {
+ return transitions.stream()
+ .map(transition -> Transition.builder(transition).from("OPEN").to("RESOLVED").build())
+ .collect(toList());
+ }
+
+ @Test
+ public void formatOperation_should_add_comments_on_issues() {
+ IssueChangeDto issueChangeDto = newIssuechangeDto(issueDto);
+ searchResponseData.setComments(List.of(issueChangeDto));
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().getComments().getCommentsList()).hasSize(1).extracting(Common.Comment::getKey).containsExactly(issueChangeDto.getKey());
+ }
+
+ @Test
+ public void formatOperation_should_not_set_severity_for_security_hotspot_issue() {
+ issueDto.setType(SECURITY_HOTSPOT);
+
+ Operation result = searchResponseFormat.formatOperation(searchResponseData);
+
+ assertThat(result.getIssue().hasSeverity()).isFalse();
+ }
+
+ private static SearchResponseData newSearchResponseData() {
+ RuleDto ruleDto = newRule();
+
+ String projectUuid = "project_uuid_" + randomAlphanumeric(5);
+ ComponentDto projectDto = newPrivateProjectDto();
+ projectDto.setProjectUuid(projectUuid);
+
+ UserDto userDto = newUserDto();
+
+ IssueDto issueDto = newIssue(ruleDto, projectUuid, "project_key_" + randomAlphanumeric(5), projectDto)
+ .setType(CODE_SMELL)
+ .setRuleDescriptionContextKey("context_key_" + randomAlphanumeric(5))
+ .setAssigneeUuid(userDto.getUuid())
+ .setResolution("resolution_" + randomAlphanumeric(5))
+ .setIssueCreationDate(new Date(currentTimeMillis() - 2_000))
+ .setIssueUpdateDate(new Date(currentTimeMillis() - 1_000))
+ .setIssueCloseDate(new Date(currentTimeMillis()));
+
+ SearchResponseData searchResponseData = new SearchResponseData(issueDto);
+ searchResponseData.addComponents(List.of(projectDto));
+ searchResponseData.addRules(List.of(ruleDto));
+ searchResponseData.addUsers(List.of(userDto));
+ return searchResponseData;
+ }
+
+}
optional string scope = 35;
optional bool quickFixAvailable = 36;
+ optional string ruleDescriptionContextKey = 37;
}
message Transitions {