Merge "application-dev.properties missing python modules paths resulting in failure...
[ccsdk/cds.git] / ms / blueprintsprocessor / application / src / test / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / BlueprintsAcceptanceTest.kt
1 /*-
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20 package org.onap.ccsdk.cds.blueprintsprocessor
21
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
35 import org.junit.Rule
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
61 import java.io.File
62 import java.nio.charset.StandardCharsets
63 import java.nio.file.Paths
64 import kotlin.test.BeforeTest
65 import kotlin.test.Test
66
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
76 ])
77 @TestPropertySource(locations = ["classpath:application-test.properties"])
78 class BlueprintsAcceptanceTest(private val blueprintName: String, private val filename: String) {
79
80     companion object {
81         const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints"
82         const val EMBEDDED_UAT_FILE = "Tests/uat.yaml"
83
84         @ClassRule
85         @JvmField
86         val springClassRule = SpringClassRule()
87
88         val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTest::class.java)
89
90         /**
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.
93          */
94         @Parameterized.Parameters(name = "{index} {0}")
95         @JvmStatic
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) }
100                     ?: emptyList()
101         }
102     }
103
104     @Rule
105     @JvmField
106     val springMethodRule = SpringMethodRule()
107
108     @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY)
109     lateinit var restClientFactory: BluePrintRestLibPropertyService
110
111     @Autowired
112     // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)}
113     @Suppress("SpringJavaInjectionPointsAutowiringInspection")
114     lateinit var tempFolder: ExtendedTemporaryFolder
115
116     @Autowired
117     lateinit var webTestClient: WebTestClient
118
119     @Autowired
120     lateinit var mapper: ObjectMapper
121
122     @BeforeTest
123     fun cleanupTemporaryFolder() {
124         tempFolder.deleteAllFiles()
125     }
126
127     @Test
128     fun testBlueprint() {
129         val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE))
130
131         uploadBlueprint(blueprintName)
132
133         // Configure mocked external services
134         val expectationPerClient = uat.externalServices.associateBy(
135                 { service -> createRestClientMock(service.selector, service.expectations) },
136                 { service -> service.expectations }
137         )
138
139         // Run processes
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))
144         }
145
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())
154             }
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)
158         }
159     }
160
161     private fun createRestClientMock(selector: String, restExpectations: List<ExpectationDefinition>)
162             : BlueprintWebClientService {
163         val restClient = mock<BlueprintWebClientService>(verboseLogging = true)
164
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())
172                 }
173         for (expectation in restExpectations) {
174             whenever(restClient.exchangeResource(
175                     eq(expectation.request.method),
176                     eq(expectation.request.path),
177                     any(),
178                     any()))
179                     .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString()))
180         }
181
182         whenever(restClientFactory.blueprintWebClientService(selector))
183                 .thenReturn(restClient)
184         return restClient
185     }
186
187     private fun uploadBlueprint(blueprintName: String) {
188         val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName))
189         webTestClient
190                 .post()
191                 .uri("/api/v1/execution-service/upload")
192                 .header("Authorization", TestSecuritySettings.clientAuthToken())
193                 .syncBody(body)
194                 .exchange()
195                 .expectStatus().isOk
196     }
197
198     private fun processBlueprint(request: JsonNode, expectedResponse: JsonNode,
199                                  responseNormalizer: (String) -> String) {
200         webTestClient
201                 .post()
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)
206                 .exchange()
207                 .expectStatus().isOk
208                 .expectBody()
209                 .consumeWith { response ->
210                     assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response)))
211                 }
212     }
213
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"
220         }
221     }
222
223     private fun assertJsonEqual(expected: JsonNode, actual: String): Boolean {
224         if ((actual == "") && (expected is MissingNode)) {
225             return true
226         }
227         JSONAssert.assertEquals(expected.toString(), actual, JSONCompareMode.LENIENT)
228         // assertEquals throws an exception whenever match fails
229         return true
230     }
231
232     private fun getBodyAsString(result: EntityExchangeResult<ByteArray>): String {
233         val body = result.responseBody
234         if ((body == null) || body.isEmpty()) {
235             return ""
236         }
237         val charset = result.responseHeaders.contentType?.charset ?: StandardCharsets.UTF_8
238         return String(body, charset)
239     }
240 }