From 7fe3fcf7febfee5b6a62f4d1b27a6ff63a53c7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ROMINA=20JULIETA=20SU=C3=81REZ?= Date: Sun, 12 Jul 2026 23:31:40 -0300 Subject: [PATCH 1/4] Feature flags for xml and jsonpatch --- .../kotlin/org/evomaster/core/EMConfig.kt | 10 +++++++++ .../rest/builder/RestActionBuilderV3.kt | 21 ++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index ed830a3220..0984284049 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -1372,6 +1372,16 @@ class EMConfig { "Only available for JVM languages") var dtoForRequestPayload = false + @Experimental + @Cfg("Enable JSON Patch (RFC 6902) gene support when the request Content-Type is 'application/json-patch+json'." + + " When false, such endpoints are treated as regular JSON bodies, reproducing the behavior before this feature was introduced.") + var enableJsonPatchGeneSupport = true + + @Experimental + @Cfg("Enable XML-aware field naming for body genes when the request Content-Type is XML." + + " When false, body gene names fall back to the pre-feature behavior (schema ref name or 'body').") + var enableXmlBodyGeneSupport = true + @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.") diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt index ebf0e6ddaa..19a168f25e 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt @@ -115,6 +115,10 @@ object RestActionBuilderV3 { val enableAdvancedFormats: Boolean = true, val inferFormatFromNames: Boolean = true, + + val enableJsonPatchGeneSupport: Boolean = true, + + val enableXmlBodyGeneSupport: Boolean = true, ){ constructor(config: EMConfig): this( enableConstraintHandling = config.enableSchemaConstraintHandling, @@ -123,7 +127,9 @@ object RestActionBuilderV3 { probUseExamples = config.probRestExamples, usingWhiteBox = !config.blackBox, enableAdvancedFormats = config.enableAdvancedFormats, - inferFormatFromNames = config.inferFormatFromNames + inferFormatFromNames = config.inferFormatFromNames, + enableJsonPatchGeneSupport = config.enableJsonPatchGeneSupport, + enableXmlBodyGeneSupport = config.enableXmlBodyGeneSupport, ) init { @@ -748,7 +754,8 @@ object RestActionBuilderV3 { listOf() } - val isJsonPatch = verb == HttpVerb.PATCH && bodies.keys.any { it.contains("json-patch") } + val isJsonPatch = options.enableJsonPatchGeneSupport && + verb == HttpVerb.PATCH && bodies.keys.any { it.contains("json-patch") } val name: String var gene: Gene @@ -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.enableXmlBodyGeneSupport) { + // $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) } From 80a4c7bb08b2e33c74a30f17cf9507866d0f22f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ROMINA=20JULIETA=20SU=C3=81REZ?= Date: Mon, 13 Jul 2026 21:31:44 -0300 Subject: [PATCH 2/4] Fix for tests --- .../spring/examples/adaptivehypermutation/DeterminismTest.java | 2 +- .../spring/examples/adaptivehypermutation/DeterminismTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core-tests/e2e-tests/spring/spring-rest-h2-v1/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java b/core-tests/e2e-tests/spring/spring-rest-h2-v1/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java index 75c9268ddd..04f266caf0 100644 --- a/core-tests/e2e-tests/spring/spring-rest-h2-v1/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java +++ b/core-tests/e2e-tests/spring/spring-rest-h2-v1/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java @@ -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))); } diff --git a/core-tests/jdk-8/spring-rest-openapi-v2-tests/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java b/core-tests/jdk-8/spring-rest-openapi-v2-tests/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java index 2ddc3b2b3e..2ea9a8143c 100644 --- a/core-tests/jdk-8/spring-rest-openapi-v2-tests/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java +++ b/core-tests/jdk-8/spring-rest-openapi-v2-tests/src/test/java/org/evomaster/e2etests/spring/examples/adaptivehypermutation/DeterminismTest.java @@ -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)); }); } From a0c37b697a571d0ea16f850484dfcbfb55b137c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ROMINA=20JULIETA=20SU=C3=81REZ?= Date: Mon, 13 Jul 2026 23:02:45 -0300 Subject: [PATCH 3/4] Update options.md --- docs/options.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/options.md b/docs/options.md index 39190eeb64..1888042468 100644 --- a/docs/options.md +++ b/docs/options.md @@ -287,9 +287,11 @@ There are 3 types of options: |`enableAdvancedFormats`| __Boolean__. Whether to enable the handling of new type formats in OpenAPI schemas, e.g., the ones introduced in 3.1.0. *Default value*: `false`.| |`enableCustomizedMethodForMockObjectHandling`| __Boolean__. Whether to apply customized method (i.e., implement 'customizeMockingRPCExternalService' for external services or 'customizeMockingDatabase' for database) to handle mock object. *Default value*: `false`.| |`enableCustomizedMethodForScheduleTaskHandling`| __Boolean__. Whether to apply customized method (i.e., implement 'customizeScheduleTaskInvocation' for invoking schedule task) to invoke schedule task. *Default value*: `false`.| +|`enableJsonPatchGeneSupport`| __Boolean__. Enable JSON Patch (RFC 6902) gene support when the request Content-Type is 'application/json-patch+json'. When false, such endpoints are treated as regular JSON bodies, reproducing the behavior before this feature was introduced. *Default value*: `true`.| |`enableRPCCustomizedTestOutput`| __Boolean__. Whether to enable customized RPC Test output if 'customizeRPCTestOutput' is implemented. *Default value*: `false`.| |`enableStaticFlakyInference`| __Boolean__. Specify whether to infer potential flakiness statically from response values, such as timestamps, UUIDs, hashes and runtime-specific messages. *Depends on*: `handleFlakiness=true`. *Default value*: `true`.| |`enableWriteSnapshotTests`| __Boolean__. Enable to print snapshots of the generated tests during the search in an interval defined in snapshotsInterval. *Default value*: `false`.| +|`enableXmlBodyGeneSupport`| __Boolean__. Enable XML-aware field naming for body genes when the request Content-Type is XML. When false, body gene names fall back to the pre-feature behavior (schema ref name or 'body'). *Default value*: `true`.| |`execNumForDetectFlakiness`| __Int__. Specify the number of re-executions for detecting flakiness in tests. Set to 0 to disable re-execution based flakiness detection. *Constraints*: `min=0.0`. *Depends on*: `handleFlakiness=true`. *Default value*: `1`.| |`executiveSummary`| __Boolean__. Generate an executive summary, containing an example of each category of potential faults found.NOTE: This option is only meaningful when used in conjunction with test suite splitting. *Default value*: `false`.| |`expectationsActive`| __Boolean__. Enable Expectation Generation. If enabled, expectations will be generated. A variable called expectationsMasterSwitch is added to the test suite, with a default value of false. If set to true, an expectation that fails will cause the test case containing it to fail. *Default value*: `false`.| From 2c3473c62dfae80b873ce80a29d8a79cb5bac62e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ROMINA=20JULIETA=20SU=C3=81REZ?= Date: Mon, 13 Jul 2026 23:52:11 -0300 Subject: [PATCH 4/4] Add tests with keys in false and assertFalse for coverage --- .../rest/bb/jsonpatch/BBJsonPatchTest.kt | 34 ++++++++++++++++ .../e2etests/spring/rest/bb/xml/BBXMLTest.kt | 40 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/jsonpatch/BBJsonPatchTest.kt b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/jsonpatch/BBJsonPatchTest.kt index f4d5879149..00915490f2 100644 --- a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/jsonpatch/BBJsonPatchTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/jsonpatch/BBJsonPatchTest.kt @@ -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 @@ -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, "enableJsonPatchGeneSupport", "false") + + 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), + "Without enableJsonPatchGeneSupport, EvoMaster should NOT cover all JSON Patch operation targets" + ) + } } \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/xml/BBXMLTest.kt b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/xml/BBXMLTest.kt index 6435011e75..cb6cb60fcd 100644 --- a/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/xml/BBXMLTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-bb/src/test/kotlin/org/evomaster/e2etests/spring/rest/bb/xml/BBXMLTest.kt @@ -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 @@ -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 enableXmlBodyGeneSupport=false, 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, "enableXmlBodyGeneSupport", "false") + + 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), + "Without enableXmlBodyGeneSupport, EvoMaster should NOT cover all XML body targets" + ) + } } \ No newline at end of file