From b27bc6545299d01bf4bb83358d22854cf5a8adc9 Mon Sep 17 00:00:00 2001 From: Juan Pablo Galeotti Date: Thu, 8 Oct 2026 21:10:15 -0300 Subject: [PATCH] add instrumentation for MongoCollection.deleteOne/deleteMany methods --- .../MongoCollectionClassReplacement.java | 109 ++++++++++- .../MongoCollectionClassReplacementTest.java | 170 ++++++++++++++++++ .../java/com/mongo/delete/MongoDeleteApp.java | 20 +++ .../mongo/delete/MongoDeleteController.java | 142 +++++++++++++++ .../com/mongo/delete/MongoDeleteData.java | 21 +++ .../delete/MongoDeleteAppController.java | 15 ++ .../rest/mongo/delete/MongoDeleteEMTest.java | 91 ++++++++++ 7 files changed, 563 insertions(+), 5 deletions(-) create mode 100644 core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteApp.java create mode 100644 core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteController.java create mode 100644 core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteData.java create mode 100644 core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/delete/MongoDeleteAppController.java create mode 100644 core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/delete/MongoDeleteEMTest.java diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java index c423be6d81..9385ce4afe 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacement.java @@ -71,16 +71,83 @@ public static long countDocuments(Object mongoCollection, @ThirdPartyCast(actual return handleCount("countDocumentsClientSessionBsonCountOptions", mongoCollection, Arrays.asList(clientSession, filter, options), filter); } + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteOneBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteOne(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter) { + return handleDelete("deleteOneBson", mongoCollection, Arrays.asList(filter), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteOneBsonDeleteOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteOne(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "com.mongodb.client.model.DeleteOptions") Object options) { + return handleDelete("deleteOneBsonDeleteOptions", mongoCollection, Arrays.asList(filter, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteOneClientSessionBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteOne_EM_0(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter) { + return handleDelete("deleteOneClientSessionBson", mongoCollection, Arrays.asList(clientSession, filter), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteOneClientSessionBsonDeleteOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteOne(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "com.mongodb.client.model.DeleteOptions") Object options) { + return handleDelete("deleteOneClientSessionBsonDeleteOptions", mongoCollection, Arrays.asList(clientSession, filter, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteManyBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteMany(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter) { + return handleDelete("deleteManyBson", mongoCollection, Arrays.asList(filter), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteManyBsonDeleteOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteMany(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "com.mongodb.client.model.DeleteOptions") Object options) { + return handleDelete("deleteManyBsonDeleteOptions", mongoCollection, Arrays.asList(filter, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteManyClientSessionBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteMany_EM_0(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter) { + return handleDelete("deleteManyClientSessionBson", mongoCollection, Arrays.asList(clientSession, filter), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "deleteManyClientSessionBsonDeleteOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.DeleteResult") + public static Object deleteMany(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "com.mongodb.client.model.DeleteOptions") Object options) { + return handleDelete("deleteManyClientSessionBsonDeleteOptions", mongoCollection, Arrays.asList(clientSession, filter, options), filter); + } + /** - * Unlike find, count is executed eagerly, so we know right away whether the filter was valid + * Processes the count handling based on the provided parameters. + * + * @param id The identifier used to locate the relevant resource. + * @param mongoCollection The collection object to interact with the database. + * @param args A list of additional arguments required for the operation. + * @param filter The filter criteria applied during the handling process. + * @return The calculated count as a long value. */ private static long handleCount(String id, Object mongoCollection, List args, Object filter) { + return (long) handleEagerQuery(id, mongoCollection, args, filter); + } + + /** + * Handles the deletion operation for a specific document in the provided collection. + * + * @param id The identifier of the document to be deleted. + * @param mongoCollection The collection from which the document will be deleted. + * @param args A list of additional arguments required for the delete operation. + * @param filter The filter conditions to locate the document for deletion. + * @return The result of the delete operation, which can vary based on the implementation. + */ + private static Object handleDelete(String id, Object mongoCollection, List args, Object filter) { + return handleEagerQuery(id, mongoCollection, args, filter); + } + + /** + * Unlike find, operations like count and delete are executed eagerly, + * so we know right away whether the filter was valid + */ + private static Object handleEagerQuery(String id, Object mongoCollection, List args, Object filter) { long start = System.currentTimeMillis(); - Method countMethod = retrieveFindMethod(id, mongoCollection); + Method method = retrieveFindMethod(id, mongoCollection); try { - long result = (long) countMethod.invoke(mongoCollection, args.toArray()); + Object result = method.invoke(mongoCollection, args.toArray()); long end = System.currentTimeMillis(); - handleMongo(mongoCollection, filter, true, end - start); + handleMongo(mongoCollection, filter, true, end - start); return result; } catch (IllegalAccessException e) { throw new RuntimeException(e); @@ -98,7 +165,19 @@ private static long handleCount(String id, Object mongoCollection, List } } - private static Object handleFind(String id, Object mongoCollection, List args, Object query) { + /** + * Handles a lazy query execution on a MongoDB collection and ensures the query is valid + * by invoking the relevant "find" method and explicitly verifying the results. + * + * @param id The identifier used to retrieve the appropriate find method for the query. + * @param mongoCollection The MongoDB collection object on which the query is executed. + * @param args The list of arguments passed to the find method for the query execution. + * @param query The query object representing the query being executed. + * @return The result of the query execution, typically an iterable containing the query results. + * @throws RuntimeException If an error occurs during method invocation or if an exception is thrown + * by the query's hasNext() method. + */ + private static Object handleLazyQuery(String id, Object mongoCollection, List args, Object query) { long start = System.currentTimeMillis(); try { Method findMethod = retrieveFindMethod(id, mongoCollection); @@ -124,6 +203,26 @@ private static Object handleFind(String id, Object mongoCollection, List } } + + /** + * Handles the invocation of a "find" operation on the given MongoDB collection. + * This method retrieves the appropriate find method for the specified identifier + * and invokes it with the provided arguments. It also ensures that the query execution + * is valid by verifying the result through its iterator. + * + * @param id The identifier used to retrieve the appropriate "find" method. + * @param mongoCollection The MongoDB collection object on which the "find" operation is to be executed. + * @param args A list of arguments to be passed to the "find" method. + * @param query The query object associated with the "find" operation, used for logging and analysis. + * @return The result of the "find" operation, typically an iterable or cursor representing the query result. + * @throws RuntimeException If an IllegalAccessException or InvocationTargetException occurs, + * or if the root cause of the exception during invocation is a runtime exception. + */ + private static Object handleFind(String id, Object mongoCollection, List args, Object query) { + return handleLazyQuery(id, mongoCollection, args, query); + } + + private static Method retrieveFindMethod(String id, Object mongoCollection) { return getOriginal(singleton, id, mongoCollection); } diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacementTest.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacementTest.java index 664aac3dc4..3b216fd3be 100644 --- a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacementTest.java +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacementTest.java @@ -4,6 +4,8 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoQueryException; import com.mongodb.client.*; +import com.mongodb.client.model.DeleteOptions; +import com.mongodb.client.result.DeleteResult; import org.bson.BsonDocument; import org.bson.BsonDocumentWriter; import org.bson.BsonWriter; @@ -450,4 +452,172 @@ public void testCountDocumentsWithInvalidFilter() { BsonDocument retrievedQuery = assertSingleRecordedCommand(false); assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue()); } + + @Test + public void testDeleteOneWithFilter() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteOne(collection, new Document("age", 30)); + + assertEquals(1, result.getDeletedCount()); + assertEquals(1, collection.countDocuments()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testDeleteOneWithFilterNoMatch() { + final MongoCollection collection = getMongoCollection(); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteOne(collection, new Document("age", 99)); + + assertEquals(0, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(99, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testDeleteOneWithFilterAndOptions() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteOne(collection, new Document("age", 30), new DeleteOptions()); + + assertEquals(1, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testDeleteOneWithClientSessionAndFilter() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 23); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteOne_EM_0(collection, clientSession, new Document("age", 23)); + + assertEquals(1, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(23, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testDeleteOneWithClientSessionFilterAndOptions() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 23); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteOne(collection, clientSession, new Document("age", 23), new DeleteOptions()); + + assertEquals(1, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(23, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testDeleteOneWithInvalidFilter() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + + Document invalidFilter = new Document("tags", new Document("$size", -1)); + + ExecutionTracer.setExecutingInitMongo(false); + assertThrows(com.mongodb.MongoException.class, () -> MongoCollectionClassReplacement.deleteOne(collection, invalidFilter)); + + BsonDocument retrievedQuery = assertSingleRecordedCommand(false); + assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue()); + } + + @Test + public void testDeleteManyWithFilter() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteMany(collection, new Document("age", 30)); + + assertEquals(1, result.getDeletedCount()); + assertEquals(1, collection.countDocuments()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testDeleteManyWithFilterNoMatch() { + final MongoCollection collection = getMongoCollection(); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteMany(collection, new Document("age", 99)); + + assertEquals(0, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(99, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testDeleteManyWithFilterAndOptions() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteMany(collection, new Document("age", 30), new DeleteOptions()); + + assertEquals(1, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testDeleteManyWithClientSessionAndFilter() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 23); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteMany_EM_0(collection, clientSession, new Document("age", 23)); + + assertEquals(1, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(23, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testDeleteManyWithClientSessionFilterAndOptions() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 23); + + ExecutionTracer.setExecutingInitMongo(false); + DeleteResult result = (DeleteResult) MongoCollectionClassReplacement.deleteMany(collection, clientSession, new Document("age", 23), new DeleteOptions()); + + assertEquals(1, result.getDeletedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(23, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testDeleteManyWithInvalidFilter() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + + Document invalidFilter = new Document("tags", new Document("$size", -1)); + + ExecutionTracer.setExecutingInitMongo(false); + assertThrows(com.mongodb.MongoException.class, () -> MongoCollectionClassReplacement.deleteMany(collection, invalidFilter)); + + BsonDocument retrievedQuery = assertSingleRecordedCommand(false); + assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue()); + } } diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteApp.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteApp.java new file mode 100644 index 0000000000..863f886f18 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteApp.java @@ -0,0 +1,20 @@ +package com.mongo.delete; + +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 MongoDeleteApp extends SwaggerConfiguration { + public MongoDeleteApp() { + super("mongodelete"); + } + + public static void main(String[] args) { + SpringApplication.run(MongoDeleteApp.class, args); + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteController.java new file mode 100644 index 0000000000..be07a4398a --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteController.java @@ -0,0 +1,142 @@ +package com.mongo.delete; + +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Collation; +import com.mongodb.client.model.DeleteOptions; +import com.mongodb.client.model.Filters; +import com.mongodb.client.result.DeleteResult; +import org.bson.conversions.Bson; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Deletes documents directly through the Mongo driver (MongoCollection.deleteOne/deleteMany), + * not through Spring Data, so the filter is only visible at the driver level. + * There is one endpoint for each overload of deleteOne and deleteMany. + */ +@RestController +@RequestMapping(path = "/mongodelete") +public class MongoDeleteController { + + /* + Each endpoint deletes from its own collection, so that a document that + makes one endpoint return 200 cannot accidentally do it for another one. + */ + private static final String DELETE_ONE_BY_CITY_AND_AGE = "deleteOneByCityAndAgeCollection"; + private static final String DELETE_ONE_BY_CITY_AND_AGE_WITH_OPTIONS = "deleteOneByCityAndAgeWithOptionsCollection"; + private static final String DELETE_ONE_BY_CITY_AND_AGE_IN_SESSION = "deleteOneByCityAndAgeInSessionCollection"; + private static final String DELETE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS = "deleteOneByCityAndAgeInSessionWithOptionsCollection"; + private static final String DELETE_MANY_BY_CITY_AND_AGE = "deleteManyByCityAndAgeCollection"; + private static final String DELETE_MANY_BY_CITY_AND_AGE_WITH_OPTIONS = "deleteManyByCityAndAgeWithOptionsCollection"; + private static final String DELETE_MANY_BY_CITY_AND_AGE_IN_SESSION = "deleteManyByCityAndAgeInSessionCollection"; + private static final String DELETE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS = "deleteManyByCityAndAgeInSessionWithOptionsCollection"; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private MongoClient mongoClient; + + private MongoCollection collection(String collectionName) { + return mongoTemplate.getDb().getCollection(collectionName, MongoDeleteData.class); + } + + private static Bson filter(String city, int minAge) { + return Filters.and(Filters.eq("city", city), Filters.gte("age", minAge)); + } + + private static DeleteOptions options() { + return new DeleteOptions().collation(Collation.builder().locale("en").build()); + } + + private static ResponseEntity response(DeleteResult result) { + return ResponseEntity.status(result.getDeletedCount() > 0 ? 200 : 404).build(); + } + + /** + * deleteOne(Bson) + */ + @DeleteMapping("deleteOneByCityAndAge") + public ResponseEntity deleteOneByCityAndAge(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(DELETE_ONE_BY_CITY_AND_AGE).deleteOne(filter(city, minAge))); + } + + /** + * deleteOne(Bson, DeleteOptions) + */ + @DeleteMapping("deleteOneByCityAndAgeWithOptions") + public ResponseEntity deleteOneByCityAndAgeWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(DELETE_ONE_BY_CITY_AND_AGE_WITH_OPTIONS).deleteOne(filter(city, minAge), options())); + } + + /** + * deleteOne(ClientSession, Bson) + */ + @DeleteMapping("deleteOneByCityAndAgeInSession") + public ResponseEntity deleteOneByCityAndAgeInSession(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(DELETE_ONE_BY_CITY_AND_AGE_IN_SESSION).deleteOne(session, filter(city, minAge))); + } + } + + /** + * deleteOne(ClientSession, Bson, DeleteOptions) + */ + @DeleteMapping("deleteOneByCityAndAgeInSessionWithOptions") + public ResponseEntity deleteOneByCityAndAgeInSessionWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(DELETE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS).deleteOne(session, filter(city, minAge), options())); + } + } + + /** + * deleteMany(Bson) + */ + @DeleteMapping("deleteManyByCityAndAge") + public ResponseEntity deleteManyByCityAndAge(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(DELETE_MANY_BY_CITY_AND_AGE).deleteMany(filter(city, minAge))); + } + + /** + * deleteMany(Bson, DeleteOptions) + */ + @DeleteMapping("deleteManyByCityAndAgeWithOptions") + public ResponseEntity deleteManyByCityAndAgeWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(DELETE_MANY_BY_CITY_AND_AGE_WITH_OPTIONS).deleteMany(filter(city, minAge), options())); + } + + /** + * deleteMany(ClientSession, Bson) + */ + @DeleteMapping("deleteManyByCityAndAgeInSession") + public ResponseEntity deleteManyByCityAndAgeInSession(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(DELETE_MANY_BY_CITY_AND_AGE_IN_SESSION).deleteMany(session, filter(city, minAge))); + } + } + + /** + * deleteMany(ClientSession, Bson, DeleteOptions) + */ + @DeleteMapping("deleteManyByCityAndAgeInSessionWithOptions") + public ResponseEntity deleteManyByCityAndAgeInSessionWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(DELETE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS).deleteMany(session, filter(city, minAge), options())); + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteData.java new file mode 100644 index 0000000000..f546aa6a02 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/delete/MongoDeleteData.java @@ -0,0 +1,21 @@ +package com.mongo.delete; + +import org.springframework.data.annotation.Id; + +public class MongoDeleteData { + + @Id + public String id; + + public String city; + + public int age; + + public MongoDeleteData() { + } + + public MongoDeleteData(String city, int age) { + this.city = city; + this.age = age; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/delete/MongoDeleteAppController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/delete/MongoDeleteAppController.java new file mode 100644 index 0000000000..dc7d0c00ee --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/delete/MongoDeleteAppController.java @@ -0,0 +1,15 @@ +package com.foo.spring.rest.mongo.delete; + +import com.foo.spring.rest.mongo.MongoController; +import com.mongo.delete.MongoDeleteApp; + +public class MongoDeleteAppController extends MongoController { + public MongoDeleteAppController() { + super("delete", MongoDeleteApp.class); + } + + @Override + public String getPackagePrefixesToCover() { + return "com.mongo.delete"; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/delete/MongoDeleteEMTest.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/delete/MongoDeleteEMTest.java new file mode 100644 index 0000000000..48a7c54714 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/delete/MongoDeleteEMTest.java @@ -0,0 +1,91 @@ +package org.evomaster.e2etests.spring.rest.mongo.delete; + +import com.foo.spring.rest.mongo.delete.MongoDeleteAppController; +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 MongoDeleteEMTest extends RestTestBase { + + @BeforeAll + public static void initClass() throws Exception { + EMConfig config = new EMConfig(); + config.setInstrumentMR_MONGO(true); + RestTestBase.initClass(new MongoDeleteAppController()); + } + + @Test + public void testRunEM() throws Throwable { + + runTestHandlingFlakyAndCompilation( + "MongoDeleteEMTest", + "org.foo.spring.rest.mongo.MongoDeleteEMTest", + 1000, + (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); + + // one endpoint per overload of MongoCollection.deleteOne and MongoCollection.deleteMany + + // deleteOne(Bson) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteOneByCityAndAge", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteOneByCityAndAge", null); + + // deleteOne(Bson, DeleteOptions) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteOneByCityAndAgeWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteOneByCityAndAgeWithOptions", null); + + // deleteOne(ClientSession, Bson) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteOneByCityAndAgeInSession", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteOneByCityAndAgeInSession", null); + + // deleteOne(ClientSession, Bson, DeleteOptions) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteOneByCityAndAgeInSessionWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteOneByCityAndAgeInSessionWithOptions", null); + + // deleteMany(Bson) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteManyByCityAndAge", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteManyByCityAndAge", null); + + // deleteMany(Bson, DeleteOptions) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteManyByCityAndAgeWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteManyByCityAndAgeWithOptions", null); + + // deleteMany(ClientSession, Bson) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteManyByCityAndAgeInSession", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteManyByCityAndAgeInSession", null); + + // deleteMany(ClientSession, Bson, DeleteOptions) + // a 200 needs a document with the requested city and age in the collection, + // which can only be generated if the delete filter is observed + assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongodelete/deleteManyByCityAndAgeInSessionWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongodelete/deleteManyByCityAndAgeInSessionWithOptions", null); + }); + } +}