diff --git a/.gitignore b/.gitignore
index 2d4b00f00..6025c0e2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -338,6 +338,9 @@ scripts/dockerize/dockerfiles
/jdk_21_maven/cs/rest/joinus/target/
/jdk_21_maven/em/external/rest/joinus/target/
/jdk_21_maven/em/embedded/rest/joinus/target/
+/jdk_21_maven/cs/rest/lovemining/target/
+/jdk_21_maven/em/external/rest/lovemining/target/
+/jdk_21_maven/em/embedded/rest/lovemining/target/
/jdk_11_maven/em/embedded/rest/tracking-system/target/
/jdk_11_maven/em/external/rest/tracking-system/target/
/jdk_17_maven/em/embedded/rest/tiltaksgjennomforing/target/
diff --git a/README.md b/README.md
index a058785f0..9ea4414c3 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ How to setup authentication information, based on the current content of the ini
Auth configuration files can found in the [auth](auth) folder.
-### REST: Java/Kotlin (41)
+### REST: Java/Kotlin (42)
* **Arimaa** (not-known license), [jdk_21_maven/cs/rest/arimaa](jdk_21_maven/cs/rest/arimaa), from [https://github.com/KEAArimaaProject/arimaa-backend](https://github.com/KEAArimaaProject/arimaa-backend)
@@ -117,6 +117,8 @@ Auth configuration files can found in the [auth](auth) folder.
* **Languagetool** (LGPL), [jdk_8_maven/cs/rest/original/languagetool](jdk_8_maven/cs/rest/original/languagetool), from [https://github.com/languagetool-org/languagetool](https://github.com/languagetool-org/languagetool)
+* **LoveMining** (not-known license), [jdk_21_maven/cs/rest/lovemining](jdk_21_maven/cs/rest/lovemining), from [https://github.com/Andryd22/LoveMining](https://github.com/Andryd22/LoveMining)
+
* **Market** (MIT), [jdk_11_maven/cs/rest-gui/market](jdk_11_maven/cs/rest-gui/market), from [https://github.com/aleksey-lukyanets/market](https://github.com/aleksey-lukyanets/market)
* **Microcks** (Apache), [jdk_21_maven/cs/rest-gui/microcks](jdk_21_maven/cs/rest-gui/microcks), from [https://github.com/microcks/microcks](https://github.com/microcks/microcks)
diff --git a/auth/lovemining-auth.yaml b/auth/lovemining-auth.yaml
new file mode 100644
index 000000000..12d8f829b
--- /dev/null
+++ b/auth/lovemining-auth.yaml
@@ -0,0 +1,17 @@
+auth:
+ - name: wfd_admin1
+ fixedHeaders:
+ - name: Authorization
+ value: Basic d2ZkX2FkbWluMUB3ZmQuaW52YWxpZDpXZmQtQWRtaW4tUGFzczE=
+ - name: wfd_admin2
+ fixedHeaders:
+ - name: Authorization
+ value: Basic d2ZkX2FkbWluMkB3ZmQuaW52YWxpZDpXZmQtQWRtaW4tUGFzczI=
+ - name: wfd_user1
+ fixedHeaders:
+ - name: Authorization
+ value: Basic d2ZkX3VzZXIxQHdmZC5pbnZhbGlkOldmZC1Vc2VyLVBhc3Mx
+ - name: wfd_user2
+ fixedHeaders:
+ - name: Authorization
+ value: Basic d2ZkX3VzZXIyQHdmZC5pbnZhbGlkOldmZC1Vc2VyLVBhc3My
diff --git a/dockerfiles/additional_files/lovemining/lovemining-mongo-seed.js b/dockerfiles/additional_files/lovemining/lovemining-mongo-seed.js
new file mode 100644
index 000000000..b5c78fc8b
--- /dev/null
+++ b/dockerfiles/additional_files/lovemining/lovemining-mongo-seed.js
@@ -0,0 +1,38 @@
+// MODIFIED (WFD-added, not part of the original SUT): the same minimal seed the drivers create -
+// only what the API can never produce itself, i.e. ADMIN accounts (/register always writes
+// is_admin=false) and reviews older than the glow-up cutoff. Profiles, likes, matches and recent
+// reviews are left for the search to create.
+// No "_class" field: MongoConfig installs DefaultMongoTypeMapper(null), which strips it.
+db = db.getSiblingDB('LoveMining');
+
+const mo = function (m) { return new Date(Date.now() - m * 30 * 24 * 60 * 60 * 1000); };
+
+const ADMIN1_HASH = '$2a$10$Q5Uui.PMfXK0RjKqJek4Cusq5tKmTeUXwm6tdD.HA6LrLew7JK0me'; // Wfd-Admin-Pass1
+const ADMIN2_HASH = '$2a$10$Zbu3QxMgPmo1ZunqPxn2SeWuEGJP9RHsBeoCOoKdlfuF8BNn0AnFy'; // Wfd-Admin-Pass2
+const USER1_HASH = '$2a$10$3gCReephTyeV2WcmQklcJuEjALyr.sTkTyUKuVmCX7fFyPSAYPFp6'; // Wfd-User-Pass1
+const USER2_HASH = '$2a$10$t1QO4YBNJaM5gQk6v9I9EeiBjHO.1EWmKcURu8/NfMaaNrpvRIC3e'; // Wfd-User-Pass2
+
+db.getCollection('users').insertMany([
+ { "_id": "wfd_admin1", "Email": "wfd_admin1@wfd.invalid", "Password": ADMIN1_HASH, "is_admin": true },
+ { "_id": "wfd_admin2", "Email": "wfd_admin2@wfd.invalid", "Password": ADMIN2_HASH, "is_admin": true },
+ {
+ "_id": "wfd_user1", "Email": "wfd_user1@wfd.invalid", "Password": USER1_HASH, "is_admin": false,
+ "age": 28, "sex": "m", "orientation": "straight", "status": "single",
+ "city": "pisa", "state": "tuscany",
+ "essay0": "I love music and travel.", "interests": ["music", "travel"]
+ },
+ {
+ "_id": "wfd_user2", "Email": "wfd_user2@wfd.invalid", "Password": USER2_HASH, "is_admin": false,
+ "age": 27, "sex": "f", "orientation": "straight", "status": "single",
+ "city": "pisa", "state": "tuscany",
+ "essay0": "Music and art are my life.", "interests": ["music", "art"]
+ }
+]);
+
+// Reviews record no author: ReviewDocument has no such field, the link lives in reviews_made.
+// Three on one target, spanning the cutoff, is the glow-up minimum. Dates must stay relative.
+db.getCollection('reviews').insertMany([
+ { "_id": "wfd_rv1", "target_id": "wfd_user1", "rating": 2, "comment": "Conversation was hard work.", "review_date": mo(9) },
+ { "_id": "wfd_rv2", "target_id": "wfd_user1", "rating": 1, "comment": "Not my type at all.", "review_date": mo(8) },
+ { "_id": "wfd_rv3", "target_id": "wfd_user1", "rating": 5, "comment": "Much better than I expected.", "review_date": mo(1) }
+]);
diff --git a/dockerfiles/additional_files/lovemining/lovemining-neo4j-seed.cypher b/dockerfiles/additional_files/lovemining/lovemining-neo4j-seed.cypher
new file mode 100644
index 000000000..b0d2684c2
--- /dev/null
+++ b/dockerfiles/additional_files/lovemining/lovemining-neo4j-seed.cypher
@@ -0,0 +1,17 @@
+// MODIFIED (WFD-added, not part of the original SUT): the graph half of the seed the drivers
+// create. Admins have no profile, so they get no :User node. No relationship between users is
+// seeded - likes, dislikes and matches are left for the search to create.
+MERGE (s:State {name: 'tuscany'})
+WITH s
+UNWIND [
+ {id: 'wfd_user1', age: 28, sex: 'm', orientation: 'straight', city: 'pisa', interests: ['music', 'travel']},
+ {id: 'wfd_user2', age: 27, sex: 'f', orientation: 'straight', city: 'pisa', interests: ['music', 'art']}
+] AS row
+MERGE (c:City {name: row.city})
+MERGE (c)-[:LOCATED_IN]->(s)
+CREATE (u:User {_id: row.id, age: row.age, sex: row.sex, orientation: row.orientation})
+CREATE (u)-[:LIVES_IN]->(c)
+WITH u, row
+UNWIND row.interests AS name
+MERGE (i:Interest {name: name})
+CREATE (u)-[:HAS_INTEREST]->(i);
diff --git a/dockerfiles/lovemining.dockerfile b/dockerfiles/lovemining.dockerfile
new file mode 100644
index 000000000..0d6c53dc4
--- /dev/null
+++ b/dockerfiles/lovemining.dockerfile
@@ -0,0 +1,13 @@
+FROM amazoncorretto:21-alpine-jdk
+
+COPY ./dist/lovemining-sut.jar .
+COPY ./dist/jacocoagent.jar .
+
+
+
+
+ENTRYPOINT \
+ java \
+ -javaagent:jacocoagent.jar=output=tcpserver,address=*,port=6300,append=false,dumponexit=false \
+ -jar lovemining-sut.jar \
+ --server.port=8080 --spring.data.mongodb.uri=mongodb://db:27017/LoveMining --spring.neo4j.uri=bolt://neo4j:7687 --spring.neo4j.authentication.username=neo4j --spring.neo4j.authentication.password=wfdNeo4jPass123
\ No newline at end of file
diff --git a/dockerfiles/lovemining.yaml b/dockerfiles/lovemining.yaml
new file mode 100644
index 000000000..a1d34e7ea
--- /dev/null
+++ b/dockerfiles/lovemining.yaml
@@ -0,0 +1,56 @@
+services:
+ mitmproxy:
+ image: mitmproxy/mitmproxy:10.2.4
+ user: "0:0"
+ environment:
+ MITM_LOG_DIR: /custom-logs
+ MITM_LOG_FILE: ${MITM_LOG_FILE:-minlog.csv}
+ command: >
+ mitmdump
+ --mode reverse:http://sut-lovemining:8080
+ --listen-host 0.0.0.0
+ --listen-port 8080
+ --set keep_host_header=true
+ -s /addons/minlog.py
+ volumes:
+ - ${HOST_LOG_DIR:-./mitm-logs}:/custom-logs
+ - ./addons:/addons:ro
+ ports:
+ - "${HOST_PORT:-8080}:8080"
+ depends_on:
+ - sut-lovemining
+ sut-lovemining:
+ image: webfuzzing/wfd-lovemining:FINAL
+# build:
+# dockerfile: ./dockerfiles/lovemining.dockerfile
+# context: ..
+ environment:
+ AUTH_PORT: ${AUTH_PORT:-8081}
+ ports:
+ - "${JACOCO_PORT:-6300}:6300"
+ depends_on:
+ db:
+ condition: service_healthy
+ neo4j:
+ condition: service_healthy
+ db:
+ image: mongo:7
+ volumes:
+ - ./additional_files/lovemining/lovemining-mongo-seed.js:/docker-entrypoint-initdb.d/lovemining-mongo-seed.js
+ healthcheck:
+ test: mongosh --quiet --eval "db.adminCommand('ping')"
+ interval: 30s
+ timeout: 30s
+ retries: 3
+ neo4j:
+ image: neo4j:5.26
+ environment:
+ NEO4J_AUTH: neo4j/wfdNeo4jPass123
+ volumes:
+ - ./additional_files/lovemining/lovemining-neo4j-seed.cypher:/seed.cypher
+ healthcheck:
+ test: cypher-shell -u neo4j -p wfdNeo4jPass123 "MATCH (:WfdSeedComplete) RETURN 1"
+ interval: 30s
+ timeout: 30s
+ retries: 3
+ command: ["bash", "-c", '/startup/docker-entrypoint.sh neo4j & pid=$!; until cypher-shell -u neo4j -p wfdNeo4jPass123 "RETURN 1" >/dev/null 2>&1; do sleep 2; done; cypher-shell -u neo4j -p wfdNeo4jPass123 -f /seed.cypher; cypher-shell -u neo4j -p wfdNeo4jPass123 "CREATE (:WfdSeedComplete)"; wait $pid']
\ No newline at end of file
diff --git a/experiments/bb-exp.py b/experiments/bb-exp.py
index c6aedd369..55617bd0e 100644
--- a/experiments/bb-exp.py
+++ b/experiments/bb-exp.py
@@ -131,6 +131,7 @@ def __init__(self, name, auth, sleep, schemaformat, classfiles):
"jdk_8_maven/cs/rest/original/languagetool/languagetool-language-modules/uk/target/classes",
"jdk_8_maven/cs/rest/original/languagetool/languagetool-language-modules/zh/target/classes",
"jdk_8_maven/cs/rest/original/languagetool/languagetool-server/target/classes"]),
+ Sut("lovemining",True,SLEEP,JSON,["jdk_21_maven/cs/rest/lovemining/target/classes"]),
Sut("market",True,SLEEP,JSON,["jdk_11_maven/cs/rest-gui/market/market-core/target/classes",
"jdk_11_maven/cs/rest-gui/market/market-rest/target/classes",
"jdk_11_maven/cs/rest-gui/market/market-web/target/classes"]),
diff --git a/experiments/wb-exp.py b/experiments/wb-exp.py
index 2040e3fda..3043e2906 100644
--- a/experiments/wb-exp.py
+++ b/experiments/wb-exp.py
@@ -197,6 +197,7 @@ def __init__(self, name, platform):
Sut("jasper", JDK_25),
Sut("joinus", JDK_21),
Sut("languagetool", JDK_8),
+ Sut("lovemining", JDK_21),
Sut("market", JDK_11),
Sut("microcks", JDK_21),
Sut("ocvn", JDK_8),
diff --git a/jacoco.dockerfile b/jacoco.dockerfile
index 3ad3d0f6e..1f2cf1712 100644
--- a/jacoco.dockerfile
+++ b/jacoco.dockerfile
@@ -67,6 +67,7 @@ COPY jdk_8_maven/cs/rest/original/languagetool/languagetool-language-modules/tl/
COPY jdk_8_maven/cs/rest/original/languagetool/languagetool-language-modules/uk/target/classes ${CLASS_FILES}/jdk_8_maven/cs/rest/original/languagetool/languagetool-language-modules/uk/target/classes
COPY jdk_8_maven/cs/rest/original/languagetool/languagetool-language-modules/zh/target/classes ${CLASS_FILES}/jdk_8_maven/cs/rest/original/languagetool/languagetool-language-modules/zh/target/classes
COPY jdk_8_maven/cs/rest/original/languagetool/languagetool-server/target/classes ${CLASS_FILES}/jdk_8_maven/cs/rest/original/languagetool/languagetool-server/target/classes
+COPY jdk_21_maven/cs/rest/lovemining/target/classes ${CLASS_FILES}/jdk_21_maven/cs/rest/lovemining/target/classes
COPY jdk_11_maven/cs/rest-gui/market/market-core/target/classes ${CLASS_FILES}/jdk_11_maven/cs/rest-gui/market/market-core/target/classes
COPY jdk_11_maven/cs/rest-gui/market/market-rest/target/classes ${CLASS_FILES}/jdk_11_maven/cs/rest-gui/market/market-rest/target/classes
COPY jdk_11_maven/cs/rest-gui/market/market-web/target/classes ${CLASS_FILES}/jdk_11_maven/cs/rest-gui/market/market-web/target/classes
diff --git a/jdk_21_maven/cs/rest/lovemining/pom.xml b/jdk_21_maven/cs/rest/lovemining/pom.xml
index c13652f75..c375c2ae9 100644
--- a/jdk_21_maven/cs/rest/lovemining/pom.xml
+++ b/jdk_21_maven/cs/rest/lovemining/pom.xml
@@ -77,6 +77,12 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+ lovemining
+ sut
+
org.projectlombok
diff --git a/jdk_21_maven/cs/rest/lovemining/src/main/java/it/unipi/LoveMining/config/MongoConfig.java b/jdk_21_maven/cs/rest/lovemining/src/main/java/it/unipi/LoveMining/config/MongoConfig.java
index 6ae92754a..818d08add 100644
--- a/jdk_21_maven/cs/rest/lovemining/src/main/java/it/unipi/LoveMining/config/MongoConfig.java
+++ b/jdk_21_maven/cs/rest/lovemining/src/main/java/it/unipi/LoveMining/config/MongoConfig.java
@@ -19,10 +19,18 @@ public class MongoConfig extends AbstractMongoClientConfiguration {
@Override
protected String getDatabaseName() { return "LoveMining"; }
+ // MODIFIED
+ // The URI was hardcoded to the original deployment VMs, and AbstractMongoClientConfiguration
+ // makes Spring Boot back off, so no property could override it. Default keeps the old value.
+ @org.springframework.beans.factory.annotation.Value(
+ "${spring.data.mongodb.uri:mongodb://10.1.1.14:27017,10.1.1.15:27017,10.1.1.16:27017/?replicaSet=lsmdb}")
+ private String mongoUri;
+ // MODIFIED
+
@Override
@Bean
public MongoClient mongoClient() {
- String uri = "mongodb://10.1.1.14:27017,10.1.1.15:27017,10.1.1.16:27017/?replicaSet=lsmdb";
+ String uri = mongoUri; // MODIFIED
// Write and Read preferences
MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(new ConnectionString(uri))
diff --git a/jdk_21_maven/cs/rest/pom.xml b/jdk_21_maven/cs/rest/pom.xml
index aa6c99cfd..070191e45 100644
--- a/jdk_21_maven/cs/rest/pom.xml
+++ b/jdk_21_maven/cs/rest/pom.xml
@@ -17,6 +17,7 @@
person-controller
redis-sample
arimaa
+ lovemining
diff --git a/jdk_21_maven/em/embedded/rest/lovemining/pom.xml b/jdk_21_maven/em/embedded/rest/lovemining/pom.xml
new file mode 100644
index 000000000..20ea519b0
--- /dev/null
+++ b/jdk_21_maven/em/embedded/rest/lovemining/pom.xml
@@ -0,0 +1,52 @@
+
+
+
+ 4.0.0
+
+ evomaster-benchmark-jdk21-em-embedded-rest-lovemining
+ jar
+
+
+ org.evomaster
+ evomaster-benchmark-jdk21-em-embedded-rest
+ 4.3.1-SNAPSHOT
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 3.4.11
+ pom
+ import
+
+
+
+
+
+
+
+ it.unipi
+ LoveMining
+ 0.0.1-SNAPSHOT
+
+
+
+ org.testcontainers
+ testcontainers
+ compile
+
+
+
+ org.mongodb
+ mongodb-driver-sync
+
+
+ org.neo4j.driver
+ neo4j-java-driver
+
+
+
+
diff --git a/jdk_21_maven/em/embedded/rest/lovemining/src/main/java/em/embedded/it/unipi/LoveMining/EmbeddedEvoMasterController.java b/jdk_21_maven/em/embedded/rest/lovemining/src/main/java/em/embedded/it/unipi/LoveMining/EmbeddedEvoMasterController.java
new file mode 100644
index 000000000..c0039c335
--- /dev/null
+++ b/jdk_21_maven/em/embedded/rest/lovemining/src/main/java/em/embedded/it/unipi/LoveMining/EmbeddedEvoMasterController.java
@@ -0,0 +1,316 @@
+package em.embedded.it.unipi.LoveMining;
+
+import it.unipi.LoveMining.LoveMiningApplication;
+import org.evomaster.client.java.controller.EmbeddedSutController;
+import org.evomaster.client.java.controller.InstrumentedSutStarter;
+import org.evomaster.client.java.controller.api.dto.auth.AuthenticationDto;
+import org.evomaster.client.java.controller.api.dto.SutInfoDto;
+import org.evomaster.client.java.sql.DbSpecification;
+import org.evomaster.client.java.controller.problem.ProblemInfo;
+import org.evomaster.client.java.controller.problem.RestProblem;
+import com.webfuzzing.commons.auth.Header;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoDatabase;
+import org.bson.Document;
+import org.neo4j.driver.AuthTokens;
+import org.neo4j.driver.Driver;
+import org.neo4j.driver.GraphDatabase;
+import org.neo4j.driver.Session;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class used to start/stop the SUT.
+ */
+public class EmbeddedEvoMasterController extends EmbeddedSutController {
+
+ private static final String MONGODB_IMAGE = "mongo:7";
+
+ private static final int MONGODB_PORT = 27017;
+
+ private static final String MONGODB_DATABASE = "LoveMining";
+
+ private static final String NEO4J_IMAGE = "neo4j:5.26";
+
+ private static final int NEO4J_BOLT_PORT = 7687;
+
+ private static final String NEO4J_USERNAME = "neo4j";
+
+ private static final String NEO4J_PASSWORD = "wfdNeo4jPass123";
+
+ private static final String STATE = "tuscany";
+
+ private static final String ADMIN1_HASH = "$2a$10$Q5Uui.PMfXK0RjKqJek4Cusq5tKmTeUXwm6tdD.HA6LrLew7JK0me";
+
+ private static final String ADMIN2_HASH = "$2a$10$Zbu3QxMgPmo1ZunqPxn2SeWuEGJP9RHsBeoCOoKdlfuF8BNn0AnFy";
+
+ private static final String USER1_HASH = "$2a$10$3gCReephTyeV2WcmQklcJuEjALyr.sTkTyUKuVmCX7fFyPSAYPFp6";
+
+ private static final String USER2_HASH = "$2a$10$t1QO4YBNJaM5gQk6v9I9EeiBjHO.1EWmKcURu8/NfMaaNrpvRIC3e";
+
+ // seed
+ private static final List USERS = List.of(
+ account("wfd_admin1", ADMIN1_HASH, true),
+ account("wfd_admin2", ADMIN2_HASH, true),
+ profile("wfd_user1", USER1_HASH, 28, "m", "straight", "single", "pisa",
+ "I love music and travel.", List.of("music", "travel")),
+ profile("wfd_user2", USER2_HASH, 27, "f", "straight", "single", "pisa",
+ "Music and art are my life.", List.of("music", "art"))
+ );
+
+ private static Document account(String id, String hash, boolean admin) {
+ return new Document("_id", id)
+ .append("Email", id + "@wfd.invalid")
+ .append("Password", hash)
+ .append("is_admin", admin);
+ }
+
+ private static Document profile(String id, String hash, int age, String sex, String orientation,
+ String status, String city, String essay0, List interests) {
+ return account(id, hash, false)
+ .append("age", age)
+ .append("sex", sex)
+ .append("orientation", orientation)
+ .append("status", status)
+ .append("city", city)
+ .append("state", STATE)
+ .append("essay0", essay0)
+ .append("interests", interests);
+ }
+
+ // Dates must stay relative: the glow-up aggregation groups reviews around a 6-month cutoff
+ private static Document review(String id, String targetId, int rating, String comment, int monthsAgo) {
+ Date date = Date.from(LocalDateTime.now().minusMonths(monthsAgo).atZone(ZoneId.systemDefault()).toInstant());
+ return new Document("_id", id)
+ .append("target_id", targetId)
+ .append("rating", rating)
+ .append("comment", comment)
+ .append("review_date", date);
+ }
+
+ // Reviews record no author: ReviewDocument has no such field, the link lives in
+ // UserDocument.reviews_made. Three on one target, spanning the cutoff, is the glow-up minimum.
+ private static List reviews() {
+ return List.of(
+ review("wfd_rv1", "wfd_user1", 2, "Conversation was hard work.", 9),
+ review("wfd_rv2", "wfd_user1", 1, "Not my type at all.", 8),
+ review("wfd_rv3", "wfd_user1", 5, "Much better than I expected.", 1)
+ );
+ }
+
+ private static final GenericContainer mongodb = new GenericContainer(MONGODB_IMAGE)
+ .withExposedPorts(MONGODB_PORT);
+
+ private static final GenericContainer neo4j = new GenericContainer(NEO4J_IMAGE)
+ .withEnv("NEO4J_AUTH", NEO4J_USERNAME + "/" + NEO4J_PASSWORD)
+ .withExposedPorts(NEO4J_BOLT_PORT)
+ .waitingFor(Wait.forLogMessage(".*Started.*", 1))
+ .withStartupTimeout(Duration.ofMinutes(2));
+
+ public static void main(String[] args) {
+
+ int port = 40100;
+ if (args.length > 0) {
+ port = Integer.parseInt(args[0]);
+ }
+
+ EmbeddedEvoMasterController controller = new EmbeddedEvoMasterController(port);
+ InstrumentedSutStarter starter = new InstrumentedSutStarter(controller);
+
+ starter.start();
+ }
+
+ private ConfigurableApplicationContext ctx;
+
+ private MongoClient mongoClient;
+
+ private Driver neo4jDriver;
+
+ public EmbeddedEvoMasterController() {
+ this(0);
+ }
+
+ public EmbeddedEvoMasterController(int port) {
+ setControllerPort(port);
+ }
+
+ private String mongodbUri() {
+ return "mongodb://" + mongodb.getHost() + ":" + mongodb.getMappedPort(MONGODB_PORT) + "/" + MONGODB_DATABASE;
+ }
+
+ private String neo4jUri() {
+ return "bolt://" + neo4j.getHost() + ":" + neo4j.getMappedPort(NEO4J_BOLT_PORT);
+ }
+
+ @Override
+ public String startSut() {
+
+ mongodb.start();
+ neo4j.start();
+ mongoClient = MongoClients.create(mongodbUri());
+ neo4jDriver = GraphDatabase.driver(neo4jUri(), AuthTokens.basic(NEO4J_USERNAME, NEO4J_PASSWORD));
+ seed();
+
+ ctx = SpringApplication.run(LoveMiningApplication.class, new String[]{
+ "--server.port=0",
+ "--spring.data.mongodb.uri=" + mongodbUri(),
+ "--spring.neo4j.uri=" + neo4jUri(),
+ "--spring.neo4j.authentication.username=" + NEO4J_USERNAME,
+ "--spring.neo4j.authentication.password=" + NEO4J_PASSWORD
+ });
+
+ return "http://localhost:" + getSutPort();
+ }
+
+ private void seed() {
+ MongoDatabase db = mongoClient.getDatabase(MONGODB_DATABASE);
+ List users = new ArrayList<>();
+ for (Document u : USERS) {
+ users.add(new Document(u));
+ }
+ db.getCollection("users").insertMany(users);
+ db.getCollection("reviews").insertMany(reviews());
+
+ try (Session session = neo4jDriver.session()) {
+ session.run("""
+ MERGE (s:State {name: $state})
+ WITH s
+ UNWIND $rows AS row
+ MERGE (c:City {name: row.city})
+ MERGE (c)-[:LOCATED_IN]->(s)
+ CREATE (u:User {_id: row.id, age: row.age, sex: row.sex, orientation: row.orientation})
+ CREATE (u)-[:LIVES_IN]->(c)
+ WITH u, row
+ UNWIND row.interests AS name
+ MERGE (i:Interest {name: name})
+ CREATE (u)-[:HAS_INTEREST]->(i)
+ """,
+ Map.of("state", STATE, "rows", graphRows())).consume();
+ }
+ }
+
+ // Admins have no profile, so they get no graph node
+ private static List