diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java index 3038e4ceb4..95a1ee6017 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/ControllerConstants.java @@ -31,4 +31,6 @@ public class ControllerConstants { public static final String MONGO_INSERTION = "/mongoInsertion"; public static final String POST_SEARCH_ACTION = "/postSearchAction"; + + public static final String DERIVE_PARAMS = "/deriveParams"; } diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/RestProblemDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/RestProblemDto.java index 52b89dcb37..fba150597a 100644 --- a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/RestProblemDto.java +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/RestProblemDto.java @@ -1,5 +1,7 @@ package org.evomaster.client.java.controller.api.dto.problem; +import org.evomaster.client.java.controller.api.dto.problem.param.RestDerivedParamDto; + import java.util.List; /** @@ -25,4 +27,7 @@ public class RestProblemDto extends ProblemInfoDto{ * should not be set */ public String openApiSchema; + + + public List derivedParams; } diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/DeriveParamResponseDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/DeriveParamResponseDto.java new file mode 100644 index 0000000000..10c13b8efa --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/DeriveParamResponseDto.java @@ -0,0 +1,10 @@ +package org.evomaster.client.java.controller.api.dto.problem.param; + +public class DeriveParamResponseDto { + + public String paramName; + + public String paramValue; + + public Integer actionIndex; +} diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/DerivedParamChangeReqDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/DerivedParamChangeReqDto.java new file mode 100644 index 0000000000..e4ba465982 --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/DerivedParamChangeReqDto.java @@ -0,0 +1,12 @@ +package org.evomaster.client.java.controller.api.dto.problem.param; + +public class DerivedParamChangeReqDto { + + public String paramName; + + public String jsonData; + + public String entryPoint; + + public Integer actionIndex; +} diff --git a/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/RestDerivedParamDto.java b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/RestDerivedParamDto.java new file mode 100644 index 0000000000..bf58e9d915 --- /dev/null +++ b/client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/problem/param/RestDerivedParamDto.java @@ -0,0 +1,35 @@ +package org.evomaster.client.java.controller.api.dto.problem.param; + +import java.util.Set; + +public class RestDerivedParamDto { + + /** + * The name of the parameter + */ + public String paramName; + + /** + * The context in which this parameter is used, eg, whether it is a body payload or a query parameter. + * This information is needed, as EM will need to determine which other values to use to derive this param. + */ + public String context; + + /** + * In case the parameter is used differently in different endpoints, specify for which endpoints this + * derivation applies. If left null, it will apply to all endpoints where this param is present. + */ + public Set endpointPaths; + + /** + * Optional positive integer specifying in which order the updates are done. + * Left empty is equivalent to set it to 0. + * + * If all updates are independent (or there is only 1), then there is no point in specifying this value. + * However, if the derivation of A depends on first deriving B, then A should get an higher order than B, + * eg 1 vs 0. + * In this case, first B is computed based on current state, and then, A is computed with current state + * updated with derived B. + */ + public Integer order; +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java index 2f00b448ef..7f01502172 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java @@ -8,6 +8,9 @@ import org.evomaster.client.java.controller.api.dto.database.operations.MongoDatabaseCommandDto; import org.evomaster.client.java.controller.api.dto.database.operations.MongoInsertionResultsDto; import org.evomaster.client.java.controller.api.dto.problem.*; +import org.evomaster.client.java.controller.api.dto.problem.param.DeriveParamResponseDto; +import org.evomaster.client.java.controller.api.dto.problem.param.DerivedParamChangeReqDto; +import org.evomaster.client.java.controller.api.dto.problem.param.RestDerivedParamDto; import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsDto; import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsResult; import org.evomaster.client.java.controller.mongo.MongoScriptRunner; @@ -206,6 +209,14 @@ public Response getSutInfo(@Context HttpServletRequest httpServletRequest) { dto.restProblem.endpointsToSkip = rp.getEndpointsToSkip(); dto.restProblem.openApiSchema = rp.getOpenApiSchema(); dto.restProblem.servicesToNotMock = servicesToNotMock; + dto.restProblem.derivedParams = rp.getDerivedParams().stream() + .map(p -> new RestDerivedParamDto(){{ + paramName = p.paramName; + context = p.context.toString(); + endpointPaths = p.endpointPaths; + order = p.order; + }}) + .collect(Collectors.toList()); } else if (info instanceof GraphQlProblem) { GraphQlProblem p = (GraphQlProblem) info; @@ -640,6 +651,42 @@ public Response scheduleTasksCommand( return Response.status(200).entity(WrappedResponseDto.withData(responseDto)).build(); } + + @Path(ControllerConstants.DERIVE_PARAMS) + @Consumes(MediaType.APPLICATION_JSON) + @POST + public Response deriveParams(List dtos){ + + List results = new ArrayList<>(); + List errors = new ArrayList<>(); + + for(DerivedParamChangeReqDto dto : dtos){ + noKillSwitch(() -> { + try { + String value = sutController.deriveObjectParameterData(dto.paramName, dto.jsonData, dto.entryPoint); + DeriveParamResponseDto response = new DeriveParamResponseDto(); + response.paramName = dto.paramName; + response.paramValue = value; + response.actionIndex = dto.actionIndex; + results.add(response); + } catch (Exception e) { + String msg = "ERROR: failed to derived object parameter for '" + + dto.paramName +"': " + e.getMessage(); + SimpleLogger.error(msg, e); + errors.add(msg); + } + }); + } + + if(!errors.isEmpty()){ + String msg = "There were " + errors.size() + " errors.\n"; + msg += String.join("\n", errors); + return Response.status(500).entity(WrappedResponseDto.withError(msg)).build(); + } + + return Response.status(200).entity(WrappedResponseDto.withData(results)).build(); + } + @Path(ControllerConstants.NEW_ACTION) @Consumes(MediaType.APPLICATION_JSON) @PUT diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java index fdd871c5a2..6735362aeb 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java @@ -241,6 +241,20 @@ public final void setControllerHost(String controllerHost) { this.controllerHost = controllerHost; } + + /** + * Function used to derived params from the other values in the same object. + * @param paramName the name of the parameter we need to derive + * @param jsonObject JSON representation of full object evolved in EM. You might need just a subset to derive needed param + * @param endpointPath the path of endpoint for this object, in case need to distinguish between same params in different endpoints + * @return a string representation of derived value for paramName + * @throws Exception + */ + public String deriveObjectParameterData(String paramName, String jsonObject, String endpointPath) throws Exception{ + throw new IllegalStateException("You must override deriveObjectParameterData method if you use derived param data"); + } + + @Override public InsertionResultsDto execInsertionsIntoDatabase(List insertions, InsertionResultsDto... previous) { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/RestProblem.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/RestProblem.java index f2bf88cc02..16176f0ad5 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/RestProblem.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/RestProblem.java @@ -1,5 +1,7 @@ package org.evomaster.client.java.controller.problem; +import org.evomaster.client.java.controller.problem.param.RestDerivedParam; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -15,10 +17,19 @@ public class RestProblem extends ProblemInfo{ private final String openApiSchema; + private final List derivedParams; + public RestProblem(String openApiUrl, List endpointsToSkip) { this(openApiUrl, endpointsToSkip, null); } + public RestProblem( + String openApiUrl, + List endpointsToSkip, + String openApiSchema + ){ + this(openApiUrl, endpointsToSkip, openApiSchema, null, null); + } /** * @@ -31,13 +42,28 @@ public RestProblem(String openApiUrl, List endpointsToSkip) { * @param openApiSchema the actual schema, as a string. Note, if this specified, then * openApiUrl must be null */ - public RestProblem(String openApiUrl, List endpointsToSkip, String openApiSchema) { + public RestProblem( + String openApiUrl, + List endpointsToSkip, + String openApiSchema, + List servicesToNotMock, + List derivedParams + ) { this.openApiUrl = openApiUrl; this.endpointsToSkip = endpointsToSkip == null - ? new ArrayList<>() - : new ArrayList<>(endpointsToSkip); + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList<>(endpointsToSkip)); this.openApiSchema = openApiSchema; + this.servicesToNotMock.clear(); + if(servicesToNotMock != null && !servicesToNotMock.isEmpty()) { + this.servicesToNotMock.addAll(servicesToNotMock); + } + + this.derivedParams = derivedParams == null + ? Collections.emptyList() + : Collections.unmodifiableList(new ArrayList<>(derivedParams)); + boolean url = openApiUrl != null && !openApiUrl.isEmpty(); boolean schema = openApiSchema != null && !openApiSchema.isEmpty(); @@ -49,22 +75,29 @@ public RestProblem(String openApiUrl, List endpointsToSkip, String openA } } + + @Override + public RestProblem withServicesToNotMock(List servicesToNotMock){ + return new RestProblem(openApiUrl, endpointsToSkip,openApiSchema,servicesToNotMock,derivedParams); + } + + public RestProblem withDerivedParams(List derivedParams){ + return new RestProblem(openApiUrl, endpointsToSkip,openApiSchema,servicesToNotMock,derivedParams); + } + public String getOpenApiUrl() { return openApiUrl; } public List getEndpointsToSkip() { - return Collections.unmodifiableList(endpointsToSkip); + return endpointsToSkip; } public String getOpenApiSchema() { return openApiSchema; } - @Override - public RestProblem withServicesToNotMock(List servicesToNotMock){ - RestProblem p = new RestProblem(this.openApiUrl, this.endpointsToSkip, this.openApiSchema); - p.servicesToNotMock.addAll(servicesToNotMock); - return p; + public List getDerivedParams() { + return derivedParams; } } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/param/DerivedParamContext.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/param/DerivedParamContext.java new file mode 100644 index 0000000000..1ce6aecd6f --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/param/DerivedParamContext.java @@ -0,0 +1,6 @@ +package org.evomaster.client.java.controller.problem.param; + +public enum DerivedParamContext { + + BODY_PAYLOAD +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/param/RestDerivedParam.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/param/RestDerivedParam.java new file mode 100644 index 0000000000..5e7cd634b8 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/problem/param/RestDerivedParam.java @@ -0,0 +1,50 @@ +package org.evomaster.client.java.controller.problem.param; + +import java.util.Objects; +import java.util.Set; + +public class RestDerivedParam { + + /** + * The name of the parameter + */ + public final String paramName; + + /** + * The context in which this parameter is used, eg, whether it is a body payload or a query parameter. + * This information is needed, as EM will need to determine which other values to use to derive this param. + */ + public final DerivedParamContext context; + + /** + * In case the parameter is used differently in different endpoints, specify for which endpoints this + * derivation applies. If left null, it will apply to all endpoints where this param is present. + */ + public final Set endpointPaths; + + /** + * Positive integer specifying in which order the updates are done, starting from lowest value, incrementally. + * If all updates are independent (or there is only 1), this value can be left to 0. + * However, if the derivation of A depends on first deriving B, then A should get an higher order than B, + * eg 1 vs 0. + * In this case, first B is computed based on current state, and then, A is computed with current state + * updated with derived B. + */ + public final Integer order; + + public RestDerivedParam( + String paramName, + DerivedParamContext context, + Set endpointPaths, + Integer order + ) { + this.paramName = Objects.requireNonNull(paramName); + this.context = Objects.requireNonNull(context); + this.endpointPaths = endpointPaths; + this.order = order; + + if(this.order < 0){ + throw new IllegalArgumentException("order must be positive: " + order); + } + } +} diff --git a/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt b/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt index 8177811eb0..a438da7711 100644 --- a/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt +++ b/core-it/src/test/kotlin/org/evomaster/core/problem/rest/SamplerVerifierTest.kt @@ -8,6 +8,8 @@ import com.netflix.governator.guice.LifecycleInjector import org.evomaster.client.java.controller.api.dto.* import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.client.java.controller.api.dto.problem.RestProblemDto +import org.evomaster.client.java.controller.api.dto.problem.param.DeriveParamResponseDto +import org.evomaster.client.java.controller.api.dto.problem.param.DerivedParamChangeReqDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsResult import org.evomaster.core.BaseModule @@ -454,6 +456,10 @@ class SamplerVerifierTest { override fun close() { } + override fun deriveParams(deriveParams: List): List { + return listOf() + } + override fun invokeScheduleTasksAndGetResults(dtos: ScheduleTaskInvocationsDto): ScheduleTaskInvocationsResult? { return null } diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 0495dc441d..6bdab7fe8b 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -2004,7 +2004,7 @@ class EMConfig { @Max(stringLengthHardLimit.toDouble()) @Cfg("The maximum length allowed for evolved strings. Without this limit, strings could in theory be" + " billions of characters long") - var maxLengthForStrings = 200 + var maxLengthForStrings = 1024 @Min(0.0) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamChangeReq.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamChangeReq.kt new file mode 100644 index 0000000000..7b9d99a084 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamChangeReq.kt @@ -0,0 +1,13 @@ +package org.evomaster.core.problem.enterprise.param + +class DerivedParamChangeReq( + + val paramName: String, + + val jsonData: String, + + val entryPoint: String, + + val actionIndex: Int +) + diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamHandler.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamHandler.kt new file mode 100644 index 0000000000..e004aa7a13 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamHandler.kt @@ -0,0 +1,119 @@ +package org.evomaster.core.problem.enterprise.param + +import org.evomaster.client.java.controller.api.dto.problem.param.RestDerivedParamDto +import org.evomaster.core.problem.rest.data.RestCallAction +import org.evomaster.core.problem.rest.data.RestIndividual +import org.evomaster.core.problem.rest.param.BodyParam +import org.evomaster.core.remote.SutProblemException +import org.evomaster.core.search.Individual +import org.evomaster.core.search.gene.ObjectGene +import org.evomaster.core.search.gene.utils.GeneUtils + +class DerivedParamHandler { + + /** + * ParamName -> info + * + */ + private val bodyParams: MutableMap = mutableMapOf() + + fun initialize(derivedParams: List){ + + if(derivedParams.isEmpty()){ + return + } + + bodyParams.clear() + + for(param in derivedParams){ + + // TODO param.context + val key = param.paramName + if(bodyParams.containsKey(key)){ + throw SutProblemException("Duplicate derived param name '$key' for the same context") + } + val entryPoints = mutableSetOf() + if(param.endpointPaths != null){ + entryPoints.addAll(param.endpointPaths) + } + val info = DerivedParamInfo(key,entryPoints,param.order) + bodyParams[key] = info + } + } + + fun getOrderLevels() : List{ + return bodyParams.map { it.value.order } + .toSet() + .sorted() + } + + fun prepareRequest(ind: Individual, orderLevel: Int) : List{ + + if(ind !is RestIndividual){ + return emptyList() + } + + val req = mutableListOf() + + val actions = ind.seeMainExecutableActions() + for(i in actions.indices){ + val a = actions[i] + val body = a.parameters.filterIsInstance() + if(body.isEmpty()){ + continue + } + val obj = body[0].primaryGene() + .getWrappedGene(ObjectGene::class.java) + ?: continue + for(f in obj.fields){ + if(!bodyParams.containsKey(f.name)){ + continue + } + val info = bodyParams[f.name]!! + if(info.order != orderLevel){ + continue + } + val entryPoints = info.entryPoints + if(entryPoints.isEmpty() || entryPoints.contains(a.path.toString())){ + val json = obj.getValueAsPrintableString(targetFormat = null, mode = GeneUtils.EscapeMode.JSON) + req.add(DerivedParamChangeReq(f.name,json,a.path.toString(),i)) + } + } + } + + return req + } + + fun modifyParam(ind: Individual, paramName: String, paramValue: String, actionIndex: Int){ + + if(ind !is RestIndividual){ + return + } + + val actions = ind.seeMainExecutableActions() + if(actionIndex < 0 || actionIndex > actions.size - 1){ + throw IllegalArgumentException("Invalid action index: $actionIndex") + } + + val error = "Failed applying param derivation for '$paramName'." + + val a = actions[actionIndex] + + //TODO context + val body = a.parameters.filterIsInstance() + if(body.isEmpty()){ + throw IllegalArgumentException("$error No body defined for ${a.getName()}") + } + val primary = body[0].primaryGene() + val obj = primary.getWrappedGene(ObjectGene::class.java) + ?: throw IllegalArgumentException("$error No object definition for body") + if(!obj.staticCheckIfImpactPhenotype()){ + //in case body payload is not required + return + } + val target = obj.fields.find { it.name == paramName } + ?: throw IllegalArgumentException("$error Not found field '$paramName'") + + target.setFromStringValue(paramValue) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamInfo.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamInfo.kt new file mode 100644 index 0000000000..bc008b97d7 --- /dev/null +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/param/DerivedParamInfo.kt @@ -0,0 +1,7 @@ +package org.evomaster.core.problem.enterprise.param + +class DerivedParamInfo( + val paramName: String, + val entryPoints: Set, + val order: Int +) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt index 04e3dd581a..dfc452a2e1 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/enterprise/service/EnterpriseSampler.kt @@ -2,15 +2,18 @@ package org.evomaster.core.problem.enterprise.service import com.google.inject.Inject import org.evomaster.client.java.controller.api.dto.SutInfoDto -import org.evomaster.core.sql.SqlAction -import org.evomaster.core.sql.SqlInsertBuilder +import org.evomaster.client.java.controller.api.dto.problem.param.DerivedParamChangeReqDto +import org.evomaster.client.java.controller.api.dto.problem.param.RestDerivedParamDto import org.evomaster.core.mongo.MongoDbAction import org.evomaster.core.mongo.MongoInsertBuilder import org.evomaster.core.output.OutputFormat +import org.evomaster.core.problem.enterprise.param.DerivedParamHandler import org.evomaster.core.remote.SutProblemException import org.evomaster.core.remote.service.RemoteController import org.evomaster.core.search.Individual import org.evomaster.core.search.service.Sampler +import org.evomaster.core.sql.SqlAction +import org.evomaster.core.sql.SqlInsertBuilder import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -23,6 +26,7 @@ abstract class EnterpriseSampler : Sampler() where T : Individual { @Inject(optional = true) protected lateinit var rc: RemoteController + protected val derivedParamHandler = DerivedParamHandler() var sqlInsertBuilder: SqlInsertBuilder? = null protected set @@ -31,6 +35,41 @@ abstract class EnterpriseSampler : Sampler() where T : Individual { protected set + override fun applyDerivedParamModifications(ind: T) { + + val levels = derivedParamHandler.getOrderLevels() + + for(level in levels) { + val req = derivedParamHandler.prepareRequest(ind, level) + if (req.isEmpty()) { + continue + } + val dto = req.map { + DerivedParamChangeReqDto() + .apply { + paramName = it.paramName + jsonData = it.jsonData + entryPoint = it.entryPoint + actionIndex = it.actionIndex + } + } + + val response = rc.deriveParams(dto) + if (response.size != req.size) { + log.warn("Retrieved only ${response.size} derived params from ${req.size} requested") + } + + for (res in response) { + derivedParamHandler.modifyParam(ind, res.paramName, res.paramValue, res.actionIndex) + } + } + } + + fun initializeDerivedParamRules(derivedParams: List){ + + derivedParamHandler.initialize(derivedParams) + } + protected fun updateConfigBasedOnSutInfoDto(infoDto: SutInfoDto) { if (config.outputFormat == OutputFormat.DEFAULT) { diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/sampler/AbstractRestSampler.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/sampler/AbstractRestSampler.kt index deffbf9424..7685b75494 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/sampler/AbstractRestSampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/sampler/AbstractRestSampler.kt @@ -88,7 +88,7 @@ abstract class AbstractRestSampler : HttpWsSampler() { ?: throw SutProblemException("Failed to retrieve the info about the system under test") val problem = infoDto.restProblem - ?: throw java.lang.IllegalStateException("Missing problem definition object") + ?: throw SutProblemException("Missing problem definition object") val openApiURL = problem.openApiUrl val openApiSchema = problem.openApiSchema @@ -121,6 +121,9 @@ abstract class AbstractRestSampler : HttpWsSampler() { addExtraHeader(actionCluster) } + if(problem.derivedParams != null) { + initializeDerivedParamRules(problem.derivedParams) + } initSqlInfo(infoDto) @@ -142,8 +145,6 @@ abstract class AbstractRestSampler : HttpWsSampler() { updateConfigBasedOnSutInfoDto(infoDto) - //partialOracles.setupForRest(swagger, config) - log.debug("Done initializing {}", AbstractRestSampler::class.simpleName) } diff --git a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteController.kt b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteController.kt index efc4ee0f8d..946bcaaf90 100644 --- a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteController.kt +++ b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteController.kt @@ -1,6 +1,9 @@ package org.evomaster.core.remote.service import org.evomaster.client.java.controller.api.dto.* +import org.evomaster.client.java.controller.api.dto.problem.param.DeriveParamResponseDto +import org.evomaster.client.java.controller.api.dto.problem.param.DerivedParamChangeReqDto +import org.evomaster.core.problem.enterprise.param.DerivedParamChangeReq import org.evomaster.core.scheduletask.ScheduleTaskExecutor import org.evomaster.core.sql.DatabaseExecutor @@ -56,4 +59,6 @@ interface RemoteController : DatabaseExecutor, ScheduleTaskExecutor { fun address() : String fun close() + + fun deriveParams(deriveParams: List) : List } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt index 2ba6c89e25..4d81a70f22 100644 --- a/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt +++ b/core/src/main/kotlin/org/evomaster/core/remote/service/RemoteControllerImplementation.kt @@ -5,6 +5,8 @@ import com.google.inject.Inject import org.evomaster.client.java.controller.api.ControllerConstants import org.evomaster.client.java.controller.api.dto.* import org.evomaster.client.java.controller.api.dto.database.operations.* +import org.evomaster.client.java.controller.api.dto.problem.param.DeriveParamResponseDto +import org.evomaster.client.java.controller.api.dto.problem.param.DerivedParamChangeReqDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsResult @@ -366,6 +368,23 @@ class RemoteControllerImplementation() : RemoteController{ return getData(dto) } + override fun deriveParams(deriveParams: List) : List{ + val response = makeHttpCall { + getWebTarget() + .path(ControllerConstants.DERIVE_PARAMS) + .request() + .post(Entity.entity(deriveParams, MediaType.APPLICATION_JSON_TYPE)) + } + + val dto = getDtoFromResponse(response, object : GenericType>>() {}) + + if (!checkResponse(response, dto, "Failed to execute call to fetch derived params")) { + return listOf() + } + + return dto?.data ?: listOf() + } + /** * execute [actionDto] through [ControllerConstants.NEW_ACTION] endpoints of EMController, diff --git a/core/src/main/kotlin/org/evomaster/core/search/service/Sampler.kt b/core/src/main/kotlin/org/evomaster/core/search/service/Sampler.kt index 6f8de84dd6..3f5bd7e708 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/service/Sampler.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/service/Sampler.kt @@ -133,6 +133,12 @@ abstract class Sampler : TrackOperator where T : Individual { .forEach { it.isActive = on } } } + + applyDerivedParamModifications(ind) + } + + open fun applyDerivedParamModifications(ind: T){ + // needs to be overridden } /** diff --git a/core/src/main/kotlin/org/evomaster/core/search/service/mutator/StandardMutator.kt b/core/src/main/kotlin/org/evomaster/core/search/service/mutator/StandardMutator.kt index f80ca4944d..ab6e53ac69 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/service/mutator/StandardMutator.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/service/mutator/StandardMutator.kt @@ -1,5 +1,6 @@ package org.evomaster.core.search.service.mutator +import com.google.inject.Inject import org.evomaster.core.EMConfig import org.evomaster.core.EMConfig.GeneMutationStrategy.ONE_OVER_N import org.evomaster.core.EMConfig.GeneMutationStrategy.ONE_OVER_N_BIASED_SQL @@ -24,6 +25,7 @@ import org.evomaster.core.search.gene.optional.CustomMutationRateGene import org.evomaster.core.search.gene.optional.OptionalGene import org.evomaster.core.search.gene.utils.GeneUtils import org.evomaster.core.search.impact.impactinfocollection.ImpactUtils +import org.evomaster.core.search.service.Sampler import org.evomaster.core.search.service.mutator.genemutation.AdditionalGeneMutationInfo import org.evomaster.core.search.service.mutator.genemutation.EvaluatedInfo import org.evomaster.core.search.service.mutator.genemutation.SubsetGeneMutationSelectionStrategy @@ -43,6 +45,9 @@ open class StandardMutator : Mutator() where T : Individual { private val log: Logger = LoggerFactory.getLogger(StandardMutator::class.java) } + @Inject + protected lateinit var sampler : Sampler + override fun doesStructureMutation(evaluatedIndividual: EvaluatedIndividual): Boolean { if(!config.enableStructureMutation) return false @@ -177,6 +182,11 @@ open class StandardMutator : Mutator() where T : Individual { return toMutate } + private fun mutationPostProcessing(individual: T) { + sampler + } + + private fun mutationPreProcessing(individual: T) { val applyEvolve = config.taintAnalysisForMapsAndArrays @@ -286,6 +296,8 @@ open class StandardMutator : Mutator() where T : Individual { } + mutationPostProcessing(copy) + if (config.trackingEnabled()) tag(copy, time.evaluatedIndividuals) return copy } diff --git a/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt b/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt index 016b4ce7da..814e83b777 100644 --- a/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt +++ b/core/src/test/kotlin/org/evomaster/core/problem/external/service/DummyController.kt @@ -2,6 +2,8 @@ package org.evomaster.core.problem.external.service import org.evomaster.client.java.controller.api.dto.* import org.evomaster.client.java.controller.api.dto.database.operations.* +import org.evomaster.client.java.controller.api.dto.problem.param.DeriveParamResponseDto +import org.evomaster.client.java.controller.api.dto.problem.param.DerivedParamChangeReqDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsResult import org.evomaster.core.remote.service.RemoteController @@ -61,6 +63,10 @@ class DummyController: RemoteController { TODO("Not yet implemented") } + override fun deriveParams(deriveParams: List): List { + TODO("Not yet implemented") + } + override fun executeDatabaseCommand(dto: DatabaseCommandDto): Boolean { TODO("Not yet implemented") } diff --git a/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt b/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt index 8589f0cb5e..377d789c56 100644 --- a/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt +++ b/core/src/test/kotlin/org/evomaster/core/problem/rest/individual/RestIndividualTestBase.kt @@ -21,6 +21,8 @@ import org.evomaster.client.java.controller.api.dto.* import org.evomaster.client.java.controller.api.dto.database.execution.SqlExecutionsDto import org.evomaster.client.java.controller.api.dto.database.operations.* import org.evomaster.client.java.controller.api.dto.problem.RestProblemDto +import org.evomaster.client.java.controller.api.dto.problem.param.DeriveParamResponseDto +import org.evomaster.client.java.controller.api.dto.problem.param.DerivedParamChangeReqDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsDto import org.evomaster.client.java.controller.api.dto.problem.rpc.ScheduleTaskInvocationsResult import org.evomaster.client.java.sql.SqlScriptRunner @@ -639,6 +641,10 @@ abstract class RestIndividualTestBase { } + override fun deriveParams(deriveParams: List): List { + return listOf() + } + override fun postSearchAction(postSearchActionDto: PostSearchActionDto): Boolean { return true } diff --git a/docs/options.md b/docs/options.md index 03dba10515..fba9a0e1be 100644 --- a/docs/options.md +++ b/docs/options.md @@ -142,7 +142,7 @@ There are 3 types of options: |`maxAssertionForDataInCollection`| __Int__. Specify a maximum number of data in a collection to be asserted in the generated tests. Note that zero means that only the size of the collection will be asserted. A negative value means all data in the collection will be asserted (i.e., no limit). *Default value*: `3`.| |`maxEvaluations`| __Int__. Maximum number of action or individual evaluations (depending on chosen stopping criterion) for the search. A fitness evaluation can be composed of 1 or more actions, like for example REST calls or SQL setups. The more actions are allowed, the better results one can expect. But then of course the test generation will take longer. Only applicable depending on the stopping criterion. *Constraints*: `min=1.0`. *Default value*: `1000`.| |`maxLengthForCommentLine`| __Int__. Max length for test comments. Needed when enumerating some names/values, making comments too long to be on a single line. *Constraints*: `min=1.0`. *Default value*: `80`.| -|`maxLengthForStrings`| __Int__. The maximum length allowed for evolved strings. Without this limit, strings could in theory be billions of characters long. *Constraints*: `min=0.0, max=20000.0`. *Default value*: `200`.| +|`maxLengthForStrings`| __Int__. The maximum length allowed for evolved strings. Without this limit, strings could in theory be billions of characters long. *Constraints*: `min=0.0, max=20000.0`. *Default value*: `1024`.| |`maxLengthForStringsAtSamplingTime`| __Int__. Maximum length when sampling a new random string. Such limit can be bypassed when a string is mutated. *Constraints*: `min=0.0`. *Default value*: `16`.| |`maxLengthOfTraces`| __Int__. Specify a maxLength of tracking when enableTrackIndividual or enableTrackEvaluatedIndividual is true. Note that the value should be specified with a non-negative number or -1 (for tracking all history). *Constraints*: `min=-1.0`. *Default value*: `10`.| |`maxResponseByteSize`| __Int__. Maximum size (in bytes) that EM handles response payloads in the HTTP responses. If larger than that, a response will not be stored internally in EM during the test generation. This is needed to avoid running out of memory. *Default value*: `1000000`.| diff --git a/e2e-tests/pom.xml b/e2e-tests/pom.xml index 42926ccdbd..3f4af5be0c 100644 --- a/e2e-tests/pom.xml +++ b/e2e-tests/pom.xml @@ -31,8 +31,7 @@ spring-graphql-bb emb-json spring-rest-multidb - - + spring-rest-rsa diff --git a/e2e-tests/spring-rest-rsa/pom.xml b/e2e-tests/spring-rest-rsa/pom.xml index c7b81ddb1b..0c75835481 100644 --- a/e2e-tests/spring-rest-rsa/pom.xml +++ b/e2e-tests/spring-rest-rsa/pom.xml @@ -13,6 +13,13 @@ + + + org.yaml + snakeyaml + 1.33 + + org.springframework.boot spring-boot-starter-web @@ -68,6 +75,40 @@ test + + + javax.validation + validation-api + 2.0.1.Final + + + + + javax.ws.rs + javax.ws.rs-api + + + org.evomaster + evomaster-e2e-tests-utils + test-jar + + + org.evomaster + evomaster-client-java-controller + + + org.evomaster + evomaster-core + test + + + + org.evomaster + evomaster-client-java-dependencies + pom + test + + \ No newline at end of file diff --git a/e2e-tests/spring-rest-rsa/src/main/java/com/example/demo/controller/DemoController.java b/e2e-tests/spring-rest-rsa/src/main/java/com/example/demo/controller/DemoController.java index e38309fdfc..2a2865bcae 100644 --- a/e2e-tests/spring-rest-rsa/src/main/java/com/example/demo/controller/DemoController.java +++ b/e2e-tests/spring-rest-rsa/src/main/java/com/example/demo/controller/DemoController.java @@ -6,6 +6,7 @@ import com.example.demo.vo.BindCardResp; import com.example.demo.vo.CommonReq; import com.example.demo.vo.CommonResp; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -26,14 +27,17 @@ public class DemoController { public static final String OTHER_PARTY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvCZFud5gXvHX2NO8VeDqvzoaOz1fGK2I5pWu8F3YSawkj3+g5Pk2aCrdKy7R1ZpKrFAxD19C96OdTVP564By/9GcuEO9vm1j2wKzyltk3Vz3HgEbWEvon5SoAyULhx2p90IOkNMWNn5YyNPsw/LU5QX0GuNl0NPrVlQl2APKqH6vU3m4FsWunMluuSegnoPxBFn9yC3c7xOpsGPjf9zOFoGkhOJvXA/y4V25vNR+QQZkNU4C0CndaGF6eAjL5rcaV03vWH3TxoDPBBdVIc4cwKlogZoLeA7J+umCk2V7+EQoa/zTuaxY5wcx8eWN/kbissVpis1CZi8tmSdqtsG3+QIDAQAB"; @PostMapping("/bind_card_apply") - public CommonResp bindCardApply(@RequestBody CommonReq req) throws Exception { + //public CommonResp bindCardApply(@RequestBody CommonReq req) throws Exception { + public ResponseEntity bindCardApply(@RequestBody CommonReq req) throws Exception { // verify the signature String signContent = req.signText(); String publicKey = OTHER_PARTY_PUBLIC_KEY; boolean checkSign = CryptoUtil.verify(signContent, req.getSign(), CryptoUtil.getPublicKey(publicKey)); if (!checkSign) { System.out.println("------ERROR! Invalid sign for the req:{}" + JSONObject.toJSONString(req)); - return handle(CommonResp.of("ERROR", "invalid signature", null)); + return ResponseEntity.status(400).body( + handle(CommonResp.of("ERROR", "invalid signature", null)) + ); } // decrypt the biz data with your private key String bizData = null; @@ -43,14 +47,18 @@ public CommonResp bindCardApply(@RequestBody CommonReq applicationClass; + + private static final String aesKey = RandomStringUtils.randomAlphanumeric(16); + + public EmController() { + super.setControllerPort(0); + this.applicationClass = MySpringBootApplication.class; + } + + + @Override + public String deriveObjectParameterData(String paramName, String jsonObject, String endpointPath) throws Exception { + + if(paramName.equals("sign")){ + CommonReq req = JSONObject.parseObject(jsonObject, CommonReq.class); + String signText = req.signText(); + return CryptoUtil.sign(signText, DemoController.OTHER_PARTY_PRIVATE_KEY); + } + + if(paramName.equals("key")){ + return CryptoUtil.encryptByPublicKey(aesKey, DemoController.YOUR_PUBLIC_KEY); + } + + if(paramName.equals("data")){ + CommonReq req = JSON.parseObject(jsonObject, new TypeReference>() {}); + return CryptoUtil.encrypt(JSONObject.toJSONString(req.getBizData()), aesKey); + } + + throw new IllegalArgumentException("Unrecognized parameter: " + paramName); + } + + + @Override + public ProblemInfo getProblemInfo() { + return new RestProblem( + "http://localhost:" + getSutPort() + "/v3/api-docs", + null + ).withDerivedParams(Arrays.asList( + new RestDerivedParam("key", DerivedParamContext.BODY_PAYLOAD, null, 0), + new RestDerivedParam("data", DerivedParamContext.BODY_PAYLOAD, null, 0), + new RestDerivedParam("sign", DerivedParamContext.BODY_PAYLOAD, null, 1) + )); + } + + @Override + public String startSut() { + + ctx = SpringApplication.run(applicationClass, "--server.port=0"); + + + return "http://localhost:" + getSutPort(); + } + + 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() { + ctx.stop(); + ctx.close(); + } + + @Override + public String getPackagePrefixesToCover() { + return "com.example."; + } + + @Override + public void resetStateOfSUT() { + //nothing to do + } + + + + @Override + public List getInfoForAuthentication() { + return null; + } + + @Override + public List getDbSpecifications() { + return null; + } + + + + @Override + public SutInfoDto.OutputFormat getPreferredOutputFormat() { + return SutInfoDto.OutputFormat.JAVA_JUNIT_5; + } + +} diff --git a/e2e-tests/spring-rest-rsa/src/test/java/com/example/demo/controller/EmControllerTest.java b/e2e-tests/spring-rest-rsa/src/test/java/com/example/demo/controller/EmControllerTest.java new file mode 100644 index 0000000000..2e1849e8d7 --- /dev/null +++ b/e2e-tests/spring-rest-rsa/src/test/java/com/example/demo/controller/EmControllerTest.java @@ -0,0 +1,72 @@ +package com.example.demo.controller; + +import com.alibaba.fastjson.JSONObject; +import com.example.demo.MySpringBootApplication; +import com.example.demo.vo.BindCardReq; +import com.example.demo.vo.CommonReq; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest(classes = MySpringBootApplication.class) +@AutoConfigureMockMvc +public class EmControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @InjectMocks + private DemoController demoController; + + @BeforeEach + public void setup() { + MockitoAnnotations.openMocks(this); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + } + + @Test + public void testController() throws Exception { + + CommonReq req = CommonReq.builder() + .requestId("4aJ7oE3NT") + .sign("_EM_1_XYZ_") + .bizData(null) + .build(); + + //String jsonObject = "{\"requestId\":\"4aJ7oE3NT\", \"sign\":\"_EM_1_XYZ_\"}"; + String jsonObject = JSONObject.toJSONString(req); + + EmController controller = new EmController(); + String key = controller.deriveObjectParameterData("key",jsonObject,null); + req.setKey(key); + String data = controller.deriveObjectParameterData("data",jsonObject,null); + req.setData(data); + + //SIGN depends on KEY and DATA + jsonObject = JSONObject.toJSONString(req); + + String sign = controller.deriveObjectParameterData("sign",jsonObject,null); + req.setSign(sign); + + String requestJson = JSONObject.toJSONString(req); + + mockMvc.perform(post("/api/bind_card_apply") + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isOk()); + } +} diff --git a/e2e-tests/spring-rest-rsa/src/test/java/org/evomaster/e2etests/spring/rest/rsa/RsaEMTest.java b/e2e-tests/spring-rest-rsa/src/test/java/org/evomaster/e2etests/spring/rest/rsa/RsaEMTest.java new file mode 100644 index 0000000000..2279e7db25 --- /dev/null +++ b/e2e-tests/spring-rest-rsa/src/test/java/org/evomaster/e2etests/spring/rest/rsa/RsaEMTest.java @@ -0,0 +1,42 @@ +package org.evomaster.e2etests.spring.rest.rsa; + +import com.example.demo.controller.EmController; +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; + + +public class +RsaEMTest extends RestTestBase { + + + + + @BeforeAll + public static void initClass() throws Exception { + + RestTestBase.initClass(new EmController()); + } + + + @Test + public void testRunEM() throws Throwable { + + runTestHandlingFlakyAndCompilation( + "RsaEM", + 50, + (args) -> { + + //UUID.randomUUID() makes assertions flaky, which we don't handle yet + setOption(args,"enableBasicAssertions", "false"); + + Solution solution = initAndRun(args); + + assertHasAtLeastOne(solution, HttpVerb.POST, 200, "/api/bind_card_apply", null); + } + ); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7afbe16911..af333c7c11 100644 --- a/pom.xml +++ b/pom.xml @@ -1065,10 +1065,13 @@ 1.2.83 + org.projectlombok lombok - 1.18.20 + 1.18.30