diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountAnnotatedData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountAnnotatedData.java new file mode 100644 index 0000000000..4202e2348a --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountAnnotatedData.java @@ -0,0 +1,27 @@ +package com.mongo.repository; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Each endpoint has its own document type and collection, so that a document that + * makes one endpoint return 200 cannot accidentally do it for another one. + */ +@Document(collection = "countAnnotatedCollection") +public class CountAnnotatedData { + + @Id + public String id; + + public String city; + + public int age; + + public CountAnnotatedData() { + } + + public CountAnnotatedData(String city, int age) { + this.city = city; + this.age = age; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountAnnotatedRepository.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountAnnotatedRepository.java new file mode 100644 index 0000000000..78490e6dc5 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountAnnotatedRepository.java @@ -0,0 +1,10 @@ +package com.mongo.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +public interface CountAnnotatedRepository extends MongoRepository { + + @Query(value = "{ 'city': ?0, 'age': { $gte: ?1 } }", count = true) + long countAnnotated(String city, int minAge); +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountDerivedData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountDerivedData.java new file mode 100644 index 0000000000..91c96c5a55 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountDerivedData.java @@ -0,0 +1,27 @@ +package com.mongo.repository; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Each endpoint has its own document type and collection, so that a document that + * makes one endpoint return 200 cannot accidentally do it for another one. + */ +@Document(collection = "countDerivedCollection") +public class CountDerivedData { + + @Id + public String id; + + public String city; + + public int age; + + public CountDerivedData() { + } + + public CountDerivedData(String city, int age) { + this.city = city; + this.age = age; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountDerivedRepository.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountDerivedRepository.java new file mode 100644 index 0000000000..87d520fdf8 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/CountDerivedRepository.java @@ -0,0 +1,8 @@ +package com.mongo.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface CountDerivedRepository extends MongoRepository { + + long countByCityAndAgeGreaterThanEqual(String city, int minAge); +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteAnnotatedData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteAnnotatedData.java new file mode 100644 index 0000000000..9ec2fc4930 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteAnnotatedData.java @@ -0,0 +1,27 @@ +package com.mongo.repository; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Each endpoint has its own document type and collection, so that a document that + * makes one endpoint return 200 cannot accidentally do it for another one. + */ +@Document(collection = "deleteAnnotatedCollection") +public class DeleteAnnotatedData { + + @Id + public String id; + + public String city; + + public int age; + + public DeleteAnnotatedData() { + } + + public DeleteAnnotatedData(String city, int age) { + this.city = city; + this.age = age; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteAnnotatedRepository.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteAnnotatedRepository.java new file mode 100644 index 0000000000..4cfdac41b0 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteAnnotatedRepository.java @@ -0,0 +1,10 @@ +package com.mongo.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +public interface DeleteAnnotatedRepository extends MongoRepository { + + @Query(value = "{ 'city': ?0, 'age': { $gte: ?1 } }", delete = true) + long deleteAnnotated(String city, int minAge); +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteDerivedData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteDerivedData.java new file mode 100644 index 0000000000..9d1484c0a8 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteDerivedData.java @@ -0,0 +1,27 @@ +package com.mongo.repository; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Each endpoint has its own document type and collection, so that a document that + * makes one endpoint return 200 cannot accidentally do it for another one. + */ +@Document(collection = "deleteDerivedCollection") +public class DeleteDerivedData { + + @Id + public String id; + + public String city; + + public int age; + + public DeleteDerivedData() { + } + + public DeleteDerivedData(String city, int age) { + this.city = city; + this.age = age; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteDerivedRepository.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteDerivedRepository.java new file mode 100644 index 0000000000..f0905e5414 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/DeleteDerivedRepository.java @@ -0,0 +1,8 @@ +package com.mongo.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface DeleteDerivedRepository extends MongoRepository { + + long deleteByCityAndAgeGreaterThanEqual(String city, int minAge); +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsAnnotatedData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsAnnotatedData.java new file mode 100644 index 0000000000..3f936e1708 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsAnnotatedData.java @@ -0,0 +1,27 @@ +package com.mongo.repository; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Each endpoint has its own document type and collection, so that a document that + * makes one endpoint return 200 cannot accidentally do it for another one. + */ +@Document(collection = "existsAnnotatedCollection") +public class ExistsAnnotatedData { + + @Id + public String id; + + public String city; + + public int age; + + public ExistsAnnotatedData() { + } + + public ExistsAnnotatedData(String city, int age) { + this.city = city; + this.age = age; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsAnnotatedRepository.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsAnnotatedRepository.java new file mode 100644 index 0000000000..2ac59541c2 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsAnnotatedRepository.java @@ -0,0 +1,10 @@ +package com.mongo.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +public interface ExistsAnnotatedRepository extends MongoRepository { + + @Query(value = "{ 'city': ?0, 'age': { $gte: ?1 } }", exists = true) + boolean existsAnnotated(String city, int minAge); +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsDerivedData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsDerivedData.java new file mode 100644 index 0000000000..cf6cce5d5f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsDerivedData.java @@ -0,0 +1,27 @@ +package com.mongo.repository; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * Each endpoint has its own document type and collection, so that a document that + * makes one endpoint return 200 cannot accidentally do it for another one. + */ +@Document(collection = "existsDerivedCollection") +public class ExistsDerivedData { + + @Id + public String id; + + public String city; + + public int age; + + public ExistsDerivedData() { + } + + public ExistsDerivedData(String city, int age) { + this.city = city; + this.age = age; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsDerivedRepository.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsDerivedRepository.java new file mode 100644 index 0000000000..4456292d42 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/ExistsDerivedRepository.java @@ -0,0 +1,8 @@ +package com.mongo.repository; + +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface ExistsDerivedRepository extends MongoRepository { + + boolean existsByCityAndAgeGreaterThanEqual(String city, int minAge); +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/MongoRepositoryApp.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/MongoRepositoryApp.java new file mode 100644 index 0000000000..b8b4d35971 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/MongoRepositoryApp.java @@ -0,0 +1,20 @@ +package com.mongo.repository; + +import com.mongo.SwaggerConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@EnableSwagger2 +@SpringBootApplication(exclude = SecurityAutoConfiguration.class) +public class MongoRepositoryApp extends SwaggerConfiguration { + public MongoRepositoryApp() { + super("mongorepository"); + } + + public static void main(String[] args) { + SpringApplication.run(MongoRepositoryApp.class, args); + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/MongoRepositoryController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/MongoRepositoryController.java new file mode 100644 index 0000000000..4a58d9abde --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/repository/MongoRepositoryController.java @@ -0,0 +1,94 @@ +package com.mongo.repository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Exercises exists/count/delete through Spring Data repositories (derived and annotated queries), + * so the Mongo driver is only called from within Spring Data and never from SUT code. + */ +@RestController +@RequestMapping(path = "/mongorepository") +public class MongoRepositoryController { + + @Autowired + private ExistsDerivedRepository existsDerivedRepository; + + @Autowired + private ExistsAnnotatedRepository existsAnnotatedRepository; + + @Autowired + private CountDerivedRepository countDerivedRepository; + + @Autowired + private CountAnnotatedRepository countAnnotatedRepository; + + @Autowired + private DeleteDerivedRepository deleteDerivedRepository; + + @Autowired + private DeleteAnnotatedRepository deleteAnnotatedRepository; + + private static ResponseEntity response(boolean found) { + return ResponseEntity.status(found ? 200 : 404).build(); + } + + /** + * Derived exists query on a Spring Data repository + */ + @GetMapping("existsDerived") + public ResponseEntity existsDerived(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(existsDerivedRepository.existsByCityAndAgeGreaterThanEqual(city, minAge)); + } + + /** + * Annotated exists query on a Spring Data repository + */ + @GetMapping("existsAnnotated") + public ResponseEntity existsAnnotated(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(existsAnnotatedRepository.existsAnnotated(city, minAge)); + } + + /** + * Derived count query on a Spring Data repository + */ + @GetMapping("countDerived") + public ResponseEntity countDerived(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(countDerivedRepository.countByCityAndAgeGreaterThanEqual(city, minAge) > 0); + } + + /** + * Annotated count query on a Spring Data repository + */ + @GetMapping("countAnnotated") + public ResponseEntity countAnnotated(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(countAnnotatedRepository.countAnnotated(city, minAge) > 0); + } + + /** + * Derived delete query on a Spring Data repository + */ + @DeleteMapping("deleteDerived") + public ResponseEntity deleteDerived(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(deleteDerivedRepository.deleteByCityAndAgeGreaterThanEqual(city, minAge) > 0); + } + + /** + * Annotated delete query on a Spring Data repository + */ + @DeleteMapping("deleteAnnotated") + public ResponseEntity deleteAnnotated(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(deleteAnnotatedRepository.deleteAnnotated(city, minAge) > 0); + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/repository/MongoRepositoryAppController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/repository/MongoRepositoryAppController.java new file mode 100644 index 0000000000..ca52103f0d --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/repository/MongoRepositoryAppController.java @@ -0,0 +1,15 @@ +package com.foo.spring.rest.mongo.repository; + +import com.foo.spring.rest.mongo.MongoController; +import com.mongo.repository.MongoRepositoryApp; + +public class MongoRepositoryAppController extends MongoController { + public MongoRepositoryAppController() { + super("repository", MongoRepositoryApp.class); + } + + @Override + public String getPackagePrefixesToCover() { + return "com.mongo.repository"; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/repository/MongoRepositoryEMTest.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/repository/MongoRepositoryEMTest.java new file mode 100644 index 0000000000..a668edeae5 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/repository/MongoRepositoryEMTest.java @@ -0,0 +1,69 @@ +package org.evomaster.e2etests.spring.rest.mongo.repository; + +import com.foo.spring.rest.mongo.repository.MongoRepositoryAppController; +import org.evomaster.core.EMConfig; +import org.evomaster.core.problem.rest.data.HttpVerb; +import org.evomaster.core.problem.rest.data.RestIndividual; +import org.evomaster.core.search.Solution; +import org.evomaster.e2etests.utils.RestTestBase; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class MongoRepositoryEMTest extends RestTestBase { + + @BeforeAll + public static void initClass() throws Exception { + EMConfig config = new EMConfig(); + config.setInstrumentMR_MONGO(true); + RestTestBase.initClass(new MongoRepositoryAppController()); + } + + @Test + public void testRunEM() throws Throwable { + + runTestHandlingFlakyAndCompilation( + "MongoRepositoryEMTest", + "org.foo.spring.rest.mongo.MongoRepositoryEMTest", + 1500, + (args) -> { + setOption(args, "heuristicsForMongo", "true"); + setOption(args, "instrumentMR_MONGO", "true"); + setOption(args, "generateMongoData", "true"); + setOption(args, "extractMongoExecutionInfo", "true"); + + Solution solution = initAndRun(args); + + assertTrue(solution.getIndividuals().size() >= 1); + + // exists/count/delete through Spring Data repositories. + // A 200 needs a document with the requested city and age in the collection, + // which can only be generated if the query run by Spring Data is observed + + // derived exists query + assertHasAtLeastOne(solution, HttpVerb.GET, 404, "/mongorepository/existsDerived", null); + assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/mongorepository/existsDerived", null); + + // annotated exists query + assertHasAtLeastOne(solution, HttpVerb.GET, 404, "/mongorepository/existsAnnotated", null); + assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/mongorepository/existsAnnotated", null); + + // derived count query + assertHasAtLeastOne(solution, HttpVerb.GET, 404, "/mongorepository/countDerived", null); + assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/mongorepository/countDerived", null); + + // annotated count query + assertHasAtLeastOne(solution, HttpVerb.GET, 404, "/mongorepository/countAnnotated", null); + assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/mongorepository/countAnnotated", null); + + // derived delete query + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongorepository/deleteDerived", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongorepository/deleteDerived", null); + + // annotated delete query + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongorepository/deleteAnnotated", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongorepository/deleteAnnotated", null); + }); + } +}