From ff445e6d950048c092c0a7ccd8a0a04cb18869b5 Mon Sep 17 00:00:00 2001 From: ebo Date: Fri, 16 Aug 2019 13:18:47 +0000 Subject: 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 --- ms/blueprintsprocessor/application/pom.xml | 6 + .../BlueprintsAcceptanceTest.kt | 240 ++++++++++++++++++ .../BlueprintsAcceptanceTests.kt | 280 --------------------- .../blueprintsprocessor/ExtendedTemporaryFolder.kt | 19 ++ .../cds/blueprintsprocessor/JsonNormalizer.kt | 79 ++++++ .../cds/blueprintsprocessor/PathDeserializer.kt | 52 ++++ .../ccsdk/cds/blueprintsprocessor/UatDefinition.kt | 68 +++++ 7 files changed, 464 insertions(+), 280 deletions(-) create mode 100644 ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt delete mode 100644 ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt create mode 100644 ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/JsonNormalizer.kt create mode 100644 ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/PathDeserializer.kt create mode 100644 ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/UatDefinition.kt (limited to 'ms') diff --git a/ms/blueprintsprocessor/application/pom.xml b/ms/blueprintsprocessor/application/pom.xml index 314b09c42..120b948be 100755 --- a/ms/blueprintsprocessor/application/pom.xml +++ b/ms/blueprintsprocessor/application/pom.xml @@ -131,6 +131,12 @@ 2.1.0 test + + com.schibsted.spt.data + jslt + 0.1.8 + test + ch.qos.logback logback-classic diff --git a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt new file mode 100644 index 000000000..ad4173c9e --- /dev/null +++ b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTest.kt @@ -0,0 +1,240 @@ +/*- + * ============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.MissingNode +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.argThat +import com.nhaarman.mockitokotlin2.atLeast +import com.nhaarman.mockitokotlin2.atLeastOnce +import com.nhaarman.mockitokotlin2.eq +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions +import com.nhaarman.mockitokotlin2.whenever +import org.junit.ClassRule +import org.junit.Rule +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService +import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse +import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.core.io.ByteArrayResource +import org.springframework.core.io.Resource +import org.springframework.http.MediaType +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 reactor.core.publisher.Mono +import java.io.File +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) +@AutoConfigureWebTestClient(timeout = "PT10S") +@ContextConfiguration(initializers = [ + WorkingFoldersInitializer::class, + TestSecuritySettings.ServerContextInitializer::class +]) +@TestPropertySource(locations = ["classpath:application-test.properties"]) +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" + const val EMBEDDED_UAT_FILE = "Tests/uat.yaml" + + @ClassRule + @JvmField + val springClassRule = SpringClassRule() + + 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 testParameters(): List> { + return File(UAT_BLUEPRINTS_BASE_DIR) + .listFiles { file -> file.isDirectory && File(file, EMBEDDED_UAT_FILE).isFile } + ?.map { file -> arrayOf(file.nameWithoutExtension, file.canonicalPath) } + ?: emptyList() + } + } + + @Rule + @JvmField + val springMethodRule = SpringMethodRule() + + @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY) + lateinit var restClientFactory: BluePrintRestLibPropertyService + + @Autowired + // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)} + @Suppress("SpringJavaInjectionPointsAutowiringInspection") + lateinit var tempFolder: ExtendedTemporaryFolder + + @Autowired + lateinit var webTestClient: WebTestClient + + @Autowired + lateinit var mapper: ObjectMapper + + @BeforeTest + fun cleanupTemporaryFolder() { + tempFolder.deleteAllFiles() + } + + @Test + fun testBlueprint() { + val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE)) + + uploadBlueprint(blueprintName) + + // Configure mocked external services + val expectationPerClient = uat.externalServices.associateBy( + { service -> createRestClientMock(service.selector, service.expectations) }, + { service -> service.expectations } + ) + + // Run processes + for (process in uat.processes) { + log.info("Executing process '${process.name}'") + processBlueprint(process.request, process.expectedResponse, + JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec)) + } + + // Validate request payloads to external services + for ((mockClient, expectations) in expectationPerClient) { + expectations.forEach { expectation -> + verify(mockClient, atLeastOnce()).exchangeResource( + 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()) + verifyNoMoreInteractions(mockClient) + } + } + + private fun createRestClientMock(selector: String, restExpectations: List) + : BlueprintWebClientService { + val restClient = mock(verboseLogging = true) + + // Delegates to overloaded exchangeResource(String, String, String, Map) + whenever(restClient.exchangeResource(any(), any(), any())) + .thenAnswer { invocation -> + val method = invocation.arguments[0] as String + val path = invocation.arguments[1] as String + val request = invocation.arguments[2] as String + restClient.exchangeResource(method, path, request, emptyMap()) + } + for (expectation in restExpectations) { + whenever(restClient.exchangeResource( + eq(expectation.request.method), + eq(expectation.request.path), + any(), + any())) + .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString())) + } + + whenever(restClientFactory.blueprintWebClientService(selector)) + .thenReturn(restClient) + return restClient + } + + private fun uploadBlueprint(blueprintName: String) { + val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName)) + webTestClient + .post() + .uri("/api/v1/execution-service/upload") + .header("Authorization", TestSecuritySettings.clientAuthToken()) + .syncBody(body) + .exchange() + .expectStatus().isOk + } + + 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.toString()), String::class.java) + .exchange() + .expectStatus().isOk + .expectBody() + .consumeWith { response -> + assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response))) + } + } + + private fun getBlueprintAsResource(blueprintName: String): Resource { + val baseDir = Paths.get(UAT_BLUEPRINTS_BASE_DIR, blueprintName) + val zipBytes = compressToBytes(baseDir) + return object : ByteArrayResource(zipBytes) { + // Filename has to be returned in order to be able to post + override fun getFilename() = "$blueprintName.zip" + } + } + + 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 getBodyAsString(result: EntityExchangeResult): 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/BlueprintsAcceptanceTests.kt b/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt deleted file mode 100644 index 0a57277ea..000000000 --- a/ms/blueprintsprocessor/application/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintsAcceptanceTests.kt +++ /dev/null @@ -1,280 +0,0 @@ -/*- - * ============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.ObjectMapper -import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.argThat -import com.nhaarman.mockitokotlin2.atLeast -import com.nhaarman.mockitokotlin2.atLeastOnce -import com.nhaarman.mockitokotlin2.eq -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions -import com.nhaarman.mockitokotlin2.whenever -import org.junit.ClassRule -import org.junit.Rule -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants -import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService -import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService -import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse -import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes -import org.skyscreamer.jsonassert.JSONAssert -import org.skyscreamer.jsonassert.JSONCompareMode -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.mock.mockito.MockBean -import org.springframework.core.io.ByteArrayResource -import org.springframework.core.io.Resource -import org.springframework.http.MediaType -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.WebTestClient -import org.yaml.snakeyaml.Yaml -import reactor.core.publisher.Mono -import java.io.File -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.test.BeforeTest -import kotlin.test.Test - -@RunWith(Parameterized::class) -// Set blueprintsprocessor.httpPort=0 to trigger a random port selection -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@AutoConfigureWebTestClient(timeout = "PT10S") -@ContextConfiguration(initializers = [ - WorkingFoldersInitializer::class, - TestSecuritySettings.ServerContextInitializer::class -]) -@TestPropertySource(locations = ["classpath:application-test.properties"]) -@Suppress("UNCHECKED_CAST") -class BlueprintsAcceptanceTests(private val blueprintName: String, private val filename: String) { - - companion object { - const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints" - const val EMBEDDED_UAT_FILE = "Tests/uat.yaml" - - @ClassRule - @JvmField - val springClassRule = SpringClassRule() - - val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTests::class.java) - - @Parameterized.Parameters(name = "{index} {0}") - @JvmStatic - fun filenames(): List> { - return File(UAT_BLUEPRINTS_BASE_DIR) - .listFiles { file -> file.isDirectory && File(file, EMBEDDED_UAT_FILE).isFile } - ?.map { file -> arrayOf(file.nameWithoutExtension, file.canonicalPath) } - ?: emptyList() - } - } - - @Rule - @JvmField - val springMethodRule = SpringMethodRule() - - @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY) - lateinit var restClientFactory: BluePrintRestLibPropertyService - - @Autowired - // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)} - @Suppress("SpringJavaInjectionPointsAutowiringInspection") - lateinit var tempFolder: ExtendedTemporaryFolder - - @Autowired - lateinit var webTestClient: WebTestClient - - @Autowired - lateinit var mapper: ObjectMapper - - @BeforeTest - fun cleanupTemporaryFolder() { - tempFolder.deleteAllFiles() - } - - @Test - fun testBlueprint() { - val yaml: Map = loadYaml(Paths.get(filename, EMBEDDED_UAT_FILE)) - - uploadBlueprint(blueprintName) - - // Configure mocked external services - val services = yaml["external-services"] as List>? ?: emptyList() - val expectationPerClient = services.map { service -> - val selector = service["selector"] as String - val expectations = (service["expectations"] as List>).map { - parseExpectation(it) - } - val mockClient = createRestClientMock(selector, expectations) - mockClient to expectations - }.toMap() - - // Run processes - for (process in (yaml["processes"] as List>)) { - val processName = process["name"] - log.info("Executing process '$processName'") - val request = mapper.writeValueAsString(process["request"]) - val expectedResponse = mapper.writeValueAsString(process["expectedResponse"]) - processBlueprint(request, expectedResponse) - } - - // Validate request payloads - 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()) - } - // Don't mind the invocations to the overloaded exchangeResource(String, String, String) - verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any()) - verifyNoMoreInteractions(mockClient) - } - } - - private fun createRestClientMock(selector: String, restExpectations: List): BlueprintWebClientService { - val restClient = mock(verboseLogging = true) - - // Delegates to overloaded exchangeResource(String, String, String, Map) - whenever(restClient.exchangeResource(any(), any(), any())) - .thenAnswer { invocation -> - val method = invocation.arguments[0] as String - val path = invocation.arguments[1] as String - val request = invocation.arguments[2] as String - restClient.exchangeResource(method, path, request, emptyMap()) - } - for (expectation in restExpectations) { - whenever(restClient.exchangeResource( - eq(expectation.method), - eq(expectation.path), - any(), - any())) - .thenReturn(WebClientResponse(expectation.statusCode, expectation.responseBody)) - } - - whenever(restClientFactory.blueprintWebClientService(selector)) - .thenReturn(restClient) - return restClient - } - - private fun uploadBlueprint(blueprintName: String) { - val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName)) - webTestClient - .post() - .uri("/api/v1/execution-service/upload") - .header("Authorization", TestSecuritySettings.clientAuthToken()) - .syncBody(body) - .exchange() - .expectStatus().isOk - } - - private fun processBlueprint(request: String, expectedResponse: 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) - .exchange() - .expectStatus().isOk - .expectBody() - .json(expectedResponse) - } - - private fun getBlueprintAsResource(blueprintName: String): Resource { - val baseDir = Paths.get(UAT_BLUEPRINTS_BASE_DIR, blueprintName) - val zipBytes = compressToBytes(baseDir) - return object : ByteArrayResource(zipBytes) { - // Filename has to be returned in order to be able to post - override fun getFilename() = "$blueprintName.zip" - } - } - - private fun loadYaml(path: Path): Map { - 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) - } - return true - } - - private fun parseExpectation(expectation: Map): RestExpectation { - val request = expectation["request"] as Map - 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? ?: 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 { - return if (contentType != null) eq(mapOf("Content-Type" to contentType)) else any() - } - } -} \ 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::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 { + 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) + +data class UatDefinition(val processes: List, + @JsonAlias("external-services") + val externalServices: List = emptyList()) { + + companion object { + fun load(mapper: ObjectMapper, path: Path): UatDefinition { + return path.toFile().reader().use { reader -> + mapper.convertValue(Yaml().load(reader), UatDefinition::class.java) + } + } + } +} -- cgit 1.2.3-korg