2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2019 Nordix Foundation.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
20 package org.onap.ccsdk.cds.blueprintsprocessor
22 import com.fasterxml.jackson.databind.JsonNode
23 import com.fasterxml.jackson.databind.ObjectMapper
24 import com.fasterxml.jackson.databind.node.MissingNode
25 import com.nhaarman.mockitokotlin2.any
26 import com.nhaarman.mockitokotlin2.argThat
27 import com.nhaarman.mockitokotlin2.atLeast
28 import com.nhaarman.mockitokotlin2.atLeastOnce
29 import com.nhaarman.mockitokotlin2.eq
30 import com.nhaarman.mockitokotlin2.mock
31 import com.nhaarman.mockitokotlin2.verify
32 import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
33 import com.nhaarman.mockitokotlin2.whenever
34 import org.junit.ClassRule
36 import org.junit.runner.RunWith
37 import org.junit.runners.Parameterized
38 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
39 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService
40 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
41 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse
42 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes
43 import org.skyscreamer.jsonassert.JSONAssert
44 import org.skyscreamer.jsonassert.JSONCompareMode
45 import org.slf4j.Logger
46 import org.slf4j.LoggerFactory
47 import org.springframework.beans.factory.annotation.Autowired
48 import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
49 import org.springframework.boot.test.context.SpringBootTest
50 import org.springframework.boot.test.mock.mockito.MockBean
51 import org.springframework.core.io.ByteArrayResource
52 import org.springframework.core.io.Resource
53 import org.springframework.http.MediaType
54 import org.springframework.test.context.ContextConfiguration
55 import org.springframework.test.context.TestPropertySource
56 import org.springframework.test.context.junit4.rules.SpringClassRule
57 import org.springframework.test.context.junit4.rules.SpringMethodRule
58 import org.springframework.test.web.reactive.server.EntityExchangeResult
59 import org.springframework.test.web.reactive.server.WebTestClient
60 import reactor.core.publisher.Mono
62 import java.nio.charset.StandardCharsets
63 import java.nio.file.Paths
64 import kotlin.test.BeforeTest
65 import kotlin.test.Test
67 // Only one runner can be configured with jUnit 4. We had to replace the SpringRunner by equivalent jUnit rules.
68 // See more on https://docs.spring.io/autorepo/docs/spring-framework/current/spring-framework-reference/testing.html#testcontext-junit4-rules
69 @RunWith(Parameterized::class)
70 // Set blueprintsprocessor.httpPort=0 to trigger a random port selection
71 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
72 @AutoConfigureWebTestClient(timeout = "PT10S")
73 @ContextConfiguration(initializers = [
74 WorkingFoldersInitializer::class,
75 TestSecuritySettings.ServerContextInitializer::class
77 @TestPropertySource(locations = ["classpath:application-test.properties"])
78 class BlueprintsAcceptanceTest(private val blueprintName: String, private val filename: String) {
81 const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints"
82 const val EMBEDDED_UAT_FILE = "Tests/uat.yaml"
86 val springClassRule = SpringClassRule()
88 val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTest::class.java)
91 * Generates the parameters to create a test instance for every blueprint found under UAT_BLUEPRINTS_BASE_DIR
92 * that contains the proper UAT definition file.
94 @Parameterized.Parameters(name = "{index} {0}")
96 fun testParameters(): List<Array<String>> {
97 return File(UAT_BLUEPRINTS_BASE_DIR)
98 .listFiles { file -> file.isDirectory && File(file, EMBEDDED_UAT_FILE).isFile }
99 ?.map { file -> arrayOf(file.nameWithoutExtension, file.canonicalPath) }
106 val springMethodRule = SpringMethodRule()
108 @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY)
109 lateinit var restClientFactory: BluePrintRestLibPropertyService
112 // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)}
113 @Suppress("SpringJavaInjectionPointsAutowiringInspection")
114 lateinit var tempFolder: ExtendedTemporaryFolder
117 lateinit var webTestClient: WebTestClient
120 lateinit var mapper: ObjectMapper
123 fun cleanupTemporaryFolder() {
124 tempFolder.deleteAllFiles()
128 fun testBlueprint() {
129 val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE))
131 uploadBlueprint(blueprintName)
133 // Configure mocked external services
134 val expectationPerClient = uat.externalServices.associateBy(
135 { service -> createRestClientMock(service.selector, service.expectations) },
136 { service -> service.expectations }
140 for (process in uat.processes) {
141 log.info("Executing process '${process.name}'")
142 processBlueprint(process.request, process.expectedResponse,
143 JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec))
146 // Validate request payloads to external services
147 for ((mockClient, expectations) in expectationPerClient) {
148 expectations.forEach { expectation ->
149 verify(mockClient, atLeastOnce()).exchangeResource(
150 eq(expectation.request.method),
151 eq(expectation.request.path),
152 argThat { assertJsonEqual(expectation.request.body, this) },
153 expectation.request.requestHeadersMatcher())
155 // Don't mind the invocations to the overloaded exchangeResource(String, String, String)
156 verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any())
157 verifyNoMoreInteractions(mockClient)
161 private fun createRestClientMock(selector: String, restExpectations: List<ExpectationDefinition>)
162 : BlueprintWebClientService {
163 val restClient = mock<BlueprintWebClientService>(verboseLogging = true)
165 // Delegates to overloaded exchangeResource(String, String, String, Map<String, String>)
166 whenever(restClient.exchangeResource(any(), any(), any()))
167 .thenAnswer { invocation ->
168 val method = invocation.arguments[0] as String
169 val path = invocation.arguments[1] as String
170 val request = invocation.arguments[2] as String
171 restClient.exchangeResource(method, path, request, emptyMap())
173 for (expectation in restExpectations) {
174 whenever(restClient.exchangeResource(
175 eq(expectation.request.method),
176 eq(expectation.request.path),
179 .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString()))
182 whenever(restClientFactory.blueprintWebClientService(selector))
183 .thenReturn(restClient)
187 private fun uploadBlueprint(blueprintName: String) {
188 val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName))
191 .uri("/api/v1/execution-service/upload")
192 .header("Authorization", TestSecuritySettings.clientAuthToken())
198 private fun processBlueprint(request: JsonNode, expectedResponse: JsonNode,
199 responseNormalizer: (String) -> String) {
202 .uri("/api/v1/execution-service/process")
203 .header("Authorization", TestSecuritySettings.clientAuthToken())
204 .contentType(MediaType.APPLICATION_JSON_UTF8)
205 .body(Mono.just(request.toString()), String::class.java)
209 .consumeWith { response ->
210 assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response)))
214 private fun getBlueprintAsResource(blueprintName: String): Resource {
215 val baseDir = Paths.get(UAT_BLUEPRINTS_BASE_DIR, blueprintName)
216 val zipBytes = compressToBytes(baseDir)
217 return object : ByteArrayResource(zipBytes) {
218 // Filename has to be returned in order to be able to post
219 override fun getFilename() = "$blueprintName.zip"
223 private fun assertJsonEqual(expected: JsonNode, actual: String): Boolean {
224 if ((actual == "") && (expected is MissingNode)) {
227 JSONAssert.assertEquals(expected.toString(), actual, JSONCompareMode.LENIENT)
228 // assertEquals throws an exception whenever match fails
232 private fun getBodyAsString(result: EntityExchangeResult<ByteArray>): String {
233 val body = result.responseBody
234 if ((body == null) || body.isEmpty()) {
237 val charset = result.responseHeaders.contentType?.charset ?: StandardCharsets.UTF_8
238 return String(body, charset)