Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,26 @@ public static Object updateMany(Object mongoCollection, @ThirdPartyCast(actualTy
return handleUpdate("updateManyClientSessionBsonUpdateOptions", mongoCollection, Arrays.asList(clientSession, filter, update, options), filter);
}

@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndDeleteBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO)
public static Object findOneAndDelete(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter) {
return handleDelete("findOneAndDeleteBson", mongoCollection, Arrays.asList(filter), filter);
}

@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndDeleteBsonFindOneAndDeleteOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO)
public static Object findOneAndDelete(Object mongoCollection, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "com.mongodb.client.model.FindOneAndDeleteOptions") Object options) {
return handleDelete("findOneAndDeleteBsonFindOneAndDeleteOptions", mongoCollection, Arrays.asList(filter, options), filter);
}

@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndDeleteClientSessionBson", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO)
public static Object findOneAndDelete_EM_0(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter) {
return handleDelete("findOneAndDeleteClientSessionBson", mongoCollection, Arrays.asList(clientSession, filter), filter);
}

@Replacement(replacingStatic = false, type = ReplacementType.TRACKER, id = "findOneAndDeleteClientSessionBsonFindOneAndDeleteOptions", usageFilter = UsageFilter.ANY, category = ReplacementCategory.MONGO)
public static Object findOneAndDelete(Object mongoCollection, @ThirdPartyCast(actualType = "com.mongodb.client.ClientSession") Object clientSession, @ThirdPartyCast(actualType = "org.bson.conversions.Bson") Object filter, @ThirdPartyCast(actualType = "com.mongodb.client.model.FindOneAndDeleteOptions") Object options) {
return handleDelete("findOneAndDeleteClientSessionBsonFindOneAndDeleteOptions", mongoCollection, Arrays.asList(clientSession, filter, options), filter);
}

