From b582183225f15f18d0c82292142c297af4df7544 Mon Sep 17 00:00:00 2001 From: Omur Date: Tue, 8 Sep 2026 10:25:51 +0300 Subject: [PATCH] lovemining drivers --- .gitignore | 3 + README.md | 4 +- auth/lovemining-auth.yaml | 17 + .../lovemining/lovemining-mongo-seed.js | 38 + .../lovemining/lovemining-neo4j-seed.cypher | 17 + dockerfiles/lovemining.dockerfile | 13 + dockerfiles/lovemining.yaml | 56 ++ experiments/bb-exp.py | 1 + experiments/wb-exp.py | 1 + jacoco.dockerfile | 1 + jdk_21_maven/cs/rest/lovemining/pom.xml | 6 + .../unipi/LoveMining/config/MongoConfig.java | 10 +- jdk_21_maven/cs/rest/pom.xml | 1 + .../em/embedded/rest/lovemining/pom.xml | 52 ++ .../EmbeddedEvoMasterController.java | 316 ++++++++ jdk_21_maven/em/embedded/rest/pom.xml | 1 + .../em/external/rest/lovemining/pom.xml | 91 +++ .../ExternalEvoMasterController.java | 371 ++++++++++ jdk_21_maven/em/external/rest/pom.xml | 1 + openapi/lovemining.json | 688 ++++++++++++++++++ scripts/build/docker-compose.build.yml | 2 + scripts/dist-wb.py | 2 + scripts/dockerize/data/sut.csv | 1 + statistics/data.csv | 1 + statistics/table_emb.md | 1 + 25 files changed, 1693 insertions(+), 2 deletions(-) create mode 100644 auth/lovemining-auth.yaml create mode 100644 dockerfiles/additional_files/lovemining/lovemining-mongo-seed.js create mode 100644 dockerfiles/additional_files/lovemining/lovemining-neo4j-seed.cypher create mode 100644 dockerfiles/lovemining.dockerfile create mode 100644 dockerfiles/lovemining.yaml create mode 100644 jdk_21_maven/em/embedded/rest/lovemining/pom.xml create mode 100644 jdk_21_maven/em/embedded/rest/lovemining/src/main/java/em/embedded/it/unipi/LoveMining/EmbeddedEvoMasterController.java create mode 100644 jdk_21_maven/em/external/rest/lovemining/pom.xml create mode 100644 jdk_21_maven/em/external/rest/lovemining/src/main/java/em/external/it/unipi/LoveMining/ExternalEvoMasterController.java create mode 100644 openapi/lovemining.json 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> graphRows() { + List> rows = new ArrayList<>(); + for (Document u : USERS) { + if (Boolean.TRUE.equals(u.getBoolean("is_admin"))) { + continue; + } + rows.add(Map.of( + "id", u.getString("_id"), + "age", u.getInteger("age"), + "sex", u.getString("sex"), + "orientation", u.getString("orientation"), + "city", u.getString("city"), + "interests", u.getList("interests", String.class))); + } + return rows; + } + + protected int getSutPort() { + return (Integer) ((Map) ctx.getEnvironment() + .getPropertySources().get("server.ports").getSource()) + .get("local.server.port"); + } + + @Override + public boolean isSutRunning() { + return ctx != null && ctx.isRunning(); + } + + @Override + public void stopSut() { + if (ctx != null) { + ctx.stop(); + ctx.close(); + ctx = null; + } + if (mongoClient != null) { + mongoClient.close(); + mongoClient = null; + } + if (neo4jDriver != null) { + neo4jDriver.close(); + neo4jDriver = null; + } + neo4j.stop(); + mongodb.stop(); + } + + @Override + public String getPackagePrefixesToCover() { + return "it.unipi.LoveMining."; + } + + // Undoes the two endpoints that can break authentication: PATCH /api/users/me overwrites the + // password of the logged-in account, DELETE /api/admin/users/{id} removes it. Black-box runs + // have no reset, so PATCH /api/users/me has to be excluded there. + @Override + public void resetStateOfSUT() { + MongoDatabase db = mongoClient.getDatabase(MONGODB_DATABASE); + for (String name : db.listCollectionNames()) { + db.getCollection(name).deleteMany(new Document()); + } + try (Session session = neo4jDriver.session()) { + session.run("MATCH (n) DETACH DELETE n").consume(); + } + seed(); + } + + @Override + public Object getMongoConnection() { + return mongoClient; + } + + @Override + public List getDbSpecifications() { + return null; + } + + // Login name is the email: CustomUserDetailsService looks accounts up by it + private static AuthenticationDto basicAuth(String name, String username, String password) { + AuthenticationDto dto = new AuthenticationDto(name); + String token = Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + Header header = new Header(); + header.setName("Authorization"); + header.setValue("Basic " + token); + dto.setFixedHeaders(List.of(header)); + return dto; + } + + @Override + public List getInfoForAuthentication() { + return List.of( + basicAuth("WfdAdmin1", "wfd_admin1@wfd.invalid", "Wfd-Admin-Pass1"), + basicAuth("WfdAdmin2", "wfd_admin2@wfd.invalid", "Wfd-Admin-Pass2"), + basicAuth("WfdUser1", "wfd_user1@wfd.invalid", "Wfd-User-Pass1"), + basicAuth("WfdUser2", "wfd_user2@wfd.invalid", "Wfd-User-Pass2") + ); + } + + @Override + public ProblemInfo getProblemInfo() { + return new RestProblem( + "http://localhost:" + getSutPort() + "/v3/api-docs", + null + ); + } + + @Override + public SutInfoDto.OutputFormat getPreferredOutputFormat() { + return SutInfoDto.OutputFormat.JAVA_JUNIT_5; + } +} diff --git a/jdk_21_maven/em/embedded/rest/pom.xml b/jdk_21_maven/em/embedded/rest/pom.xml index 56674500c..f1e5222cb 100644 --- a/jdk_21_maven/em/embedded/rest/pom.xml +++ b/jdk_21_maven/em/embedded/rest/pom.xml @@ -16,6 +16,7 @@ person-controller redis-sample arimaa + lovemining diff --git a/jdk_21_maven/em/external/rest/lovemining/pom.xml b/jdk_21_maven/em/external/rest/lovemining/pom.xml new file mode 100644 index 000000000..92053e032 --- /dev/null +++ b/jdk_21_maven/em/external/rest/lovemining/pom.xml @@ -0,0 +1,91 @@ + + + + 4.0.0 + + evomaster-benchmark-jdk21-em-external-rest-lovemining + jar + + + org.evomaster + evomaster-benchmark-jdk21-em-external-rest + 4.3.1-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-dependencies + 3.4.11 + pom + import + + + + + + + org.testcontainers + testcontainers + compile + + + + org.mongodb + mongodb-driver-sync + + + org.neo4j.driver + neo4j-java-driver + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.2 + + + package + + shade + + + lovemining-evomaster-runner + + + + em.external.it.unipi.LoveMining.ExternalEvoMasterController + + org.evomaster.client.java.instrumentation.InstrumentingAgent + + org.evomaster.client.java.instrumentation.InstrumentingAgent + + true + true + + + + + + org.slf4j + shaded.emb.org.slf4j + + + com.fasterxml.jackson.annotation + shaded.emb.com.fasterxml.jackson.annotation + + + + + + + + + diff --git a/jdk_21_maven/em/external/rest/lovemining/src/main/java/em/external/it/unipi/LoveMining/ExternalEvoMasterController.java b/jdk_21_maven/em/external/rest/lovemining/src/main/java/em/external/it/unipi/LoveMining/ExternalEvoMasterController.java new file mode 100644 index 000000000..0dbdcb059 --- /dev/null +++ b/jdk_21_maven/em/external/rest/lovemining/src/main/java/em/external/it/unipi/LoveMining/ExternalEvoMasterController.java @@ -0,0 +1,371 @@ +package em.external.it.unipi.LoveMining; + +import org.evomaster.client.java.controller.ExternalSutController; +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.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; + +public class ExternalEvoMasterController extends ExternalSutController { + + private static final int DEFAULT_CONTROLLER_PORT = 40100; + + private static final int DEFAULT_SUT_PORT = 12345; + + 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 controllerPort = DEFAULT_CONTROLLER_PORT; + if (args.length > 0) { + controllerPort = Integer.parseInt(args[0]); + } + int sutPort = DEFAULT_SUT_PORT; + if (args.length > 1) { + sutPort = Integer.parseInt(args[1]); + } + String jarLocation = "cs/rest/lovemining/target"; + if (args.length > 2) { + jarLocation = args[2]; + } + if (!jarLocation.endsWith(".jar")) { + jarLocation += "/lovemining-sut.jar"; + } + + int timeoutSeconds = 120; + if (args.length > 3) { + timeoutSeconds = Integer.parseInt(args[3]); + } + + String command = "java"; + if (args.length > 4) { + command = args[4]; + } + + ExternalEvoMasterController controller = + new ExternalEvoMasterController(controllerPort, jarLocation, sutPort, timeoutSeconds, command); + + controller.setNeedsJdk17Options(true); + + InstrumentedSutStarter starter = new InstrumentedSutStarter(controller); + + starter.start(); + } + + private final int timeoutSeconds; + + private final int sutPort; + + private String jarLocation; + + private MongoClient mongoClient; + + private Driver neo4jDriver; + + public ExternalEvoMasterController() { + this(DEFAULT_CONTROLLER_PORT, "../target/lovemining-sut.jar", DEFAULT_SUT_PORT, 120, "java"); + } + + public ExternalEvoMasterController(String jarLocation) { + this(); + this.jarLocation = jarLocation; + } + + public ExternalEvoMasterController(int controllerPort, String jarLocation, int sutPort, int timeoutSeconds, String command) { + this.sutPort = sutPort; + this.jarLocation = jarLocation; + this.timeoutSeconds = timeoutSeconds; + + setControllerPort(controllerPort); + setJavaCommand(command); + } + + 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[] getInputParameters() { + return new String[]{ + "--server.port=" + sutPort, + "--spring.data.mongodb.uri=" + mongodbUri(), + "--spring.neo4j.uri=" + neo4jUri(), + "--spring.neo4j.authentication.username=" + NEO4J_USERNAME, + "--spring.neo4j.authentication.password=" + NEO4J_PASSWORD + }; + } + + @Override + public String[] getJVMParameters() { + return new String[]{}; + } + + @Override + public String getBaseURL() { + return "http://localhost:" + sutPort; + } + + @Override + public String getPathToExecutableJar() { + return jarLocation; + } + + @Override + public String getLogMessageOfInitializedServer() { + return "Started LoveMiningApplication in "; + } + + @Override + public long getMaxAwaitForInitializationInSeconds() { + return timeoutSeconds; + } + + @Override + public void preStart() { + mongodb.start(); + neo4j.start(); + mongoClient = MongoClients.create(mongodbUri()); + neo4jDriver = GraphDatabase.driver(neo4jUri(), AuthTokens.basic(NEO4J_USERNAME, NEO4J_PASSWORD)); + seed(); + } + + 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> graphRows() { + List> rows = new ArrayList<>(); + for (Document u : USERS) { + if (Boolean.TRUE.equals(u.getBoolean("is_admin"))) { + continue; + } + rows.add(Map.of( + "id", u.getString("_id"), + "age", u.getInteger("age"), + "sex", u.getString("sex"), + "orientation", u.getString("orientation"), + "city", u.getString("city"), + "interests", u.getList("interests", String.class))); + } + return rows; + } + + @Override + public void postStart() { + } + + @Override + public void preStop() { + } + + @Override + public void postStop() { + if (mongoClient != null) { + mongoClient.close(); + mongoClient = null; + } + if (neo4jDriver != null) { + neo4jDriver.close(); + neo4jDriver = null; + } + neo4j.stop(); + mongodb.stop(); + } + + @Override + public String getPackagePrefixesToCover() { + return "it.unipi.LoveMining."; + } + + // Undoes the two endpoints that can break authentication: PATCH /api/users/me overwrites the + // password of the logged-in account, DELETE /api/admin/users/{id} removes it. Black-box runs + // have no reset, so PATCH /api/users/me has to be excluded there. + @Override + public void resetStateOfSUT() { + MongoDatabase db = mongoClient.getDatabase(MONGODB_DATABASE); + for (String name : db.listCollectionNames()) { + db.getCollection(name).deleteMany(new Document()); + } + try (Session session = neo4jDriver.session()) { + session.run("MATCH (n) DETACH DELETE n").consume(); + } + seed(); + } + + @Override + public Object getMongoConnection() { + return mongoClient; + } + + @Override + public List getDbSpecifications() { + return null; + } + + // Login name is the email: CustomUserDetailsService looks accounts up by it + private static AuthenticationDto basicAuth(String name, String username, String password) { + AuthenticationDto dto = new AuthenticationDto(name); + String token = Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + Header header = new Header(); + header.setName("Authorization"); + header.setValue("Basic " + token); + dto.setFixedHeaders(List.of(header)); + return dto; + } + + @Override + public List getInfoForAuthentication() { + return List.of( + basicAuth("WfdAdmin1", "wfd_admin1@wfd.invalid", "Wfd-Admin-Pass1"), + basicAuth("WfdAdmin2", "wfd_admin2@wfd.invalid", "Wfd-Admin-Pass2"), + basicAuth("WfdUser1", "wfd_user1@wfd.invalid", "Wfd-User-Pass1"), + basicAuth("WfdUser2", "wfd_user2@wfd.invalid", "Wfd-User-Pass2") + ); + } + + @Override + public ProblemInfo getProblemInfo() { + return new RestProblem( + getBaseURL() + "/v3/api-docs", + null + ); + } + + @Override + public SutInfoDto.OutputFormat getPreferredOutputFormat() { + return SutInfoDto.OutputFormat.JAVA_JUNIT_5; + } + +} diff --git a/jdk_21_maven/em/external/rest/pom.xml b/jdk_21_maven/em/external/rest/pom.xml index 76bedae68..7ab1129c0 100644 --- a/jdk_21_maven/em/external/rest/pom.xml +++ b/jdk_21_maven/em/external/rest/pom.xml @@ -17,6 +17,7 @@ person-controller redis-sample arimaa + lovemining \ No newline at end of file diff --git a/openapi/lovemining.json b/openapi/lovemining.json new file mode 100644 index 000000000..bce4586eb --- /dev/null +++ b/openapi/lovemining.json @@ -0,0 +1,688 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "LoveMining API", + "description": "Documentation for LoveMining Application", + "version": "1.0" + }, + "servers": [ + { + "url": "http://localhost:39090", + "description": "Generated server url" + } + ], + "security": [ + { + "basicAuth": [] + } + ], + "paths": { + "/api/users/{id}/review": { + "post": { + "tags": [ + "user-controller" + ], + "operationId": "createReview", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDocument" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/users/{id}/like": { + "post": { + "tags": [ + "user-controller" + ], + "operationId": "likeUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/users/{id}/dislike": { + "post": { + "tags": [ + "user-controller" + ], + "operationId": "dislikeUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/authentication/register": { + "post": { + "tags": [ + "authentication-controller" + ], + "operationId": "registerUser", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDocument" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/users/me": { + "patch": { + "tags": [ + "user-controller" + ], + "operationId": "updateUser", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDocument" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/users": { + "get": { + "tags": [ + "user-controller" + ], + "operationId": "getMe", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/users/{id}": { + "get": { + "tags": [ + "user-controller" + ], + "operationId": "getUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/UserDocument" + } + } + } + } + } + } + }, + "/api/users/reviews": { + "get": { + "tags": [ + "user-controller" + ], + "operationId": "getReviews", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/users/recommendations/{filters}": { + "get": { + "tags": [ + "user-controller" + ], + "operationId": "getRecommendations", + "parameters": [ + { + "name": "locationFilter", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "minAge", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAge", + "in": "query", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/users/matches": { + "get": { + "tags": [ + "user-controller" + ], + "operationId": "getMatches", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "object" + } + } + } + } + } + } + }, + "/api/admin/userReviews/{id}": { + "get": { + "tags": [ + "admin-controller" + ], + "operationId": "getReview", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/ReviewDocument" + } + } + } + } + } + } + }, + "/api/admin/user/{id}": { + "get": { + "tags": [ + "admin-controller" + ], + "operationId": "getUser_1", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/UserDocument" + } + } + } + } + } + } + }, + "/api/admin/analytics/unhappy-cities": { + "get": { + "tags": [ + "admin-controller" + ], + "operationId": "getUnhappyCitiesAnalytics", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "/api/admin/analytics/status-by-age-group": { + "get": { + "tags": [ + "admin-controller" + ], + "operationId": "getStatusAnalytics", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "/api/admin/analytics/orientation-by-age-group": { + "get": { + "tags": [ + "admin-controller" + ], + "operationId": "getOrientationAnalytics", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "/api/admin/analytics/love-points": { + "get": { + "tags": [ + "admin-controller" + ], + "operationId": "getLovePointsAnalytic", + "parameters": [ + { + "name": "state", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "/api/admin/analytics/glow-up": { + "get": { + "tags": [ + "admin-controller" + ], + "operationId": "getGlowUpAnalytics", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "/api/admin/users/{id}": { + "delete": { + "tags": [ + "admin-controller" + ], + "operationId": "deleteUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ReviewDocument": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "targetId": { + "type": "string" + }, + "rating": { + "type": "integer", + "format": "int32" + }, + "comment": { + "type": "string" + }, + "date": { + "type": "string", + "format": "date-time" + } + } + }, + "ReviewSummary": { + "type": "object", + "properties": { + "reviewId": { + "type": "string" + }, + "targetId": { + "type": "string" + }, + "rating": { + "type": "integer", + "format": "int32" + } + } + }, + "UserDocument": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "isAdmin": { + "type": "boolean" + }, + "age": { + "type": "integer", + "format": "int32" + }, + "status": { + "type": "string" + }, + "sex": { + "type": "string" + }, + "orientation": { + "type": "string" + }, + "bodyType": { + "type": "string" + }, + "diet": { + "type": "string" + }, + "drinks": { + "type": "string" + }, + "education": { + "type": "string" + }, + "ethnicity": { + "type": "string" + }, + "height": { + "type": "integer", + "format": "int32" + }, + "income": { + "type": "integer", + "format": "int32" + }, + "job": { + "type": "string" + }, + "offspring": { + "type": "string" + }, + "pets": { + "type": "string" + }, + "religion": { + "type": "string" + }, + "smokes": { + "type": "string" + }, + "speaks": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "essay0": { + "type": "string" + }, + "interests": { + "type": "array", + "items": { + "type": "string" + } + }, + "reviewsMade": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReviewSummary" + } + } + } + } + }, + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic" + } + } + } +} diff --git a/scripts/build/docker-compose.build.yml b/scripts/build/docker-compose.build.yml index 0fe3bebbd..dbc896ce9 100644 --- a/scripts/build/docker-compose.build.yml +++ b/scripts/build/docker-compose.build.yml @@ -258,6 +258,7 @@ services: cp cs/rest/redis-sample/target/redis-sample-sut.jar /dist/ cp cs/rest/arimaa/target/arimaa-sut.jar /dist/ cp cs/rest/joinus/target/joinus-sut.jar /dist/ + cp cs/rest/lovemining/target/lovemining-sut.jar /dist/ cp cs/rest-gui/webgoat/target/webgoat-sut.jar /dist/ cp cs/rest-gui/microcks/webapp/target/microcks-sut.jar /dist/ if [ "$${BUILD_EVOMASTER:-false}" = "true" ]; then @@ -265,6 +266,7 @@ services: cp em/external/rest/redis-sample/target/redis-sample-evomaster-runner.jar /dist/ cp em/external/rest/arimaa/target/arimaa-evomaster-runner.jar /dist/ cp em/external/rest/joinus/target/joinus-evomaster-runner.jar /dist/ + cp em/external/rest/lovemining/target/lovemining-evomaster-runner.jar /dist/ cp em/external/rest-gui/webgoat/target/webgoat-evomaster-runner.jar /dist/ cp em/external/rest-gui/microcks/target/microcks-evomaster-runner.jar /dist/ fi diff --git a/scripts/dist-wb.py b/scripts/dist-wb.py index 884f9ffba..62b9a26bf 100755 --- a/scripts/dist-wb.py +++ b/scripts/dist-wb.py @@ -301,6 +301,8 @@ def build_jdk_21_maven(): copy(folder + "/em/external/rest/arimaa/target/arimaa-evomaster-runner.jar", DIST) copy(folder + "/cs/rest/joinus/target/joinus-sut.jar", DIST) copy(folder + "/em/external/rest/joinus/target/joinus-evomaster-runner.jar", DIST) + copy(folder + "/cs/rest/lovemining/target/lovemining-sut.jar", DIST) + copy(folder + "/em/external/rest/lovemining/target/lovemining-evomaster-runner.jar", DIST) copy(folder + "/cs/rest-gui/webgoat/target/webgoat-sut.jar", DIST) copy(folder + "/em/external/rest-gui/webgoat/target/webgoat-evomaster-runner.jar", DIST) diff --git a/scripts/dockerize/data/sut.csv b/scripts/dockerize/data/sut.csv index ddb63ea14..c1ddb0b99 100644 --- a/scripts/dockerize/data/sut.csv +++ b/scripts/dockerize/data/sut.csv @@ -40,3 +40,4 @@ jasper,TRUE,,--server.port=8080 --spring.profiles.active=api-docs --spring.datas redis-sample,TRUE,"",--server.port=8080 --spring.data.redis.host=db --spring.data.redis.port=6379 --spring.data.redis.password=53cret --spring.autoconfigure.exclude=org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration --app.numberOfRatings=5000 --app.ratingStars=5,http://localhost:8080/v3/api-docs,http://localhost:8080,FALSE,"","[{""image_name"": ""redis:7.4-alpine"", ""tmp_fs"": """", ""environment"": """", ""volume"": """", ""health_check_command"": """", ""command"": ""redis-server --requirepass 53cret""}]" arimaa,TRUE,,--server.port=8080 --spring.datasource.url=jdbc:mysql://db:3306/arimaadockermysqldb --spring.datasource.username=admin --spring.datasource.password=AdminPassword123! --spring.mongodb.uri=mongodb://mongodb:27017/arimaadb --spring.neo4j.uri=bolt://neo4j:7687 --spring.neo4j.authentication.username=neo4j --spring.neo4j.authentication.password=wfdNeo4jPass123,http://localhost:8080/v3/api-docs,http://localhost:8080,FALSE,db;mongodb;neo4j,"[{""image_name"": ""mysql:9.5.0"", ""tmp_fs"": """", ""environment"": ""MYSQL_DATABASE: arimaadockermysqldb;MYSQL_ROOT_PASSWORD: wfdRootPass123!"", ""volume"": ""../jdk_21_maven/cs/rest/arimaa/Database/mysql:/docker-entrypoint-initdb.d"", ""health_check_command"": ""mysqladmin ping -h localhost -u root -pwfdRootPass123! --silent""}, {""name"": ""mongodb"", ""image_name"": ""mongo:7"", ""tmp_fs"": """", ""environment"": """", ""volume"": ""./additional_files/arimaa/arimaa-mongo-seed.js:/docker-entrypoint-initdb.d/arimaa-mongo-seed.js"", ""health_check_command"": ""mongosh --quiet --eval \""db.adminCommand('ping')\""""}, {""name"": ""neo4j"", ""image_name"": ""neo4j:5.26"", ""tmp_fs"": """", ""environment"": ""NEO4J_AUTH: neo4j/wfdNeo4jPass123"", ""volume"": ""./additional_files/arimaa/arimaa-neo4j-seed.cypher:/seed.cypher"", ""health_check_command"": ""cypher-shell -u neo4j -p wfdNeo4jPass123 \""MATCH (:WfdSeedComplete) RETURN 1\"""", ""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']""}]" joinus,TRUE,,--server.port=8080 --spring.mongodb.uri=mongodb://db:27017/joinUs --spring.neo4j.uri=bolt://neo4j:7687 --spring.neo4j.authentication.username=neo4j --spring.neo4j.authentication.password=wfdNeo4jPass123 --logging.level.org.mongodb.driver=INFO --logging.level.org.neo4j.driver=INFO --logging.level.org.springframework.security=INFO,http://localhost:8080/v3/api-docs,http://localhost:8080,FALSE,db;neo4j,"[{""image_name"": ""mongo:7"", ""tmp_fs"": """", ""environment"": """", ""volume"": ""./additional_files/joinus/joinus-mongo-seed.js:/docker-entrypoint-initdb.d/joinus-mongo-seed.js"", ""health_check_command"": ""mongosh --quiet --eval \""db.adminCommand('ping')\""""}, {""name"": ""neo4j"", ""image_name"": ""neo4j:5.26"", ""tmp_fs"": """", ""environment"": ""NEO4J_AUTH: neo4j/wfdNeo4jPass123"", ""volume"": ""./additional_files/joinus/joinus-neo4j-seed.cypher:/seed.cypher"", ""health_check_command"": ""cypher-shell -u neo4j -p wfdNeo4jPass123 \""MATCH (:WfdSeedComplete) RETURN 1\"""", ""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']""}]" +lovemining,TRUE,,--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,http://localhost:8080/v3/api-docs,http://localhost:8080,FALSE,db;neo4j,"[{""image_name"": ""mongo:7"", ""tmp_fs"": """", ""environment"": """", ""volume"": ""./additional_files/lovemining/lovemining-mongo-seed.js:/docker-entrypoint-initdb.d/lovemining-mongo-seed.js"", ""health_check_command"": ""mongosh --quiet --eval \""db.adminCommand('ping')\""""}, {""name"": ""neo4j"", ""image_name"": ""neo4j:5.26"", ""tmp_fs"": """", ""environment"": ""NEO4J_AUTH: neo4j/wfdNeo4jPass123"", ""volume"": ""./additional_files/lovemining/lovemining-neo4j-seed.cypher:/seed.cypher"", ""health_check_command"": ""cypher-shell -u neo4j -p wfdNeo4jPass123 \""MATCH (:WfdSeedComplete) RETURN 1\"""", ""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']""}]" diff --git a/statistics/data.csv b/statistics/data.csv index 42149c14d..f8fedf045 100644 --- a/statistics/data.csv +++ b/statistics/data.csv @@ -50,6 +50,7 @@ TRUE,jasper,REST,Java,JDK 25,Maven,337,46791,PostgreSQL,MIT,61,TRUE,https://gith TRUE,joinus,REST,Java,JDK 21,Maven,101,7906,MongoDB;Neo4j,UNDEFINED,37,TRUE,https://github.com/KLAJDI16/joinUs TRUE,redis-sample,REST,Java,JDK 21,Maven,18,1006,Redis,UNDEFINED,4,FALSE,https://github.com/hendisantika/spring-boot-redis-sample TRUE,arimaa,REST,Java,JDK 21,Maven,141,9460,MySQL;MongoDB;Neo4j,UNDEFINED,38,TRUE,https://github.com/KEAArimaaProject/arimaa-backend +TRUE,lovemining,REST,Java,JDK 21,Maven,25,1793,MongoDB;Neo4j,UNDEFINED,18,TRUE,https://github.com/Andryd22/LoveMining FALSE,ind0,REST,Java,JDK 8,Gradle,103,17039,PostgreSQL,Proprietary,20,FALSE,UNDEFINED FALSE,ind1,REST,Java;Kotlin,JDK 11,Gradle,163,15240,PostgreSQL,Proprietary,53,FALSE,UNDEFINED diff --git a/statistics/table_emb.md b/statistics/table_emb.md index db7eb6fec..dfcdfe36c 100644 --- a/statistics/table_emb.md +++ b/statistics/table_emb.md @@ -23,6 +23,7 @@ |REST|__jasper__|46791|337|61|Java|JDK 25|Maven|PostgreSQL|✓| |REST|__joinus__|7906|101|37|Java|JDK 21|Maven|MongoDB, Neo4j|✓| |REST|__languagetool__|174781|1385|2|Java|JDK 8|Maven||| +|REST|__lovemining__|1793|25|18|Java|JDK 21|Maven|MongoDB, Neo4j|✓| |REST|__market__|9861|124|13|Java|JDK 11|Maven|H2|✓| |REST|__microcks__|66186|471|88|Java|JDK 21|Maven|MongoDB|✓| |REST|__ocvn__|45521|526|258|Java|JDK 8|Maven|H2, MongoDB|✓|