Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: match scenarios to functionalities and problems to executions #628

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.decathlon.ara.service.exception.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -62,21 +63,15 @@ public void processUploadedContent(long projectId, String sourceCode, Technology
throw new BadRequestException(message, Entities.SCENARIO, "wrong_technology");
}

// Remove the previous scenarios from the same source => the database will remove the associations between functionalities and these scenarios
var allScenarios = scenarioRepository.findAllBySourceId(source.getId());
LOG.debug("SCENARIO|{} scenarios found for source {}", allScenarios.size(), sourceCode);
allScenarios.forEach(this::removeScenarioAssociationSafely);
scenarioRepository.deleteAll(allScenarios);

// Let Hibernate make the DELETE SQL statements now, because we will add the same scenarios below: don't mess with old and new instances
entityManager.flush();
entityManager.clear();

List<Scenario> newScenarios = scenarioExtractor.get(source);

// Check functionality IDs
// (first get all functionalities with their remaining scenarios eagerly-fetched)
Set<Functionality> functionalities = functionalityRepository.findAllByProjectIdAndType(projectId, FunctionalityType.FUNCTIONALITY);

deleteScenariosFromSameSource(source, functionalities);


assignWrongFunctionalityIds(functionalities, newScenarios);
assignWrongSeverityCode(getSeverityCodes(projectId), newScenarios);
assignWrongCountryCodes(getCountryCodes(projectId), newScenarios);
Expand All @@ -93,19 +88,19 @@ public void processUploadedContent(long projectId, String sourceCode, Technology
LOG.info("SCENARIO|Coverage complete!");
}

public void removeScenarioAssociationSafely(Scenario scenario) {
var scenarioFunctionalities = scenario.getFunctionalities();
while (!scenarioFunctionalities.isEmpty()) {
var initialSize = scenarioFunctionalities.size();
scenarioFunctionalities.stream()
.findFirst()
.ifPresent(scenario::removeFunctionality);
if (scenarioFunctionalities.size() >= initialSize) {
// Check implementation of removeFunctionality decrease the size of functionalities set
throw new IllegalStateException(
"Error during scenario-functionality link deletion: prevent infinite loop");
}
}
private void deleteScenariosFromSameSource(Source source, Set<Functionality> functionalities) {
var functionalitiesWithoutSourceScenarios = functionalities.stream()
.map(f -> Pair.of(f, f.getScenarios().stream().filter(s -> !s.getSource().equals(source)).toList()))
.map(p -> Pair.of(p.getFirst(), new TreeSet<>(p.getSecond())))
.map(p -> {
var f = p.getFirst();
var s = p.getSecond();
f.setScenarios(s);
return f;
})
.toList();
functionalityRepository.saveAll(functionalitiesWithoutSourceScenarios);
scenarioRepository.deleteAllBySource(source);
}

@FunctionalInterface
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,12 @@
package com.decathlon.ara.repository.util;

import static org.mockito.ArgumentMatchers.any;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;

import com.decathlon.ara.domain.Error;
import com.decathlon.ara.domain.*;
import com.decathlon.ara.domain.enumeration.DefectExistence;
import com.decathlon.ara.domain.enumeration.ProblemStatus;
import com.decathlon.ara.domain.enumeration.ProblemStatusFilter;
import com.decathlon.ara.domain.filter.ProblemFilter;
import com.decathlon.ara.util.TestUtil;
import org.hibernate.query.criteria.internal.PathImplementor;
import org.hibernate.query.criteria.internal.expression.function.ParameterizedFunctionExpression;
import org.junit.jupiter.api.Assertions;
Expand All @@ -38,17 +20,16 @@
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.jpa.domain.Specification;

import com.decathlon.ara.domain.Country;
import com.decathlon.ara.domain.Error;
import com.decathlon.ara.domain.ExecutedScenario;
import com.decathlon.ara.domain.Problem;
import com.decathlon.ara.domain.ProblemPattern;
import com.decathlon.ara.domain.Type;
import com.decathlon.ara.domain.enumeration.DefectExistence;
import com.decathlon.ara.domain.enumeration.ProblemStatus;
import com.decathlon.ara.domain.enumeration.ProblemStatusFilter;
import com.decathlon.ara.domain.filter.ProblemFilter;
import com.decathlon.ara.util.TestUtil;
import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

import static org.mockito.ArgumentMatchers.any;

@DataJpaTest
@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -505,7 +486,7 @@ void errorSpecificationShouldHaveEqualsPredicateOnExceptionWhenExceptionProperty
CriteriaQuery<Error> criteriaQuery = criteriaBuilder.createQuery(Error.class);
Root<Error> root = criteriaQuery.from(Error.class);
criteriaBuilder = Mockito.spy(criteriaBuilder);
prepareTest(criteriaBuilder);
prepareTest(criteriaBuilder, PredicateType.LIKE);
ProblemPattern problemPattern = new ProblemPattern();
TestUtil.setField(problemPattern, "exception", "exception");
Specification<Error> errorSpecification = SpecificationUtil.toErrorSpecification(1, problemPattern, null);
Expand All @@ -516,9 +497,9 @@ void errorSpecificationShouldHaveEqualsPredicateOnExceptionWhenExceptionProperty
Assertions.assertEquals(1l, predicates.get(0).value());
Assertions.assertEquals("root.executedScenario.run.execution.cycleDefinition.projectId", predicates.get(0).getName());
Assertions.assertEquals(PredicateType.EQUAL, predicates.get(0).type());
Assertions.assertEquals(problemPattern.getException(), predicates.get(1).value());
Assertions.assertEquals(problemPattern.getException() + "%", predicates.get(1).value());
Assertions.assertEquals("root.exception", predicates.get(1).getName());
Assertions.assertEquals(PredicateType.EQUAL, predicates.get(1).type());
Assertions.assertEquals(PredicateType.LIKE, predicates.get(1).type());
}

@Test
Expand Down Expand Up @@ -744,35 +725,16 @@ void errorSpecificationShouldHaveInPredicateOnErrorIdsWhenErrorIdsIsNotNull() {
Root<Error> root = criteriaQuery.from(Error.class);
criteriaBuilder = Mockito.spy(criteriaBuilder);
prepareTest(criteriaBuilder);
Path<Object> executedScenario = root.get("executedScenario");
Path<Object> run = executedScenario.get("run");
Path<Object> idPath = run.get("id");
Path<Object> idPath = root.get("id");
root = Mockito.spy(root);
Path<Object> executedScenarioSpy = Mockito.spy(executedScenario);
Path<Object> runSpy = Mockito.spy(run);
Path<Object> idPathSpy = Mockito.spy(idPath);
Mockito.doAnswer(invocationOnMock -> {
if ("executedScenario".equals(invocationOnMock.getArgument(0))) {
return executedScenarioSpy;
} else {
return invocationOnMock.callRealMethod();
}
}).when(root).get(Mockito.anyString());
Mockito.doAnswer(invocationOnMock -> {
if ("run".equals(invocationOnMock.getArgument(0))) {
return runSpy;
} else {
return invocationOnMock.callRealMethod();
}
}).when(executedScenarioSpy).get(Mockito.anyString());
;
Mockito.doAnswer(invocationOnMock -> {
if ("id".equals(invocationOnMock.getArgument(0))) {
return idPathSpy;
} else {
return invocationOnMock.callRealMethod();
}
}).when(runSpy).get(Mockito.anyString());
}).when(root).get(Mockito.anyString());
Mockito.doAnswer(invocationOnMock -> {
List<Long> value = invocationOnMock.getArgument(0);
Predicate predicate = (Predicate) invocationOnMock.callRealMethod();
Expand All @@ -788,7 +750,7 @@ void errorSpecificationShouldHaveInPredicateOnErrorIdsWhenErrorIdsIsNotNull() {
Assertions.assertEquals("root.executedScenario.run.execution.cycleDefinition.projectId", predicates.get(0).getName());
Assertions.assertEquals(PredicateType.EQUAL, predicates.get(0).type());
Assertions.assertEquals(errorIds, predicates.get(1).value());
Assertions.assertEquals("root.executedScenario.run.id", predicates.get(1).getName());
Assertions.assertEquals("root.id", predicates.get(1).getName());
Assertions.assertEquals(PredicateType.IN, predicates.get(1).type());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,15 @@

package com.decathlon.ara.domain;

import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.nullsFirst;

import java.util.Comparator;
import java.util.Date;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.SequenceGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.hibernate.annotations.SortNatural;

import com.decathlon.ara.domain.enumeration.CoverageLevel;
import com.decathlon.ara.domain.enumeration.FunctionalitySeverity;
import com.decathlon.ara.domain.enumeration.FunctionalityType;
import org.hibernate.annotations.SortNatural;

import javax.persistence.*;
import java.util.*;

import static java.util.Comparator.*;

@Entity
@NamedEntityGraph(name = "Functionality.scenarios", attributeNodes = @NamedAttributeNode("scenarios"))
Expand Down Expand Up @@ -352,4 +327,7 @@ public Set<Scenario> getScenarios() {
return scenarios;
}

public void setScenarios(Set<Scenario> scenarios) {
this.scenarios = scenarios;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@

package com.decathlon.ara.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import com.decathlon.ara.domain.Country;
import com.decathlon.ara.domain.Scenario;
import com.decathlon.ara.domain.Source;
import com.decathlon.ara.domain.projection.CountryCodeCheck;
import com.decathlon.ara.domain.projection.IgnoredScenario;
import com.decathlon.ara.domain.projection.ScenarioIgnoreCount;
import com.decathlon.ara.domain.projection.ScenarioSummary;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* Spring Data JPA repository for the Scenario entity.
Expand All @@ -38,6 +38,8 @@ public interface ScenarioRepository extends JpaRepository<Scenario, Long> {

List<Scenario> findAllBySourceId(Long sourceId);

void deleteAllBySource(Source source);

boolean existsBySourceId(Long sourceId);

List<CountryCodeCheck> findDistinctBySourceProjectIdAndCountryCodesContaining(long projectId, String countryCode);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package com.decathlon.ara.repository.util;

import com.decathlon.ara.domain.Error;
import com.decathlon.ara.domain.*;
import com.decathlon.ara.domain.enumeration.DefectExistence;
import com.decathlon.ara.domain.enumeration.ProblemStatus;
import com.decathlon.ara.domain.enumeration.ProblemStatusFilter;
import com.decathlon.ara.domain.filter.ProblemFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
Expand All @@ -10,29 +23,6 @@
import java.util.Optional;
import java.util.function.BiFunction;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import com.decathlon.ara.domain.Country;
import com.decathlon.ara.domain.Error;
import com.decathlon.ara.domain.ExecutedScenario;
import com.decathlon.ara.domain.Execution;
import com.decathlon.ara.domain.Problem;
import com.decathlon.ara.domain.ProblemPattern;
import com.decathlon.ara.domain.Run;
import com.decathlon.ara.domain.Type;
import com.decathlon.ara.domain.enumeration.DefectExistence;
import com.decathlon.ara.domain.enumeration.ProblemStatus;
import com.decathlon.ara.domain.enumeration.ProblemStatusFilter;
import com.decathlon.ara.domain.filter.ProblemFilter;

import liquibase.util.StringUtil;

public class SpecificationUtil {

private static final String NAME_ATTRIBUTE = "name";
Expand Down Expand Up @@ -82,7 +72,7 @@ private static void addEqualsOrStartWithPredicate(List<Predicate> predicates, Cr
}

private static void addEqualsPredicate(List<Predicate> predicates, CriteriaBuilder criteriaBuilder, Expression<String> expression, String value) {
if (StringUtil.isNotEmpty(value)) {
if (StringUtils.isNotEmpty(value)) {
predicates.add(criteriaBuilder.equal(expression, value));
}
}
Expand Down Expand Up @@ -197,7 +187,7 @@ public static Specification<Error> toErrorSpecification(long projectId, ProblemP
addEqualsOrStartWithPredicate(predicates, criteriaBuilder, executedScenario.get(NAME_ATTRIBUTE), problemPattern.getScenarioName(), problemPattern.isScenarioNameStartsWith());
addEqualsOrStartWithPredicate(predicates, criteriaBuilder, root.get("step"), problemPattern.getStep(), problemPattern.isStepStartsWith());
addEqualsOrStartWithPredicate(predicates, criteriaBuilder, root.get("stepDefinition"), problemPattern.getStepDefinition(), problemPattern.isStepDefinitionStartsWith());
addEqualsPredicate(predicates, criteriaBuilder, root.get("exception"), problemPattern.getException());
addEqualsOrStartWithPredicate(predicates, criteriaBuilder, root.get("exception"), problemPattern.getException(), true);
addEqualsPredicate(predicates, criteriaBuilder, execution.get("release"), problemPattern.getRelease());
Country country = problemPattern.getCountry();
if (country != null && StringUtils.isNotEmpty(country.getCode())) {
Expand All @@ -219,7 +209,7 @@ public static Specification<Error> toErrorSpecification(long projectId, ProblemP
predicates.add(criteriaBuilder.equal(typePath.get("isMobile"), typeIsMobile));
}
if (errorIds != null) {
predicates.add(run.get("id").in(errorIds));
predicates.add(root.get("id").in(errorIds));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
};
Expand Down