diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/mongo/BsonDocumentConverter.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/mongo/BsonDocumentConverter.java
index 8574f83724..e8b3b3a6e8 100644
--- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/mongo/BsonDocumentConverter.java
+++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/mongo/BsonDocumentConverter.java
@@ -18,6 +18,10 @@ public class BsonDocumentConverter {
public static final String DECODE = "decode";
public static final String BUILDER = "builder";
public static final String BUILD = "build";
+ private static final String ORG_BSON_CODECS_CONFIGURATION_CODEC_REGISTRY = "org.bson.codecs.configuration.CodecRegistry";
+ private static final String COM_MONGODB_MONGO_CLIENT_SETTINGS = "com.mongodb.MongoClientSettings";
+ private static final String COM_MONGODB_MONGO_CLIENT = "com.mongodb.MongoClient";
+ private static final String GET_DEFAULT_CODEC_REGISTRY = "getDefaultCodecRegistry";
/**
* Checks if the given object is an instance of a org.bson.BsonDocument.
@@ -62,14 +66,41 @@ public static boolean isBsonDocument(Object value) {
if (bsonDocumentClass.isInstance(bson)) {
return bson;
}
- Object bsonDocument = bsonClass.getMethod(TO_BSON_DOCUMENT).invoke(bson);
- return bsonDocument;
+ return invokeToBsonDocument(bsonClass, bsonDocumentClass, bson);
} catch (Exception | LinkageError e) {
SimpleLogger.uniqueWarn("Failed to convert Mongo query from Bson: " + e);
return null;
}
}
+ /**
+ * Calls the no-arg Bson.toBsonDocument(), which only exists since driver 4.2.
+ * For older drivers, falls back to toBsonDocument(Class, CodecRegistry) with the default registry.
+ */
+ private static Object invokeToBsonDocument(Class> bsonClass, Class> bsonDocumentClass, Object bson) throws Exception {
+ try {
+ return bsonClass.getMethod(TO_BSON_DOCUMENT).invoke(bson);
+ } catch (NoSuchMethodException e) {
+ // driver < 4.2
+ Class> codecRegistryClass = loadClass(ORG_BSON_CODECS_CONFIGURATION_CODEC_REGISTRY, bson);
+ Object registry = getDefaultCodecRegistry(bson);
+ return bsonClass.getMethod(TO_BSON_DOCUMENT, Class.class, codecRegistryClass)
+ .invoke(bson, bsonDocumentClass, registry);
+ }
+ }
+
+ private static Object getDefaultCodecRegistry(Object instance) throws Exception {
+ try {
+ // driver >= 3.7
+ return loadClass(COM_MONGODB_MONGO_CLIENT_SETTINGS, instance)
+ .getMethod(GET_DEFAULT_CODEC_REGISTRY).invoke(null);
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ // legacy driver (eg, 3.2)
+ return loadClass(COM_MONGODB_MONGO_CLIENT, instance)
+ .getMethod(GET_DEFAULT_CODEC_REGISTRY).invoke(null);
+ }
+ }
+
/**
* Converts an object of type org.bson.BsonDocument to a corresponding org.bson.Document object.
* If the input object is null, the method returns null. It also handles cases where the input
diff --git a/core-tests/e2e-tests/spring/pom.xml b/core-tests/e2e-tests/spring/pom.xml
index 909a91e9a2..36b633a94a 100644
--- a/core-tests/e2e-tests/spring/pom.xml
+++ b/core-tests/e2e-tests/spring/pom.xml
@@ -23,6 +23,7 @@
spring-rest-h2-z3solver
spring-web
spring-rest-mongo
+ spring-rest-mongo-legacy
spring-rest-bb
spring-graphql-bb
spring-rest-multidb
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/pom.xml b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/pom.xml
new file mode 100644
index 0000000000..23b0ac146f
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/pom.xml
@@ -0,0 +1,112 @@
+
+
+ 4.0.0
+
+
+ evomaster-e2e-tests-spring
+ org.evomaster
+ 6.2.1-SNAPSHOT
+
+
+ evomaster-e2e-tests-spring-rest-mongo-legacy
+ jar
+
+
+ ${skipMongo}
+
+ 3.8.2
+
+
+
+
+ org.evomaster
+ evomaster-e2e-tests-utils
+ test-jar
+
+
+ org.evomaster
+ evomaster-client-java-controller
+
+
+ org.evomaster
+ evomaster-core
+ test
+
+
+ org.evomaster
+ evomaster-client-java-instrumentation
+ test-jar
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.mongodb
+ mongo-java-driver
+ ${mongo.legacy.driver.version}
+
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+
+ org.testcontainers
+ testcontainers
+
+
+ io.springfox
+ springfox-swagger2
+
+
+ io.swagger
+ *
+
+
+
+
+ io.rest-assured
+ rest-assured
+
+
+
+
+
+
+
+ kotlin-maven-plugin
+ org.jetbrains.kotlin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ false
+
+
+
+
+
+
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/SwaggerConfiguration.java b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/SwaggerConfiguration.java
new file mode 100644
index 0000000000..13896abe05
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/SwaggerConfiguration.java
@@ -0,0 +1,37 @@
+package com.mongolegacy;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.context.request.WebRequest;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import static springfox.documentation.builders.PathSelectors.regex;
+
+public class SwaggerConfiguration {
+
+ String path;
+
+ public SwaggerConfiguration(String path) {
+ this.path = path;
+ }
+
+ @Bean
+ public Docket docketApi() {
+ return new Docket(DocumentationType.SWAGGER_2)
+ .apiInfo(apiInfo())
+ .select()
+ .paths(regex("/" + path + "/.*"))
+ .build()
+ .ignoredParameterTypes(WebRequest.class);
+ }
+
+ private ApiInfo apiInfo() {
+ return new ApiInfoBuilder()
+ .title("API")
+ .description("Some description")
+ .version("1.0")
+ .build();
+ }
+}
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyMongoPersonsApp.java b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyMongoPersonsApp.java
new file mode 100644
index 0000000000..354b841da9
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyMongoPersonsApp.java
@@ -0,0 +1,34 @@
+package com.mongolegacy.persons;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongolegacy.SwaggerConfiguration;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * SUT using directly the legacy MongoDB driver 3.8 (no Spring Data).
+ * Spring Boot auto-configuration for Mongo is excluded, as it targets 4.x drivers.
+ */
+@EnableSwagger2
+@SpringBootApplication(exclude = MongoAutoConfiguration.class)
+public class LegacyMongoPersonsApp extends SwaggerConfiguration {
+
+ public LegacyMongoPersonsApp() {
+ super("legacymongo");
+ }
+
+ @Bean(destroyMethod = "close")
+ public MongoClient mongoClient(@Value("${spring.data.mongodb.host}") String host,
+ @Value("${spring.data.mongodb.port}") int port) {
+ return MongoClients.create("mongodb://" + host + ":" + port);
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(LegacyMongoPersonsApp.class, args);
+ }
+}
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyMongoPersonsRest.java b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyMongoPersonsRest.java
new file mode 100644
index 0000000000..5c3a1237ed
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyMongoPersonsRest.java
@@ -0,0 +1,47 @@
+package com.mongolegacy.persons;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.model.Filters;
+import org.bson.Document;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping(path = "/legacymongo")
+public class LegacyMongoPersonsRest {
+
+ @Autowired
+ private MongoClient mongoClient;
+
+ @Value("${spring.data.mongodb.database}")
+ private String databaseName;
+
+ private MongoCollection persons() {
+ return mongoClient.getDatabase(databaseName).getCollection("persons");
+ }
+
+ @PostMapping("/persons")
+ public ResponseEntity create(@RequestBody LegacyPersonDto dto) {
+ if (dto == null || dto.name == null || dto.age == null) {
+ return ResponseEntity.status(400).build();
+ }
+ persons().insertOne(new Document("name", dto.name).append("age", dto.age));
+ return ResponseEntity.status(201).build();
+ }
+
+ @GetMapping("/persons")
+ public ResponseEntity find(@RequestParam String name, @RequestParam int age) {
+ /*
+ Filters do not return a BsonDocument. With driver 3.x, converting them
+ requires Bson.toBsonDocument(Class, CodecRegistry), as the no-arg version only exists since 4.2
+ */
+ Document found = persons()
+ .find(Filters.and(Filters.eq("name", name), Filters.gte("age", age)))
+ .first();
+ int status = found != null ? 200 : 404;
+ return ResponseEntity.status(status).build();
+ }
+}
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyPersonDto.java b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyPersonDto.java
new file mode 100644
index 0000000000..ab7c39ab2c
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/main/java/com/mongolegacy/persons/LegacyPersonDto.java
@@ -0,0 +1,8 @@
+package com.mongolegacy.persons;
+
+public class LegacyPersonDto {
+
+ public String name;
+
+ public Integer age;
+}
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/com/foo/spring/rest/mongolegacy/LegacyMongoPersonsAppController.java b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/com/foo/spring/rest/mongolegacy/LegacyMongoPersonsAppController.java
new file mode 100644
index 0000000000..c15be87a69
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/com/foo/spring/rest/mongolegacy/LegacyMongoPersonsAppController.java
@@ -0,0 +1,121 @@
+package com.foo.spring.rest.mongolegacy;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongolegacy.persons.LegacyMongoPersonsApp;
+import org.evomaster.client.java.controller.EmbeddedSutController;
+import org.evomaster.client.java.controller.api.dto.SutInfoDto;
+import org.evomaster.client.java.controller.api.dto.auth.AuthenticationDto;
+import org.evomaster.client.java.controller.problem.ProblemInfo;
+import org.evomaster.client.java.controller.problem.RestProblem;
+import org.evomaster.client.java.sql.DbSpecification;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.testcontainers.containers.GenericContainer;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class LegacyMongoPersonsAppController extends EmbeddedSutController {
+
+ private static final int MONGODB_PORT = 27017;
+
+ private static final String DATABASE_NAME = "legacymongo";
+
+ private MongoClient mongoClient;
+
+ /*
+ MongoDB 4.4 is a server version supported by the legacy 3.8 driver wire protocol,
+ and it has images for both amd64 and arm64
+ */
+ private final GenericContainer> mongodb = new GenericContainer<>("mongo:4.4")
+ .withTmpFs(Collections.singletonMap("/data/db", "rw"))
+ .withExposedPorts(MONGODB_PORT);
+
+ private ConfigurableApplicationContext ctx;
+
+ public LegacyMongoPersonsAppController() {
+ super.setControllerPort(0);
+ }
+
+ @Override
+ public String startSut() {
+ mongodb.start();
+
+ String host = mongodb.getHost();
+ int port = mongodb.getMappedPort(MONGODB_PORT);
+
+ mongoClient = MongoClients.create("mongodb://" + host + ":" + port + "/" + DATABASE_NAME);
+
+ SpringApplicationBuilder app = new SpringApplicationBuilder(LegacyMongoPersonsApp.class);
+
+ app.properties(
+ "--server.port=0",
+ "spring.data.mongodb.host=" + host,
+ "spring.data.mongodb.port=" + port,
+ "spring.data.mongodb.database=" + DATABASE_NAME
+ );
+
+ ctx = app.run();
+
+ return "http://localhost:" + getSutPort();
+ }
+
+ @Override
+ public void stopSut() {
+ ctx.stop();
+ ctx.close();
+ mongoClient.close();
+ mongodb.stop();
+ }
+
+ @Override
+ public void resetStateOfSUT() {
+ mongoClient.getDatabase(DATABASE_NAME).drop();
+ }
+
+ @Override
+ public String getPackagePrefixesToCover() {
+ return "com.mongolegacy.persons";
+ }
+
+ @Override
+ public List getDbSpecifications() {
+ return null;
+ }
+
+ @Override
+ public boolean isSutRunning() {
+ return ctx != null && ctx.isRunning();
+ }
+
+ @Override
+ public List getInfoForAuthentication() {
+ return null;
+ }
+
+ @Override
+ public ProblemInfo getProblemInfo() {
+ return new RestProblem(
+ "http://localhost:" + getSutPort() + "/v2/api-docs",
+ null
+ );
+ }
+
+ @Override
+ public SutInfoDto.OutputFormat getPreferredOutputFormat() {
+ return null;
+ }
+
+ protected int getSutPort() {
+ return (Integer) ((Map) ctx.getEnvironment()
+ .getPropertySources().get("server.ports").getSource())
+ .get("local.server.port");
+ }
+
+ @Override
+ public MongoClient getMongoConnection() {
+ return mongoClient;
+ }
+}
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/org/evomaster/e2etests/spring/rest/mongolegacy/persons/LegacyMongoBsonDocumentConverterTest.java b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/org/evomaster/e2etests/spring/rest/mongolegacy/persons/LegacyMongoBsonDocumentConverterTest.java
new file mode 100644
index 0000000000..cac40ba235
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/org/evomaster/e2etests/spring/rest/mongolegacy/persons/LegacyMongoBsonDocumentConverterTest.java
@@ -0,0 +1,33 @@
+package org.evomaster.e2etests.spring.rest.mongolegacy.persons;
+
+import com.mongodb.client.model.Filters;
+import org.bson.BsonDocument;
+import org.bson.conversions.Bson;
+import org.evomaster.client.java.instrumentation.mongo.BsonDocumentConverter;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Checks that the conversion of Bson queries works with the legacy MongoDB driver 3.8
+ */
+public class LegacyMongoBsonDocumentConverterTest {
+
+ @Test
+ public void testLegacyDriverIsOnClasspath() {
+ // the no-arg Bson.toBsonDocument() was introduced in driver 4.2
+ assertThrows(NoSuchMethodException.class, () -> Bson.class.getMethod("toBsonDocument"));
+ }
+
+ @Test
+ public void testToBsonDocumentFromFilters() {
+ Bson filter = Filters.and(Filters.eq("name", "foo"), Filters.gte("age", 42));
+ Object converted = BsonDocumentConverter.toBsonDocument(filter);
+
+ assertNotNull(converted);
+ assertTrue(converted instanceof BsonDocument);
+ assertEquals(
+ BsonDocument.parse("{\"name\": \"foo\", \"age\": {\"$gte\": 42}}"),
+ converted);
+ }
+}
diff --git a/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/org/evomaster/e2etests/spring/rest/mongolegacy/persons/LegacyMongoPersonsEMTest.java b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/org/evomaster/e2etests/spring/rest/mongolegacy/persons/LegacyMongoPersonsEMTest.java
new file mode 100644
index 0000000000..791b26bc99
--- /dev/null
+++ b/core-tests/e2e-tests/spring/spring-rest-mongo-legacy/src/test/java/org/evomaster/e2etests/spring/rest/mongolegacy/persons/LegacyMongoPersonsEMTest.java
@@ -0,0 +1,46 @@
+package org.evomaster.e2etests.spring.rest.mongolegacy.persons;
+
+import com.foo.spring.rest.mongolegacy.LegacyMongoPersonsAppController;
+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.assertFalse;
+
+/**
+ * Same as other Mongo E2E tests, but with the SUT using the legacy MongoDB driver 3.8
+ */
+public class LegacyMongoPersonsEMTest extends RestTestBase {
+
+ @BeforeAll
+ public static void initClass() throws Exception {
+ EMConfig config = new EMConfig();
+ config.setInstrumentMR_MONGO(true);
+ RestTestBase.initClass(new LegacyMongoPersonsAppController(), config);
+ }
+
+ @Test
+ public void testRunEM() throws Throwable {
+
+ runTestHandlingFlakyAndCompilation(
+ "LegacyMongoPersonsEM",
+ 2000,
+ (args) -> {
+ setOption(args, "heuristicsForMongo", "true");
+ setOption(args, "instrumentMR_MONGO", "true");
+ setOption(args, "generateMongoData", "false");
+ setOption(args, "extractMongoExecutionInfo", "true");
+
+ Solution solution = initAndRun(args);
+
+ assertFalse(solution.getIndividuals().isEmpty());
+ assertHasAtLeastOne(solution, HttpVerb.POST, 201, "/legacymongo/persons", null);
+ assertHasAtLeastOne(solution, HttpVerb.GET, 404, "/legacymongo/persons", null);
+ assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/legacymongo/persons", null);
+ });
+ }
+}