Skip to content

json patch dto conversion#1603

Open
suarezrominajulieta wants to merge 16 commits into
masterfrom
feature/jsonPatch_DTOConvertion
Open

json patch dto conversion#1603
suarezrominajulieta wants to merge 16 commits into
masterfrom
feature/jsonPatch_DTOConvertion

Conversation

@suarezrominajulieta

@suarezrominajulieta suarezrominajulieta commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

In this pr we fix the TO-DO on json patch dtoWritter:

Idea: Create two separate applications and two end-to-end (E2E) tests, clearly documenting what is being tested. These tests should follow the format outlined in core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/dtoreflectiveassert/DtoReflectiveAssertEMTest.kt.

The first test should verify that Evomaster can generate the DTO for JSON patch when the values ​​of its operations are primitive( and dtoForRequestPayload=true). The second test should verify that the DTO is generated when the object's value is complex, not primitive.

Additionally, the DTOs can be checked in the generated test suite files.

@suarezrominajulieta suarezrominajulieta changed the title json patch dto convertion json patch dto conversion Jun 20, 2026
@suarezrominajulieta suarezrominajulieta marked this pull request as ready for review June 20, 2026 03:35
}

// Registers the shared JsonPatchOperation DTO and collects nested DTOs for object/array values.
private fun calculateDtoFromJsonPatch(gene: JsonPatchDocumentGene) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace comment with Javadoc

private fun calculateDtoFromJsonPatch(gene: JsonPatchDocumentGene) {
val dtoName = GeneToDto.JSON_PATCH_OPERATION_DTO
val dtoClass = dtoCollector.computeIfAbsent(dtoName) { DtoClass(it) }
dtoClass.addField(GeneToDto.FIELD_OP, DtoField(GeneToDto.FIELD_OP, "String"))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace "String" with constant value

}

private fun anyType(): String {
return if (outputFormat.isJava()) "Object" else "Any"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace "Object" and "Any" with constant values

}

// Renders a JSON Patch document as a List<JsonPatchOperation>, one DTO per active operation.
private fun getJsonPatchDtoCall(gene: JsonPatchDocumentGene, counters: MutableList<Int>): DtoCall {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace comment with Javadoc

}

// Renders a single RFC 6902 operation as a JsonPatchOperation DTO with only its relevant fields set.
private fun getJsonPatchOperationCall(operation: JsonPatchOperationGene, counters: MutableList<Int>): DtoCall {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace comment with Javadoc

}

// Sets the "value" field: primitives are inlined as literals, objects/arrays delegate to DTO generation.
private fun setJsonPatchValue(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace comment with Javadoc

Base automatically changed from feature/jsonPatch_e2e to master June 26, 2026 09:08
@suarezrominajulieta

Copy link
Copy Markdown
Collaborator Author

JsonPatchObjectValueDtoEM:

/**
 * Calls:
 * (200) PATCH:/resource/{id}
 * Found 1 potential fault of type-code 208
 */
 @Test @Timeout(60)
 fun test_4_patchOnResourcAnonymousModifications()  {
     
     // Fault208. Anonymous Modifications.
     val list_JsonPatchOperation_1 = mutableListOf<JsonPatchOperation>()
     val dto_JsonPatchOperation_1_1 = JsonPatchOperation()
     dto_JsonPatchOperation_1_1.setOp("copy")
     dto_JsonPatchOperation_1_1.setFrom("/archived")
     dto_JsonPatchOperation_1_1.setPath("/archived")
     list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_1)
     val dto_JsonPatchOperation_1_2 = JsonPatchOperation()
     dto_JsonPatchOperation_1_2.setOp("replace")
     dto_JsonPatchOperation_1_2.setPath("/archived")
     val list_value_1_2 = mutableListOf<Item>()
     val dto_Item_1_2_1 = Item()
     dto_Item_1_2_1.setLabel("old")
     dto_Item_1_2_1.setQuantity(332)
     list_value_1_2.add(dto_Item_1_2_1)
     val dto_Item_1_2_2 = Item()
     dto_Item_1_2_2.setLabel("_EM_1_XYZ_")
     dto_Item_1_2_2.setQuantity(1)
     list_value_1_2.add(dto_Item_1_2_2)
     val dto_Item_1_2_3 = Item()
     dto_Item_1_2_3.setLabel("_EM_2_XYZ_")
     dto_Item_1_2_3.setQuantity(0)
     list_value_1_2.add(dto_Item_1_2_3)
     val dto_Item_1_2_4 = Item()
     dto_Item_1_2_4.setLabel("SawbRz")
     dto_Item_1_2_4.setQuantity(17)
     list_value_1_2.add(dto_Item_1_2_4)
     dto_JsonPatchOperation_1_2.setValue(list_value_1_2)
     list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_2)
     
     given().accept("*/*")
             .header("x-EMextraHeader123", "_EM_4_XYZ_")
             .contentType("application/json-patch+json")
             .body(list_JsonPatchOperation_1)
             .patch("${baseUrlOfSut}/resource/1938135004?EMextraParam123=_EM_3_XYZ_")
             .then()
             .statusCode(200)
             .assertThat()
             .contentType("text/plain")
             .body(containsString("OK"))
 }
 
 
 /**
 * Calls:
 * (200) PATCH:/resource/{id}
 */
 @Test @Timeout(60)
 fun test_5_patchOnResourcReturns200()  {
     
     val list_JsonPatchOperation_1 = mutableListOf<JsonPatchOperation>()
     val dto_JsonPatchOperation_1_1 = JsonPatchOperation()
     dto_JsonPatchOperation_1_1.setOp("move")
     dto_JsonPatchOperation_1_1.setFrom("/archived")
     dto_JsonPatchOperation_1_1.setPath("/archived")
     list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_1)
     val dto_JsonPatchOperation_1_2 = JsonPatchOperation()
     dto_JsonPatchOperation_1_2.setOp("replace")
     dto_JsonPatchOperation_1_2.setPath("/items")
     val list_value_1_2 = mutableListOf<Item>()
     dto_JsonPatchOperation_1_2.setValue(list_value_1_2)
     list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_2)
     val dto_JsonPatchOperation_1_3 = JsonPatchOperation()
     dto_JsonPatchOperation_1_3.setOp("remove")
     dto_JsonPatchOperation_1_3.setPath("/archived")
     list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_3)
     
     given().accept("*/*")
             .header("x-EMextraHeader123", "")
             .contentType("application/json-patch+json")
             .body(list_JsonPatchOperation_1)
             .patch("${baseUrlOfSut}/resource/-1280941182")
             .then()
             .statusCode(200)
             .assertThat()
             .contentType("text/plain")
             .body(containsString("OK"))
 }

