aboutsummaryrefslogtreecommitdiffstats
path: root/ms/blueprintsprocessor/application/src
diff options
context:
space:
mode:
authorebo <eliezio.oliveira@est.tech>2019-08-16 13:18:47 +0000
committerebo <eliezio.oliveira@est.tech>2019-08-16 13:18:47 +0000
commitff445e6d950048c092c0a7ccd8a0a04cb18869b5 (patch)
treef73f0ccbe16c1d74c2b230b7990c4fc8b7505973 /ms/blueprintsprocessor/application/src
parent11dbd17296d9572a04bb4e3aea9063c2d1ee3f1d (diff)
Fixed validation of resource-assignment-params.config-assign
- In order to preserve the validation of the whole BPP response, I added a normalization step that will convert any non-JSON part of the response into a JSON-equivalent representation. This normalization is carried out by the JSLT library that provides many JSON manipulations using a JSON-like specification. See https://github.com/schibsted/jslt for more details. - Fix UAT not being run by maven The Surefire plugin does accept '*Tests.java' but DOESN'T '*Tests.kt'! The UAT test class was renamed to use the 'Test' suffix only. - Improved maintainability of UAT-Engine by switching from explicitly field handling to POJO-based parsing using Jackson along with SnakeYaml. UAT-related POJOs created on new module UatDefinition.kt - Added a Protobuf-like description of an UAT YAML document. Change-Id: Id1178489caa4e97808747a99bc9324fc84e9b96e Issue-ID: CCSDK-1620 Signed-off-by: ebo <eliezio.oliveira@est.tech>
Diffstat (limited to 'ms/blueprintsprocessor/application/src')
-rw-r--r--ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt (renamed from ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt)138
-rw-r--r--ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt19
-rw-r--r--ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt79
-rw-r--r--ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt52
-rw-r--r--ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt68
5 files changed, 267 insertions, 89 deletions
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt
index 0a57277ea..ad4173c9e 100644
--- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt
+++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt
@@ -19,7 +19,9 @@
*/
package org.onap.ccsdk.cds.blueprintsprocessor
+import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.node.MissingNode
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argThat
import com.nhaarman.mockitokotlin2.atLeast
@@ -53,15 +55,17 @@ import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestPropertySource
import org.springframework.test.context.junit4.rules.SpringClassRule
import org.springframework.test.context.junit4.rules.SpringMethodRule
+import org.springframework.test.web.reactive.server.EntityExchangeResult
import org.springframework.test.web.reactive.server.WebTestClient
-import org.yaml.snakeyaml.Yaml
import reactor.core.publisher.Mono
import java.io.File
-import java.nio.file.Path
+import java.nio.charset.StandardCharsets
import java.nio.file.Paths
import kotlin.test.BeforeTest
import kotlin.test.Test
+// Only one runner can be configured with jUnit 4. We had to replace the SpringRunner by equivalent jUnit rules.
+// See more on https://docs.spring.io/autorepo/docs/spring-framework/current/spring-framework-reference/testing.html#testcontext-junit4-rules
@RunWith(Parameterized::class)
// Set blueprintsprocessor.httpPort=0 to trigger a random port selection
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@@ -71,8 +75,7 @@ import kotlin.test.Test
TestSecuritySettings.ServerContextInitializer::class
])
@TestPropertySource(locations = ["classpath:application-test.properties"])
-@Suppress("UNCHECKED_CAST")
-class BlueprintsAcceptanceTests(private val blueprintName: String, private val filename: String) {
+class BlueprintsAcceptanceTest(private val blueprintName: String, private val filename: String) {
companion object {
const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints"
@@ -82,11 +85,15 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f
@JvmField
val springClassRule = SpringClassRule()
- val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTests::class.java)
+ val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTest::class.java)
+ /**
+ * Generates the parameters to create a test instance for every blueprint found under UAT_BLUEPRINTS_BASE_DIR
+ * that contains the proper UAT definition file.
+ */
@Parameterized.Parameters(name = "{index} {0}")
@JvmStatic
- fun filenames(): List<Array<String>> {
+ fun testParameters(): List<Array<String>> {
return File(UAT_BLUEPRINTS_BASE_DIR)
.listFiles { file -> file.isDirectory && File(file, EMBEDDED_UAT_FILE).isFile }
?.map { file -> arrayOf(file.nameWithoutExtension, file.canonicalPath) }
@@ -119,38 +126,31 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f
@Test
fun testBlueprint() {
- val yaml: Map<String, *> = loadYaml(Paths.get(filename, EMBEDDED_UAT_FILE))
+ val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE))
uploadBlueprint(blueprintName)
// Configure mocked external services
- val services = yaml["external-services"] as List<Map<String, *>>? ?: emptyList()
- val expectationPerClient = services.map { service ->
- val selector = service["selector"] as String
- val expectations = (service["expectations"] as List<Map<String, *>>).map {
- parseExpectation(it)
- }
- val mockClient = createRestClientMock(selector, expectations)
- mockClient to expectations
- }.toMap()
+ val expectationPerClient = uat.externalServices.associateBy(
+ { service -> createRestClientMock(service.selector, service.expectations) },
+ { service -> service.expectations }
+ )
// Run processes
- for (process in (yaml["processes"] as List<Map<String, *>>)) {
- val processName = process["name"]
- log.info("Executing process '$processName'")
- val request = mapper.writeValueAsString(process["request"])
- val expectedResponse = mapper.writeValueAsString(process["expectedResponse"])
- processBlueprint(request, expectedResponse)
+ for (process in uat.processes) {
+ log.info("Executing process '${process.name}'")
+ processBlueprint(process.request, process.expectedResponse,
+ JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec))
}
- // Validate request payloads
+ // Validate request payloads to external services
for ((mockClient, expectations) in expectationPerClient) {
expectations.forEach { expectation ->
verify(mockClient, atLeastOnce()).exchangeResource(
- eq(expectation.method),
- eq(expectation.path),
- argThat { assertJsonEqual(expectation.expectedRequestBody, this) },
- expectation.requestHeadersMatcher())
+ eq(expectation.request.method),
+ eq(expectation.request.path),
+ argThat { assertJsonEqual(expectation.request.body, this) },
+ expectation.request.requestHeadersMatcher())
}
// Don't mind the invocations to the overloaded exchangeResource(String, String, String)
verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any())
@@ -158,7 +158,8 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f
}
}
- private fun createRestClientMock(selector: String, restExpectations: List<RestExpectation>): BlueprintWebClientService {
+ private fun createRestClientMock(selector: String, restExpectations: List<ExpectationDefinition>)
+ : BlueprintWebClientService {
val restClient = mock<BlueprintWebClientService>(verboseLogging = true)
// Delegates to overloaded exchangeResource(String, String, String, Map<String, String>)
@@ -171,11 +172,11 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f
}
for (expectation in restExpectations) {
whenever(restClient.exchangeResource(
- eq(expectation.method),
- eq(expectation.path),
+ eq(expectation.request.method),
+ eq(expectation.request.path),
any(),
any()))
- .thenReturn(WebClientResponse(expectation.statusCode, expectation.responseBody))
+ .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString()))
}
whenever(restClientFactory.blueprintWebClientService(selector))
@@ -194,17 +195,20 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f
.expectStatus().isOk
}
- private fun processBlueprint(request: String, expectedResponse: String) {
+ private fun processBlueprint(request: JsonNode, expectedResponse: JsonNode,
+ responseNormalizer: (String) -> String) {
webTestClient
.post()
.uri("/api/v1/execution-service/process")
.header("Authorization", TestSecuritySettings.clientAuthToken())
.contentType(MediaType.APPLICATION_JSON_UTF8)
- .body(Mono.just(request), String::class.java)
+ .body(Mono.just(request.toString()), String::class.java)
.exchange()
.expectStatus().isOk
.expectBody()
- .json(expectedResponse)
+ .consumeWith { response ->
+ assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response)))
+ }
}
private fun getBlueprintAsResource(blueprintName: String): Resource {
@@ -216,65 +220,21 @@ class BlueprintsAcceptanceTests(private val blueprintName: String, private val f
}
}
- private fun loadYaml(path: Path): Map<String, Any> {
- return path.toFile().reader().use { reader ->
- Yaml().load(reader)
- }
- }
-
- private fun assertJsonEqual(expected: Any, actual: String): Boolean {
- if (actual != expected) {
- // assertEquals throws an exception whenever match fails
- JSONAssert.assertEquals(mapper.writeValueAsString(expected), actual, JSONCompareMode.LENIENT)
+ private fun assertJsonEqual(expected: JsonNode, actual: String): Boolean {
+ if ((actual == "") && (expected is MissingNode)) {
+ return true
}
+ JSONAssert.assertEquals(expected.toString(), actual, JSONCompareMode.LENIENT)
+ // assertEquals throws an exception whenever match fails
return true
}
- private fun parseExpectation(expectation: Map<String, *>): RestExpectation {
- val request = expectation["request"] as Map<String, Any>
- val method = request["method"] as String
- val path = joinPath(request.getValue("path"))
- val contentType = request["content-type"] as String?
- val requestBody = request.getOrDefault("body", "")
-
- val response = expectation["response"] as Map<String, Any>? ?: emptyMap()
- val status = response["status"] as Int? ?: 200
- val responseBody = when (val body = response["body"] ?: "") {
- is String -> body
- else -> mapper.writeValueAsString(body)
- }
-
- return RestExpectation(method, path, contentType, requestBody, status, responseBody)
- }
-
- /**
- * Join a multilevel lists of strings.
- * Example: joinPath(listOf("a", listOf("b", "c"), "d")) will result in "a/b/c/d".
- */
- private fun joinPath(any: Any): String {
- fun recursiveJoin(any: Any, sb: StringBuilder): StringBuilder {
- when (any) {
- is List<*> -> any.filterNotNull().forEach { recursiveJoin(it, sb) }
- is String -> {
- if (sb.isNotEmpty()) {
- sb.append('/')
- }
- sb.append(any)
- }
- else -> throw IllegalArgumentException("Unsupported type: ${any.javaClass}")
- }
- return sb
- }
-
- return recursiveJoin(any, StringBuilder()).toString()
- }
-
- data class RestExpectation(val method: String, val path: String, val contentType: String?,
- val expectedRequestBody: Any,
- val statusCode: Int, val responseBody: String) {
-
- fun requestHeadersMatcher(): Map<String, String> {
- return if (contentType != null) eq(mapOf("Content-Type" to contentType)) else any()
+ private fun getBodyAsString(result: EntityExchangeResult<ByteArray>): String {
+ val body = result.responseBody
+ if ((body == null) || body.isEmpty()) {
+ return ""
}
+ val charset = result.responseHeaders.contentType?.charset ?: StandardCharsets.UTF_8
+ return String(body, charset)
}
} \ No newline at end of file
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt
index 4576f2761..3c517e6ac 100644
--- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt
+++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/ExtendedTemporaryFolder.kt
@@ -1,3 +1,22 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
package org.onap.ccsdk.cds.blueprintsprocessor
import org.junit.rules.TemporaryFolder
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt
new file mode 100644
index 000000000..69673f931
--- /dev/null
+++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt
@@ -0,0 +1,79 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.node.ContainerNode
+import com.fasterxml.jackson.databind.node.MissingNode
+import com.fasterxml.jackson.databind.node.ObjectNode
+import com.schibsted.spt.data.jslt.Parser
+
+class JsonNormalizer {
+
+ companion object {
+
+ fun getNormalizer(mapper: ObjectMapper, jsltSpec: JsonNode): (String) -> String {
+ if (jsltSpec is MissingNode) {
+ return { it }
+ }
+ return { s: String ->
+ val input = mapper.readTree(s)
+ val expandedJstlSpec = expandJstlSpec(jsltSpec)
+ val jslt = Parser.compileString(expandedJstlSpec)
+ val output = jslt.apply(input)
+ output.toString()
+ }
+ }
+
+ /**
+ * Creates an extended JSTL spec by appending the "*: ." wildcard pattern to every inner JSON object, and
+ * removing the extra quotes added by the standard YAML/JSON converters on fields prefixed by "?".
+ *
+ * @param jstlSpec the JSTL spec as a structured JSON object.
+ * @return the string representation of the extended JSTL spec.
+ */
+ private fun expandJstlSpec(jstlSpec: JsonNode): String {
+ val extendedJstlSpec = updateObjectNodes(jstlSpec, "*", ".")
+ return extendedJstlSpec.toString()
+ // Handle the "?" as a prefix to literal/non-quoted values
+ .replace("\"\\?([^\"]+)\"".toRegex(), "$1")
+ // Also, remove the quotes added by Jackson for key and value of the wildcard matcher
+ .replace("\"([.*])\"".toRegex(), "$1")
+ }
+
+ /**
+ * Expands a structured JSON object, by adding the given key and value to every nested ObjectNode.
+ *
+ * @param jsonNode the root node.
+ * @param fieldName the fixed field name.
+ * @param fieldValue the fixed field value.
+ */
+ private fun updateObjectNodes(jsonNode: JsonNode, fieldName: String, fieldValue: String): JsonNode {
+ if (jsonNode is ContainerNode<*>) {
+ (jsonNode as? ObjectNode)?.put(fieldName, fieldValue)
+ jsonNode.forEach { child ->
+ updateObjectNodes(child, fieldName, fieldValue)
+ }
+ }
+ return jsonNode
+ }
+ }
+}
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt
new file mode 100644
index 000000000..1a232f2d3
--- /dev/null
+++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt
@@ -0,0 +1,52 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
+
+class PathDeserializer : StdDeserializer<String>(String::class.java) {
+ override fun deserialize(jp: JsonParser, ctxt: DeserializationContext?): String {
+ val path = jp.codec.readValue(jp, Any::class.java)
+ return flatJoin(path)
+ }
+
+ /**
+ * Join a multilevel lists of strings.
+ * Example: flatJoin(listOf("a", listOf("b", "c"), "d")) will result in "a/b/c/d".
+ */
+ private fun flatJoin(path: Any): String {
+ fun flatJoinTo(sb: StringBuilder, path: Any): StringBuilder {
+ when (path) {
+ is List<*> -> path.filterNotNull().forEach { flatJoinTo(sb, it) }
+ is String -> {
+ if (sb.isNotEmpty()) {
+ sb.append('/')
+ }
+ sb.append(path)
+ }
+ else -> throw IllegalArgumentException("Unsupported type: ${path.javaClass}")
+ }
+ return sb
+ }
+ return flatJoinTo(StringBuilder(), path).toString()
+ }
+} \ No newline at end of file
diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt
new file mode 100644
index 000000000..ce2061168
--- /dev/null
+++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt
@@ -0,0 +1,68 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2019 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.ccsdk.cds.blueprintsprocessor
+
+import com.fasterxml.jackson.annotation.JsonAlias
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize
+import com.fasterxml.jackson.databind.node.MissingNode
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.eq
+import org.yaml.snakeyaml.Yaml
+import java.nio.file.Path
+
+data class ProcessDefinition(val name: String, val request: JsonNode, val expectedResponse: JsonNode,
+ val responseNormalizerSpec: JsonNode = MissingNode.getInstance())
+
+data class RequestDefinition(val method: String,
+ @JsonDeserialize(using = PathDeserializer::class)
+ val path: String,
+ @JsonAlias("content-type")
+ val contentType: String? = null,
+ val body: JsonNode = MissingNode.getInstance()) {
+ fun requestHeadersMatcher(): Map<String, String> {
+ return if (contentType != null) eq(mapOf("Content-Type" to contentType)) else any()
+ }
+}
+
+data class ResponseDefinition(val status: Int = 200, val body: JsonNode = MissingNode.getInstance()) {
+ companion object {
+ val DEFAULT_RESPONSE = ResponseDefinition()
+ }
+}
+
+data class ExpectationDefinition(val request: RequestDefinition,
+ val response: ResponseDefinition = ResponseDefinition.DEFAULT_RESPONSE)
+
+data class ServiceDefinition(val selector: String, val expectations: List<ExpectationDefinition>)
+
+data class UatDefinition(val processes: List<ProcessDefinition>,
+ @JsonAlias("external-services")
+ val externalServices: List<ServiceDefinition> = emptyList()) {
+
+ companion object {
+ fun load(mapper: ObjectMapper, path: Path): UatDefinition {
+ return path.toFile().reader().use { reader ->
+ mapper.convertValue(Yaml().load(reader), UatDefinition::class.java)
+ }
+ }
+ }
+}