/
ExpectationsWriter.kt
159 lines (139 loc) · 7.93 KB
/
ExpectationsWriter.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package org.evomaster.core.output
import com.google.gson.Gson
import io.swagger.models.Swagger
import org.evomaster.core.output.service.PartialOracles
import org.evomaster.core.problem.rest.RestCallAction
import org.evomaster.core.problem.rest.RestCallResult
import org.evomaster.core.search.gene.GeneUtils
import javax.ws.rs.core.MediaType
class ExpectationsWriter {
private var format: OutputFormat = OutputFormat.JAVA_JUNIT_4
private val expectationsMasterSwitch = "expectationsMasterSwitch"
private val responseStructureOracle = "responseStructureOracle"
private lateinit var swagger: Swagger
private lateinit var partialOracles: PartialOracles
fun setFormat(format: OutputFormat){
this.format = format
}
fun setSwagger(swagger: Swagger){
this.swagger = swagger
}
fun setPartialOracles(partialOracles: PartialOracles){
this.partialOracles = partialOracles
}
fun addDeclarations(lines: Lines){
lines.addEmpty()
when{
format.isJava() -> lines.append("ExpectationHandler expectationHandler = expectationHandler()")
format.isKotlin() -> lines.append("val expectationHandler: ExpectationHandler = expectationHandler()")
}
lines.indented {
lines.add(".expect($expectationsMasterSwitch)")
if (format.isJava()) lines.append(";")
}
}
fun handleGenericFirstLine(call: RestCallAction, lines: Lines, res: RestCallResult, name: String, header: String){
lines.addEmpty()
when {
format.isKotlin() -> lines.append("val $name: ValidatableResponse = ")
format.isJava() -> lines.append("ValidatableResponse $name = ")
}
lines.append("given()$header")
}
fun handleExpectationSpecificLines(call: RestCallAction, lines: Lines, res: RestCallResult, name: String){
lines.addEmpty()
when{
format.isKotlin() -> lines.add("val json_$name: JsonPath = $name")
format.isJava() -> lines.add("JsonPath json_$name = $name")
}
lines.append(".extract().response().jsonPath()")
if(format.isJava()) {lines.append(";")}
}
fun handleExpectations(call: RestCallAction, lines: Lines, result: RestCallResult, active: Boolean, name: String) {
/*
TODO: This is a WiP to show the basic idea of the expectations:
An exception is thrown ONLY if the expectations are set to active.
If inactive, the condition will still be processed (with a goal to later adding to summaries or
other information processing/handling that might be needed), but it does not cause
the test case to fail regardless of truth value.
The example below aims to show this behaviour and provide a reminder.
As it is still work in progress, expect quite significant changes to this.
*/
lines.add("expectationHandler")
lines.indented {
lines.add(".expect()")
addExpectationsWithoutObjects(result, lines, name)
partialOracles.responseStructure(call, lines, result, name)
if (format.isJava()) { lines.append(";")}
}
}
private fun addExpectationsWithoutObjects(result: RestCallResult, lines: Lines, name: String) {
if (result.getBodyType() != null) {
// if there is a body, add expectations based on the body type. Right now only application/json is supported
when {
result.getBodyType()!!.isCompatible(MediaType.APPLICATION_JSON_TYPE) -> {
when (result.getBody()?.first()) {
'[' -> {
// This would be run if the JSON contains an array of objects
val resContents = Gson().fromJson(result.getBody(), ArrayList::class.java)
val printableTh = "numbersMatch(" +
"json_$name.getJsonObject(\"size\"), " +
"${resContents.size})"
lines.add(".that($expectationsMasterSwitch, ($printableTh))")
//TODO: individual objects in this collection also need handling
resContents.forEachIndexed { index, result ->
val fieldName = "get($index)"
val printableElement = handleFieldValuesExpect(name, fieldName, result)
if (printableElement != "null"
&& printableElement != TestCaseWriter.NOT_COVERED_YET
&& !printableTh.contains("logged")
) {
lines.add(".that($expectationsMasterSwitch, $printableElement)")
}
}
}
'{' -> {
// This would be run if the JSON contains a single object
val resContents = Gson().fromJson(result.getBody(), Object::class.java)
(resContents as Map<*, *>).keys
.filter { !it.toString().contains("timestamp") && !it.toString().contains("cache") }
.forEach {
val printableTh = handleFieldValuesExpect(name, "\'${it.toString()}\'", resContents[it])
if (printableTh != "null"
&& printableTh != TestCaseWriter.NOT_COVERED_YET
&& !printableTh.contains("logged") //again, unpleasant, but IDs logged as part of the error message are a problem
//TODO: find a more elegant way to deal with IDs, object refs, timestamps, etc.
) {
lines.add(".that($expectationsMasterSwitch, $printableTh)")
}
}
}
else -> {
// this shouldn't be run if the JSON is okay. Panic! Update: could also be null. Pause, then panic!
if(result.getBody() != null) lines.add(".that($expectationsMasterSwitch, subStringsMatch($name.extract().response().asString(), \"${GeneUtils.applyEscapes(result.getBody().toString(), mode = GeneUtils.EscapeMode.ASSERTION, format = format)}\"))")
else lines.add(".that($expectationsMasterSwitch, json_$name.toString().isEmpty())")
}
}
}
result.getBodyType()!!.isCompatible(MediaType.TEXT_PLAIN_TYPE) -> {
if(result.getBody() != null) lines.add(".that($expectationsMasterSwitch, subStringsMatch($name.extract().response().asString(), \"${GeneUtils.applyEscapes(result.getBody().toString(), mode = GeneUtils.EscapeMode.ASSERTION, format = format)}\"))")
else lines.add(".that($expectationsMasterSwitch, json_$name.toString().isEmpty())")
}
}
}
}
private fun handleFieldValuesExpect(objectName: String, fieldName: String, resContentsItem: Any?): String{
if (resContentsItem == null) {
return TestCaseWriter.NOT_COVERED_YET
}
else{
when(resContentsItem::class) {
Double::class -> return "numbersMatch(json_$objectName.getJsonObject(\"$fieldName\")," +
" ${resContentsItem as Double})"
String::class -> return "subStringsMatch(json_$objectName.getJsonObject(\"$fieldName\")," +
"\"${GeneUtils.applyEscapes((resContentsItem as String), mode = GeneUtils.EscapeMode.EXPECTATION, format = format)}\")"
else -> return TestCaseWriter.NOT_COVERED_YET
}
}
}
}