@suarezrominajulieta

Copy link
Copy Markdown
Collaborator Author

JsonPatchStringValueDtoEM:


    /**
    * Calls:
    * (200) PATCH:/resource/{id}
    * Found 1 potential fault of type-code 208
    */
    @Test @Timeout(60)
    fun test_4_patchOnResourcAnonymousModifications()  {
        
        // Fault208. Anonymous Modifications.
        val list_JsonPatchOperation_1 = mutableListOf<JsonPatchOperation>()
        val dto_JsonPatchOperation_1_1 = JsonPatchOperation()
        dto_JsonPatchOperation_1_1.setOp("copy")
        dto_JsonPatchOperation_1_1.setFrom("/description")
        dto_JsonPatchOperation_1_1.setPath("/description")
        list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_1)
        val dto_JsonPatchOperation_1_2 = JsonPatchOperation()
        dto_JsonPatchOperation_1_2.setOp("move")
        dto_JsonPatchOperation_1_2.setFrom("/description")
        dto_JsonPatchOperation_1_2.setPath("/description")
        list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_2)
        
        given().accept("*/*")
                .header("x-EMextraHeader123", "_EM_4_XYZ_")
                .contentType("application/json-patch+json")
                .body(list_JsonPatchOperation_1)
                .patch("${baseUrlOfSut}/resource/1938135004")
                .then()
                .statusCode(200)
                .assertThat()
                .contentType("text/plain")
                .body(containsString("OK"))
    }
    
    
    /**
    * Calls:
    * (200) PATCH:/resource/{id}
    */
    @Test @Timeout(60)
    fun test_5_patchOnResourcReturns200()  {
        
        val list_JsonPatchOperation_1 = mutableListOf<JsonPatchOperation>()
        val dto_JsonPatchOperation_1_1 = JsonPatchOperation()
        dto_JsonPatchOperation_1_1.setOp("replace")
        dto_JsonPatchOperation_1_1.setPath("/name")
        dto_JsonPatchOperation_1_1.setValue("_EM_10_XYZ_")
        list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_1)
        val dto_JsonPatchOperation_1_2 = JsonPatchOperation()
        dto_JsonPatchOperation_1_2.setOp("copy")
        dto_JsonPatchOperation_1_2.setFrom("/name")
        dto_JsonPatchOperation_1_2.setPath("/description")
        list_JsonPatchOperation_1.add(dto_JsonPatchOperation_1_2)
        
        given().accept("*/*")
                .header("x-EMextraHeader123", "")
                .contentType("application/json-patch+json")
                .body(list_JsonPatchOperation_1)
                .patch("${baseUrlOfSut}/resource/-869447987?EMextraParam123=_EM_11_XYZ_")
                .then()
                .statusCode(200)
                .assertThat()
                .contentType("text/plain")
                .body(containsString("OK"))
    }

@suarezrominajulieta

suarezrominajulieta commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

I created the two applications and tests, but not with POST, since the POST OpenAPI, lacking a body, would not have the schema to create the resource of a non-primitive object. i left this comment for our meeting, ty!

@jgaleotti

Copy link
Copy Markdown
Collaborator

Please edit your comments to write them in English.

@jgaleotti jgaleotti requested a review from arcuri82 July 8, 2026 17:49
@suarezrominajulieta

Copy link
Copy Markdown
Collaborator Author

Hi @jgaleotti and @arcuri82!

I wanted to flag the relationship between this PR and #1630 before the review continues.

This PR (#1603) is the one that should be merged. It fully implements JSON Patch DTO conversion: when dtoForRequestPayload=true, the generated test renders the PATCH body as List instead of a raw JSON string. It includes unit tests and E2E tests covering both primitive and complex value scenarios.

PR #1630 was created to address an earlier request from Andrea, proving that a POST (DTO) + PATCH (string) generated test compiles. At the time, JSON Patch DTO wasn't implemented yet, so PATCH silently fell back to string even with the flag ON — that was a limitation, not intended behavior. The assertion in #1630 (assertFalse { generatedCode.contains("JsonPatchOperation") }) now tests the wrong behavior and would fail after merging this PR.

For the fallback case (flag OFF → raw string), that path was already implemented before either PR and remains untouched.

TL;DR: This PR (#1603) implements the correct behavior we want to mantain. PR #1630 is superseded and can/should be closed.

What do you think? I'm sorry for causing the conflict.

return Pair(klass, klass.createInstance())
}

private fun assertProperty(klass: KClass<out Any>, instance: Any, propertyName: String, propertyValue: Any?) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this function copied&pasted from other test? avoid code duplication

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @arcuri82, i extracted those functions into SpringTestBase.kt‎, and updated the tests that used it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants