ce7434f8ec4d89fdb8aaadacd547d90082eb618f
[ccsdk/cds.git] /
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.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
62 import java.io.File
63 import java.nio.charset.StandardCharsets
64 import java.nio.file.Paths
65 import kotlin.test.BeforeTest
66 import kotlin.test.Test
67
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
77 ])
78 @TestPropertySource(locations = ["classpath:application-test.properties"])
79 class BlueprintsAcceptanceTest(private val blueprintName: String, private val filename: String) {
80
81     companion object {
82         const val UAT_BLUEPRINTS_BASE_DIR = "../../../components/model-catalog/blueprint-model/uat-blueprints"
83         const val EMBEDDED_UAT_FILE = "Tests/uat.yaml"
84
85         @ClassRule
86         @JvmField
87         val springClassRule = SpringClassRule()
88
89         val log: Logger = LoggerFactory.getLogger(BlueprintsAcceptanceTest::class.java)
90
91         /**
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.
94          */
95         @Parameterized.Parameters(name = "{index} {0}")
96         @JvmStatic
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) }
101                     ?: emptyList()
102         }
103     }
104
105     @Rule
106     @JvmField
107     val springMethodRule = SpringMethodRule()
108
109     @MockBean(name = RestLibConstants.SERVICE_BLUEPRINT_REST_LIB_PROPERTY, answer = Answers.RETURNS_SMART_NULLS)
110     lateinit var restClientFactory: BluePrintRestLibPropertyService
111
112     @Autowired
113     // Bean is created programmatically by {@link WorkingFoldersInitializer#initialize(String)}
114     @Suppress("SpringJavaInjectionPointsAutowiringInspection")
115     lateinit var tempFolder: ExtendedTemporaryFolder
116
117     @Autowired
118     lateinit var webTestClient: WebTestClient
119
120     @Autowired
121     lateinit var mapper: ObjectMapper
122
123     @BeforeTest
124     fun cleanupTemporaryFolder() {
125         tempFolder.deleteAllFiles()
126     }
127
128     @Test
129     fun testBlueprint() {
130         val uat = UatDefinition.load(mapper, Paths.get(filename, EMBEDDED_UAT_FILE))
131
132         uploadBlueprint(blueprintName)
133
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 } }
138         )
139
140         // Run processes
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))
145         }
146
147         // Validate requests to external services
148         for ((mockClient, requests) in requestsPerClient) {
149             requests.forEach { request ->
150                 verify(mockClient, atLeastOnce()).exchangeResource(
151                         eq(request.method),
152                         eq(request.path),
153                         argThat { assertJsonEqual(request.body, this) },
154                         argThat(RequiredMapEntriesMatcher(request.headers)))
155             }
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)
159         }
160     }
161
162     private fun createRestClientMock(selector: String, restExpectations: List<ExpectationDefinition>)
163             : BlueprintWebClientService {
164         val restClient = mock<BlueprintWebClientService>(verboseLogging = true,
165                 defaultAnswer = Answers.RETURNS_SMART_NULLS)
166
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())
174                 }
175         for (expectation in restExpectations) {
176             whenever(restClient.exchangeResource(
177                     eq(expectation.request.method),
178                     eq(expectation.request.path),
179                     any(),
180                     any()))
181                     .thenReturn(WebClientResponse(expectation.response.status, expectation.response.body.toString()))
182         }
183
184         whenever(restClientFactory.blueprintWebClientService(selector))
185                 .thenReturn(restClient)
186         return restClient
187     }
188
189     private fun uploadBlueprint(blueprintName: String) {
190         val body = toMultiValueMap("file", getBlueprintAsResource(blueprintName))
191         webTestClient
192                 .post()
193                 .uri("/api/v1/blueprint-model/publish")
194                 .header("Authorization", TestSecuritySettings.clientAuthToken())
195                 .syncBody(body)
196                 .exchange()
197                 .expectStatus().isOk
198     }
199
200     private fun processBlueprint(request: JsonNode, expectedResponse: JsonNode,
201                                  responseNormalizer: (String) -> String) {
202         webTestClient
203                 .post()
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)
208                 .exchange()
209                 .expectStatus().isOk
210                 .expectBody()
211                 .consumeWith { response ->
212                     assertJsonEqual(expectedResponse, responseNormalizer(getBodyAsString(response)))
213                 }
214     }
215
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"
222         }
223     }
224
225     private fun assertJsonEqual(expected: JsonNode, actual: String): Boolean {
226         if ((actual == "") && (expected is MissingNode)) {
227             return true
228         }
229         JSONAssert.assertEquals(expected.toString(), actual, JSONCompareMode.LENIENT)
230         // assertEquals throws an exception whenever match fails
231         return true
232     }
233
234     private fun getBodyAsString(result: EntityExchangeResult<ByteArray>): String {
235         val body = result.responseBody
236         if ((body == null) || body.isEmpty()) {
237             return ""
238         }
239         val charset = result.responseHeaders.contentType?.charset ?: StandardCharsets.UTF_8
240         return String(body, charset)
241     }
242 }