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 3d7cb09f4d..6ec1e1fa15 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 @@ -173,6 +173,27 @@ public static Object findOneAndDelete(Object mongoCollection, @ThirdPartyCast(ac return handleDelete("findOneAndDeleteClientSessionBsonFindOneAndDeleteOptions", mongoCollection, Arrays.asList(clientSession, filter, options), filter); } + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update) { + return handleUpdate("findOneAndUpdateBson", mongoCollection, Arrays.asList(filter, update), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateBsonFindOneAndUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update, @ThirdPartyCast(actualType = "com.mongodb.client.model.FindOneAndUpdateOptions") Object options) { + return handleUpdate("findOneAndUpdateBsonFindOneAndUpdateOptions", mongoCollection, Arrays.asList(filter, update, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateClientSessionBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate_EM_0(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update) { + return handleUpdate("findOneAndUpdateClientSessionBson", mongoCollection, Arrays.asList(clientSession, filter, update), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateClientSessionBsonFindOneAndUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update, @ThirdPartyCast(actualType = "com.mongodb.client.model.FindOneAndUpdateOptions") Object options) { + return handleUpdate("findOneAndUpdateClientSessionBsonFindOneAndUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, update, options), filter); + } + + /** * Processes the count handling based on the provided parameters. * diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacement.java index 8e8f3e30b9..aff4f41b2a 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacement.java @@ -109,4 +109,25 @@ public static Object updateMany(Object mongoCollection, @ThirdPartyCast(actualTy return handleEagerQuery(singleton, "updateManyClientSessionBsonPipelineUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, pipeline, options), filter); } + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateBsonPipeline", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline) { + return handleEagerQuery(singleton,"findOneAndUpdateBsonPipeline", mongoCollection, Arrays.asList(filter, pipeline), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateBsonPipelineFindOneAndUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline, @ThirdPartyCast(actualType = "com.mongodb.client.model.FindOneAndUpdateOptions") Object options) { + return handleEagerQuery(singleton,"findOneAndUpdateBsonPipelineFindOneAndUpdateOptions", mongoCollection, Arrays.asList(filter, pipeline, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateClientSessionBsonPipeline", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline) { + return handleEagerQuery(singleton,"findOneAndUpdateClientSessionBsonPipeline", mongoCollection, Arrays.asList(clientSession, filter, pipeline), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndUpdateClientSessionBsonPipelineFindOneAndUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO) + public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline, @ThirdPartyCast(actualType = "com.mongodb.client.model.FindOneAndUpdateOptions") Object options) { + return handleEagerQuery(singleton,"findOneAndUpdateClientSessionBsonPipelineFindOneAndUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, pipeline, options), filter); + } + + } 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 072986707d..6b7827e7ce 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 @@ -3,6 +3,7 @@ import com.mongodb.MongoQueryException; import com.mongodb.client.*; import com.mongodb.client.model.DeleteOptions; +import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.client.model.FindOneAndDeleteOptions; import com.mongodb.client.result.DeleteResult; import org.bson.BsonDocument; @@ -621,4 +622,102 @@ public void testFindOneAndDeleteWithInvalidFilter() { BsonDocument retrievedQuery = assertSingleRecordedCommand(false); assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue()); } + + @Test + public void testFindOneAndUpdateWithFilter() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionClassReplacement.findOneAndUpdate(collection, new Document("age", 30), setVerified()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testFindOneAndUpdateWithFilterAndOptions() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionClassReplacement.findOneAndUpdate(collection, new Document("age", 30), setVerified(), new FindOneAndUpdateOptions()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testFindOneAndUpdateWithClientSessionAndFilter() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionClassReplacement.findOneAndUpdate_EM_0(collection, clientSession, new Document("age", 30), setVerified()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testFindOneAndUpdateWithClientSessionFilterAndOptions() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionClassReplacement.findOneAndUpdate(collection, clientSession, new Document("age", 30), setVerified(), new FindOneAndUpdateOptions()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } + + + + + + @Test + public void testFindOneAndUpdateWithFilterNoMatch() { + final MongoCollection collection = getMongoCollection(); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionClassReplacement.findOneAndUpdate(collection, new Document("age", 99), setVerified()); + + assertNull(found); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(99, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testFindOneAndUpdateWithInvalidFilter() { + 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.findOneAndUpdate(collection, invalidFilter, setVerified())); + + BsonDocument retrievedQuery = assertSingleRecordedCommand(false); + assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue()); + } } diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacementTest.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacementTest.java index c4f7327c20..03b21177b2 100644 --- a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacementTest.java +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacementTest.java @@ -1,6 +1,7 @@ package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses; import com.mongodb.client.*; +import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.client.model.UpdateOptions; import com.mongodb.client.result.UpdateResult; import org.bson.BsonDocument; @@ -122,4 +123,75 @@ public void testUpdateOneWithFilterPipelineAndOptions() { BsonDocument retrievedQuery = assertSingleRecordedCommand(true); assertEquals(30, retrievedQuery.getInt32("age").getValue()); } + + @Test + public void testFindOneAndUpdateWithClientSessionFilterAndPipeline() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionPipelineUpdateClassReplacement.findOneAndUpdate(collection, clientSession, new Document("age", 30), verifiedPipeline()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } + + + @Test + public void testFindOneAndUpdateWithClientSessionFilterPipelineAndOptions() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionPipelineUpdateClassReplacement.findOneAndUpdate(collection, clientSession, new Document("age", 30), verifiedPipeline(), new FindOneAndUpdateOptions()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testFindOneAndUpdateWithFilterAndPipeline() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionPipelineUpdateClassReplacement.findOneAndUpdate(collection, new Document("age", 30), verifiedPipeline()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + + @Test + public void testFindOneAndUpdateWithFilterPipelineAndOptions() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + Document found = (Document) MongoCollectionPipelineUpdateClassReplacement.findOneAndUpdate(collection, new Document("age", 30), verifiedPipeline(), new FindOneAndUpdateOptions()); + + assertNotNull(found); + assertEquals("John Doe", found.getString("name")); + assertEquals(1, collection.countDocuments(new Document("verified", true))); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateApp.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateApp.java new file mode 100644 index 0000000000..76721f5d57 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateApp.java @@ -0,0 +1,20 @@ +package com.mongo.findoneandupdate; + +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 MongoFindOneAndUpdateApp extends SwaggerConfiguration { + public MongoFindOneAndUpdateApp() { + super("mongofindoneandupdate"); + } + + public static void main(String[] args) { + SpringApplication.run(MongoFindOneAndUpdateApp.class, args); + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateController.java new file mode 100644 index 0000000000..cdd7ad86dd --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateController.java @@ -0,0 +1,171 @@ +package com.mongo.findoneandupdate; + +import com.mongodb.MongoClientSettings; +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.FindOneAndUpdateOptions; +import com.mongodb.client.model.ReturnDocument; +import com.mongodb.client.model.Updates; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.PojoCodecProvider; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; + +/** + * Updates documents directly through the Mongo driver (MongoCollection.findOneAndUpdate), + * not through Spring Data, so the filter is only visible at the driver level. + * There is one endpoint for each overload of findOneAndUpdate. + */ +@RestController +@RequestMapping(path = "/mongofindoneandupdate") +public class MongoFindOneAndUpdateController { + + /* + Each endpoint uses its own collection, so that a document that + makes one endpoint return 200 cannot accidentally do it for another one. + */ + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE = "findOneAndUpdateByCityAndAgeCollection"; + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_WITH_OPTIONS = "findOneAndUpdateByCityAndAgeWithOptionsCollection"; + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION = "findOneAndUpdateByCityAndAgeInSessionCollection"; + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS = "findOneAndUpdateByCityAndAgeInSessionWithOptionsCollection"; + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_WITH_PIPELINE = "findOneAndUpdateByCityAndAgeWithPipelineCollection"; + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_WITH_PIPELINE_AND_OPTIONS = "findOneAndUpdateByCityAndAgeWithPipelineAndOptionsCollection"; + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE = "findOneAndUpdateByCityAndAgeInSessionWithPipelineCollection"; + private static final String FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE_AND_OPTIONS = "findOneAndUpdateByCityAndAgeInSessionWithPipelineAndOptionsCollection"; + + /* + findOneAndUpdate returns the document, so (unlike count, delete or update) + the driver needs a codec to decode it into our own class + */ + private static final CodecRegistry POJO_CODEC_REGISTRY = CodecRegistries.fromRegistries( + MongoClientSettings.getDefaultCodecRegistry(), + CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build())); + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private MongoClient mongoClient; + + private MongoCollection collection(String collectionName) { + return mongoTemplate.getDb() + .getCollection(collectionName, MongoFindOneAndUpdateData.class) + .withCodecRegistry(POJO_CODEC_REGISTRY); + } + + private static Bson filter(String city, int minAge) { + return Filters.and(Filters.eq("city", city), Filters.gte("age", minAge)); + } + + private static Bson update() { + return Updates.set("verified", true); + } + + private static List pipeline() { + return Collections.singletonList(new Document("$set", new Document("verified", true))); + } + + private static FindOneAndUpdateOptions options() { + return new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER); + } + + /* + findOneAndUpdate returns the document, or null if no document matched the filter + */ + private static ResponseEntity response(MongoFindOneAndUpdateData updated) { + return ResponseEntity.status(updated != null ? 200 : 404).build(); + } + + /** + * findOneAndUpdate(Bson, Bson) + */ + @PutMapping("findOneAndUpdateByCityAndAge") + public ResponseEntity findOneAndUpdateByCityAndAge(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE).findOneAndUpdate(filter(city, minAge), update())); + } + + /** + * findOneAndUpdate(Bson, Bson, FindOneAndUpdateOptions) + */ + @PutMapping("findOneAndUpdateByCityAndAgeWithOptions") + public ResponseEntity findOneAndUpdateByCityAndAgeWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_WITH_OPTIONS).findOneAndUpdate(filter(city, minAge), update(), options())); + } + + /** + * findOneAndUpdate(ClientSession, Bson, Bson) + */ + @PutMapping("findOneAndUpdateByCityAndAgeInSession") + public ResponseEntity findOneAndUpdateByCityAndAgeInSession(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION).findOneAndUpdate(session, filter(city, minAge), update())); + } + } + + /** + * findOneAndUpdate(ClientSession, Bson, Bson, FindOneAndUpdateOptions) + */ + @PutMapping("findOneAndUpdateByCityAndAgeInSessionWithOptions") + public ResponseEntity findOneAndUpdateByCityAndAgeInSessionWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS).findOneAndUpdate(session, filter(city, minAge), update(), options())); + } + } + + /** + * findOneAndUpdate(Bson, List) + */ + @PutMapping("findOneAndUpdateByCityAndAgeWithPipeline") + public ResponseEntity findOneAndUpdateByCityAndAgeWithPipeline(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_WITH_PIPELINE).findOneAndUpdate(filter(city, minAge), pipeline())); + } + + /** + * findOneAndUpdate(Bson, List, FindOneAndUpdateOptions) + */ + @PutMapping("findOneAndUpdateByCityAndAgeWithPipelineAndOptions") + public ResponseEntity findOneAndUpdateByCityAndAgeWithPipelineAndOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_WITH_PIPELINE_AND_OPTIONS).findOneAndUpdate(filter(city, minAge), pipeline(), options())); + } + + /** + * findOneAndUpdate(ClientSession, Bson, List) + */ + @PutMapping("findOneAndUpdateByCityAndAgeInSessionWithPipeline") + public ResponseEntity findOneAndUpdateByCityAndAgeInSessionWithPipeline(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE).findOneAndUpdate(session, filter(city, minAge), pipeline())); + } + } + + /** + * findOneAndUpdate(ClientSession, Bson, List, FindOneAndUpdateOptions) + */ + @PutMapping("findOneAndUpdateByCityAndAgeInSessionWithPipelineAndOptions") + public ResponseEntity findOneAndUpdateByCityAndAgeInSessionWithPipelineAndOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(FIND_ONE_AND_UPDATE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE_AND_OPTIONS).findOneAndUpdate(session, filter(city, minAge), pipeline(), options())); + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateData.java new file mode 100644 index 0000000000..a44a3ed514 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/findoneandupdate/MongoFindOneAndUpdateData.java @@ -0,0 +1,32 @@ +package com.mongo.findoneandupdate; + +import org.bson.BsonType; +import org.bson.codecs.pojo.annotations.BsonRepresentation; +import org.springframework.data.annotation.Id; + +public class MongoFindOneAndUpdateData { + + /* + The documents in the collection have an ObjectId as _id, while this is a String. + As findOneAndUpdate decodes the returned document with the driver's POJO codec, + it needs to be told how to read it. + */ + @Id + @BsonRepresentation(BsonType.OBJECT_ID) + public String id; + + public String city; + + public int age; + + public boolean verified; + + public MongoFindOneAndUpdateData() { + } + + public MongoFindOneAndUpdateData(String city, int age, boolean verified) { + this.city = city; + this.age = age; + this.verified = verified; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/findoneandupdate/MongoFindOneAndUpdateAppController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/findoneandupdate/MongoFindOneAndUpdateAppController.java new file mode 100644 index 0000000000..58bce35598 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/findoneandupdate/MongoFindOneAndUpdateAppController.java @@ -0,0 +1,15 @@ +package com.foo.spring.rest.mongo.findoneandupdate; + +import com.foo.spring.rest.mongo.MongoController; +import com.mongo.findoneandupdate.MongoFindOneAndUpdateApp; + +public class MongoFindOneAndUpdateAppController extends MongoController { + public MongoFindOneAndUpdateAppController() { + super("findoneandupdate", MongoFindOneAndUpdateApp.class); + } + + @Override + public String getPackagePrefixesToCover() { + return "com.mongo.findoneandupdate"; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/findoneandupdate/MongoFindOneAndUpdateEMTest.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/findoneandupdate/MongoFindOneAndUpdateEMTest.java new file mode 100644 index 0000000000..e2c6aae970 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/findoneandupdate/MongoFindOneAndUpdateEMTest.java @@ -0,0 +1,77 @@ +package org.evomaster.e2etests.spring.rest.mongo.findoneandupdate; + +import com.foo.spring.rest.mongo.findoneandupdate.MongoFindOneAndUpdateAppController; +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 MongoFindOneAndUpdateEMTest extends RestTestBase { + + @BeforeAll + public static void initClass() throws Exception { + EMConfig config = new EMConfig(); + config.setInstrumentMR_MONGO(true); + RestTestBase.initClass(new MongoFindOneAndUpdateAppController()); + } + + @Test + public void testRunEM() throws Throwable { + + runTestHandlingFlakyAndCompilation( + "MongoFindOneAndUpdateEMTest", + "org.foo.spring.rest.mongo.MongoFindOneAndUpdateEMTest", + 2000, + (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.findOneAndUpdate. + // A 200 needs a document with the requested city and age in the collection, + // which can only be generated if the findOneAndUpdate filter is observed + + // findOneAndUpdate(Bson, Bson) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAge", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAge", null); + + // findOneAndUpdate(Bson, Bson, FindOneAndUpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeWithOptions", null); + + // findOneAndUpdate(ClientSession, Bson, Bson) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSession", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSession", null); + + // findOneAndUpdate(ClientSession, Bson, Bson, FindOneAndUpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSessionWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSessionWithOptions", null); + + // findOneAndUpdate(Bson, List) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeWithPipeline", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeWithPipeline", null); + + // findOneAndUpdate(Bson, List, FindOneAndUpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeWithPipelineAndOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeWithPipelineAndOptions", null); + + // findOneAndUpdate(ClientSession, Bson, List) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSessionWithPipeline", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSessionWithPipeline", null); + + // findOneAndUpdate(ClientSession, Bson, List, FindOneAndUpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSessionWithPipelineAndOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandupdate/findOneAndUpdateByCityAndAgeInSessionWithPipelineAndOptions", null); + }); + } +}