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.mockito.Answers
39 import org.onap.ccsdk.cds.blueprintsprocessor.rest.RestLibConstants
40 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService
41 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
42 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService.WebClientResponse
43 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes
44 import org.skyscreamer.jsonassert.JSONAssert
45 import org.skyscreamer.jsonassert.JSONCompareMode
46 import org.slf4j.Logger
47 import org.slf4j.LoggerFactory
48 import org.springframework.beans.factory.annotation.Autowired
49 import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
50 import org.springframework.boot.test.context.SpringBootTest
51 import org.springframework.boot.test.mock.mockito.MockBean
52 import org.springframework.core.io.ByteArrayResource
53 import org.springframework.core.io.Resource
54 import org.springframework.http.MediaType
55 import org.springframework.test.context.ContextConfiguration
56 import org.springframework.test.context.TestPropertySource
57 import org.springframework.test.context.junit4.rules.SpringClassRule
58 import org.springframework.test.context.junit4.rules.SpringMethodRule
59 import org.springframework.test.web.reactive.server.EntityExchangeResult
60 import org.springframework.test.web.reactive.server.WebTestClient
61 import reactor.core.publisher.Mono
63 import java.nio.charset.StandardCharsets
64 import java.nio.file.Paths
65 import kotlin.test.BeforeTest
66 import kotlin.test.Test
68 // Only one runner can be configured with jUnit 4. We had to replace the SpringRunner by equivalent jUnit rules.
69 // See more on https://docs.spring.io/autorepo/docs/spring-framework/current/spring-framework-reference/testing.html#testcontext-junit4-rules
70 @RunWith(Parameterized::class)
71 // Set blueprintsprocessor.httpPort=0 to trigger a random port selection
72 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
73 @AutoConfigureWebTestClient(timeout = "PT10S")
74 @ContextConfiguration(initializers = [
75 WorkingFoldersInitializer::class,
76 TestSecuritySettings.ServerContextInitializer::class
78 @TestPropertySource(locations = ["classpath:application-test.properties"])
79 class BlueprintsAcceptanceTest(private val blueprintName: String, private val filename: String) {
82 const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints"
83 const val EMBEDDED_UAT_FILE = "Tests/uat.yaml"
87 val springClassRule = SpringClassRule()
89 val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTest::class.java)
92 * Generates the parameters to create a test instance for every blueprint found under UAT_BLUEPRINTS_BASE_DIR
93 * that contains the proper UAT definition file.
95 @Parameterized.Parameters(name = "{index} {0}")
97 fun testParameters(): List<Array<String>> {
98 return File(UAT_BLUEPRINTS_BASE_DIR)
99 .listFiles { file -> file.isDirectory && File(file, EMBEDDED_UAT_FILE).isFile }
100 ?.map { file -> arrayOf(file.nameWithoutExtension, file.canonicalPath) }
107 val springMethodRule = SpringMethodRule()
109 @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY, answer = Answers.RETURNS_SMART_NULLS)
110 lateinit var restClientFactory: BluePrintRestLibPropertyService
113 // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)}
114 @Suppress("SpringJavaInjectionPointsAutowiringInspection")
115 lateinit var tempFolder: ExtendedTemporaryFolder
118 lateinit var webTestClient: WebTestClient
121 lateinit var mapper: ObjectMapper
124 fun cleanupTemporaryFolder() {
125 tempFolder.deleteAllFiles()
129 fun testBlueprint() {
130 val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE))
132 uploadBlueprint(blueprintName)
134 // Configure mocked external services and save their expected requests for further validation
135 val requestsPerClient = uat.externalServices.associateBy(
136 { service -> createRestClientMock(service.selector, service.expectations) },
137 { service -> service.expectations.map { it.request } }
141 for (process in uat.processes) {
142 log.info("Executing process '${process.name}'")
143 processBlueprint(process.request, process.expectedResponse,
144 JsonNormalizer.getNormalizer(mapper, process.responseNormalizerSpec))
147 // Validate requests to external services
148 for ((mockClient, requests) in requestsPerClient) {
149 requests.forEach { request ->
150 verify(mockClient, atLeastOnce()).exchangeResource(
153 argThat { assertJsonEqual(request.body, this) },
154 argThat(RequiredMapEntriesMatcher(request.headers)))
156 // Don't mind the invocations to the overloaded exchangeResource(String, String, String)
157 verify(mockClient, atLeast(0)).exchangeResource(any(), any(), any())
158 verifyNoMoreInteractions(mockClient)
162 private fun createRestClientMock(selector: String, restExpectations: List<ExpectationDefinition>)
163 : BlueprintWebClientService {
164 val restClient = mock<BlueprintWebClientService>(verboseLogging = true,
165 defaultAnswer = Answers.RETURNS_SMART_NULLS)
167 // Delegates to overloaded exchangeResource(String, String, String, Map<String, String>)
168 whenever(restClient.exchangeResource(any(), any(), any()))
169 .thenAnswer { invocation ->
170 val method = invocation.arguments[0] as String
171 val path = invocation.arguments[1] as String
172 val request = invocation.arguments[2] as String
173 restClient.exchangeResource(method, path, request, emptyMap())
175 for (expectation in restExpectations) {
176 whenever(restClient.exchangeResource(
177 eq(expectation.request.method),
178 eq(expectation.request.path),
181 .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString()))
184 whenever(restClientFactory.blueprintWebClientService(selector))
185 .thenReturn(restClient)
189 private fun uploadBlueprint(blueprintName: String) {
190 val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName))
193 .uri("/api/v1/blueprint-model/publish")
194 .header("Authorization", TestSecuritySettings.clientAuthToken())
200 private fun processBlueprint(request: JsonNode, expectedResponse: JsonNode,
201 responseNormalizer: (String) -> String) {
204 .uri("/api/v1/execution-service/process")
205 .header("Authorization", TestSecuritySettings.clientAuthToken())
206 .contentType(MediaType.APPLICATION_JSON_UTF8)
207 .body(Mono.just(request.toString()), String::class.java)
211 .consumeWith { response ->
212 assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response)))
216 private fun getBlueprintAsResource(blueprintName: String): Resource {
217 val baseDir = Paths.get(UAT_BLUEPRINTS_BASE_DIR, blueprintName)
218 val zipBytes = compressToBytes(baseDir)
219 return object : ByteArrayResource(zipBytes) {
220 // Filename has to be returned in order to be able to post
221 override fun getFilename() = "$blueprintName.zip"
225 private fun assertJsonEqual(expected: JsonNode, actual: String): Boolean {
226 if ((actual == "") && (expected is MissingNode)) {
229 JSONAssert.assertEquals(expected.toString(), actual, JSONCompareMode.LENIENT)
230 // assertEquals throws an exception whenever match fails
234 private fun getBodyAsString(result: EntityExchangeResult<ByteArray>): String {
235 val body = result.responseBody
236 if ((body == null) || body.isEmpty()) {
239 val charset = result.responseHeaders.contentType?.charset ?: StandardCharsets.UTF_8
240 return String(body, charset)