Skip to content

Commit 237ceb6

Browse files
authored
Merge pull request #1464 from WebFuzzing/overlay
starting with Overlay
2 parents 575cfd9 + 0dc9ca4 commit 237ceb6

14 files changed

Lines changed: 449 additions & 9 deletions

File tree

core-parent/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
<artifactId>swagger-request-validator-core</artifactId>
6868
<version>2.44.9</version>
6969
</dependency>
70+
<dependency>
71+
<groupId>com.webfuzzing</groupId>
72+
<artifactId>overlay-jvm</artifactId>
73+
<version>0.2.0</version>
74+
</dependency>
7075
</dependencies>
7176
</dependencyManagement>
7277
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.foo.rest.examples.spring.openapi.v3.overlay
2+
3+
import org.springframework.boot.SpringApplication
4+
import org.springframework.boot.autoconfigure.SpringBootApplication
5+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
6+
7+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
8+
open class OverlayApplication {
9+
10+
companion object {
11+
@JvmStatic
12+
fun main(args: Array<String>) {
13+
SpringApplication.run(OverlayApplication::class.java, *args)
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.foo.rest.examples.spring.openapi.v3.overlay
2+
3+
import com.foo.rest.examples.spring.openapi.v3.charescaperegex.CharEscapeRegexDto
4+
import com.foo.rest.examples.spring.openapi.v3.stringlength.StringLengthDto
5+
import org.springframework.http.MediaType
6+
import org.springframework.http.ResponseEntity
7+
import org.springframework.web.bind.annotation.GetMapping
8+
import org.springframework.web.bind.annotation.PathVariable
9+
import org.springframework.web.bind.annotation.PostMapping
10+
import org.springframework.web.bind.annotation.RequestBody
11+
import org.springframework.web.bind.annotation.RequestMapping
12+
import org.springframework.web.bind.annotation.RestController
13+
import java.util.regex.Pattern
14+
import javax.validation.Valid
15+
import javax.ws.rs.QueryParam
16+
17+
@RestController
18+
@RequestMapping(path = ["/api/overlay"])
19+
open class OverlayRest {
20+
21+
@GetMapping
22+
open fun get( @QueryParam("x") x: String, @QueryParam("y") y: String) : ResponseEntity<String>{
23+
24+
return ResponseEntity.ok("x=$x , y=$y")
25+
}
26+
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
overlay: 1.1.0
2+
info:
3+
title: Add example to x parameter
4+
version: 1.0.0
5+
actions:
6+
- target: $.paths.['/api/overlay'].get.parameters[?(@.name == 'y')]
7+
update:
8+
examples:
9+
foo:
10+
value: "Some Y value like 777"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
overlay: 1.1.0
2+
info:
3+
title: Add example to x parameter
4+
version: 1.0.0
5+
actions:
6+
- target: $.paths.['/api/overlay'].get.parameters[?(@.name == 'x')]
7+
update:
8+
examples:
9+
foo:
10+
value: "Some X value like 1234"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"overlay": "1.1.0",
3+
"info": {
4+
"title": "Add example to non-existing z parameter",
5+
"version": "1.0.0"
6+
},
7+
"actions": [
8+
{
9+
"target": "$.paths.['/api/overlay'].get.parameters[?(@.name == 'z')]",
10+
"update": {
11+
"examples": {
12+
"foo": {
13+
"value": "Some X value like 1234"
14+
}
15+
}
16+
}
17+
}
18+
]
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.foo.rest.examples.spring.openapi.v3.overlay
2+
3+
import com.foo.rest.examples.spring.openapi.v3.SpringController
4+
5+
class OverlayController : SpringController(OverlayApplication::class.java)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package org.evomaster.e2etests.spring.openapi.v3.overlay
2+
3+
import com.foo.rest.examples.spring.openapi.v3.overlay.OverlayController
4+
import com.foo.rest.examples.spring.openapi.v3.stringlength.StringLengthController
5+
import org.evomaster.ci.utils.JUnitExtra
6+
import org.evomaster.core.config.ConfigProblemException
7+
import org.evomaster.core.problem.rest.data.HttpVerb
8+
import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase
9+
import org.junit.jupiter.api.Assertions
10+
import org.junit.jupiter.api.BeforeAll
11+
import org.junit.jupiter.api.Test
12+
import org.junit.jupiter.api.assertThrows
13+
14+
class OverlayEMTest : SpringTestBase(){
15+
16+
companion object {
17+
@BeforeAll
18+
@JvmStatic
19+
fun init() {
20+
initClass(OverlayController())
21+
}
22+
}
23+
24+
//these should be kept in sink with what written in the Overlay files
25+
private val X = "Some X value like 1234"
26+
private val Y = "Some Y value like 777"
27+
28+
@Test
29+
fun testRunEM_Overlay_None() {
30+
31+
runTestHandlingFlakyAndCompilation(
32+
"Overlay_None",
33+
100
34+
) { args: MutableList<String> ->
35+
36+
val solution = initAndRun(args)
37+
38+
Assertions.assertTrue(solution.individuals.size >= 1)
39+
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/overlay", null)
40+
assertNone(solution, HttpVerb.GET, 200, "/api/overlay", X)
41+
assertNone(solution, HttpVerb.GET, 200, "/api/overlay", Y)
42+
}
43+
}
44+
45+
46+
@Test
47+
fun testRunEM_Overlay_X() {
48+
49+
runTestHandlingFlakyAndCompilation(
50+
"Overlay_X",
51+
100
52+
) { args: MutableList<String> ->
53+
54+
setOption(args, "overlay", "src/main/resources/overlay/x.yaml")
55+
56+
val solution = initAndRun(args)
57+
58+
Assertions.assertTrue(solution.individuals.size >= 1)
59+
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/overlay", X)
60+
assertNone(solution, HttpVerb.GET, 200, "/api/overlay", Y)
61+
}
62+
}
63+
64+
@Test
65+
fun testRunEM_Overlay_Y_BB() {
66+
67+
runTestHandlingFlakyAndCompilation(
68+
"Overlay_Y_BB",
69+
100
70+
) { args: MutableList<String> ->
71+
72+
setOption(args, "overlay", "src/main/resources/overlay/subfolder/y.yaml")
73+
setOption(args, "blackBox", "true")
74+
setOption(args, "bbTargetUrl", baseUrlOfSut)
75+
setOption(args, "bbSwaggerUrl", "$baseUrlOfSut/v3/api-docs")
76+
77+
val solution = initAndRun(args)
78+
79+
Assertions.assertTrue(solution.individuals.size >= 1)
80+
assertNone(solution, HttpVerb.GET, 200, "/api/overlay", X)
81+
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/overlay", Y)
82+
}
83+
}
84+
85+
86+
@Test
87+
fun testRunEM_Overlay_Z_fail() {
88+
89+
runTestHandlingFlaky(
90+
"Overlay_Z_fail",
91+
"org.foo.Overlay_Z_fail",
92+
100,
93+
false
94+
) { args: MutableList<String> ->
95+
96+
setOption(args, "overlay", "src/main/resources/overlay/z.json")
97+
//default behavior must be non-lenient
98+
99+
//z does not exist
100+
JUnitExtra.assertThrowsInnermost(ConfigProblemException::class.java) {
101+
initAndRun(args)
102+
}
103+
}
104+
}
105+
106+
@Test
107+
fun testRunEM_Overlay_Z_lenient() {
108+
109+
runTestHandlingFlakyAndCompilation(
110+
"Overlay_Z_lenient",
111+
100
112+
) { args: MutableList<String> ->
113+
114+
setOption(args, "overlay", "src/main/resources/overlay/z.json")
115+
setOption(args, "overlayLenient", "true")
116+
117+
//z does not exist... but, when lenient, shouldn't fail
118+
val solution = initAndRun(args)
119+
120+
Assertions.assertTrue(solution.individuals.size >= 1)
121+
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/overlay", null)
122+
assertNone(solution, HttpVerb.GET, 200, "/api/overlay", X)
123+
assertNone(solution, HttpVerb.GET, 200, "/api/overlay", Y)
124+
}
125+
}
126+
127+
@Test
128+
fun testRunEM_Overlay_folder() {
129+
130+
runTestHandlingFlaky(
131+
"Overlay_folder",
132+
"org.foo.Overlay_folder",
133+
100,
134+
false,
135+
) { args: MutableList<String> ->
136+
137+
setOption(args, "overlay", "src/main/resources/overlay")
138+
//by default, z.json will be picked, and so failed because non-lenient
139+
140+
//z does not exist
141+
JUnitExtra.assertThrowsInnermost(ConfigProblemException::class.java) {
142+
initAndRun(args)
143+
}
144+
}
145+
}
146+
147+
148+
@Test
149+
fun testRunEM_Overlay_folder_filtered() {
150+
151+
runTestHandlingFlakyAndCompilation(
152+
"Overlay_folder_filtered",
153+
100
154+
) { args: MutableList<String> ->
155+
156+
setOption(args, "overlay", "src/main/resources/overlay")
157+
//make sure to not pick-up z.json
158+
setOption(args, "overlayFileSuffixes", ".yaml")
159+
160+
val solution = initAndRun(args)
161+
162+
Assertions.assertTrue(solution.individuals.size >= 1)
163+
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/overlay", X)
164+
assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/overlay", Y)
165+
}
166+
}
167+
168+
169+
}

core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
<groupId>com.webfuzzing</groupId>
4040
<artifactId>commons</artifactId>
4141
</dependency>
42+
<dependency>
43+
<groupId>com.webfuzzing</groupId>
44+
<artifactId>overlay-jvm</artifactId>
45+
</dependency>
4246

4347

4448
<dependency>

core/src/main/kotlin/org/evomaster/core/EMConfig.kt

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,6 @@ class EMConfig {
634634
throw ConfigProblemException("'bbTargetUrl' should be set only in black-box mode")
635635
}
636636

637-
// ONUR, this line is changed since it did not compile in the previous case.
638637
if (!endpointFocus.isNullOrBlank() && !endpointPrefix.isNullOrBlank()) {
639638
throw ConfigProblemException("both 'endpointFocus' and 'endpointPrefix' are set")
640639
}
@@ -913,14 +912,34 @@ class EMConfig {
913912

914913
if (Files.exists(path) && !Files.isWritable(path)) {
915914
throw ConfigProblemException("Parameter '${m.name}' refers to a file that already" +
916-
" exists, but that cannot be written/replace to: $path")
915+
" exists, but that cannot be written/replaced to: $path")
917916
}
918917
if (Files.exists(path) && Files.isDirectory(path)) {
919918
throw ConfigProblemException("Parameter '${m.name}' refers to a file that is instead an" +
920919
" existing folder: $path")
921920
}
922921
}
923922
}
923+
924+
m.annotations.find { it is ExistingPath }?.also{
925+
val ep = it as ExistingPath
926+
if(! (ep.canBeBlank && parameterValue.isBlank())){
927+
val path = try {
928+
Paths.get(parameterValue).toAbsolutePath()
929+
} catch (e: InvalidPathException) {
930+
throw ConfigProblemException("Parameter '${m.name}' is not a valid FS path: ${e.message}")
931+
}
932+
933+
if (!Files.exists(path)){
934+
throw ConfigProblemException("File/folder for '${m.name}' does not exist: $path")
935+
}
936+
937+
if( ep.shouldBeWritable && !Files.isWritable(path)){
938+
throw ConfigProblemException("Parameter '${m.name}' refers to a file that" +
939+
" exists, but it cannot be written/replaced to: $path")
940+
}
941+
}
942+
}
924943
}
925944

926945

@@ -1054,7 +1073,7 @@ class EMConfig {
10541073

10551074

10561075
/**
1057-
* This represent one of the main properties to set in EvoMaster.
1076+
* This represents one of the main properties to set in EvoMaster.
10581077
* Those are the ones most likely going to be set by practitioners.
10591078
* Note: most of the other properties are mainly for experiments
10601079
*/
@@ -1076,6 +1095,13 @@ class EMConfig {
10761095
@MustBeDocumented
10771096
annotation class FilePath(val canBeBlank: Boolean = false)
10781097

1098+
/**
1099+
* Either a file or a folder, that MUST already exist and can be read.
1100+
*/
1101+
@Target(AnnotationTarget.PROPERTY)
1102+
@MustBeDocumented
1103+
annotation class ExistingPath(val canBeBlank: Boolean = false, val shouldBeWritable: Boolean = false)
1104+
10791105

10801106
//------------------------------------------------------------------------
10811107

@@ -3053,6 +3079,32 @@ class EMConfig {
30533079
" path element of the URL will not change).")
30543080
var overrideAuthExternalEndpointURL : String? = null
30553081

3082+
@Experimental
3083+
@ExistingPath(true,false)
3084+
@Cfg("Specify an OAI Overlay file path, or a folder containing those." +
3085+
" In this latter case, Overlay files will be searched recursively in the nested folder, matching" +
3086+
" a given list of configurable suffixes." +
3087+
" Each Overlay will be applied to the target OpenAPI schema." +
3088+
" If more than one Overlay file is applied, no specific ordering of transformations is enforced.")
3089+
var overlay = ""
3090+
3091+
@Experimental
3092+
@Cfg("Comma ',' separated list of file name suffixes." +
3093+
" When scanning a folder for OAI Overlay files, any file with name matching any one of these" +
3094+
" suffixes will be loaded and applied." +
3095+
" For example, '.json' could be used to match all JSON files." +
3096+
" If the folder contains also other types of files with same extension, you might need to define" +
3097+
" some naming convention, and then use suffixes based on it, e.g., '-overlay.yaml' to match" +
3098+
" all YAML files whose name ends in 'overlay', like 'example-overlay.yaml'.")
3099+
var overlayFileSuffixes = ".json,.yaml,.yml"
3100+
3101+
@Experimental
3102+
@Cfg("When applying Overlay transformations, by default EvoMaster will crash immediately" +
3103+
" if there is any issue with the transformations, e.g., if some transformations are not applied" +
3104+
" because the JSON Path selectors found no applicable node in the OpenAPI schema." +
3105+
" This option can be used to override such behavior, and let the fuzzing go on without" +
3106+
" applying any overlay.")
3107+
var overlayLenient = false
30563108

30573109
fun getProbabilityUseDataPool() : Double{
30583110
return if(blackBox){

0 commit comments

Comments
 (0)