Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,21 @@ abstract class HttpWsTestCaseWriter : ApiTestCaseWriter() {
}
else -> lines.add(".$send(\"$body\")")
}
} else if (bodyParam.isXml()) {

val xml = bodyParam.getValueAsPrintableString(mode = GeneUtils.EscapeMode.XML, targetFormat = format)

when {

format.isCsharp() -> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't add for isCsharp(), as it is deprecated, and no longer in use (although we haven't cleaned the code to remove its occurrences)

lines.append("new StringContent($xml, Encoding.UTF8, \"${bodyParam.contentType()}\")")
}
format.isPython() -> {
lines.add("body = $xml")
}
else -> lines.add(".$send($xml)")
}
} else {
//TODO XML
LoggingUtil.uniqueWarn(log, "Unhandled type for body payload: " + bodyParam.contentType())
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,6 @@ object RestActionBuilderV3 {
body
}

val name = "body"
val description = operation.description ?: null

val bodies = resolvedBody.content?.filter {
Expand Down Expand Up @@ -748,8 +747,12 @@ object RestActionBuilderV3 {
listOf()
}

var gene = getGene("body", obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples)
val deref = obj.schema.`$ref`?.let { ref -> val name = ref.substringAfterLast("/")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hard to read. use better indentation, eg:

val deref = obj.schema.`$ref`?.let { ref -> 
                            val name = ref.substringAfterLast("/")
                            SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) 
                   } ?: obj.schema

also, is name variable used in this block?

SchemaUtils.getReferenceSchema(schemaHolder, currentSchema, ref, messages) } ?: obj.schema

val name = deref?.xml?.name ?: deref?.`$ref`?.substringAfterLast("/") ?: "body"

var gene = getGene(name, obj.schema, schemaHolder,currentSchema, referenceClassDef = null, options = options, messages = messages, examples = examples)

if (resolvedBody.required != true && gene !is OptionalGene) {
gene = OptionalGene(name, gene)
Expand Down Expand Up @@ -944,7 +947,40 @@ object RestActionBuilderV3 {
}

"object" -> {
return createObjectGene(name, schema, schemaHolder,currentSchema, history, referenceClassDef, options, examples, messages)
val properties = schema.properties ?: emptyMap()

val attributeNames = properties
.filterValues { it.xml?.attribute == true }
.keys

if (attributeNames.isNotEmpty()) {
val fields = properties.map { (propName, propSchema) ->
getGene(
propName,
propSchema,
schemaHolder,
currentSchema,
history,
referenceClassDef,
options,
false,
examples,
messages
)
}

return ObjectWithAttributesGene(
name = schema.xml?.name ?: name,
fixedFields = fields,
refType = referenceClassDef,
isFixed = true,
template = null,
additionalFields = mutableListOf(),
attributeNames = attributeNames
)
}else{
return createObjectGene(name, schema, schemaHolder,currentSchema, history, referenceClassDef, options, examples, messages)
}
}
//TODO file is a hack. I want to find a more elegant way of dealing with it (BMR)
//FIXME is this even a standard type???
Expand Down Expand Up @@ -1102,6 +1138,23 @@ object RestActionBuilderV3 {
valueTemplate.copy())
}

val attributeNames = schema.properties
?.filter { (_, propSchema) -> propSchema.xml?.attribute == true }
?.map { it.key }
?: emptyList()

if (attributeNames.isNotEmpty()) {
return ObjectWithAttributesGene(
name = name,
fixedFields = fields,
refType = if (schema is ObjectSchema) referenceTypeName ?: schema.title else null,
isFixed = false,
template = additionalFieldTemplate,
additionalFields = mutableListOf(),
attributeNames = attributeNames.toSet()
)
}

return assembleObjectGeneWithConstraints(
name,
schema,
Expand Down
125 changes: 111 additions & 14 deletions core/src/main/kotlin/org/evomaster/core/search/gene/ObjectGene.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import org.evomaster.core.Lazy
import org.evomaster.core.logging.LoggingUtil
import org.evomaster.core.output.OutputFormat
import org.evomaster.core.problem.graphql.GqlConst
import org.evomaster.core.search.gene.collection.ArrayGene
import org.evomaster.core.search.gene.collection.EnumGene
import org.evomaster.core.search.gene.collection.PairGene
import org.evomaster.core.search.gene.collection.TupleGene
import org.evomaster.core.search.gene.numeric.DoubleGene
import org.evomaster.core.search.gene.numeric.FloatGene
import org.evomaster.core.search.gene.numeric.IntegerGene
import org.evomaster.core.search.gene.wrapper.FlexibleGene
import org.evomaster.core.search.gene.wrapper.OptionalGene
import org.evomaster.core.search.gene.placeholder.CycleObjectGene
Expand Down Expand Up @@ -37,7 +41,7 @@ import java.net.URLEncoder
* - type: string
* - type: integer
*/
class ObjectGene(
open class ObjectGene(
name: String,
val fixedFields: List<out Gene>,
val refType: String? = null,
Expand Down Expand Up @@ -78,6 +82,7 @@ class ObjectGene(
private const val PROB_MODIFY_SIZE_ADDITIONAL_FIELDS = 0.1
// the default maximum size for additional fields
private const val MAX_SIZE_ADDITIONAL_FIELDS = 5
const val contentXMLTag = "#text"

private val mapper = ObjectMapper()
}
Expand Down Expand Up @@ -304,6 +309,99 @@ class ObjectGene(
return mode == null || mode == GeneUtils.EscapeMode.JSON || mode == GeneUtils.EscapeMode.TEXT
}

private fun escapeXmlSafe(s: String): String =
s.replace(Regex("(?<!&)&(?![a-zA-Z]+;)"), "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;")

private fun singularize(n: String): String =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for? also, it would not be working for irregular terms, eg, foot vs feet.
also, if really needed, should go into org.evomaster.core.utils.StringUtils

when {
n.endsWith("s") && n.length > 1 -> n.removeSuffix("s")
else -> n
}.replaceFirstChar { it.uppercase() }

private fun unwrap(v: Any?): Any? =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you want to unwrap genes, need to use Gene.getLeafGene().
also if needed, generic utils on genes should be in GeneUtils

when (v) {
is OptionalGene -> v.gene
else -> v
}

public fun cleanXmlValueString(v: String): String =
v.removeSurrounding("\"").let(::escapeXmlSafe)

private fun getPrintedValue(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPrintedValueForXml?

previousGenes: List<Gene>,
v: Gene,
targetFormat: OutputFormat?
): String =
cleanXmlValueString(
v.getValueAsPrintableString(
previousGenes,
GeneUtils.EscapeMode.XML,
targetFormat
)
)

private fun isPrimitiveGene(value: Any?): Boolean =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the definition of "primitive" here? and is it something specific for XML only? many other genes could have to be included here... eg Regex, Uri, URL, and so on.
Depending on how it is used, and its definition, might need to consider to add a Gene.isPrimitive

when (unwrap(value)) {
is StringGene, is BooleanGene, is IntegerGene, is DoubleGene, is FloatGene,
is String, is Number, is Boolean -> true
else -> false
}

private fun serializeXml(
previousGenes: List<Gene>,
name: String,
value: Any?,
targetFormat: OutputFormat?
): String {

if (name == contentXMLTag) {
return when (val v = unwrap(value)) {
is Gene -> getPrintedValue(previousGenes, v, targetFormat)
null -> ""
else -> escapeXmlSafe(v.toString())
}
}

val v = unwrap(value) ?: return "<$name></$name>"

return when (v) {

is ObjectWithAttributesGene -> {
v.getValueAsPrintableString(previousGenes, GeneUtils.EscapeMode.XML, targetFormat)
}

is ObjectGene -> {
val inner = v.fields.joinToString("") { f ->
serializeXml(previousGenes, f.name, unwrap(f), targetFormat)
}
"<$name>$inner</$name>"
}

is Collection<*> -> v.joinToString("", "<$name>", "</$name>") {
val itemName = singularize(name)
serializeXml(previousGenes, itemName, it, targetFormat)
}

is Map<*, *> -> v.entries.joinToString("", "<$name>", "</$name>") {
serializeXml(previousGenes, it.key.toString(), it.value, targetFormat)
}

is ArrayGene<*> -> {
val itemName = singularize(name)
v.getViewOfElements().joinToString("", "<$name>", "</$name>") {
serializeXml(previousGenes, itemName, it, targetFormat)
}
}

is Gene -> "<$name>${getPrintedValue(previousGenes, v, targetFormat)}</$name>"

else -> "<$name>${cleanXmlValueString(v.toString())}</$name>"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we actually considering cases in which v is not a Gene? how would that happen? leave comment in the code if indeed it is possible

}
}

override fun getValueAsPrintableString(previousGenes: List<Gene>, mode: GeneUtils.EscapeMode?, targetFormat: OutputFormat?, extraCheck: Boolean): String {

Expand Down Expand Up @@ -339,22 +437,21 @@ class ObjectGene(

} else if (mode == GeneUtils.EscapeMode.XML) {

// TODO might have to handle here: <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
/*
Note: this is a very basic support, which should not really depend
much on. Problem is that we would need to access to the XSD schema
to decide when fields should be represented with tags or attributes
*/
val inner = includedFields.joinToString("") { f ->
serializeXml(previousGenes, f.name, unwrap(f), targetFormat)
}

val singleField = includedFields.singleOrNull()
val inlinePrimitive = singleField?.let { isPrimitiveGene(unwrap(it)) } == true

buffer.append(openXml(name))
includedFields.forEach {
//FIXME put back, but then update all broken tests
//buffer.append(openXml(it.name))
buffer.append(it.getValueAsPrintableString(previousGenes, mode, targetFormat))
//buffer.append(closeXml(it.name))
val xmlPayload = if (inlinePrimitive) {
val childValue = getPrintedValue(previousGenes, unwrap(singleField) as Gene, targetFormat)
"<$name>$childValue</$name>"
} else {
"<$name>$inner</$name>"
}
buffer.append(closeXml(name))

buffer.append(xmlPayload)
} else if (mode == GeneUtils.EscapeMode.X_WWW_FORM_URLENCODED) {

buffer.append(includedFields.map {
Expand Down
Loading