Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import org.evomaster.core.EMConfig
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.problem.rest.data.HttpVerb
import org.evomaster.e2etests.spring.rest.bb.SpringTestBase
import org.evomaster.e2etests.utils.CoveredTargets
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource

Expand Down Expand Up @@ -69,4 +72,35 @@ class BBJsonPatchTest : SpringTestBase() {
assertHasAtLeastOne(solution, HttpVerb.PATCH, 400, "/pets/{id}/sequence", null)
}
}

@Test
fun testBlackBoxWithoutJsonPatchSupport() {
val specificOpTargets = listOf(
"JSON_PATCH_ADD",
"JSON_PATCH_REMOVE",
"JSON_PATCH_REPLACE",
"JSON_PATCH_MOVE",
"JSON_PATCH_COPY",
"JSON_PATCH_TEST",
"JSON_PATCH_SEQUENCE"
)

runBlackBoxEM(OutputFormat.KOTLIN_JUNIT_5, "BBJsonPatchEM_NoSupport", 1000, 3, false) { args ->
setOption(args, "disableJsonPatchSupport", "true")

val solution = initAndRun(args)
assertTrue(solution.individuals.size >= 1)
}

val coveredWithFlagOff = specificOpTargets.count { CoveredTargets.isCovered(it) }
println("=== Flag OFF: $coveredWithFlagOff/${specificOpTargets.size} operation-specific targets covered ===")
specificOpTargets.forEach { target ->
println(" [${ if (CoveredTargets.isCovered(target)) "X" else " " }] $target")
}

assertFalse(
CoveredTargets.areCovered(specificOpTargets),
"With disableJsonPatchSupport=true, EvoMaster should NOT cover all JSON Patch operation targets"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import org.evomaster.core.EMConfig
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.problem.rest.data.HttpVerb
import org.evomaster.e2etests.spring.rest.bb.SpringTestBase
import org.evomaster.e2etests.utils.CoveredTargets
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.EnumSource

Expand Down Expand Up @@ -67,4 +70,41 @@ class BBXMLTest : SpringTestBase() {
assertHasAtLeastOne(solution, HttpVerb.POST, 200, "/api/bbxml/projects", null)
}
}

@Test
fun testBlackBoxWithoutXmlBodySupport() {
// These targets require a well-formed XML body to reach a 200 response.
// With disableXMLSupport=true, EvoMaster falls back to generic field
// naming (schema ref name or 'body') instead of the actual JAXB element names,
// so Spring's XML deserializer receives structurally wrong documents and returns
// 400 for most requests → the 200-branch targets stay uncovered.
val xmlBodyTargets = listOf(
"XML_TO_STRING",
"EMPLOYEE",
"COMPANY",
"DEPARTMENT",
"ORGANIZATION",
"PERSON_ATTR",
"PROJECT",
"PROJECTS"
)

runBlackBoxEM(OutputFormat.KOTLIN_JUNIT_5, "BBXmlEM_NoSupport", 1000, 3, false) { args ->
setOption(args, "disableXMLSupport", "true")

val solution = initAndRun(args)
assertTrue(solution.individuals.size >= 1)
}

val coveredWithFlagOff = xmlBodyTargets.count { CoveredTargets.isCovered(it) }
println("=== Flag OFF: $coveredWithFlagOff/${xmlBodyTargets.size} XML body targets covered ===")
xmlBodyTargets.forEach { target ->
println(" [${if (CoveredTargets.isCovered(target)) "X" else " "}] $target")
}

assertFalse(
CoveredTargets.areCovered(xmlBodyTargets),
"With disableXMLSupport=true, EvoMaster should NOT cover all XML body targets"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void testDeterminismOfLog(boolean enableConstraintHandling){
OpenAPI schema = (new OpenAPIParser()).readLocation("swagger-ahm/ahm.json", null, null).getOpenAPI();
isDeterminismConsumer( new ArrayList<>(), (args) -> RestActionBuilderV3.INSTANCE
.getModelsFromSwagger(schema, new LinkedHashMap<>(),
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false)));
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false,true,true)));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void testDeterminismOfLog(boolean enableConstraintHandling){
OpenAPI schema = (new OpenAPIParser()).readLocation("swagger-ahm/ahm.json", null, null).getOpenAPI();
isDeterminismConsumer( new ArrayList<>(), (args) -> {
RestActionBuilderV3.INSTANCE.getModelsFromSwagger(schema, new LinkedHashMap<>(),
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false));
new RestActionBuilderV3.Options(false,enableConstraintHandling,false,0.0,0.0,true,false,false,true,true));
});
}

