Skip to content

Commit 401da70

Browse files
authored
Add support for @RequestHeader annotation (#2504)
1 parent d3180df commit 401da70

File tree

1 file changed

+129
-6
lines changed

1 file changed

+129
-6
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt

+129-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.utbot.framework.plugin.api.util
22

33
import org.utbot.common.tryLoadClass
4+
import org.utbot.common.withToStringThreadLocalReentrancyGuard
5+
import org.utbot.framework.plugin.api.isNotNull
46
import org.utbot.framework.plugin.api.ClassId
57
import org.utbot.framework.plugin.api.MethodId
68
import org.utbot.framework.plugin.api.UtArrayModel
@@ -10,6 +12,7 @@ import org.utbot.framework.plugin.api.UtModel
1012
import org.utbot.framework.plugin.api.UtNullModel
1113
import org.utbot.framework.plugin.api.UtPrimitiveModel
1214
import org.utbot.framework.plugin.api.UtSpringContextModel
15+
import java.util.Optional
1316

1417
object SpringModelUtils {
1518
val autowiredClassId = ClassId("org.springframework.beans.factory.annotation.Autowired")
@@ -121,6 +124,7 @@ object SpringModelUtils {
121124
///region spring-web
122125
private val requestMappingClassId = ClassId("org.springframework.web.bind.annotation.RequestMapping")
123126
private val pathVariableClassId = ClassId("org.springframework.web.bind.annotation.PathVariable")
127+
private val requestHeaderClassId = ClassId("org.springframework.web.bind.annotation.RequestHeader")
124128
private val requestBodyClassId = ClassId("org.springframework.web.bind.annotation.RequestBody")
125129
private val requestParamClassId = ClassId("org.springframework.web.bind.annotation.RequestParam")
126130
private val uriComponentsBuilderClassId = ClassId("org.springframework.web.util.UriComponentsBuilder")
@@ -141,6 +145,7 @@ object SpringModelUtils {
141145
private val viewResultMatchersClassId = ClassId("org.springframework.test.web.servlet.result.ViewResultMatchers")
142146
private val mockHttpServletRequestBuilderClassId = ClassId("org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder")
143147
private val modelAndViewClassId = ClassId("org.springframework.web.servlet.ModelAndView")
148+
private val httpHeaderClassId = ClassId("org.springframework.http.HttpHeaders")
144149

145150
private val objectMapperClassId = ClassId("com.fasterxml.jackson.databind.ObjectMapper")
146151

@@ -314,10 +319,70 @@ object SpringModelUtils {
314319
)
315320
)
316321

317-
// TODO #2462 (support @RequestParam, @RequestHeader, @CookieValue, @RequestAttribute, ...)
322+
val headersContentModel = createHeadersContentModel(methodId, arguments, idGenerator)
323+
324+
requestBuilderModel = addHeadersToRequestBuilderModel(headersContentModel, requestBuilderModel, idGenerator)
325+
326+
// TODO #2462 (support @CookieValue, @RequestAttribute, ...)
318327
return addContentToRequestBuilderModel(methodId, arguments, requestBuilderModel, idGenerator)
319328
}
320329

330+
private fun addHeadersToRequestBuilderModel(
331+
headersContentModel: UtAssembleModel,
332+
requestBuilderModel: UtAssembleModel,
333+
idGenerator: () -> Int
334+
): UtAssembleModel {
335+
@Suppress("NAME_SHADOWING")
336+
var requestBuilderModel = requestBuilderModel
337+
338+
if (headersContentModel.modificationsChain.isEmpty()) {
339+
return requestBuilderModel
340+
}
341+
342+
val headers = UtAssembleModel(
343+
id = idGenerator(),
344+
classId = httpHeaderClassId,
345+
modelName = "headers",
346+
instantiationCall = UtExecutableCallModel(
347+
instance = null,
348+
executable = constructorId(httpHeaderClassId),
349+
params = emptyList(),
350+
),
351+
modificationsChainProvider = {
352+
listOf(
353+
UtExecutableCallModel(
354+
instance = this,
355+
executable = methodId(
356+
classId = httpHeaderClassId,
357+
name = "setAll",
358+
returnType = voidClassId,
359+
arguments = arrayOf(Map::class.java.id),
360+
),
361+
params = listOf(headersContentModel)
362+
)
363+
)
364+
}
365+
)
366+
367+
requestBuilderModel = UtAssembleModel(
368+
id = idGenerator(),
369+
classId = mockHttpServletRequestBuilderClassId,
370+
modelName = "requestBuilder",
371+
instantiationCall = UtExecutableCallModel(
372+
instance = requestBuilderModel,
373+
executable = MethodId(
374+
classId = mockHttpServletRequestBuilderClassId,
375+
name = "headers",
376+
returnType = mockHttpServletRequestBuilderClassId,
377+
parameters = listOf(httpHeaderClassId)
378+
),
379+
params = listOf(headers)
380+
)
381+
)
382+
383+
return requestBuilderModel
384+
}
385+
321386
private fun addContentToRequestBuilderModel(
322387
methodId: MethodId,
323388
arguments: List<UtModel>,
@@ -330,7 +395,6 @@ object SpringModelUtils {
330395
@Suppress("UNCHECKED_CAST")
331396
param.getAnnotation(requestBodyClassId.jClass as Class<out Annotation>) ?: return@forEach
332397

333-
// TODO filter out `null` and `Optional.empty()` values of `arg`
334398
val mediaTypeModel = UtAssembleModel(
335399
id = idGenerator(),
336400
classId = mediaTypeClassId,
@@ -403,14 +467,48 @@ object SpringModelUtils {
403467
return requestBuilderModel
404468
}
405469

470+
private fun createHeadersContentModel(
471+
methodId: MethodId,
472+
arguments: List<UtModel>,
473+
idGenerator: () -> Int,
474+
): UtAssembleModel {
475+
// Converts Map models values to String because `HttpHeaders.setAll(...)` method takes `Map<String, String>`
476+
val headersContent = collectArgumentsWithAnnotationModels(methodId, requestHeaderClassId, arguments)
477+
.mapValues { (_, model) -> convertModelValueToString(model) }
478+
479+
return UtAssembleModel(
480+
id = idGenerator(),
481+
classId = Map::class.java.id,
482+
modelName = "headersContent",
483+
instantiationCall = UtExecutableCallModel(
484+
instance = null,
485+
executable = HashMap::class.java.getConstructor().executableId,
486+
params = emptyList()
487+
),
488+
modificationsChainProvider = {
489+
headersContent.map { (name, value) ->
490+
UtExecutableCallModel(
491+
instance = this,
492+
// Actually it is a `Map<String, String>`, but we use `Object::class.java` to avoid concrete failures
493+
executable = Map::class.java.getMethod(
494+
"put",
495+
Object::class.java,
496+
Object::class.java
497+
).executableId,
498+
params = listOf(UtPrimitiveModel(name), value)
499+
)
500+
}
501+
}
502+
)
503+
}
504+
406505
private fun createPathVariablesModel(
407506
methodId: MethodId,
408507
arguments: List<UtModel>,
409508
idGenerator: () -> Int
410509
): UtAssembleModel {
411510
val pathVariables = collectArgumentsWithAnnotationModels(methodId, pathVariableClassId, arguments)
412511

413-
// TODO filter out `null` and `Optional.empty()` values of `arg`
414512
return UtAssembleModel(
415513
id = idGenerator(),
416514
classId = Map::class.java.id,
@@ -443,7 +541,6 @@ object SpringModelUtils {
443541
): List< Pair<UtPrimitiveModel, UtAssembleModel> > {
444542
val requestParams = collectArgumentsWithAnnotationModels(methodId, requestParamClassId, arguments)
445543

446-
// TODO filter out `null` and `Optional.empty()` values of `arg`
447544
return requestParams.map { (name, value) ->
448545
Pair(UtPrimitiveModel(name),
449546
UtAssembleModel(
@@ -479,20 +576,46 @@ object SpringModelUtils {
479576
annotationClassId: ClassId,
480577
arguments: List<UtModel>
481578
): MutableMap<String, UtModel> {
482-
val argumentsModels = mutableMapOf<String, UtModel>()
579+
fun UtModel.isEmptyOptional(): Boolean {
580+
return classId == Optional::class.java.id && this is UtAssembleModel &&
581+
instantiationCall is UtExecutableCallModel && instantiationCall.executable.name == "empty"
582+
}
483583

584+
val argumentsModels = mutableMapOf<String, UtModel>()
484585
methodId.method.parameters.zip(arguments).forEach { (param, arg) ->
485586
@Suppress("UNCHECKED_CAST") val paramAnnotation =
486587
param.getAnnotation(annotationClassId.jClass as Class<out Annotation>) ?: return@forEach
487588
val name = (annotationClassId.jClass.getMethod("name").invoke(paramAnnotation) as? String).orEmpty()
488589
.ifEmpty { annotationClassId.jClass.getMethod("value").invoke(paramAnnotation) as? String }.orEmpty()
489590
.ifEmpty { param.name }
490-
argumentsModels[name] = arg
591+
592+
if (arg.isNotNull() && !arg.isEmptyOptional()) {
593+
argumentsModels[name] = arg
594+
}
491595
}
492596

493597
return argumentsModels
494598
}
495599

600+
/**
601+
* Converts the model into a form that is understandable for annotations.
602+
* Example: UtArrayModel([UtPrimitiveModel("a"), UtPrimitiveModel("b"), UtPrimitiveModel("c")]) -> UtPrimitiveModel("a, b, c")
603+
*
604+
* There is known issue when using `model.toString()` is not reliable:
605+
* https://github.com/UnitTestBot/UTBotJava/issues/2505
606+
* This issue may be improved in the future.
607+
*/
608+
private fun convertModelValueToString(model: UtModel): UtModel {
609+
return UtPrimitiveModel(
610+
when(model){
611+
is UtArrayModel -> withToStringThreadLocalReentrancyGuard {
612+
(0 until model.length).map { model.stores[it] ?: model.constModel }.joinToString(", ")
613+
}
614+
else -> model.toString()
615+
}
616+
)
617+
}
618+
496619
private fun createUrlTemplateModel(
497620
pathVariablesModel: UtAssembleModel,
498621
requestParamModel: List<Pair<UtPrimitiveModel, UtAssembleModel>>,

0 commit comments

Comments
 (0)