@@ -24,6 +24,11 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
2424
2525 companion object {
2626 private val mapper = ObjectMapper ()
27+ /*
28+ HTML entities may be decoded differently by clients/servers, making exact string assertions flaky.
29+ as this relates to asseration not sut, we fix it in the test generation instead of flakiness handling
30+ */
31+ private val HTML_ENTITY_REGEX = Regex (" &(?:#[0-9]+|#x[0-9a-fA-F]+|[A-Za-z][A-Za-z0-9]+);" )
2732 }
2833
2934 protected fun createUniqueResponseVariableName (): String {
@@ -116,6 +121,10 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
116121 // TODO in the call above BODY was used... what's difference from TEXT?
117122 bodyIsString(bodyString, GeneUtils .EscapeMode .TEXT , bodyVarName)
118123 }
124+ // with bodyIsString, it may return null, then add this
125+ if (assertion == null ) {
126+ return
127+ }
119128 if (flakyBodyString == null || flakyBodyString == bodyString) {
120129 lines.add(assertion)
121130 }else {
@@ -165,7 +174,7 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
165174 }
166175 }
167176 ' "' -> {
168- val isString = bodyIsString(bodyString, GeneUtils .EscapeMode .BODY , bodyVarName)
177+ val isString = bodyIsString(bodyString, GeneUtils .EscapeMode .BODY , bodyVarName) ? : return
169178 if (flakyBodyString == null || flakyBodyString == bodyString) {
170179 lines.add(isString)
171180 }else {
@@ -216,7 +225,7 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
216225 object and array.
217226 The rest is either ignored or leads to crash
218227 */
219- val value = bodyIsString(s,GeneUtils .EscapeMode .BODY , responseVariableName)
228+ val value = bodyIsString(s, GeneUtils .EscapeMode .BODY , responseVariableName) ? : return
220229
221230 val fs = flakyBodyString?.trim()
222231 if (fs == null || fs == s) {
@@ -248,7 +257,7 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
248257 payload (Java and Python don't seem to have such issue)
249258 */
250259 // TODO flaky
251- lines.add( bodyIsString(s, GeneUtils .EscapeMode .BODY , responseVariableName))
260+ bodyIsString(s, GeneUtils .EscapeMode .BODY , responseVariableName)?. let { lines.add(it) }
252261 }
253262 else -> throw IllegalStateException (" Format not supported yet: $format " )
254263 }
@@ -266,7 +275,8 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
266275 TODO should do check for when there are spaces in the field name
267276 TODO also need more tests to check all these edge cases
268277 */
269- format.isJavaOrKotlin() -> if (fieldPath.isEmpty()) " " else if (fieldPath.startsWith(" '" )) " $fieldPath ." else " '$fieldPath '."
278+ // field path starts with [ is an array index, do not need additional quote
279+ format.isJavaOrKotlin() -> if (fieldPath.isEmpty()) " " else if (fieldPath.startsWith(" '" ) || fieldPath.startsWith(" [" )) " $fieldPath ." else " '$fieldPath '."
270280 format.isJavaScript() -> if (fieldPath.isEmpty()) " " else " ${if (fieldPath.startsWith(" [" ) || fieldPath.startsWith(" ." )) " " else " ." }$fieldPath "
271281 format.isCsharp() -> if (fieldPath.isEmpty()) " " else " ${if (fieldPath.startsWith(" [" )) " " else " ." }$fieldPath "
272282 format.isPython() -> if (fieldPath.isEmpty()) " " else " ${if (fieldPath.startsWith(" [" ) || fieldPath.startsWith(" ." )) " " else " ." }$fieldPath "
@@ -374,9 +384,13 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
374384 val left = when (value) {
375385 is Boolean -> " equalTo($value )"
376386 is Number -> " numberMatches(${handleNumberInJavaOrKotlinTest(value)} )"
377- is String -> " containsString(" +
378- " \" ${GeneUtils .applyEscapes(value as String , mode = GeneUtils .EscapeMode .ASSERTION , format = format)} " +
379- " \" )"
387+ is String -> {
388+ val content = GeneUtils .applyEscapes(value, mode = GeneUtils .EscapeMode .ASSERTION , format = format)
389+ val assertionContent = handleHtmlEntity(content) ? : return
390+ " containsString(" +
391+ " \" $assertionContent " +
392+ " \" )"
393+ }
380394 else -> throw IllegalStateException (" Unsupported type: ${value::class } " )
381395 }
382396 if (isSuitableToPrint(left)) {
@@ -491,6 +505,9 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
491505 val items = (list as List <String >).joinToString {
492506 " \" ${GeneUtils .applyEscapes(it, mode = GeneUtils .EscapeMode .ASSERTION , format = format)} \" "
493507 }
508+ if (! isSuitableToPrint(items)) {
509+ return
510+ }
494511
495512 if (flakyList != null && (! flakyList.containsAll(list) || flakyList.size != list.size)) {
496513
@@ -590,16 +607,18 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
590607 return instruction
591608 }
592609
593- protected fun bodyIsString (bodyString : String , mode : GeneUtils .EscapeMode , responseVariableName : String? ): String {
610+ protected fun bodyIsString (bodyString : String , mode : GeneUtils .EscapeMode , responseVariableName : String? ): String? {
594611
595- val content = GeneUtils .applyEscapes(bodyString, mode, format = format)
612+ val originalContent = GeneUtils .applyEscapes(bodyString, mode, format = format)
613+ val content = handleHtmlEntity(originalContent) ? : return null
596614
597615 if (format.isJavaOrKotlin()) {
598616 return " .body(containsString(\" $content \" ))"
599617 }
600618
601619 if (format.isJavaScript()) {
602- return " expect($responseVariableName .text).toBe(\" $content \" );"
620+ return " expect($responseVariableName .text).toContain(\" $content \" );"
621+
603622 }
604623
605624 if (format.isCsharp()) {
@@ -608,7 +627,8 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
608627 content.startsWith(" \\\" " ) -> content.substring(2 , content.length - 2 )
609628 else -> content
610629 }
611- return " Assert.True($responseVariableName == \" $k \" );"
630+ return " Assert.True($responseVariableName .Contains(\" $k \" ));"
631+
612632 }
613633
614634 if (format.isPython()) {
@@ -618,6 +638,22 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
618638 throw IllegalStateException (" Not supported format $format " )
619639 }
620640
641+ /* *
642+ * handle assertion text that may contain HTML entities.
643+ * If none are found, the original text is returned.
644+ * Otherwise, returns the first non-empty text segment.
645+ * Returns null if no non-empty segment can be extracted.
646+ */
647+ private fun handleHtmlEntity (content : String ): String? {
648+ if (! HTML_ENTITY_REGEX .containsMatchIn(content)) {
649+ return content
650+ }
651+
652+ return HTML_ENTITY_REGEX .split(content)
653+ .map { it.trim() }
654+ .firstOrNull { it.isNotEmpty() }
655+ }
656+
621657
622658 /* *
623659 * Some fields might lead to flackiness, eg assertions on timestamps.
@@ -646,6 +682,7 @@ abstract class ApiTestCaseWriter : TestCaseWriter() {
646682 return (
647683 printableContent != " null" // TODO not so sure about this one... need to double-check
648684 && ! printableContent.contains(" logged" )
685+ && ! HTML_ENTITY_REGEX .containsMatchIn(printableContent)
649686 // is this for IP host:port addresses?
650687 && ! printableContent.contains(""" \w+:\d{4,5}""" .toRegex()))
651688 }
0 commit comments