import org.sonar.ce.task.projectanalysis.filemove.ScoreMatrixDumperImpl;
import org.sonar.ce.task.projectanalysis.filemove.SourceSimilarityImpl;
import org.sonar.ce.task.projectanalysis.filesystem.ComputationTempFolderProvider;
+import org.sonar.ce.task.projectanalysis.issue.AnticipatedTransitionRepositoryImpl;
import org.sonar.ce.task.projectanalysis.issue.BaseIssuesLoader;
import org.sonar.ce.task.projectanalysis.issue.CloseIssuesOnRemovedComponentsVisitor;
import org.sonar.ce.task.projectanalysis.issue.ClosedIssuesInputFactory;
import org.sonar.ce.task.projectanalysis.issue.TrackerReferenceBranchInputFactory;
import org.sonar.ce.task.projectanalysis.issue.TrackerSourceBranchInputFactory;
import org.sonar.ce.task.projectanalysis.issue.TrackerTargetBranchInputFactory;
+import org.sonar.ce.task.projectanalysis.issue.TransitionIssuesToAnticipatedStatesVisitor;
import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver;
import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
import org.sonar.ce.task.projectanalysis.language.LanguageRepositoryImpl;
// debt)
RuleTagsCopier.class,
IssueCreationDateCalculator.class,
+ TransitionIssuesToAnticipatedStatesVisitor.class,
ComputeLocationHashesVisitor.class,
DebtCalculator.class,
EffortAggregator.class,
WebhookPostTask.class,
// notifications
- NotificationFactory.class);
+ NotificationFactory.class,
+
+ // anticipated transitions
+ AnticipatedTransitionRepositoryImpl.class);
}
}
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.ce.task.projectanalysis.issue;
+
+import java.util.Collection;
+import org.sonar.core.issue.AnticipatedTransition;
+
+public interface AnticipatedTransitionRepository {
+ Collection<AnticipatedTransition> getAnticipatedTransitionByProjectUuid(String projectKey, String filePath);
+
+}
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.ce.task.projectanalysis.issue;
+
+import java.util.Collection;
+import java.util.List;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.AnticipatedTransition;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.entity.EntityDto;
+import org.sonar.db.issue.AnticipatedTransitionDto;
+
+public class AnticipatedTransitionRepositoryImpl implements AnticipatedTransitionRepository {
+
+ private final DbClient dbClient;
+
+ public AnticipatedTransitionRepositoryImpl(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public Collection<AnticipatedTransition> getAnticipatedTransitionByProjectUuid(String componentUuid, String filePath) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ EntityDto entityDto = dbClient.entityDao().selectByComponentUuid(dbSession, componentUuid).orElseThrow(IllegalStateException::new);
+ List<AnticipatedTransitionDto> anticipatedTransitionDtos = dbClient.anticipatedTransitionDao().selectByProjectUuid(dbSession, entityDto.getUuid());
+ return getAnticipatedTransitions(anticipatedTransitionDtos);
+ }
+ }
+
+ private Collection<AnticipatedTransition> getAnticipatedTransitions(List<AnticipatedTransitionDto> anticipatedTransitionDtos) {
+ return anticipatedTransitionDtos
+ .stream()
+ .map(this::getAnticipatedTransition)
+ .toList();
+ }
+
+ private AnticipatedTransition getAnticipatedTransition(AnticipatedTransitionDto transitionDto) {
+ return new AnticipatedTransition(
+ transitionDto.getProjectUuid(),
+ "branch",
+ transitionDto.getUserUuid(),
+ RuleKey.parse(transitionDto.getRuleKey()),
+ transitionDto.getMessage(),
+ "filepath",
+ transitionDto.getLine(),
+ transitionDto.getLineHash(),
+ transitionDto.getTransition(),
+ transitionDto.getComment()
+ );
+ }
+
+}
import java.util.Date;
import java.util.Optional;
import javax.inject.Inject;
+import org.jetbrains.annotations.NotNull;
import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
workflow.doAutomaticTransition(issue, changeContext);
}
+ public void doManualTransition(DefaultIssue issue, String transitionKey, String userUuid) {
+ workflow.doManualTransition(issue, transitionKey, getIssueChangeContextWithUser(userUuid));
+ }
+
+ public void addComment(DefaultIssue issue, String comment, String userUuid) {
+ updater.addComment(issue, comment, getIssueChangeContextWithUser(userUuid));
+ }
+
+ @NotNull
+ private IssueChangeContext getIssueChangeContextWithUser(String userUuid) {
+ return IssueChangeContext.newBuilder()
+ .setDate(changeContext.date())
+ .setWebhookSource(changeContext.getWebhookSource())
+ .setUserUuid(userUuid).build();
+ }
+
private void copyFields(DefaultIssue toIssue, DefaultIssue fromIssue) {
toIssue.setType(fromIssue.type());
toIssue.setCreationDate(fromIssue.creationDate());
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.ce.task.projectanalysis.issue;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.core.issue.AnticipatedTransition;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.AnticipatedTransitionTracker;
+import org.sonar.core.issue.tracking.Tracking;
+
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
+
+/**
+ * Updates issues if an anticipated transition from SonarLint is found
+ */
+public class TransitionIssuesToAnticipatedStatesVisitor extends IssueVisitor {
+
+ private Collection<AnticipatedTransition> anticipatedTransitions;
+ private final AnticipatedTransitionTracker<DefaultIssue, AnticipatedTransition> tracker = new AnticipatedTransitionTracker<>();
+ private final IssueLifecycle issueLifecycle;
+
+ private final AnticipatedTransitionRepository anticipatedTransitionRepository;
+
+ public TransitionIssuesToAnticipatedStatesVisitor(AnticipatedTransitionRepository anticipatedTransitionRepository, IssueLifecycle issueLifecycle) {
+ this.anticipatedTransitionRepository = anticipatedTransitionRepository;
+ this.issueLifecycle = issueLifecycle;
+ }
+
+ @Override
+ public void beforeComponent(Component component) {
+ if (FILE.equals(component.getType())) {
+ anticipatedTransitions = anticipatedTransitionRepository.getAnticipatedTransitionByProjectUuid(component.getUuid(), component.getName());
+ }
+ }
+
+ @Override
+ public void onIssue(Component component, DefaultIssue issue) {
+ if (issue.isNew()) {
+ Tracking<DefaultIssue, AnticipatedTransition> tracking = tracker.track(List.of(issue), anticipatedTransitions);
+ Map<DefaultIssue, AnticipatedTransition> matchedRaws = tracking.getMatchedRaws();
+ if (matchedRaws.containsKey(issue)) {
+ performAnticipatedTransition(issue, matchedRaws.get(issue));
+ }
+ }
+ }
+
+ @Override
+ public void afterComponent(Component component) {
+ anticipatedTransitions.clear();
+ }
+
+ private void performAnticipatedTransition(DefaultIssue issue, AnticipatedTransition anticipatedTransition) {
+ issue.setBeingClosed(true);
+ issue.setAnticipatedTransitions(true);
+ issueLifecycle.doManualTransition(issue, anticipatedTransition.getTransition(), anticipatedTransition.getUserUuid());
+ issueLifecycle.addComment(issue, anticipatedTransition.getComment(), anticipatedTransition.getUserUuid());
+ }
+
+}
verify(workflow).doAutomaticTransition(issue, issueChangeContext);
}
+ @Test
+ public void doManualTransition() {
+ DefaultIssue issue = new DefaultIssue();
+ String transitionKey = "transitionKey";
+ String userUuid = "userUuid";
+
+ underTest.doManualTransition(issue, transitionKey, userUuid);
+
+ verify(workflow).doManualTransition(issue, transitionKey, getIssueChangeContextWithUser(userUuid));
+ }
+
+ @Test
+ public void addComment() {
+ DefaultIssue issue = new DefaultIssue();
+ String comment = "comment";
+ String userUuid = "userUuid";
+
+ underTest.addComment(issue, comment, userUuid);
+
+ verify(updater).addComment(issue, comment, getIssueChangeContextWithUser(userUuid));
+ }
+
+ private IssueChangeContext getIssueChangeContextWithUser(String userUuid) {
+ return IssueChangeContext.newBuilder()
+ .setDate(issueChangeContext.date())
+ .setWebhookSource(issueChangeContext.getWebhookSource())
+ .setUserUuid(userUuid).build();
+ }
+
@Test
public void mergeExistingOpenIssue() {
DefaultIssue raw = new DefaultIssue()
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.ce.task.projectanalysis.issue;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ComponentImpl;
+import org.sonar.ce.task.projectanalysis.component.ProjectAttributes;
+import org.sonar.ce.task.projectanalysis.component.ReportAttributes;
+import org.sonar.core.issue.AnticipatedTransition;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
+
+public class TransitionIssuesToAnticipatedStatesVisitorTest {
+
+ private final IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
+
+ private final AnticipatedTransitionRepository anticipatedTransitionRepository = mock(AnticipatedTransitionRepository.class);
+
+ private final TransitionIssuesToAnticipatedStatesVisitor underTest = new TransitionIssuesToAnticipatedStatesVisitor(anticipatedTransitionRepository, issueLifecycle);
+
+ @Test
+ public void givenMatchingAnticipatedTransitions_transitionsShouldBeAppliedToIssues() {
+ Component component = getComponent(Component.Type.FILE);
+ when(anticipatedTransitionRepository.getAnticipatedTransitionByComponent(component)).thenReturn(getAnticipatedTransitions("projectKey", "fileName"));
+
+ DefaultIssue issue = getDefaultIssue(1, "abcdefghi", "issue message");
+
+ underTest.beforeComponent(component);
+ underTest.onIssue(component, issue);
+ underTest.afterComponent(component);
+
+ assertThat(issue.isBeingClosed()).isTrue();
+ assertThat(issue.hasAnticipatedTransitions()).isTrue();
+ verify(issueLifecycle).doManualTransition(issue, "wontfix", "admin");
+ verify(issueLifecycle).addComment(issue, "doing the transition in an anticipated way", "admin");
+ }
+
+ @Test
+ public void givenNonMatchingAnticipatedTransitions_transitionsAreNotAppliedToIssues() {
+ Component component = getComponent(Component.Type.FILE);
+ when(anticipatedTransitionRepository.getAnticipatedTransitionByComponent(component)).thenReturn(getAnticipatedTransitions("projectKey", "fileName"));
+
+ DefaultIssue issue = getDefaultIssue(2, "abcdefghf", "another issue message");
+
+ underTest.beforeComponent(component);
+ underTest.onIssue(component, issue);
+ underTest.afterComponent(component);
+
+ assertThat(issue.isBeingClosed()).isFalse();
+ assertThat(issue.hasAnticipatedTransitions()).isFalse();
+ verifyNoInteractions(issueLifecycle);
+ }
+
+ @Test
+ public void givenAFileComponent_theRepositoryIsHitForFetchingAnticipatedTransitions() {
+ Component component = getComponent(Component.Type.FILE);
+ when(anticipatedTransitionRepository.getAnticipatedTransitionByComponent(component)).thenReturn(Collections.emptyList());
+
+ underTest.beforeComponent(component);
+
+ verify(anticipatedTransitionRepository).getAnticipatedTransitionByComponent(component);
+ }
+
+ @Test
+ public void givenAProjecComponent_theRepositoryIsNotQueriedForAnticipatedTransitions() {
+ Component component = getComponent(PROJECT);
+ when(anticipatedTransitionRepository.getAnticipatedTransitionByComponent(component)).thenReturn(Collections.emptyList());
+
+ underTest.beforeComponent(component);
+
+ verifyNoInteractions(anticipatedTransitionRepository);
+ }
+
+ private Collection<AnticipatedTransition> getAnticipatedTransitions(String projecKey, String fileName) {
+ return Stream.of(new AnticipatedTransition(projecKey, null, "admin", RuleKey.parse("repo:id"), "issue message", fileName, 1, "abcdefghi", "wontfix", "doing the transition in an anticipated way")).collect(Collectors.toList());
+ }
+
+ private Component getComponent(Component.Type type) {
+ ComponentImpl.Builder builder = ComponentImpl.builder(type)
+ .setUuid("componentUuid")
+ .setKey("projectKey:filename")
+ .setName("filename")
+ .setStatus(Component.Status.ADDED)
+ .setShortName("filename")
+ .setReportAttributes(mock(ReportAttributes.class));
+
+ if (PROJECT.equals(type)) {
+ builder.setProjectAttributes(mock(ProjectAttributes.class));
+ }
+
+ return builder.build();
+ }
+
+ private DefaultIssue getDefaultIssue(Integer line, String hash, String message) {
+ DefaultIssue defaultIssue = new DefaultIssue();
+ defaultIssue.setLine(line);
+ defaultIssue.setChecksum(hash);
+ defaultIssue.setMessage(message);
+ defaultIssue.setRuleKey(RuleKey.of("repo", "id"));
+ return defaultIssue;
+ }
+
+}
changeDto.setProjectUuid(issue.projectUuid());
mapper.insert(changeDto);
}
- } else if (!issue.isNew() && diffs != null) {
+ } else if ((!issue.isNew() || issue.hasAnticipatedTransitions()) && diffs != null) {
IssueChangeDto changeDto = IssueChangeDto.of(issue.key(), diffs, issue.projectUuid());
changeDto.setUuid(uuidFactory.create());
changeDto.setProjectUuid(issue.projectUuid());
private String ruleDescriptionContextKey = null;
+ private boolean anticipatedTransitions = false;
+
@Override
public String key() {
return key;
return this;
}
+ public boolean hasAnticipatedTransitions() {
+ return anticipatedTransitions;
+ }
+
+ public DefaultIssue setAnticipatedTransitions(boolean anticipatedTransitions) {
+ this.anticipatedTransitions = anticipatedTransitions;
+ return this;
+ }
+
@Override
public Integer getLine() {
return line;
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.core.issue.tracking;
+
+import java.util.Collection;
+
+/**
+ * A simplified version of {@link Tracker}, which doesn't use line hash sequences nor block hash sequences and
+ * only has two steps instead of 5 steps.
+ */
+public class AnticipatedTransitionTracker<RAW extends Trackable, BASE extends Trackable> extends AbstractTracker<RAW, BASE> {
+
+ public Tracking<RAW, BASE> track(Collection<RAW> rawInput, Collection<BASE> baseInput) {
+ Tracking<RAW, BASE> tracking = new Tracking<>(rawInput, baseInput);
+
+ // 1. match by rule, line, line hash and message
+ match(tracking, LineAndLineHashAndMessage::new);
+
+ // 2. match issues with same rule, same line and same line hash, but not necessarily with same message
+ match(tracking, LineAndLineHashKey::new);
+
+ // 3. match issues with same rule, same message and same line hash
+ match(tracking, LineHashAndMessageKey::new);
+
+ // 4. match issues with same rule, same line and same message
+ match(tracking, LineAndMessageKey::new);
+
+ // 5. match issues with same rule and same line hash but different line and different message.
+ // See SONAR-2812
+ match(tracking, LineHashKey::new);
+
+ return tracking;
+ }
+}
public void codeVariants_whenNull_shouldReturnEmptySet() {
assertThat(issue.codeVariants()).isEmpty();
}
+
+ @Test
+ public void issueByDefault_shouldNotHaveAppliedAnticipatedTransitions() {
+ DefaultIssue defaultIssue = new DefaultIssue();
+ assertThat(defaultIssue.hasAnticipatedTransitions()).isFalse();
+ }
+
+ @Test
+ public void anticipatedTransitions_WhenSetTrue_shouldReturnTrue() {
+ DefaultIssue defaultIssue = new DefaultIssue();
+ defaultIssue.setAnticipatedTransitions(true);
+ assertThat(defaultIssue.hasAnticipatedTransitions()).isTrue();
+ }
}
--- /dev/null
+/*
+ * 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.
+ */
+package org.sonar.core.issue.tracking;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.AnticipatedTransition;
+import org.sonar.core.issue.DefaultIssue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AnticipatedTransitionTrackerTest {
+
+ private final AnticipatedTransitionTracker<DefaultIssue, AnticipatedTransition> underTest = new AnticipatedTransitionTracker<>();
+
+ @Test
+ public void givenIssuesAndAnticipatedTransitions_trackerShouldReturnTheExpectedMatching() {
+
+ List<DefaultIssue> issues = getIssues();
+ List<AnticipatedTransition> anticipatedTransitions = getAnticipatedTransitions();
+
+ Tracking<DefaultIssue, AnticipatedTransition> tracking = underTest.track(issues, anticipatedTransitions);
+
+ var matchedRaws = tracking.getMatchedRaws();
+ var unmatchedRaws = tracking.getUnmatchedRaws().collect(Collectors.toList());
+
+ assertThat(matchedRaws).hasSize(5);
+ assertThat(unmatchedRaws).hasSize(2);
+
+ assertThat(matchedRaws.keySet()).containsExactlyInAnyOrder(issues.get(0), issues.get(1), issues.get(2), issues.get(3), issues.get(4));
+ assertThat(unmatchedRaws).containsExactlyInAnyOrder(issues.get(5), issues.get(6));
+
+ assertThat(matchedRaws).containsEntry(issues.get(0), anticipatedTransitions.get(1))
+ .containsEntry(issues.get(1), anticipatedTransitions.get(0))
+ .containsEntry(issues.get(2), anticipatedTransitions.get(3))
+ .containsEntry(issues.get(3), anticipatedTransitions.get(2))
+ .containsEntry(issues.get(4), anticipatedTransitions.get(6));
+ }
+
+ private List<DefaultIssue> getIssues() {
+ return List.of(
+ getIssue(1, "message1", "hash1", "rule:key1"), //should match transition 2 due to lvl 1 matching
+ getIssue(2, "message2", "hash2", "rule:key2"), //should match transition 1 due to lvl 2 matching
+ getIssue(3, "message3", "hash3", "rule:key3"), //should match transition 4 due to lvl 3 matching
+ getIssue(4, "message4", "hash4", "rule:key4"), //should match transition 3 due to lvl 4 matching
+ getIssue(5, "message5", "hash5", "rule:key5"), //should match transition 7 due to lvl 5 matching
+ getIssue(6, "message6", "hash6", "rule:key6"), //should not match
+ getIssue(7, "message7", "hash7", "rule:key7") //should not match
+ );
+ }
+
+ private List<AnticipatedTransition> getAnticipatedTransitions() {
+ //Anticipated Transitions with random order
+ return List.of(
+ getAnticipatedTransition(2, "message a bit different 2", "hash2", "rule:key2"),
+ getAnticipatedTransition(1, "message1", "hash1", "rule:key1"),
+ getAnticipatedTransition(4, "message4", "different hash", "rule:key4"),
+ getAnticipatedTransition(13, "message3", "hash3", "rule:key3"),
+ getAnticipatedTransition(16, "different message", "different hash", "rule:key6"),
+ getAnticipatedTransition(7, "different message", "different hash", "rule:key17"),
+ getAnticipatedTransition(15, "different message", "hash5", "rule:key5")
+
+ );
+ }
+
+ private DefaultIssue getIssue(Integer line, String message, String hash, String ruleKey) {
+ return new DefaultIssue()
+ .setKey("key" + line)
+ .setLine(line)
+ .setMessage(message)
+ .setChecksum(hash)
+ .setRuleKey(RuleKey.parse(ruleKey));
+ }
+
+ private AnticipatedTransition getAnticipatedTransition(Integer line, String message, String hash, String ruleKey) {
+ return new AnticipatedTransition("projectKey",
+ null,
+ "userUuid",
+ RuleKey.parse(ruleKey),
+ message,
+ "filePath",
+ line,
+ hash,
+ "transition",
+ null);
+ }
+
+}