diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java index 484e039fb4..463a26b131 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/ReplacementList.java @@ -59,6 +59,7 @@ public static List getList() { new MessageBodyReaderClassReplacement(), new MethodClassReplacement(), new MongoCollectionClassReplacement(), + new MongoCollectionPipelineUpdateClassReplacement(), new MongoTemplateClassReplacement(), new OpenSearchClientClassReplacement(), new Neo4JSessionClassReplacement(), 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 9385ce4afe..76c102c451 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,6 +71,7 @@ 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); @@ -111,6 +112,47 @@ public static Object deleteMany(Object mongoCollection, @ThirdPartyCast(actualTy return handleDelete("deleteManyClientSessionBsonDeleteOptions", mongoCollection, Arrays.asList(clientSession, filter, options), filter); } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update) { + return handleUpdate("updateOneBson", mongoCollection, Arrays.asList(filter, update), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneBsonUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update, @ThirdPartyCast(actualType = "com.mongodb.client.model.UpdateOptions") Object options) { + return handleUpdate("updateOneBsonUpdateOptions", mongoCollection, Arrays.asList(filter, update, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneClientSessionBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne_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("updateOneClientSessionBson", mongoCollection, Arrays.asList(clientSession, filter, update), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneClientSessionBsonUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne(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.UpdateOptions") Object options) { + return handleUpdate("updateOneClientSessionBsonUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, update, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update) { + return handleUpdate("updateManyBson", mongoCollection, Arrays.asList(filter, update), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyBsonUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object update, @ThirdPartyCast(actualType = "com.mongodb.client.model.UpdateOptions") Object options) { + return handleUpdate("updateManyBsonUpdateOptions", mongoCollection, Arrays.asList(filter, update, options), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyClientSessionBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany_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("updateManyClientSessionBson", mongoCollection, Arrays.asList(clientSession, filter, update), filter); + } + + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyClientSessionBsonUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany(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.UpdateOptions") Object options) { + return handleUpdate("updateManyClientSessionBsonUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, update, options), filter); + } + /** * Processes the count handling based on the provided parameters. * @@ -137,32 +179,25 @@ 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, + * Unlike find, operations like count, delete and update 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 method = retrieveFindMethod(id, mongoCollection); - try { - Object result = method.invoke(mongoCollection, args.toArray()); - long end = System.currentTimeMillis(); - handleMongo(mongoCollection, filter, true, end - start); - return result; - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - long end = System.currentTimeMillis(); - handleMongo(mongoCollection, filter, false, end - start); - Throwable cause = e.getCause(); - if (cause instanceof RuntimeException) { - throw (RuntimeException) cause; - } - if (cause instanceof Error) { - throw (Error) cause; - } - throw new RuntimeException(cause); - } + return handleEagerQuery(singleton, id, mongoCollection, args, filter); } /** 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 new file mode 100644 index 0000000000..8e8f3e30b9 --- /dev/null +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacement.java @@ -0,0 +1,112 @@ +package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses; + +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast; +import org.evomaster.client.java.instrumentation.coverage.methodreplacement.UsageFilter; +import org.evomaster.client.java.instrumentation.shared.ReplacementCategory; +import org.evomaster.client.java.instrumentation.shared.ReplacementType; + +import java.util.Arrays; +import java.util.List; + +/** + * Replacements for the overloads of updateOne, updateMany and findOneAndUpdate that take an + * update pipeline (a list of stages) instead of a single update document. + * Those overloads were added to MongoCollection in driver 3.11. + *

+ * This cannot be in {@link MongoCollectionClassReplacement}: all the original methods of a + * replacement class are resolved together, and they all must exist in the driver version + * in use. With older drivers, having these methods there would break all the other + * replacements for MongoCollection. Here, instead, the methods are resolved + * only if a call with a pipeline is replaced, which can only happen with drivers having it. + */ +public class MongoCollectionPipelineUpdateClassReplacement extends MongoOperationClassReplacement { + + private static final MongoCollectionPipelineUpdateClassReplacement singleton = new MongoCollectionPipelineUpdateClassReplacement(); + + @Override + protected String getNameOfThirdPartyTargetClass() { + return "com.mongodb.client.MongoCollection"; + } + + /** + * Replacement for {@code MongoCollection.updateOne(Bson, List)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneBsonPipeline", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline) { + return handleEagerQuery(singleton, "updateOneBsonPipeline", mongoCollection, Arrays.asList(filter, pipeline), filter); + } + + /** + * Replacement for {@code MongoCollection.updateOne(Bson, List, UpdateOptions)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneBsonPipelineUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline, @ThirdPartyCast(actualType = "com.mongodb.client.model.UpdateOptions") Object options) { + return handleEagerQuery(singleton, "updateOneBsonPipelineUpdateOptions", mongoCollection, Arrays.asList(filter, pipeline, options), filter); + } + + /** + * Replacement for {@code MongoCollection.updateOne(ClientSession, Bson, List)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneClientSessionBsonPipeline", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline) { + return handleEagerQuery(singleton, "updateOneClientSessionBsonPipeline", mongoCollection, Arrays.asList(clientSession, filter, pipeline), filter); + } + + /** + * Replacement for {@code MongoCollection.updateOne(ClientSession, Bson, List, UpdateOptions)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateOneClientSessionBsonPipelineUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateOne(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.UpdateOptions") Object options) { + return handleEagerQuery(singleton, "updateOneClientSessionBsonPipelineUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, pipeline, options), filter); + } + + /** + * Replacement for {@code MongoCollection.updateMany(Bson, List)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyBsonPipeline", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline) { + return handleEagerQuery(singleton, "updateManyBsonPipeline", mongoCollection, Arrays.asList(filter, pipeline), filter); + } + + /** + * Replacement for {@code MongoCollection.updateMany(Bson, List, UpdateOptions)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyBsonPipelineUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline, @ThirdPartyCast(actualType = "com.mongodb.client.model.UpdateOptions") Object options) { + return handleEagerQuery(singleton, "updateManyBsonPipelineUpdateOptions", mongoCollection, Arrays.asList(filter, pipeline, options), filter); + } + + /** + * Replacement for {@code MongoCollection.updateMany(ClientSession, Bson, List)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyClientSessionBsonPipeline", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, List pipeline) { + return handleEagerQuery(singleton, "updateManyClientSessionBsonPipeline", mongoCollection, Arrays.asList(clientSession, filter, pipeline), filter); + } + + /** + * Replacement for {@code MongoCollection.updateMany(ClientSession, Bson, List, UpdateOptions)}. + *

+ * Present in the MongoDB Java driver from 3.11.0 (not present in 3.10.2). + */ + @Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "updateManyClientSessionBsonPipelineUpdateOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO, castTo = "com.mongodb.client.result.UpdateResult") + public static Object updateMany(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.UpdateOptions") Object options) { + return handleEagerQuery(singleton, "updateManyClientSessionBsonPipelineUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, pipeline, options), filter); + } + +} diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoOperationClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoOperationClassReplacement.java index 44be213a3a..e695104dc3 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoOperationClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoOperationClassReplacement.java @@ -9,6 +9,7 @@ import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Collections; import java.util.List; @@ -23,6 +24,40 @@ protected static void handleMongo(Object mongoCollection, Object bson, boolean s ExecutionTracer.addMongoInfo(info); } + /** + * Executes an operation that, unlike find, is executed eagerly (eg, count, delete and update), + * so we know right away whether the filter was valid. The query is stored in any case. + * + * @param singleton the replacement class holding the replacement with the given id + * @param id the id of the replacement, used to retrieve the original method + * @param mongoCollection the collection on which the operation is executed + * @param args the arguments of the original call + * @param filter the query used in the operation + * @return the result of the original call + */ + protected static Object handleEagerQuery(ThirdPartyMethodReplacementClass singleton, String id, Object mongoCollection, List args, Object filter) { + long start = System.currentTimeMillis(); + Method method = getOriginal(singleton, id, mongoCollection); + try { + Object result = method.invoke(mongoCollection, args.toArray()); + long end = System.currentTimeMillis(); + handleMongo(mongoCollection, filter, true, end - start); + return result; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + long end = System.currentTimeMillis(); + handleMongo(mongoCollection, filter, false, end - start); + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + throw new RuntimeException(cause); + } + } private static Class extractDocumentsType(Object collection) { try { 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 3b216fd3be..a376f3de4c 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 @@ -21,70 +21,15 @@ import org.evomaster.client.java.instrumentation.object.CustomTypeToOasConverter; import org.evomaster.client.java.instrumentation.object.GeoJsonPointToOasConverter; import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.testcontainers.containers.GenericContainer; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; -import static org.bson.codecs.configuration.CodecRegistries.fromProviders; -import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; import static org.junit.jupiter.api.Assertions.*; -public class MongoCollectionClassReplacementTest { - - private static MongoClient mongoClient; - private static final int MONGODB_PORT = 27017; - private static final GenericContainer mongodb = new GenericContainer<>("mongo:6.0") - .withExposedPorts(MONGODB_PORT); - - @BeforeAll - public static void initMongoClient() { - mongodb.start(); - int port = mongodb.getMappedPort(MONGODB_PORT); - - CodecRegistry codecRegistry = fromRegistries( - MongoClientSettings.getDefaultCodecRegistry(), - fromProviders(PojoCodecProvider.builder().automatic(true).build()), - CodecRegistries.fromCodecs(new MongoCollectionTestDtoCodec()) - ); - - MongoClientSettings.Builder builder = MongoClientSettings.builder(); - builder.codecRegistry(codecRegistry); - builder.applyConnectionString(new ConnectionString("mongodb://localhost:" + port + "/" + "aDatabase")); - MongoClientSettings settings = builder - .build(); - - mongoClient = MongoClients.create(settings); - - ExecutionTracer.reset(); - } - - @AfterAll - public static void resetExecutionTracer() { - ExecutionTracer.reset(); - } - - private final static String DATABASE_NAME = "myDatabase"; - private final static String COLLECTION_NAME = "myCollection"; - - @BeforeEach - public void clearDatabase() { - - final MongoCollection collection = getMongoCollection(); - - // delete all documents in collection (if any) - collection.deleteMany(new Document()); - - ExecutionTracer.reset(); - } - +public class MongoCollectionClassReplacementTest extends MongoCollectionClassReplacementTestBase { @Test public void testFindOnly() { @@ -206,12 +151,6 @@ public void testFindWithResultClass() { assertEquals(bsonDocumentSchema, documentType); } - private static @NotNull MongoCollection getMongoCollection() { - MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME); - MongoCollection collection = database.getCollection(COLLECTION_NAME); - return collection; - } - @Test public void testFindWithFilterAndResultClass() { final MongoCollection collection = getMongoCollection(); @@ -351,24 +290,6 @@ public void testFindWithInvalidFilter() { } - private BsonDocument assertSingleRecordedCommand(boolean expectedSuccess) { - List additionalInfoList = ExecutionTracer.exposeAdditionalInfoList(); - assertEquals(1, additionalInfoList.size()); - Set mongoFindCommands = additionalInfoList.get(0).getMongoInfoData(); - assertEquals(1, mongoFindCommands.size()); - - MongoFindCommand mongoFindCommand = mongoFindCommands.iterator().next(); - assertEquals(COLLECTION_NAME, mongoFindCommand.getCollectionName()); - assertEquals(DATABASE_NAME, mongoFindCommand.getDatabaseName()); - assertEquals(expectedSuccess, mongoFindCommand.isSuccessfullyExecuted()); - assertNotNull(mongoFindCommand.getQuery()); - return (BsonDocument) mongoFindCommand.getQuery(); - } - - private void insertPerson(String name, int age) { - getMongoCollection().insertOne(new Document("name", name).append("age", age)); - } - @Test public void testCountDocumentsWithFilter() { final MongoCollection collection = getMongoCollection(); diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacementTestBase.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacementTestBase.java new file mode 100644 index 0000000000..bf2f8f4f44 --- /dev/null +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionClassReplacementTestBase.java @@ -0,0 +1,117 @@ +package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses; + +import com.mongodb.ConnectionString; +import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.PojoCodecProvider; +import org.evomaster.client.java.instrumentation.AdditionalInfo; +import org.evomaster.client.java.instrumentation.MongoFindCommand; +import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.testcontainers.containers.GenericContainer; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.bson.codecs.configuration.CodecRegistries.fromProviders; +import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public abstract class MongoCollectionClassReplacementTestBase { + + protected static MongoClient mongoClient; + + protected static final int MONGODB_PORT = 27017; + + protected static final GenericContainer mongodb = new GenericContainer<>("mongo:6.0") + .withExposedPorts(MONGODB_PORT); + + protected final static String DATABASE_NAME = "myDatabase"; + + protected final static String COLLECTION_NAME = "myCollection"; + + protected static @NotNull MongoCollection getMongoCollection() { + MongoDatabase database = mongoClient.getDatabase(DATABASE_NAME); + MongoCollection collection = database.getCollection(COLLECTION_NAME); + return collection; + } + + protected static void insertPerson(String name, int age) { + getMongoCollection().insertOne(new Document("name", name).append("age", age)); + } + + protected static List verifiedPipeline() { + return Collections.singletonList(setVerified()); + } + + protected static Document setVerified() { + return new Document("$set", new Document("verified", true)); + } + + protected BsonDocument assertSingleRecordedCommand(boolean expectedSuccess) { + List additionalInfoList = ExecutionTracer.exposeAdditionalInfoList(); + assertEquals(1, additionalInfoList.size()); + Set mongoFindCommands = additionalInfoList.get(0).getMongoInfoData(); + assertEquals(1, mongoFindCommands.size()); + + MongoFindCommand mongoFindCommand = mongoFindCommands.iterator().next(); + assertEquals(COLLECTION_NAME, mongoFindCommand.getCollectionName()); + assertEquals(DATABASE_NAME, mongoFindCommand.getDatabaseName()); + assertEquals(expectedSuccess, mongoFindCommand.isSuccessfullyExecuted()); + assertNotNull(mongoFindCommand.getQuery()); + return (BsonDocument) mongoFindCommand.getQuery(); + } + + + @BeforeAll + public static void initMongoClient() { + mongodb.start(); + int port = mongodb.getMappedPort(MONGODB_PORT); + + CodecRegistry codecRegistry = fromRegistries( + MongoClientSettings.getDefaultCodecRegistry(), + fromProviders(PojoCodecProvider.builder().automatic(true).build()), + CodecRegistries.fromCodecs(new MongoCollectionTestDtoCodec()) + ); + + MongoClientSettings.Builder builder = MongoClientSettings.builder(); + builder.codecRegistry(codecRegistry); + builder.applyConnectionString(new ConnectionString("mongodb://localhost:" + port + "/" + "aDatabase")); + MongoClientSettings settings = builder + .build(); + + mongoClient = MongoClients.create(settings); + + ExecutionTracer.reset(); + } + + @AfterAll + public static void resetExecutionTracer() { + ExecutionTracer.reset(); + } + + + @BeforeEach + public void clearDatabase() { + + final MongoCollection collection = getMongoCollection(); + + // delete all documents in collection (if any) + collection.deleteMany(new Document()); + + ExecutionTracer.reset(); + } + +} 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 new file mode 100644 index 0000000000..c4f7327c20 --- /dev/null +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/MongoCollectionPipelineUpdateClassReplacementTest.java @@ -0,0 +1,125 @@ +package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses; + +import com.mongodb.client.*; +import com.mongodb.client.model.UpdateOptions; +import com.mongodb.client.result.UpdateResult; +import org.bson.BsonDocument; +import org.bson.Document; +import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class MongoCollectionPipelineUpdateClassReplacementTest extends MongoCollectionClassReplacementTestBase { + + @Test + public void testUpdateManyWithClientSessionFilterPipelineAndOptions() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + UpdateResult result = (UpdateResult) MongoCollectionPipelineUpdateClassReplacement.updateMany(collection, clientSession, new Document("age", 30), verifiedPipeline(), new UpdateOptions()); + + assertEquals(2, result.getMatchedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testUpdateManyWithFilterAndPipeline() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + UpdateResult result = (UpdateResult) MongoCollectionPipelineUpdateClassReplacement.updateMany(collection, new Document("age", 30), verifiedPipeline()); + + assertEquals(2, result.getMatchedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testUpdateManyWithFilterPipelineAndOptions() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + UpdateResult result = (UpdateResult) MongoCollectionPipelineUpdateClassReplacement.updateMany(collection, new Document("age", 30), verifiedPipeline(), new UpdateOptions()); + + assertEquals(2, result.getMatchedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testUpdateOneWithClientSessionFilterAndPipeline() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + UpdateResult result = (UpdateResult) MongoCollectionPipelineUpdateClassReplacement.updateOne(collection, clientSession, new Document("age", 30), verifiedPipeline()); + + assertEquals(1, result.getMatchedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testUpdateOneWithClientSessionFilterPipelineAndOptions() { + try (ClientSession clientSession = mongoClient.startSession()) { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + UpdateResult result = (UpdateResult) MongoCollectionPipelineUpdateClassReplacement.updateOne(collection, clientSession, new Document("age", 30), verifiedPipeline(), new UpdateOptions()); + + assertEquals(1, result.getMatchedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + } + + @Test + public void testUpdateOneWithFilterAndPipeline() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + UpdateResult result = (UpdateResult) MongoCollectionPipelineUpdateClassReplacement.updateOne(collection, new Document("age", 30), verifiedPipeline()); + + assertEquals(1, result.getMatchedCount()); + BsonDocument retrievedQuery = assertSingleRecordedCommand(true); + assertEquals(30, retrievedQuery.getInt32("age").getValue()); + } + + @Test + public void testUpdateOneWithFilterPipelineAndOptions() { + final MongoCollection collection = getMongoCollection(); + insertPerson("John Doe", 30); + insertPerson("Jane Doe", 30); + insertPerson("Jim Roe", 25); + + ExecutionTracer.setExecutingInitMongo(false); + UpdateResult result = (UpdateResult) MongoCollectionPipelineUpdateClassReplacement.updateOne(collection, new Document("age", 30), verifiedPipeline(), new UpdateOptions()); + + assertEquals(1, result.getMatchedCount()); + 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/update/MongoUpdateApp.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateApp.java new file mode 100644 index 0000000000..bba21320fd --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateApp.java @@ -0,0 +1,20 @@ +package com.mongo.update; + +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 MongoUpdateApp extends SwaggerConfiguration { + public MongoUpdateApp() { + super("mongoupdate"); + } + + public static void main(String[] args) { + SpringApplication.run(MongoUpdateApp.class, args); + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateController.java new file mode 100644 index 0000000000..e7e7859519 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateController.java @@ -0,0 +1,245 @@ +package com.mongo.update; + +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.UpdateOptions; +import com.mongodb.client.model.Updates; +import com.mongodb.client.result.UpdateResult; +import org.bson.Document; +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.updateOne/updateMany), + * not through Spring Data, so the filter is only visible at the driver level. + * There is one endpoint for each overload of updateOne and updateMany. + */ +@RestController +@RequestMapping(path = "/mongoupdate") +public class MongoUpdateController { + + /* + Each endpoint updates its own collection, so that a document that + makes one endpoint return 200 cannot accidentally do it for another one. + */ + private static final String UPDATE_ONE_BY_CITY_AND_AGE = "updateOneByCityAndAgeCollection"; + private static final String UPDATE_ONE_BY_CITY_AND_AGE_WITH_OPTIONS = "updateOneByCityAndAgeWithOptionsCollection"; + private static final String UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION = "updateOneByCityAndAgeInSessionCollection"; + private static final String UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS = "updateOneByCityAndAgeInSessionWithOptionsCollection"; + private static final String UPDATE_ONE_BY_CITY_AND_AGE_WITH_PIPELINE = "updateOneByCityAndAgeWithPipelineCollection"; + private static final String UPDATE_ONE_BY_CITY_AND_AGE_WITH_PIPELINE_AND_OPTIONS = "updateOneByCityAndAgeWithPipelineAndOptionsCollection"; + private static final String UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE = "updateOneByCityAndAgeInSessionWithPipelineCollection"; + private static final String UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE_AND_OPTIONS = "updateOneByCityAndAgeInSessionWithPipelineAndOptionsCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE = "updateManyByCityAndAgeCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE_WITH_OPTIONS = "updateManyByCityAndAgeWithOptionsCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION = "updateManyByCityAndAgeInSessionCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS = "updateManyByCityAndAgeInSessionWithOptionsCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE_WITH_PIPELINE = "updateManyByCityAndAgeWithPipelineCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE_WITH_PIPELINE_AND_OPTIONS = "updateManyByCityAndAgeWithPipelineAndOptionsCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE = "updateManyByCityAndAgeInSessionWithPipelineCollection"; + private static final String UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE_AND_OPTIONS = "updateManyByCityAndAgeInSessionWithPipelineAndOptionsCollection"; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private MongoClient mongoClient; + + private MongoCollection collection(String collectionName) { + return mongoTemplate.getDb().getCollection(collectionName, MongoUpdateData.class); + } + + 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 UpdateOptions options() { + return new UpdateOptions().bypassDocumentValidation(true); + } + + /* + matched (and not modified) count is used, as an existing document might already be verified + */ + private static ResponseEntity response(UpdateResult result) { + return ResponseEntity.status(result.getMatchedCount() > 0 ? 200 : 404).build(); + } + + /** + * updateOne(Bson, Bson) + */ + @PutMapping("updateOneByCityAndAge") + public ResponseEntity updateOneByCityAndAge(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE).updateOne(filter(city, minAge), update())); + } + + /** + * updateOne(Bson, Bson, UpdateOptions) + */ + @PutMapping("updateOneByCityAndAgeWithOptions") + public ResponseEntity updateOneByCityAndAgeWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE_WITH_OPTIONS).updateOne(filter(city, minAge), update(), options())); + } + + /** + * updateOne(ClientSession, Bson, Bson) + */ + @PutMapping("updateOneByCityAndAgeInSession") + public ResponseEntity updateOneByCityAndAgeInSession(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION).updateOne(session, filter(city, minAge), update())); + } + } + + /** + * updateOne(ClientSession, Bson, Bson, UpdateOptions) + */ + @PutMapping("updateOneByCityAndAgeInSessionWithOptions") + public ResponseEntity updateOneByCityAndAgeInSessionWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS).updateOne(session, filter(city, minAge), update(), options())); + } + } + + /** + * updateOne(Bson, List) + */ + @PutMapping("updateOneByCityAndAgeWithPipeline") + public ResponseEntity updateOneByCityAndAgeWithPipeline(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE_WITH_PIPELINE).updateOne(filter(city, minAge), pipeline())); + } + + /** + * updateOne(Bson, List, UpdateOptions) + */ + @PutMapping("updateOneByCityAndAgeWithPipelineAndOptions") + public ResponseEntity updateOneByCityAndAgeWithPipelineAndOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE_WITH_PIPELINE_AND_OPTIONS).updateOne(filter(city, minAge), pipeline(), options())); + } + + /** + * updateOne(ClientSession, Bson, List) + */ + @PutMapping("updateOneByCityAndAgeInSessionWithPipeline") + public ResponseEntity updateOneByCityAndAgeInSessionWithPipeline(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE).updateOne(session, filter(city, minAge), pipeline())); + } + } + + /** + * updateOne(ClientSession, Bson, List, UpdateOptions) + */ + @PutMapping("updateOneByCityAndAgeInSessionWithPipelineAndOptions") + public ResponseEntity updateOneByCityAndAgeInSessionWithPipelineAndOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_ONE_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE_AND_OPTIONS).updateOne(session, filter(city, minAge), pipeline(), options())); + } + } + + /** + * updateMany(Bson, Bson) + */ + @PutMapping("updateManyByCityAndAge") + public ResponseEntity updateManyByCityAndAge(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE).updateMany(filter(city, minAge), update())); + } + + /** + * updateMany(Bson, Bson, UpdateOptions) + */ + @PutMapping("updateManyByCityAndAgeWithOptions") + public ResponseEntity updateManyByCityAndAgeWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE_WITH_OPTIONS).updateMany(filter(city, minAge), update(), options())); + } + + /** + * updateMany(ClientSession, Bson, Bson) + */ + @PutMapping("updateManyByCityAndAgeInSession") + public ResponseEntity updateManyByCityAndAgeInSession(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION).updateMany(session, filter(city, minAge), update())); + } + } + + /** + * updateMany(ClientSession, Bson, Bson, UpdateOptions) + */ + @PutMapping("updateManyByCityAndAgeInSessionWithOptions") + public ResponseEntity updateManyByCityAndAgeInSessionWithOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS).updateMany(session, filter(city, minAge), update(), options())); + } + } + + /** + * updateMany(Bson, List) + */ + @PutMapping("updateManyByCityAndAgeWithPipeline") + public ResponseEntity updateManyByCityAndAgeWithPipeline(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE_WITH_PIPELINE).updateMany(filter(city, minAge), pipeline())); + } + + /** + * updateMany(Bson, List, UpdateOptions) + */ + @PutMapping("updateManyByCityAndAgeWithPipelineAndOptions") + public ResponseEntity updateManyByCityAndAgeWithPipelineAndOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE_WITH_PIPELINE_AND_OPTIONS).updateMany(filter(city, minAge), pipeline(), options())); + } + + /** + * updateMany(ClientSession, Bson, List) + */ + @PutMapping("updateManyByCityAndAgeInSessionWithPipeline") + public ResponseEntity updateManyByCityAndAgeInSessionWithPipeline(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE).updateMany(session, filter(city, minAge), pipeline())); + } + } + + /** + * updateMany(ClientSession, Bson, List, UpdateOptions) + */ + @PutMapping("updateManyByCityAndAgeInSessionWithPipelineAndOptions") + public ResponseEntity updateManyByCityAndAgeInSessionWithPipelineAndOptions(@RequestParam(name = "city") String city, + @RequestParam(name = "minAge") int minAge) { + try (ClientSession session = mongoClient.startSession()) { + return response(collection(UPDATE_MANY_BY_CITY_AND_AGE_IN_SESSION_WITH_PIPELINE_AND_OPTIONS).updateMany(session, filter(city, minAge), pipeline(), options())); + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateData.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateData.java new file mode 100644 index 0000000000..7ce1b526ce --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/main/java/com/mongo/update/MongoUpdateData.java @@ -0,0 +1,24 @@ +package com.mongo.update; + +import org.springframework.data.annotation.Id; + +public class MongoUpdateData { + + @Id + public String id; + + public String city; + + public int age; + + public boolean verified; + + public MongoUpdateData() { + } + + public MongoUpdateData(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/update/MongoUpdateAppController.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/update/MongoUpdateAppController.java new file mode 100644 index 0000000000..eb9a262bcc --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/com/foo/spring/rest/mongo/update/MongoUpdateAppController.java @@ -0,0 +1,15 @@ +package com.foo.spring.rest.mongo.update; + +import com.foo.spring.rest.mongo.MongoController; +import com.mongo.update.MongoUpdateApp; + +public class MongoUpdateAppController extends MongoController { + public MongoUpdateAppController() { + super("update", MongoUpdateApp.class); + } + + @Override + public String getPackagePrefixesToCover() { + return "com.mongo.update"; + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/update/MongoUpdateEMTest.java b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/update/MongoUpdateEMTest.java new file mode 100644 index 0000000000..9f875e5ce6 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mongo/src/test/java/org/evomaster/e2etests/spring/rest/mongo/update/MongoUpdateEMTest.java @@ -0,0 +1,109 @@ +package org.evomaster.e2etests.spring.rest.mongo.update; + +import com.foo.spring.rest.mongo.update.MongoUpdateAppController; +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 MongoUpdateEMTest extends RestTestBase { + + @BeforeAll + public static void initClass() throws Exception { + EMConfig config = new EMConfig(); + config.setInstrumentMR_MONGO(true); + RestTestBase.initClass(new MongoUpdateAppController()); + } + + @Test + public void testRunEM() throws Throwable { + + runTestHandlingFlakyAndCompilation( + "MongoUpdateEMTest", + "org.foo.spring.rest.mongo.MongoUpdateEMTest", + 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.updateOne and MongoCollection.updateMany. + // A 200 needs a document with the requested city and age in the collection, + // which can only be generated if the update filter is observed + + // updateOne(Bson, Bson) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAge", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAge", null); + + // updateOne(Bson, Bson, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAgeWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAgeWithOptions", null); + + // updateOne(ClientSession, Bson, Bson) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAgeInSession", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAgeInSession", null); + + // updateOne(ClientSession, Bson, Bson, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAgeInSessionWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAgeInSessionWithOptions", null); + + // updateOne(Bson, List) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAgeWithPipeline", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAgeWithPipeline", null); + + // updateOne(Bson, List, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAgeWithPipelineAndOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAgeWithPipelineAndOptions", null); + + // updateOne(ClientSession, Bson, List) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAgeInSessionWithPipeline", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAgeInSessionWithPipeline", null); + + // updateOne(ClientSession, Bson, List, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateOneByCityAndAgeInSessionWithPipelineAndOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateOneByCityAndAgeInSessionWithPipelineAndOptions", null); + + // updateMany(Bson, Bson) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAge", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAge", null); + + // updateMany(Bson, Bson, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAgeWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAgeWithOptions", null); + + // updateMany(ClientSession, Bson, Bson) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAgeInSession", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAgeInSession", null); + + // updateMany(ClientSession, Bson, Bson, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAgeInSessionWithOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAgeInSessionWithOptions", null); + + // updateMany(Bson, List) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAgeWithPipeline", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAgeWithPipeline", null); + + // updateMany(Bson, List, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAgeWithPipelineAndOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAgeWithPipelineAndOptions", null); + + // updateMany(ClientSession, Bson, List) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAgeInSessionWithPipeline", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAgeInSessionWithPipeline", null); + + // updateMany(ClientSession, Bson, List, UpdateOptions) + assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongoupdate/updateManyByCityAndAgeInSessionWithPipelineAndOptions", null); + assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongoupdate/updateManyByCityAndAgeInSessionWithPipelineAndOptions", null); + }); + } +}