Skip to content
Open
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 @@ -194,6 +194,26 @@ public static Object findOneAndUpdate(Object mongoCollection, @ThirdPartyCast(ac
}


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

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

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

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

/**
* Processes the count handling based on the provided parameters.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.mongodb.MongoQueryException;
import com.mongodb.client.*;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.result.DeleteResult;
Expand Down Expand Up @@ -720,4 +721,110 @@ public void testFindOneAndUpdateWithInvalidFilter() {
BsonDocument retrievedQuery = assertSingleRecordedCommand(false);
assertEquals(-1, retrievedQuery.getDocument("tags").getInt32("$size").getValue());
}

private static Document replacement() {
return new Document("name", "Replaced").append("age", 30);
}

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

ExecutionTracer.setExecutingInitMongo(false);
Document found = (Document) MongoCollectionClassReplacement.findOneAndReplace(collection, new Document("age", 30), replacement());

// by default, the document before the replacement is returned
assertNotNull(found);
assertEquals("John Doe", found.getString("name"));
assertEquals(1, collection.countDocuments(new Document("name", "Replaced")));
assertEquals(0, collection.countDocuments(new Document("name", "John Doe")));
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(30, retrievedQuery.getInt32("age").getValue());
}

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

ExecutionTracer.setExecutingInitMongo(false);
Document found = (Document) MongoCollectionClassReplacement.findOneAndReplace_EM_0(collection, new Document("age", 30), replacement(), new FindOneAndReplaceOptions());

// by default, the document before the replacement is returned
assertNotNull(found);
assertEquals("John Doe", found.getString("name"));
assertEquals(1, collection.countDocuments(new Document("name", "Replaced")));
assertEquals(0, collection.countDocuments(new Document("name", "John Doe")));
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(30, retrievedQuery.getInt32("age").getValue());
}

@Test
public void testFindOneAndReplaceWithClientSessionFilterAndReplacement() {
try (ClientSession clientSession = mongoClient.startSession()) {
final MongoCollection<Document> collection = getMongoCollection();
insertPerson("John Doe", 30);
insertPerson("Jim Roe", 25);

ExecutionTracer.setExecutingInitMongo(false);
Document found = (Document) MongoCollectionClassReplacement.findOneAndReplace(collection, clientSession, new Document("age", 30), replacement());

// by default, the document before the replacement is returned
assertNotNull(found);
assertEquals("John Doe", found.getString("name"));
assertEquals(1, collection.countDocuments(new Document("name", "Replaced")));
assertEquals(0, collection.countDocuments(new Document("name", "John Doe")));
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(30, retrievedQuery.getInt32("age").getValue());
}
}

@Test
public void testFindOneAndReplaceWithClientSessionFilterReplacementAndOptions() {
try (ClientSession clientSession = mongoClient.startSession()) {
final MongoCollection<Document> collection = getMongoCollection();
insertPerson("John Doe", 30);
insertPerson("Jim Roe", 25);

ExecutionTracer.setExecutingInitMongo(false);
Document found = (Document) MongoCollectionClassReplacement.findOneAndReplace(collection, clientSession, new Document("age", 30), replacement(), new FindOneAndReplaceOptions());

// by default, the document before the replacement is returned
assertNotNull(found);
assertEquals("John Doe", found.getString("name"));
assertEquals(1, collection.countDocuments(new Document("name", "Replaced")));
assertEquals(0, collection.countDocuments(new Document("name", "John Doe")));
BsonDocument retrievedQuery = assertSingleRecordedCommand(true);
assertEquals(30, retrievedQuery.getInt32("age").getValue());
}
}

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

ExecutionTracer.setExecutingInitMongo(false);
Document found = (Document) MongoCollectionClassReplacement.findOneAndReplace(collection, new Document("age", 99), replacement());

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

@Test
public void testFindOneAndReplaceWithInvalidFilter() {
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.findOneAndReplace(collection, invalidFilter, replacement()));

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.findoneandreplace;

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

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

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

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.FindOneAndReplaceOptions;
import com.mongodb.client.model.ReturnDocument;
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;

/**
* Replaces documents directly through the Mongo driver (MongoCollection.findOneAndReplace),
* not through Spring Data, so the filter is only visible at the driver level.
* There is one endpoint for each overload of findOneAndReplace.
*/
@RestController
@RequestMapping(path = "/mongofindoneandreplace")
public class MongoFindOneAndReplaceController {

/*
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_REPLACE_BY_CITY_AND_AGE = "findOneAndReplaceByCityAndAgeCollection";
private static final String FIND_ONE_AND_REPLACE_BY_CITY_AND_AGE_WITH_OPTIONS = "findOneAndReplaceByCityAndAgeWithOptionsCollection";
private static final String FIND_ONE_AND_REPLACE_BY_CITY_AND_AGE_IN_SESSION = "findOneAndReplaceByCityAndAgeInSessionCollection";
private static final String FIND_ONE_AND_REPLACE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS = "findOneAndReplaceByCityAndAgeInSessionWithOptionsCollection";

/*
findOneAndReplace 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<MongoFindOneAndReplaceData> collection(String collectionName) {
return mongoTemplate.getDb()
.getCollection(collectionName, MongoFindOneAndReplaceData.class)
.withCodecRegistry(POJO_CODEC_REGISTRY);
}

private static Bson filter(String city, int minAge) {
return Filters.and(Filters.eq("city", city), Filters.gte("age", minAge));
}

/*
the replacement has no id, so the replaced document keeps its own _id
*/
private static MongoFindOneAndReplaceData replacement(String city, int minAge) {
return new MongoFindOneAndReplaceData(city, minAge, true);
}

private static FindOneAndReplaceOptions options() {
return new FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER);
}

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

/**
* findOneAndReplace(Bson, TDocument)
*/
@PutMapping("findOneAndReplaceByCityAndAge")
public ResponseEntity<Void> findOneAndReplaceByCityAndAge(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
return response(collection(FIND_ONE_AND_REPLACE_BY_CITY_AND_AGE).findOneAndReplace(filter(city, minAge), replacement(city, minAge)));
}

/**
* findOneAndReplace(Bson, TDocument, FindOneAndReplaceOptions)
*/
@PutMapping("findOneAndReplaceByCityAndAgeWithOptions")
public ResponseEntity<Void> findOneAndReplaceByCityAndAgeWithOptions(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
return response(collection(FIND_ONE_AND_REPLACE_BY_CITY_AND_AGE_WITH_OPTIONS).findOneAndReplace(filter(city, minAge), replacement(city, minAge), options()));
}

/**
* findOneAndReplace(ClientSession, Bson, TDocument)
*/
@PutMapping("findOneAndReplaceByCityAndAgeInSession")
public ResponseEntity<Void> findOneAndReplaceByCityAndAgeInSession(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
try (ClientSession session = mongoClient.startSession()) {
return response(collection(FIND_ONE_AND_REPLACE_BY_CITY_AND_AGE_IN_SESSION).findOneAndReplace(session, filter(city, minAge), replacement(city, minAge)));
}
}

/**
* findOneAndReplace(ClientSession, Bson, TDocument, FindOneAndReplaceOptions)
*/
@PutMapping("findOneAndReplaceByCityAndAgeInSessionWithOptions")
public ResponseEntity<Void> findOneAndReplaceByCityAndAgeInSessionWithOptions(@RequestParam(name = "city") String city,
@RequestParam(name = "minAge") int minAge) {
try (ClientSession session = mongoClient.startSession()) {
return response(collection(FIND_ONE_AND_REPLACE_BY_CITY_AND_AGE_IN_SESSION_WITH_OPTIONS).findOneAndReplace(session, filter(city, minAge), replacement(city, minAge), options()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.mongo.findoneandreplace;

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

public class MongoFindOneAndReplaceData {

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

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

import com.foo.spring.rest.mongo.MongoController;
import com.mongo.findoneandreplace.MongoFindOneAndReplaceApp;

public class MongoFindOneAndReplaceAppController extends MongoController {
public MongoFindOneAndReplaceAppController() {
super("findoneandreplace", MongoFindOneAndReplaceApp.class);
}

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

import com.foo.spring.rest.mongo.findoneandreplace.MongoFindOneAndReplaceAppController;
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 MongoFindOneAndReplaceEMTest extends RestTestBase {

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

@Test
public void testRunEM() throws Throwable {

runTestHandlingFlakyAndCompilation(
"MongoFindOneAndReplaceEMTest",
"org.foo.spring.rest.mongo.MongoFindOneAndReplaceEMTest",
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.findOneAndReplace.
// A 200 needs a document with the requested city and age in the collection,
// which can only be generated if the findOneAndReplace filter is observed

// findOneAndReplace(Bson, TDocument)
assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandreplace/findOneAndReplaceByCityAndAge", null);
assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandreplace/findOneAndReplaceByCityAndAge", null);

// findOneAndReplace(Bson, TDocument, FindOneAndReplaceOptions)
assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandreplace/findOneAndReplaceByCityAndAgeWithOptions", null);
assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandreplace/findOneAndReplaceByCityAndAgeWithOptions", null);

// findOneAndReplace(ClientSession, Bson, TDocument)
assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandreplace/findOneAndReplaceByCityAndAgeInSession", null);
assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandreplace/findOneAndReplaceByCityAndAgeInSession", null);

// findOneAndReplace(ClientSession, Bson, TDocument, FindOneAndReplaceOptions)
assertHasAtLeastOne(solution, HttpVerb.PUT, 404, "/mongofindoneandreplace/findOneAndReplaceByCityAndAgeInSessionWithOptions", null);
assertHasAtLeastOne(solution, HttpVerb.PUT, 200, "/mongofindoneandreplace/findOneAndReplaceByCityAndAgeInSessionWithOptions", null);
});
}
}
Loading