Skip to content

Commit 6cd618c

Browse files
committed
fixing failed modification
1 parent b05432d commit 6cd618c

16 files changed

Lines changed: 880 additions & 135 deletions

File tree

core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/httporacle/failmodification/base/FailModificationApplication.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ open class FailModificationApplication {
103103
@PostMapping(path = ["/notempty"])
104104
open fun createnotempty(@RequestBody body: ResourceData): ResponseEntity<ResourceData> {
105105
val id = dataAlreadyExists.size + 1
106-
data[id] = body.copy()
107-
return ResponseEntity.status(201).body(data[id])
106+
dataAlreadyExists[id] = body.copy()
107+
return ResponseEntity.status(201).body(dataAlreadyExists[id])
108108
}
109109

110110
@GetMapping(path = ["/notempty/{id}"])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification.urlencoded
2+
3+
import org.springframework.boot.SpringApplication
4+
import org.springframework.boot.autoconfigure.SpringBootApplication
5+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
6+
import org.springframework.http.ResponseEntity
7+
import org.springframework.web.bind.annotation.GetMapping
8+
import org.springframework.web.bind.annotation.PatchMapping
9+
import org.springframework.web.bind.annotation.PathVariable
10+
import org.springframework.web.bind.annotation.PostMapping
11+
import org.springframework.web.bind.annotation.PutMapping
12+
import org.springframework.web.bind.WebDataBinder
13+
import org.springframework.web.bind.annotation.InitBinder
14+
import org.springframework.web.bind.annotation.ModelAttribute
15+
import org.springframework.web.bind.annotation.RequestMapping
16+
import org.springframework.web.bind.annotation.RestController
17+
import java.beans.PropertyEditorSupport
18+
19+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
20+
@RequestMapping(path = ["/api/resources"])
21+
@RestController
22+
open class UrlencodedFailModificationApplication {
23+
24+
companion object {
25+
@JvmStatic
26+
fun main(args: Array<String>) {
27+
SpringApplication.run(UrlencodedFailModificationApplication::class.java, *args)
28+
}
29+
30+
private val dataAlreadyExists = mutableMapOf<Int, ResourceData>()
31+
32+
fun reset(){
33+
dataAlreadyExists.clear()
34+
dataAlreadyExists[0] = ResourceData("existing", 42)
35+
}
36+
}
37+
38+
open class ResourceData(
39+
var name: String = "",
40+
var value: Int = 0
41+
)
42+
43+
open class UpdateRequest(
44+
var name: String = "",
45+
var value: Int = 0
46+
)
47+
48+
49+
@PostMapping(path = ["/notempty"], consumes = ["application/x-www-form-urlencoded"], produces = ["application/json"])
50+
open fun createnotempty(@ModelAttribute body: ResourceData): ResponseEntity<ResourceData> {
51+
val id = dataAlreadyExists.size + 1
52+
dataAlreadyExists[id] = ResourceData(body.name, body.value)
53+
return ResponseEntity.status(201).body(dataAlreadyExists[id])
54+
}
55+
56+
@GetMapping(path = ["/notempty/{id}"], produces = ["application/json"])
57+
open fun getnotempty(@PathVariable("id") id: Int): ResponseEntity<ResourceData> {
58+
val resource = dataAlreadyExists[id]
59+
?: return ResponseEntity.status(404).build()
60+
return ResponseEntity.status(200).body(resource)
61+
}
62+
63+
@PutMapping(path = ["/notempty/{id}"], consumes = ["application/x-www-form-urlencoded"], produces = ["text/plain"])
64+
open fun putnotempty(
65+
@PathVariable("id") id: Int,
66+
@ModelAttribute body: UpdateRequest
67+
): ResponseEntity<Any> {
68+
69+
val resource = dataAlreadyExists[id]
70+
?: return ResponseEntity.status(404).build()
71+
72+
resource.name = body.name
73+
resource.value = body.value
74+
75+
// returns 400 Bad Request, but the data was already modified above
76+
return ResponseEntity.status(400).body("Invalid request")
77+
}
78+
79+
@PatchMapping(path = ["/notempty/{id}"], consumes = ["application/x-www-form-urlencoded"], produces = ["text/plain"])
80+
open fun patchnotempty(
81+
@PathVariable("id") id: Int,
82+
@ModelAttribute body: UpdateRequest
83+
): ResponseEntity<Any> {
84+
85+
val resource = dataAlreadyExists[id]
86+
?: return ResponseEntity.status(404).build()
87+
88+
// correct: validation first, reject without modifying
89+
return ResponseEntity.status(400).body("No fields to update")
90+
91+
// correct: does NOT modify data, just returns 4xx
92+
return ResponseEntity.status(403).body("Forbidden")
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification.xml
2+
3+
import org.springframework.boot.SpringApplication
4+
import org.springframework.boot.autoconfigure.SpringBootApplication
5+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
6+
import org.springframework.http.ResponseEntity
7+
import org.springframework.web.bind.annotation.GetMapping
8+
import org.springframework.web.bind.annotation.PatchMapping
9+
import org.springframework.web.bind.annotation.PathVariable
10+
import org.springframework.web.bind.annotation.PostMapping
11+
import org.springframework.web.bind.annotation.PutMapping
12+
import org.springframework.web.bind.annotation.RequestBody
13+
import org.springframework.web.bind.annotation.RequestMapping
14+
import org.springframework.web.bind.annotation.RestController
15+
import javax.xml.bind.annotation.XmlAccessType
16+
import javax.xml.bind.annotation.XmlAccessorType
17+
import javax.xml.bind.annotation.XmlRootElement
18+
19+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
20+
@RequestMapping(path = ["/api/resources"])
21+
@RestController
22+
open class XmlFailModificationApplication {
23+
24+
companion object {
25+
@JvmStatic
26+
fun main(args: Array<String>) {
27+
SpringApplication.run(XmlFailModificationApplication::class.java, *args)
28+
}
29+
30+
private val data = mutableMapOf<Int, ResourceData>()
31+
private val dataAlreadyExists = mutableMapOf<Int, ResourceData>()
32+
33+
fun reset(){
34+
data.clear()
35+
dataAlreadyExists.clear()
36+
dataAlreadyExists[0] = ResourceData("existing", 42)
37+
}
38+
}
39+
40+
@XmlRootElement(name = "resourceData")
41+
@XmlAccessorType(XmlAccessType.FIELD)
42+
open class ResourceData(
43+
var name: String = "",
44+
var value: Int = 0
45+
)
46+
47+
@XmlRootElement(name = "updateRequest")
48+
@XmlAccessorType(XmlAccessType.FIELD)
49+
open class UpdateRequest(
50+
var name: String = "",
51+
var value: Int = 0
52+
)
53+
54+
55+
@PostMapping(path = ["/empty"], consumes = ["application/xml"], produces = ["application/xml"])
56+
open fun create(@RequestBody body: ResourceData): ResponseEntity<ResourceData> {
57+
val id = data.size + 1
58+
data[id] = ResourceData(body.name, body.value)
59+
return ResponseEntity.status(201).body(data[id])
60+
}
61+
62+
@GetMapping(path = ["/empty/{id}"], produces = ["application/xml"])
63+
open fun get(@PathVariable("id") id: Int): ResponseEntity<ResourceData> {
64+
val resource = data[id]
65+
?: return ResponseEntity.status(404).build()
66+
return ResponseEntity.status(200).body(resource)
67+
}
68+
69+
@PutMapping(path = ["/empty/{id}"], consumes = ["application/xml"], produces = ["text/plain"])
70+
open fun put(
71+
@PathVariable("id") id: Int,
72+
@RequestBody body: UpdateRequest
73+
): ResponseEntity<Any> {
74+
75+
val resource = data[id]
76+
?: return ResponseEntity.status(404).build()
77+
78+
// bug: modifies data even though it will return 4xx
79+
if(body.name != null) {
80+
resource.name = body.name
81+
}
82+
if(body.value != null) {
83+
resource.value = body.value
84+
}
85+
86+
// returns 400 Bad Request, but the data was already modified above
87+
return ResponseEntity.status(400).body("Invalid request")
88+
}
89+
90+
@PatchMapping(path = ["/empty/{id}"], consumes = ["application/xml"], produces = ["text/plain"])
91+
open fun patch(
92+
@PathVariable("id") id: Int,
93+
@RequestBody body: UpdateRequest
94+
): ResponseEntity<Any> {
95+
96+
val resource = data[id]
97+
?: return ResponseEntity.status(404).build()
98+
99+
// correct: validation first, reject without modifying
100+
if(body.name == null && body.value == null) {
101+
return ResponseEntity.status(400).body("No fields to update")
102+
}
103+
104+
// correct: does NOT modify data, just returns 4xx
105+
return ResponseEntity.status(403).body("Forbidden")
106+
}
107+
108+
// pre-populated resource to test that it is not modified by failed PUT
109+
110+
@PostMapping(path = ["/notempty"], consumes = ["application/xml"], produces = ["application/xml"])
111+
open fun createnotempty(@RequestBody body: ResourceData): ResponseEntity<ResourceData> {
112+
val id = dataAlreadyExists.size + 1
113+
dataAlreadyExists[id] = ResourceData(body.name, body.value)
114+
return ResponseEntity.status(201).body(dataAlreadyExists[id])
115+
}
116+
117+
@GetMapping(path = ["/notempty/{id}"], produces = ["application/xml"])
118+
open fun getnotempty(@PathVariable("id") id: Int): ResponseEntity<ResourceData> {
119+
val resource = dataAlreadyExists[id]
120+
?: return ResponseEntity.status(404).build()
121+
return ResponseEntity.status(200).body(resource)
122+
}
123+
124+
@PutMapping(path = ["/notempty/{id}"], consumes = ["application/xml"], produces = ["text/plain"])
125+
open fun putnotempty(
126+
@PathVariable("id") id: Int,
127+
@RequestBody body: UpdateRequest
128+
): ResponseEntity<Any> {
129+
130+
val resource = dataAlreadyExists[id]
131+
?: return ResponseEntity.status(404).build()
132+
133+
resource.name = body.name
134+
resource.value = body.value
135+
136+
// returns 400 Bad Request, but the data was already modified above
137+
return ResponseEntity.status(400).body("Invalid request")
138+
}
139+
140+
@PatchMapping(path = ["/notempty/{id}"], consumes = ["application/xml"], produces = ["text/plain"])
141+
open fun patchnotempty(
142+
@PathVariable("id") id: Int,
143+
@RequestBody body: UpdateRequest
144+
): ResponseEntity<Any> {
145+
146+
val resource = dataAlreadyExists[id]
147+
?: return ResponseEntity.status(404).build()
148+
149+
// correct: validation first, reject without modifying
150+
return ResponseEntity.status(400).body("No fields to update")
151+
152+
// correct: does NOT modify data, just returns 4xx
153+
return ResponseEntity.status(403).body("Forbidden")
154+
}
155+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
import com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification.urlencoded.UrlencodedFailModificationApplication
5+
import com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification.xml.XmlFailModificationApplication
6+
7+
8+
class FailModificationURLEncodedController: SpringController(UrlencodedFailModificationApplication::class.java){
9+
override fun resetStateOfSUT() {
10+
UrlencodedFailModificationApplication.reset()
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
import com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification.xml.XmlFailModificationApplication
5+
6+
7+
class FailModificationXMLController: SpringController(XmlFailModificationApplication::class.java){
8+
override fun resetStateOfSUT() {
9+
XmlFailModificationApplication.reset()
10+
}
11+
}

core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/httporacle/failmodification/FailModificationEMTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class FailModificationEMTest : SpringTestBase(){
2626

2727
runTestHandlingFlakyAndCompilation(
2828
"FailedModificationEM",
29-
2000
29+
1000
3030
) { args: MutableList<String> ->
3131

3232
setOption(args, "schemaOracles", "false")

core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/httporacle/failmodification/FailModificationForbiddenEMTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class FailModificationForbiddenEMTest : SpringTestBase(){
2525

2626
runTestHandlingFlakyAndCompilation(
2727
"FailedModificationForbiddenEM",
28-
3000
28+
2500
2929
) { args: MutableList<String> ->
3030

3131
setOption(args, "security", "true")

core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/httporacle/failmodification/FailModificationNotFoundEMTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class FailModificationNotFoundEMTest : SpringTestBase(){
2626

2727
runTestHandlingFlakyAndCompilation(
2828
"FailedModificationNotFoundEM",
29-
2000
29+
50
3030
) { args: MutableList<String> ->
3131

3232
setOption(args, "schemaOracles", "false")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.evomaster.e2etests.spring.openapi.v3.httporacle.failmodification
2+
3+
import com.foo.rest.examples.spring.openapi.v3.httporacle.failmodification.FailModificationURLEncodedController
4+
import org.evomaster.core.problem.enterprise.DetectedFaultUtils
5+
import org.evomaster.core.problem.enterprise.ExperimentalFaultCategory
6+
import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase
7+
import org.junit.jupiter.api.Assertions.assertEquals
8+
import org.junit.jupiter.api.Assertions.assertTrue
9+
import org.junit.jupiter.api.BeforeAll
10+
import org.junit.jupiter.api.Test
11+
12+
class URLEncodedFailModificationEMTest : SpringTestBase(){
13+
14+
companion object {
15+
@BeforeAll
16+
@JvmStatic
17+
fun init() {
18+
initClass(FailModificationURLEncodedController())
19+
}
20+
}
21+
22+
23+
@Test
24+
fun testRunEM() {
25+
26+
runTestHandlingFlakyAndCompilation(
27+
"URLEncodedFailedModificationEM",
28+
100
29+
) { args: MutableList<String> ->
30+
31+
setOption(args, "schemaOracles", "false")
32+
setOption(args, "httpOracles", "true")
33+
setOption(args, "useExperimentalOracles", "true")
34+
35+
val solution = initAndRun(args)
36+
37+
assertTrue(solution.individuals.size >= 1)
38+
39+
val faults = DetectedFaultUtils.getDetectedFaults(solution)
40+
41+
assertEquals(1, faults.size)
42+
assertEquals(ExperimentalFaultCategory.HTTP_SIDE_EFFECTS_FAILED_MODIFICATION, faults.first().category)
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)