Skip to content

Commit

Permalink
feat : enhance tests (#1555)
Browse files Browse the repository at this point in the history
  • Loading branch information
rajadilipkolli authored Dec 5, 2024
1 parent b659fcd commit 60f0a62
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@

import com.example.keysetpagination.entities.Animal;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface AnimalRepository extends JpaRepository<Animal, Long>, CustomRepository<Animal> {}
public interface AnimalRepository
extends JpaRepository<Animal, Long>, CustomRepository<Animal>, JpaSpecificationExecutor<Animal> {}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public interface CustomRepository<T> {
/**
* Finds all entities matching the given specification using keyset pagination.
*
* @param spec The specification to filter entites
* @param spec The specification to filter entities
* @param pageRequest The pagination information
* @param scrollPosition The current position in the result set
* @param entityClass The entity class on which operation should occur
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.ManagedType;
import jakarta.persistence.metamodel.Metamodel;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.springframework.data.jpa.domain.Specification;
Expand Down Expand Up @@ -77,7 +80,9 @@ private Predicate createPredicate(SearchCriteria searchCriteria, Root<T> root, C
criteriaBuilder::and);
case LIKE, CONTAINS -> combinePredicates(
typedValues,
value -> criteriaBuilder.like(root.get(fieldName), "%" + value + "%"),
value -> criteriaBuilder.like(
criteriaBuilder.lower(root.get(fieldName)),
"%" + value.toString().toLowerCase() + "%"),
criteriaBuilder::or);
case STARTS_WITH -> combinePredicates(
typedValues, value -> criteriaBuilder.like(root.get(fieldName), value + "%"), criteriaBuilder::and);
Expand All @@ -102,6 +107,10 @@ private Object convertToType(String value, Class<?> fieldType) {
try {
if (fieldType.equals(String.class)) {
return value;
} else if (fieldType.equals(BigDecimal.class)) {
return new BigDecimal(value);
} else if (fieldType.equals(UUID.class)) {
return UUID.fromString(value);
} else if (fieldType.equals(Integer.class) || fieldType.equals(int.class)) {
return Integer.valueOf(value);
} else if (fieldType.equals(Long.class) || fieldType.equals(long.class)) {
Expand All @@ -111,9 +120,9 @@ private Object convertToType(String value, Class<?> fieldType) {
} else if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
return Boolean.valueOf(value);
} else if (fieldType.equals(LocalDate.class)) {
return LocalDate.parse(value);
return LocalDate.parse(value, DateTimeFormatter.ISO_DATE);
} else if (fieldType.equals(LocalDateTime.class)) {
return LocalDateTime.parse(value);
return LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME);
} else if (Enum.class.isAssignableFrom(fieldType)) {
return Enum.valueOf((Class<Enum>) fieldType, value);
} else {
Expand All @@ -129,10 +138,14 @@ private Predicate combinePredicates(
List<Object> values,
Function<Object, Predicate> predicateFunction,
BiFunction<Predicate, Predicate, Predicate> combiner) {
if (values.size() == 1) {
return predicateFunction.apply(values.getFirst());
}
return values.stream()
.map(predicateFunction)
.reduce(combiner::apply)
.orElseThrow(() -> new IllegalArgumentException("No predicates could be generated from values"));
.orElseThrow(() -> new IllegalArgumentException(
String.format("No predicates could be generated from values: %s", values)));
}

private void validateMetadata(List<SearchCriteria> searchCriteriaList, Class<T> entityClass) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,120 +1,165 @@
package com.example.keysetpagination.services;
package com.example.keysetpagination.repository;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.example.keysetpagination.common.AbstractIntegrationTest;
import com.example.keysetpagination.common.ContainersConfig;
import com.example.keysetpagination.config.JpaAuditConfig;
import com.example.keysetpagination.entities.Animal;
import com.example.keysetpagination.model.query.QueryOperator;
import com.example.keysetpagination.model.query.SearchCriteria;
import com.example.keysetpagination.repositories.AnimalRepository;
import com.example.keysetpagination.services.EntitySpecification;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.domain.Specification;

class EntitySpecificationIntTest extends AbstractIntegrationTest {
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DataJpaTest
@Import({ContainersConfig.class, JpaAuditConfig.class})
class EntitySpecificationTest {

private EntitySpecification<Animal> entitySpecification;

@PersistenceContext
private EntityManager entityManager;

@BeforeEach
@Autowired
private AnimalRepository animalRepository;

@BeforeAll
void setUp() {
entitySpecification = new EntitySpecification<>(entityManager);
// Add test data
Animal mammal = new Animal().setName("Lion").setType("Mammal").setHabitat("Savanna");
Animal bird = new Animal().setName("Eagle").setType("Bird").setHabitat("Forest");
Animal fish = new Animal().setName("Shark").setType("Fish").setHabitat("Ocean");
animalRepository.saveAll(List.of(mammal, bird, fish));
}

@Test
void shouldBuildSpecificationForEQOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.EQ, "type", List.of("Mammal"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal");
}

@Test
void shouldBuildSpecificationForNEOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.NE, "type", List.of("Reptile"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal", "Bird", "Fish");
}

@Test
void shouldBuildSpecificationForLTOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.LT, "id", List.of("5"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal", "Bird", "Fish");
}

@Test
void shouldBuildSpecificationForGTOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.GT, "id", List.of("2"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Fish");
}

@Test
void shouldBuildSpecificationForGTEOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.GTE, "id", List.of("3"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Fish");
}

@Test
void shouldBuildSpecificationForLTEOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.LTE, "id", List.of("7"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal", "Bird", "Fish");
}

@Test
void shouldBuildSpecificationForBetweenOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.BETWEEN, "id", List.of("1", "5"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal", "Bird", "Fish");
}

@Test
void shouldBuildSpecificationForINOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.IN, "type", List.of("Mammal", "Bird"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal", "Bird");
}

@Test
void shouldBuildSpecificationForNOTINOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.NOT_IN, "type", List.of("Fish", "Reptile"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal", "Bird");
}

@Test
void shouldBuildSpecificationForLIKEOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.LIKE, "name", List.of("%Lion%"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal");
}

@Test
void shouldBuildSpecificationForCONTAINSOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.CONTAINS, "name", List.of("ar"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Fish");
}

@Test
void shouldBuildSpecificationForSTARTS_WITHOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.STARTS_WITH, "name", List.of("E"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Bird");
}

@Test
void shouldBuildSpecificationForENDS_WITHOperator() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.ENDS_WITH, "name", List.of("e"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteria), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Bird");
}

@Test
Expand All @@ -126,12 +171,25 @@ void shouldBuildSpecificationForANDOperator() {
Specification<Animal> spec =
entitySpecification.specificationBuilder(List.of(criteria1, criteriaAnd, criteria2), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Mammal");
}

@Test
void shouldBuildSpecificationForOROperator() {
SearchCriteria criteriaOr = new SearchCriteria(QueryOperator.OR, "type", List.of("Amphibian", "Fish"));
Specification<Animal> spec = entitySpecification.specificationBuilder(List.of(criteriaOr), Animal.class);
assertThat(spec).isNotNull();
List<Animal> results = animalRepository.findAll(spec);
assertThat(results).isNotEmpty().extracting("type").containsOnly("Fish");
}

@Test
void shouldThrowExceptionForInvalidFieldName() {
SearchCriteria criteria = new SearchCriteria(QueryOperator.EQ, "invalidField", List.of("value"));
assertThatThrownBy(() -> entitySpecification.specificationBuilder(List.of(criteria), Animal.class))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(
"Unable to locate Attribute with the given name [invalidField] on this ManagedType [com.example.keysetpagination.entities.Animal]");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.keysetpagination;
package com.example.keysetpagination.repository;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ void shouldReturnResultForNotEqualType() throws Exception {
]
"""))
.andExpect(status().isOk())
// Total animals (10) - Mammals (3) - Birds (3) = 4 animals
.andExpect(jsonPath("$.content", hasSize(4)))
.andExpect(jsonPath("$.last", is(true)));
}
Expand All @@ -173,6 +174,7 @@ void shouldReturnEmptyResultForNonExistentType() throws Exception {
"""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content", hasSize(0)))
.andExpect(jsonPath("$.content").isArray())
.andExpect(jsonPath("$.last", is(true)));
}

Expand Down

0 comments on commit 60f0a62

Please sign in to comment.