/**
* Processes the count handling based on the provided parameters.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoQueryException;
import com.mongodb.client.*;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.result.DeleteResult;
import org.bson.BsonDocument;
import org.bson.BsonDocumentWriter;
import org.bson.BsonWriter;
import org.bson.Document;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.EncoderContext;
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.object.ClassToSchema;
Expand Down Expand Up @@ -541,4 +536,89 @@ public void testDeleteManyWithInvalidFilter() {
BsonDocument retrievedQuery = assertSingleRecordedCommand(false);
assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue());
}

@Test
public void testFindOneAndDeleteWithFilter() {
final MongoCollection<Document> collection = getMongoCollection();
insertPerson("John Doe", 30);
insertPerson("Jane Doe", 25);

ExecutionTracer.setExecutingInitMongo(false);
Document deleted = (Document) MongoCollectionClassReplacement.findOneAndDelete(collection, new Document("age", 30));

assertNotNull(deleted);
assertEquals("John Doe", deleted.getString("name"));
assertEquals(1, collection.countDocuments());
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(30, retrievedQuery.getInt32("age").getValue());
}

@Test
public void testFindOneAndDeleteWithFilterNoMatch() {
final MongoCollection<Document> collection = getMongoCollection();

ExecutionTracer.setExecutingInitMongo(false);
Document deleted = (Document) MongoCollectionClassReplacement.findOneAndDelete(collection, new Document("age", 99));

assertNull(deleted);
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(99, retrievedQuery.getInt32("age").getValue());
}

@Test
public void testFindOneAndDeleteWithFilterAndOptions() {
final MongoCollection<Document> collection = getMongoCollection();
insertPerson("John Doe", 30);

ExecutionTracer.setExecutingInitMongo(false);
Document deleted = (Document) MongoCollectionClassReplacement.findOneAndDelete(collection, new Document("age", 30), new FindOneAndDeleteOptions());

assertNotNull(deleted);
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(30, retrievedQuery.getInt32("age").getValue());
}

@Test
public void testFindOneAndDeleteWithClientSessionAndFilter() {
try (ClientSession clientSession = mongoClient.startSession()) {
final MongoCollection<Document> collection = getMongoCollection();
insertPerson("John Doe", 23);

ExecutionTracer.setExecutingInitMongo(false);
Document deleted = (Document) MongoCollectionClassReplacement.findOneAndDelete_EM_0(collection, clientSession, new Document("age", 23));

assertNotNull(deleted);
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(23, retrievedQuery.getInt32("age").getValue());
}
}

@Test
public void testFindOneAndDeleteWithClientSessionFilterAndOptions() {
try (ClientSession clientSession = mongoClient.startSession()) {
final MongoCollection<Document> collection = getMongoCollection();
insertPerson("John Doe", 23);

ExecutionTracer.setExecutingInitMongo(false);
Document deleted = (Document) MongoCollectionClassReplacement.findOneAndDelete(collection, clientSession, new Document("age", 23), new FindOneAndDeleteOptions());

assertNotNull(deleted);
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(23, retrievedQuery.getInt32("age").getValue());
}
}

@Test
public void testFindOneAndDeleteWithInvalidFilter() {
final MongoCollection<Document> collection = getMongoCollection();
insertPerson("John Doe", 30);

Document invalidFilter = new Document("tags", new Document("$size", -1));

ExecutionTracer.setExecutingInitMongo(false);
assertThrows(com.mongodb.MongoException.class, () -> MongoCollectionClassReplacement.findOneAndDelete(collection, invalidFilter));

BsonDocument retrievedQuery = assertSingleRecordedCommand(false);
assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.mongo.findoneanddelete;

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 MongoFindOneAndDeleteApp extends SwaggerConfiguration {
public MongoFindOneAndDeleteApp() {
super("mongofindoneanddelete");
}

public static void main(String[] args) {
SpringApplication.run(MongoFindOneAndDeleteApp.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.mongo.findoneanddelete;

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.FindOneAndDeleteOptions;
import com.mongodb.client.model.Sorts;
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.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.findOneAndDelete),
* not through Spring Data, so the filter is only visible at the driver level.
* There is one endpoint for each overload of findOneAndDelete.
*/
@RestController
@RequestMapping(path = "/mongofindoneanddelete")
public class MongoFindOneAndDeleteController {

/*
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_DELETE_BY_CITY_AND_AGE = "findOneAndDeleteByCityAndAgeCollection";
private static final String FIND_ONE_AND_DELETE_BY_CITY_AND_AGE_WITH_OPTIONS = "findOneAndDeleteByCityAndAgeWithOptionsCollection";
private static final String FIND_ONE_AND_DELETE_BY_CITY_AND_AGE_IN_SESSION = "findOneAndDeleteByCityAndAgeInSessionCollection";
private static final String FIND_ONE_AND_DELETE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS = "findOneAndDeleteByCityAndAgeInSessionWithOptionsCollection";

@Autowired
private MongoTemplate mongoTemplate;

@Autowired
private MongoClient mongoClient;

/*
findOneAndDelete returns the deleted 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()));

private MongoCollection<MongoFindOneAndDeleteData> collection(String collectionName) {
return mongoTemplate.getDb()
.getCollection(collectionName, MongoFindOneAndDeleteData.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 FindOneAndDeleteOptions options() {
return new FindOneAndDeleteOptions().sort(Sorts.ascending("age"));
}

/*
findOneAndDelete returns the deleted document, or null if no document matched the filter
*/
private static ResponseEntity<Void> response(MongoFindOneAndDeleteData deleted) {
return ResponseEntity.status(deleted != null ? 200 : 404).build();
}

/**
* findOneAndDelete(Bson)
*/
@DeleteMapping("findOneAndDeleteByCityAndAge")
public ResponseEntity<Void> findOneAndDeleteByCityAndAge(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
return response(collection(FIND_ONE_AND_DELETE_BY_CITY_AND_AGE).findOneAndDelete(filter(city, minAge)));
}

/**
* findOneAndDelete(Bson, FindOneAndDeleteOptions)
*/
@DeleteMapping("findOneAndDeleteByCityAndAgeWithOptions")
public ResponseEntity<Void> findOneAndDeleteByCityAndAgeWithOptions(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
return response(collection(FIND_ONE_AND_DELETE_BY_CITY_AND_AGE_WITH_OPTIONS).findOneAndDelete(filter(city, minAge), options()));
}

/**
* findOneAndDelete(ClientSession, Bson)
*/
@DeleteMapping("findOneAndDeleteByCityAndAgeInSession")
public ResponseEntity<Void> findOneAndDeleteByCityAndAgeInSession(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
try (ClientSession session = mongoClient.startSession()) {
return response(collection(FIND_ONE_AND_DELETE_BY_CITY_AND_AGE_IN_SESSION).findOneAndDelete(session, filter(city, minAge)));
}
}

/**
* findOneAndDelete(ClientSession, Bson, FindOneAndDeleteOptions)
*/
@DeleteMapping("findOneAndDeleteByCityAndAgeInSessionWithOptions")
public ResponseEntity<Void> findOneAndDeleteByCityAndAgeInSessionWithOptions(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
try (ClientSession session = mongoClient.startSession()) {
return response(collection(FIND_ONE_AND_DELETE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS).findOneAndDelete(session, filter(city, minAge), options()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.mongo.findoneanddelete;

import org.bson.BsonType;
import org.bson.codecs.pojo.annotations.BsonRepresentation;
import org.springframework.data.annotation.Id;

public class MongoFindOneAndDeleteData {

/*
The documents in the collection have an ObjectId as _id, while this is a String.
As findOneAndDelete decodes the deleted 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 MongoFindOneAndDeleteData() {
}

public MongoFindOneAndDeleteData(String city, int age) {
this.city = city;
this.age = age;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.foo.spring.rest.mongo.findoneanddelete;

import com.foo.spring.rest.mongo.MongoController;
import com.mongo.findoneanddelete.MongoFindOneAndDeleteApp;

public class MongoFindOneAndDeleteAppController extends MongoController {
public MongoFindOneAndDeleteAppController() {
super("findoneanddelete", MongoFindOneAndDeleteApp.class);
}

@Override
public String getPackagePrefixesToCover() {
return "com.mongo.findoneanddelete";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.evomaster.e2etests.spring.rest.mongo.findoneanddelete;

import com.foo.spring.rest.mongo.findoneanddelete.MongoFindOneAndDeleteAppController;
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 MongoFindOneAndDeleteEMTest extends RestTestBase {

@BeforeAll
public static void initClass() throws Exception {
EMConfig config = new EMConfig();
config.setInstrumentMR_MONGO(true);
RestTestBase.initClass(new MongoFindOneAndDeleteAppController());
}

@Test
public void testRunEM() throws Throwable {

runTestHandlingFlakyAndCompilation(
"MongoFindOneAndDeleteEMTest",
"org.foo.spring.rest.mongo.MongoFindOneAndDeleteEMTest",
1000,
(args) -> {
setOption(args, "heuristicsForMongo", "true");
setOption(args, "instrumentMR_MONGO", "true");
setOption(args, "generateMongoData", "true");
setOption(args, "extractMongoExecutionInfo", "true");

Solution<RestIndividual> solution = initAndRun(args);

assertTrue(solution.getIndividuals().size() >= 1);

// one endpoint per overload of MongoCollection.findOneAndDelete.
// A 200 needs a document with the requested city and age in the collection,
// which can only be generated if the findOneAndDelete filter is observed

// findOneAndDelete(Bson)
assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongofindoneanddelete/findOneAndDeleteByCityAndAge", null);
assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongofindoneanddelete/findOneAndDeleteByCityAndAge", null);

// findOneAndDelete(Bson, FindOneAndDeleteOptions)
assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongofindoneanddelete/findOneAndDeleteByCityAndAgeWithOptions", null);
assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongofindoneanddelete/findOneAndDeleteByCityAndAgeWithOptions", null);

// findOneAndDelete(ClientSession, Bson)
assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongofindoneanddelete/findOneAndDeleteByCityAndAgeInSession", null);
assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongofindoneanddelete/findOneAndDeleteByCityAndAgeInSession", null);

// findOneAndDelete(ClientSession, Bson, FindOneAndDeleteOptions)
assertHasAtLeastOne(solution, HttpVerb.DELETE, 404, "/mongofindoneanddelete/findOneAndDeleteByCityAndAgeInSessionWithOptions", null);
assertHasAtLeastOne(solution, HttpVerb.DELETE, 200, "/mongofindoneanddelete/findOneAndDeleteByCityAndAgeInSessionWithOptions", null);
});
}
}
Loading