Skip to content

Commit 0ab5b55

Browse files
authored
Merge pull request #1455 from WebFuzzing/feature-xml
Feature xml
2 parents 78eb6d4 + 4186a16 commit 0ab5b55

16 files changed

Lines changed: 1917 additions & 146 deletions

File tree

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
package com.foo.rest.examples.bb.xml
2+
3+
import io.swagger.v3.oas.annotations.responses.ApiResponse
4+
import io.swagger.v3.oas.annotations.responses.ApiResponses
5+
import org.evomaster.e2etests.utils.CoveredTargets
6+
import org.springframework.http.ResponseEntity
7+
import org.springframework.web.bind.annotation.*
8+
import org.springframework.boot.SpringApplication
9+
import org.springframework.boot.autoconfigure.SpringBootApplication
10+
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
11+
import org.springframework.http.MediaType
12+
import javax.xml.bind.annotation.*
13+
14+
@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
15+
@RequestMapping(path = ["/api/bbxml"])
16+
@RestController
17+
open class BBXMLApplication {
18+
19+
companion object {
20+
@JvmStatic
21+
fun main(args: Array<String>) {
22+
SpringApplication.run(BBXMLApplication::class.java, *args)
23+
}
24+
}
25+
26+
/* ===================== ENDPOINTS ===================== */
27+
28+
// 1. String -> XML
29+
@ApiResponses(value = [
30+
ApiResponse(responseCode = "200", description = "XML representation of the input string"),
31+
ApiResponse(responseCode = "400", description = "Body is blank")
32+
])
33+
@PostMapping(
34+
"/receive-string-respond-xml",
35+
consumes = ["text/plain"],
36+
produces = ["application/xml"]
37+
)
38+
fun stringToXml(@RequestBody body: String): ResponseEntity<Person> {
39+
if (body.isBlank())
40+
return ResponseEntity.status(400).build()
41+
42+
CoveredTargets.cover("STRING_TO_XML")
43+
return ResponseEntity.status(200)
44+
.body(Person(name = body, age = body.length))
45+
}
46+
47+
// 2. XML -> String
48+
@ApiResponses(value = [
49+
ApiResponse(responseCode = "200", description = "String representation of the XML person"),
50+
ApiResponse(responseCode = "400", description = "Person name is blank or age is negative")
51+
])
52+
@PostMapping(
53+
"/receive-xml-respond-string",
54+
consumes = ["application/xml"],
55+
produces = ["text/plain"]
56+
)
57+
fun xmlToString(@RequestBody person: Person): ResponseEntity<String> {
58+
if (!isValid(person))
59+
return ResponseEntity
60+
.status(400).contentType(MediaType.TEXT_PLAIN).body("invalid person")
61+
62+
CoveredTargets.cover("XML_TO_STRING")
63+
return ResponseEntity.status(200).body("not ok")
64+
}
65+
66+
// 3. Employee (2-level nesting)
67+
@ApiResponses(value = [
68+
ApiResponse(responseCode = "200", description = "Employee processed successfully"),
69+
ApiResponse(responseCode = "400", description = "Employee person name is blank or age is negative")
70+
])
71+
@PostMapping(
72+
"/employee",
73+
consumes = ["application/xml"],
74+
produces = ["text/plain"]
75+
)
76+
fun employee(@RequestBody employee: Employee): ResponseEntity<String> {
77+
if (!isValid(employee))
78+
return ResponseEntity.status(400)
79+
.contentType(MediaType.TEXT_PLAIN)
80+
.body("invalid input")
81+
82+
CoveredTargets.cover("EMPLOYEE")
83+
return ResponseEntity.status(200)
84+
.body(
85+
if (employee.role == Role.ADMIN && employee.person.age > 30)
86+
"admin"
87+
else
88+
"not admin or too young"
89+
)
90+
}
91+
92+
// 4. Company (3-level nesting)
93+
@ApiResponses(value = [
94+
ApiResponse(responseCode = "200", description = "Company processed successfully"),
95+
ApiResponse(responseCode = "400", description = "Company name is blank")
96+
])
97+
@PostMapping(
98+
"/company",
99+
consumes = ["application/xml"],
100+
produces = ["text/plain"]
101+
)
102+
fun company(@RequestBody company: Company): ResponseEntity<String> {
103+
if (!isValid(company))
104+
return ResponseEntity.status(400).contentType(MediaType.TEXT_PLAIN).body("invalid company")
105+
106+
CoveredTargets.cover("COMPANY")
107+
return ResponseEntity.status(200)
108+
.body(if (company.employees.isEmpty()) "small company" else "big company")
109+
}
110+
111+
// 5. Department (recursive)
112+
@ApiResponses(value = [
113+
ApiResponse(responseCode = "200", description = "Department processed successfully"),
114+
ApiResponse(responseCode = "400", description = "Department name is blank")
115+
])
116+
@PostMapping(
117+
"/department",
118+
consumes = ["application/xml"],
119+
produces = ["text/plain"]
120+
)
121+
fun department(@RequestBody department: Department): ResponseEntity<String> {
122+
if (!isValid(department))
123+
return ResponseEntity.status(400)
124+
.contentType(MediaType.TEXT_PLAIN)
125+
.body("invalid input")
126+
127+
CoveredTargets.cover("DEPARTMENT")
128+
return ResponseEntity.status(200)
129+
.body("department with ${department.employees.size + 1} employees")
130+
}
131+
132+
// 6. Organization (3 lists)
133+
@ApiResponses(value = [
134+
ApiResponse(responseCode = "200", description = "Organization processed successfully"),
135+
ApiResponse(responseCode = "400", description = "Organization name is blank")
136+
])
137+
@PostMapping(
138+
"/organization",
139+
consumes = ["application/xml"],
140+
produces = ["text/plain"]
141+
)
142+
fun organization(@RequestBody organization: Organization): ResponseEntity<String> {
143+
if (!isValid(organization))
144+
return ResponseEntity.status(400)
145+
.contentType(MediaType.TEXT_PLAIN)
146+
.body("invalid input")
147+
148+
CoveredTargets.cover("ORGANIZATION")
149+
return ResponseEntity.status(200)
150+
.body("organization with ${organization.people.size} people")
151+
}
152+
153+
// 7. Project (attributes)
154+
@ApiResponses(value = [
155+
ApiResponse(responseCode = "200", description = "Project processed successfully"),
156+
ApiResponse(responseCode = "400", description = "Project code is blank")
157+
])
158+
@PostMapping(
159+
"/project",
160+
consumes = ["application/xml"],
161+
produces = ["text/plain"]
162+
)
163+
fun project(@RequestBody project: Project): ResponseEntity<String> {
164+
if (!isValid(project))
165+
return ResponseEntity.status(400).contentType(MediaType.TEXT_PLAIN).body("invalid project")
166+
167+
var adults = 0
168+
for (m in project.members) {
169+
if (m.id.isNotBlank() && m.age >= 18)
170+
adults++
171+
}
172+
173+
CoveredTargets.cover("PROJECT")
174+
return ResponseEntity.status(200).body(
175+
if (adults > 0)
176+
"project ${project.code} has $adults adult members"
177+
else
178+
"project ${project.code} has only minors"
179+
)
180+
}
181+
182+
// 8. Project list
183+
@PostMapping(
184+
"/projects",
185+
consumes = ["application/xml"],
186+
produces = ["text/plain"]
187+
)
188+
fun postProjects(@RequestBody list: ProjectList): ResponseEntity<String> {
189+
if (!isValid(list))
190+
return ResponseEntity.status(400).body("")
191+
192+
var members = 0
193+
var hasCode = false
194+
195+
for (p in list.projects) {
196+
if (p.code.isNotBlank())
197+
hasCode = true
198+
for (m in p.members) {
199+
if (m.id.isNotBlank())
200+
members++
201+
}
202+
}
203+
204+
CoveredTargets.cover("PROJECTS")
205+
return ResponseEntity.status(200)
206+
.body(
207+
if (hasCode && members > 0)
208+
"valid projects with $members members"
209+
else
210+
"invalid projects"
211+
)
212+
}
213+
214+
// 9. Person with attributes
215+
@ApiResponses(value = [
216+
ApiResponse(responseCode = "200", description = "Person with attributes is valid"),
217+
ApiResponse(responseCode = "400", description = "Person id or name is blank, or age is negative")
218+
])
219+
@PostMapping(
220+
"/person-with-attr",
221+
consumes = ["application/xml"],
222+
produces = ["text/plain"]
223+
)
224+
fun personWithAttr(@RequestBody person: PersonWithAttr): ResponseEntity<String> {
225+
226+
if (!isValid(person))
227+
return ResponseEntity.status(400).body("invalid person")
228+
229+
CoveredTargets.cover("PERSON_ATTR")
230+
return ResponseEntity.status(200)
231+
.body("person ${person.id} is valid")
232+
}
233+
234+
/* ===================== SCHEMA-LIKE VALIDATION ===================== */
235+
236+
private fun isValid(p: Person): Boolean =
237+
p.name.isNotBlank() && p.age >= 0
238+
239+
private fun isValid(e: Employee): Boolean =
240+
isValid(e.person)
241+
242+
private fun isValid(c: Company): Boolean =
243+
c.name.isNotBlank()
244+
245+
private fun isValid(d: Department): Boolean =
246+
d.name.isNotBlank()
247+
248+
private fun isValid(o: Organization): Boolean =
249+
o.name.isNotBlank()
250+
251+
private fun isValid(p: PersonWithAttr): Boolean =
252+
p.id.isNotBlank() &&
253+
p.name.isNotBlank() &&
254+
p.age >= 0
255+
256+
private fun isValid(p: Project): Boolean =
257+
p.code.isNotBlank()
258+
259+
private fun isValid(pl: ProjectList): Boolean =
260+
true
261+
}
262+
263+
/* ===================== MODELS (JAXB) ===================== */
264+
265+
@XmlRootElement(name = "person")
266+
@XmlAccessorType(XmlAccessType.FIELD)
267+
open class Person(
268+
var name: String = "",
269+
var age: Int = 0
270+
)
271+
272+
@XmlRootElement(name = "employee")
273+
@XmlAccessorType(XmlAccessType.FIELD)
274+
open class Employee(
275+
var person: Person = Person(),
276+
var role: Role = Role.USER
277+
)
278+
279+
@XmlRootElement(name = "company")
280+
@XmlAccessorType(XmlAccessType.FIELD)
281+
open class Company(
282+
var name: String = "",
283+
@field:XmlElement(name = "Person", namespace = "")
284+
var employees: MutableList<Person> = mutableListOf()
285+
)
286+
287+
enum class Role { ADMIN, USER, GUEST }
288+
289+
@XmlRootElement(name = "department")
290+
@XmlAccessorType(XmlAccessType.FIELD)
291+
open class Department(
292+
var name: String = "",
293+
@field:XmlElement(name = "Employee", namespace = "")
294+
var employees: List<Employee> = emptyList(),
295+
@field:XmlElement(name = "Department", namespace = "")
296+
var subDepartments: MutableList<Department> = mutableListOf()
297+
)
298+
299+
@XmlRootElement(name = "organization")
300+
@XmlAccessorType(XmlAccessType.FIELD)
301+
open class Organization(
302+
var name: String = "",
303+
@field:XmlElement(name = "Person", namespace = "")
304+
var people: MutableList<Person> = mutableListOf(),
305+
@field:XmlElement(name = "Employee", namespace = "")
306+
var employees: MutableList<Employee> = mutableListOf(),
307+
@field:XmlElement(name = "Company", namespace = "")
308+
var companies: MutableList<Company> = mutableListOf()
309+
)
310+
311+
@XmlRootElement(name = "personWithAttr")
312+
@XmlAccessorType(XmlAccessType.FIELD)
313+
open class PersonWithAttr(
314+
@XmlAttribute(name = "id")
315+
var id: String = "",
316+
var name: String = "",
317+
var age: Int = 0
318+
)
319+
320+
@XmlRootElement(name = "project")
321+
@XmlAccessorType(XmlAccessType.FIELD)
322+
open class Project(
323+
@XmlAttribute(name = "code")
324+
var code: String = "",
325+
@field:XmlElement(name = "PersonWithAttr", namespace = "")
326+
var members: MutableList<PersonWithAttr> = mutableListOf()
327+
)
328+
329+
@XmlRootElement(name = "projectList")
330+
@XmlAccessorType(XmlAccessType.FIELD)
331+
open class ProjectList(
332+
@field:XmlElement(name = "Project", namespace = "")
333+
var projects: MutableList<Project> = mutableListOf()
334+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.foo.rest.examples.bb.xml
2+
3+
import com.foo.rest.examples.bb.SpringController
4+
5+
class BBXMLController : SpringController(BBXMLApplication::class.java)

0 commit comments

Comments
 (0)