1
1
package org.utbot.framework.plugin.api.util
2
2
3
3
import org.utbot.common.tryLoadClass
4
+ import org.utbot.common.withToStringThreadLocalReentrancyGuard
5
+ import org.utbot.framework.plugin.api.isNotNull
4
6
import org.utbot.framework.plugin.api.ClassId
5
7
import org.utbot.framework.plugin.api.MethodId
6
8
import org.utbot.framework.plugin.api.UtArrayModel
@@ -10,6 +12,7 @@ import org.utbot.framework.plugin.api.UtModel
10
12
import org.utbot.framework.plugin.api.UtNullModel
11
13
import org.utbot.framework.plugin.api.UtPrimitiveModel
12
14
import org.utbot.framework.plugin.api.UtSpringContextModel
15
+ import java.util.Optional
13
16
14
17
object SpringModelUtils {
15
18
val autowiredClassId = ClassId (" org.springframework.beans.factory.annotation.Autowired" )
@@ -121,6 +124,7 @@ object SpringModelUtils {
121
124
// /region spring-web
122
125
private val requestMappingClassId = ClassId (" org.springframework.web.bind.annotation.RequestMapping" )
123
126
private val pathVariableClassId = ClassId (" org.springframework.web.bind.annotation.PathVariable" )
127
+ private val requestHeaderClassId = ClassId (" org.springframework.web.bind.annotation.RequestHeader" )
124
128
private val requestBodyClassId = ClassId (" org.springframework.web.bind.annotation.RequestBody" )
125
129
private val requestParamClassId = ClassId (" org.springframework.web.bind.annotation.RequestParam" )
126
130
private val uriComponentsBuilderClassId = ClassId (" org.springframework.web.util.UriComponentsBuilder" )
@@ -141,6 +145,7 @@ object SpringModelUtils {
141
145
private val viewResultMatchersClassId = ClassId (" org.springframework.test.web.servlet.result.ViewResultMatchers" )
142
146
private val mockHttpServletRequestBuilderClassId = ClassId (" org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder" )
143
147
private val modelAndViewClassId = ClassId (" org.springframework.web.servlet.ModelAndView" )
148
+ private val httpHeaderClassId = ClassId (" org.springframework.http.HttpHeaders" )
144
149
145
150
private val objectMapperClassId = ClassId (" com.fasterxml.jackson.databind.ObjectMapper" )
146
151
@@ -314,10 +319,70 @@ object SpringModelUtils {
314
319
)
315
320
)
316
321
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, ...)
318
327
return addContentToRequestBuilderModel(methodId, arguments, requestBuilderModel, idGenerator)
319
328
}
320
329
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
+
321
386
private fun addContentToRequestBuilderModel (
322
387
methodId : MethodId ,
323
388
arguments : List <UtModel >,
@@ -330,7 +395,6 @@ object SpringModelUtils {
330
395
@Suppress(" UNCHECKED_CAST" )
331
396
param.getAnnotation(requestBodyClassId.jClass as Class <out Annotation >) ? : return @forEach
332
397
333
- // TODO filter out `null` and `Optional.empty()` values of `arg`
334
398
val mediaTypeModel = UtAssembleModel (
335
399
id = idGenerator(),
336
400
classId = mediaTypeClassId,
@@ -403,14 +467,48 @@ object SpringModelUtils {
403
467
return requestBuilderModel
404
468
}
405
469
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
+
406
505
private fun createPathVariablesModel (
407
506
methodId : MethodId ,
408
507
arguments : List <UtModel >,
409
508
idGenerator : () -> Int
410
509
): UtAssembleModel {
411
510
val pathVariables = collectArgumentsWithAnnotationModels(methodId, pathVariableClassId, arguments)
412
511
413
- // TODO filter out `null` and `Optional.empty()` values of `arg`
414
512
return UtAssembleModel (
415
513
id = idGenerator(),
416
514
classId = Map ::class .java.id,
@@ -443,7 +541,6 @@ object SpringModelUtils {
443
541
): List < Pair <UtPrimitiveModel , UtAssembleModel > > {
444
542
val requestParams = collectArgumentsWithAnnotationModels(methodId, requestParamClassId, arguments)
445
543
446
- // TODO filter out `null` and `Optional.empty()` values of `arg`
447
544
return requestParams.map { (name, value) ->
448
545
Pair (UtPrimitiveModel (name),
449
546
UtAssembleModel (
@@ -479,20 +576,46 @@ object SpringModelUtils {
479
576
annotationClassId : ClassId ,
480
577
arguments : List <UtModel >
481
578
): 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
+ }
483
583
584
+ val argumentsModels = mutableMapOf<String , UtModel >()
484
585
methodId.method.parameters.zip(arguments).forEach { (param, arg) ->
485
586
@Suppress(" UNCHECKED_CAST" ) val paramAnnotation =
486
587
param.getAnnotation(annotationClassId.jClass as Class <out Annotation >) ? : return @forEach
487
588
val name = (annotationClassId.jClass.getMethod(" name" ).invoke(paramAnnotation) as ? String ).orEmpty()
488
589
.ifEmpty { annotationClassId.jClass.getMethod(" value" ).invoke(paramAnnotation) as ? String }.orEmpty()
489
590
.ifEmpty { param.name }
490
- argumentsModels[name] = arg
591
+
592
+ if (arg.isNotNull() && ! arg.isEmptyOptional()) {
593
+ argumentsModels[name] = arg
594
+ }
491
595
}
492
596
493
597
return argumentsModels
494
598
}
495
599
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
+
496
619
private fun createUrlTemplateModel (
497
620
pathVariablesModel : UtAssembleModel ,
498
621
requestParamModel : List <Pair <UtPrimitiveModel , UtAssembleModel >>,
0 commit comments