Expand Down
10 changes: 10 additions & 0 deletions core/src/main/kotlin/org/evomaster/core/EMConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,16 @@ class EMConfig {
"Only available for JVM languages")
var dtoForRequestPayload = false

@Experimental
@Cfg("Disable JSON Patch (RFC 6902) gene support when the request Content-Type is 'application/json-patch+json'." +
" When true, such endpoints are treated as regular JSON bodies, reproducing the behavior before this feature was introduced.")
var disableJsonPatchSupport = false

@Experimental
@Cfg("Disable XML-aware field naming for body genes when the request Content-Type is XML." +
" When true, body gene names fall back to the pre-feature behavior (schema ref name or 'body').")
var disableXMLSupport = false

@Important(6.0)
@Cfg("Host name or IP address of where the SUT EvoMaster Controller Driver is listening on." +
" This option is only needed for white-box testing.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ object RestActionBuilderV3 {
val enableAdvancedFormats: Boolean = true,

val inferFormatFromNames: Boolean = true,

val disableJsonPatchSupport: Boolean = false,

val disableXMLSupport: Boolean = false,
){
constructor(config: EMConfig): this(
enableConstraintHandling = config.enableSchemaConstraintHandling,
Expand All @@ -123,7 +127,9 @@ object RestActionBuilderV3 {
probUseExamples = config.probRestExamples,
usingWhiteBox = !config.blackBox,
enableAdvancedFormats = config.enableAdvancedFormats,
inferFormatFromNames = config.inferFormatFromNames
inferFormatFromNames = config.inferFormatFromNames,
disableJsonPatchSupport = config.disableJsonPatchSupport,
disableXMLSupport = config.disableXMLSupport,
)

init {
Expand Down Expand Up @@ -748,7 +754,8 @@ object RestActionBuilderV3 {
listOf()
}

val isJsonPatch = verb == HttpVerb.PATCH && bodies.keys.any { it.contains("json-patch") }
val isJsonPatch = !options.disableJsonPatchSupport &&
verb == HttpVerb.PATCH && bodies.keys.any { it.contains("json-patch") }

val name: String
var gene: Gene
Expand All @@ -774,9 +781,13 @@ object RestActionBuilderV3 {
}
gene = JsonPatchDocumentGene(name, resourceGene)
} else {
// $ref schemas do not carry XML metadata; resolving the reference is required to obtain the correct XML element name from the target schema
val deref = obj.schema.`$ref`?.let { ref -> SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) } ?: obj.schema
name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body"
if (!options.disableXMLSupport) {
// $ref schemas do not carry XML metadata; resolving the reference is required to obtain the correct XML element name from the target schema
val deref = obj.schema.`$ref`?.let { ref -> SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) } ?: obj.schema
name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body"
} else {
name = obj.schema.`$ref`?.substringAfterLast("/") ?: "body"
}
gene = getGene(name, obj.schema, schemaHolder, currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples)
}

Expand Down
2 changes: 2 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ There are 3 types of options:
|`callbackURLHostname`| __String__. HTTP callback verifier hostname. Default is set to 'localhost'. If the SUT is running inside a container (i.e., Docker), 'localhost' will refer to the container. This can be used to change the hostname. *Default value*: `localhost`.|
|`cgaNeighborhoodModel`| __Enum__. Cellular GA: neighborhood model (RING, L5, C9, C13). *Valid values*: `RING, L5, C9, C13`. *Default value*: `RING`.|
|`classificationRepairThreshold`| __Double__. If using THRESHOLD for AI Classification Repair, specify its value. All classifications with probability equal or above such threshold value will be accepted. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.5`.|
|`disableJsonPatchSupport`| __Boolean__. Disable JSON Patch (RFC 6902) gene support when the request Content-Type is 'application/json-patch+json'. When true, such endpoints are treated as regular JSON bodies, reproducing the behavior before this feature was introduced. *Default value*: `false`.|
|`disableXMLSupport`| __Boolean__. Disable XML-aware field naming for body genes when the request Content-Type is XML. When true, body gene names fall back to the pre-feature behavior (schema ref name or 'body'). *Default value*: `false`.|
|`discoveredInfoRewardedInFitness`| __Boolean__. If there is new discovered information from a test execution, reward it in the fitness function. *Default value*: `false`.|
|`dockerLocalhost`| __Boolean__. Replace references to 'localhost' to point to the actual host machine. Only needed when running EvoMaster inside Docker. *Default value*: `false`.|
|`dpcTargetTestSize`| __Int__. Specify a max size of a test to be targeted when either DPC_INCREASING or DPC_DECREASING is enabled. *Default value*: `1`.|
Expand Down
Loading