Skip to content

Commit 1c2e59d

Browse files
authored
Merge pull request #1548 from WebFuzzing/allof-examples
Allof examples
2 parents b6726ec + 430b05a commit 1c2e59d

4 files changed

Lines changed: 184 additions & 10 deletions

File tree

core/src/main/kotlin/org/evomaster/core/problem/rest/builder/RestActionBuilderV3.kt

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,12 +1369,11 @@ object RestActionBuilderV3 {
13691369
}
13701370

13711371
/**
1372-
*
13731372
* handled constraints include
13741373
* - allOf
13751374
* - anyOf
13761375
* - oneOf
1377-
* - not (OpenAPI not support this yet)
1376+
* - not
13781377
*/
13791378
private fun assembleObjectGeneWithConstraints(
13801379
name: String,
@@ -1394,37 +1393,61 @@ object RestActionBuilderV3 {
13941393
https://spec.openapis.org/oas/latest.html#discriminator-object
13951394
*/
13961395

1397-
if (!options.enableConstraintHandling)
1396+
if (!options.enableConstraintHandling){
13981397
return assembleObjectGene(name, options, schema, fields, additionalFieldTemplate, referenceTypeName, examples, messages)
1398+
}
1399+
1400+
/*
1401+
THIS IS VERY TRICKY
1402+
in theory, the use of allOf/anyOf/oneOf/not provides a rich language where many different kinds of
1403+
constraints can be specified.
1404+
this is the case when "const" is used in schemas to specify particular values, e.g., oneOf could be then
1405+
used to specify only particular combinations of field values.
1406+
the use of "const" is also quite tricky with "not", as can use to say some specific value combinations are not valid.
1407+
furthermore, those keywords are NOT mutually exclusive.
1408+
1409+
support all possible constraint would be a GIGANTIC piece of work... yet, these kinds of constraints do
1410+
not seem so common, apart for allOf on merging fields.
1411+
TODO we should have full support, but not high priority task
1412+
*/
1413+
1414+
/*
1415+
A further complication is how to use, and interpret, "examples" values (if any).
1416+
Eg. what to nested referenced $ref objects that have on examples?
1417+
should those be merged/hanled like the fields?
1418+
TODO possibly something to handle (but again, not high priority)
1419+
*/
13991420

14001421
val allOf = schema.allOf?.map { s->
1401-
//createObjectGene(name, s, swagger, history, null, enableConstraintHandling)
14021422
getGene(name, s, schemaHolder,currentSchema, history, null, options, messages = messages, examples = examples)
14031423
}
14041424

14051425
val anyOf = schema.anyOf?.map { s->
1406-
//createObjectGene(name, s, swagger, history, null, enableConstraintHandling)
14071426
getGene(name, s, schemaHolder,currentSchema, history, null, options, messages = messages, examples = examples)
14081427
}
14091428

14101429
if (!allOf.isNullOrEmpty() && !anyOf.isNullOrEmpty()){
1411-
messages.add("Cannot handle allOf and oneOf at same time for a schema with name $name")
1412-
return assembleObjectGene(name, options, schema, fields, additionalFieldTemplate, referenceTypeName, examples, messages)
1430+
messages.add("Currently cannot handle allOf and oneOf at same time for a schema with name $name")
14131431
}
14141432

14151433
val oneOf = schema.oneOf?.map { s->
1416-
//createObjectGene(name, s, swagger, history, null, enableConstraintHandling)
14171434
getGene(name, s, schemaHolder,currentSchema, history, null, options = options, messages = messages)
14181435
}
14191436

14201437
if (!oneOf.isNullOrEmpty() && (!allOf.isNullOrEmpty() || !anyOf.isNullOrEmpty())){
1421-
messages.add("cannot handle oneOf and allOf/oneOf at same time for a schema with name $name")
1422-
return assembleObjectGene(name, options, schema, fields, additionalFieldTemplate, referenceTypeName, examples, messages)
1438+
messages.add("Currently cannot handle oneOf and allOf/oneOf at same time for a schema with name $name")
14231439
}
14241440

14251441
if (!allOf.isNullOrEmpty()){
1442+
1443+
if(allOf.size == 1 && fields.isEmpty()){
1444+
//just use it as it is
1445+
return allOf[0]
1446+
}
1447+
14261448
val allFields = allOf.mapNotNull {
14271449
when (it) {
1450+
is ChoiceGene<*> -> extractOriginalObject(it)?.fields
14281451
is ObjectGene -> it.fields
14291452
else -> null
14301453
}
@@ -1495,6 +1518,19 @@ object RestActionBuilderV3 {
14951518
//TODO not
14961519
}
14971520

1521+
private fun extractOriginalObject(choice: ChoiceGene<*>): ObjectGene? {
1522+
/*
1523+
this only makes sense if we are in this case:
1524+
Choice( Choice(examples), Object)
1525+
in which we would return Object, null otherwise
1526+
*/
1527+
val children = choice.getViewOfChildren()
1528+
if(children.size != 2 || children.none { it is UserExamplesGene && it.isUsedForExamples() }){
1529+
return null
1530+
}
1531+
return children.find { it is ObjectGene } as ObjectGene?
1532+
}
1533+
14981534
/**
14991535
* assemble ObjectGene based on [fields] and [additionalFieldTemplate]
15001536
*/

core/src/test/kotlin/org/evomaster/core/problem/rest/RestActionBuilderV3Test.kt

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,4 +2289,62 @@ class RestActionBuilderV3Test{
22892289
assertTrue(withFooAgain.contains("xfoo"), withFooAgain)
22902290
assertTrue(withFooAgain.contains("ybar"), withFooAgain)
22912291
}
2292+
2293+
2294+
@Test
2295+
fun testSingleAllOf() {
2296+
2297+
val path = "/swagger/artificial/defaultandexamples/constraints/single_allof.yaml"
2298+
2299+
val a = loadAndAssertActions(path, 1, RestActionBuilderV3.Options(probUseExamples = 1.0))
2300+
.values.first()
2301+
2302+
val rand = Randomness()
2303+
a.doInitialize(rand)
2304+
2305+
val found = mutableSetOf<String>()
2306+
2307+
for (i in 0..100) {
2308+
a.randomize(rand, false)
2309+
val s = a.seeTopGenes().first().getValueAsRawString()
2310+
found.add(s)
2311+
if(found.size == 2){
2312+
break
2313+
}
2314+
}
2315+
2316+
//both examples must be there
2317+
assertEquals(2, found.size)
2318+
assertTrue(found.any{it.contains("Foo")})
2319+
assertTrue(found.any{it.contains("Bar")})
2320+
}
2321+
2322+
2323+
@Test
2324+
fun testMultiAllOf() {
2325+
2326+
val path = "/swagger/artificial/defaultandexamples/constraints/multi_allof.yaml"
2327+
2328+
val a = loadAndAssertActions(path, 1, RestActionBuilderV3.Options(probUseExamples = 1.0))
2329+
.values.first()
2330+
2331+
val rand = Randomness()
2332+
a.doInitialize(rand)
2333+
2334+
val found = mutableSetOf<String>()
2335+
2336+
for (i in 0..100) {
2337+
a.randomize(rand, false)
2338+
val s = a.seeTopGenes().first().getValueAsRawString()
2339+
found.add(s)
2340+
}
2341+
2342+
/*
2343+
Only 1 example should be there, the one declared at top level.
2344+
TODO: maybe in future we merge examples as well... in that case, then
2345+
would need to update this test
2346+
*/
2347+
assertTrue(found.any{it.contains("Foo")})
2348+
assertEquals(1, found.size)
2349+
}
22922350
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
openapi: 3.1.0
3+
info:
4+
title: a schema
5+
version: 1.0.0
6+
servers:
7+
- url: "/v2"
8+
paths:
9+
"/api":
10+
post:
11+
requestBody:
12+
required: true
13+
content:
14+
application/json:
15+
schema:
16+
allOf:
17+
- type: object
18+
additionalProperties: false
19+
properties:
20+
id:
21+
type: integer
22+
name:
23+
type: string
24+
examples:
25+
- id : 42
26+
name: Bar
27+
- $ref: '#/components/schemas/Dto'
28+
examples:
29+
Foo:
30+
value:
31+
id: 123
32+
name: Foo
33+
extra: some extra
34+
responses:
35+
'200':
36+
description: OK
37+
components:
38+
schemas:
39+
Dto:
40+
type: object
41+
additionalProperties: false
42+
properties:
43+
extra:
44+
type: string
45+
example:
46+
extra: Hello
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
openapi: 3.1.0
3+
info:
4+
title: a schema
5+
version: 1.0.0
6+
servers:
7+
- url: "/v2"
8+
paths:
9+
"/api":
10+
post:
11+
requestBody:
12+
required: true
13+
content:
14+
application/json:
15+
schema:
16+
allOf:
17+
- type: object
18+
additionalProperties: false
19+
properties:
20+
id:
21+
type: integer
22+
name:
23+
type: string
24+
examples:
25+
- id : 42
26+
name: Bar
27+
examples:
28+
Foo:
29+
value:
30+
id: 123
31+
name: Foo
32+
responses:
33+
'200':
34+
description: OK

0 commit comments

Comments
 (0)