fe2d539e34b901402bdef19178fe1ef931ac0a01
[ccsdk/cds.git] / ms / blueprintsprocessor / application / src / test / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / uat / UatServicesTest.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.uat
21
22 import com.fasterxml.jackson.databind.ObjectMapper
23 import com.github.tomakehurst.wiremock.WireMockServer
24 import com.github.tomakehurst.wiremock.client.MappingBuilder
25 import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder
26 import com.github.tomakehurst.wiremock.client.VerificationException
27 import com.github.tomakehurst.wiremock.client.WireMock.aResponse
28 import com.github.tomakehurst.wiremock.client.WireMock.equalTo
29 import com.github.tomakehurst.wiremock.client.WireMock.equalToJson
30 import com.github.tomakehurst.wiremock.client.WireMock.request
31 import com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo
32 import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
33 import com.github.tomakehurst.wiremock.http.HttpHeader
34 import com.github.tomakehurst.wiremock.http.HttpHeaders
35 import org.apache.http.HttpStatus
36 import org.apache.http.client.methods.HttpPost
37 import org.apache.http.entity.ContentType
38 import org.apache.http.entity.mime.HttpMultipartMode
39 import org.apache.http.entity.mime.MultipartEntityBuilder
40 import org.apache.http.impl.client.CloseableHttpClient
41 import org.apache.http.impl.client.HttpClientBuilder
42 import org.apache.http.message.BasicHeader
43 import org.hamcrest.CoreMatchers
44 import org.hamcrest.MatcherAssert.assertThat
45 import org.hamcrest.Matchers.equalToIgnoringCase
46 import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.COLOR_WIREMOCK
47 import org.onap.ccsdk.cds.blueprintsprocessor.uat.logging.LogColor.markerOf
48 import org.onap.ccsdk.cds.blueprintsprocessor.uat.utils.MarkedSlf4jNotifier
49 import org.onap.ccsdk.cds.blueprintsprocessor.uat.utils.ServiceDefinition
50 import org.onap.ccsdk.cds.blueprintsprocessor.uat.utils.TestSecuritySettings
51 import org.onap.ccsdk.cds.blueprintsprocessor.uat.utils.UatDefinition
52 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants.UAT_SPECIFICATION_FILE
53 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintArchiveUtils.Companion.compressToBytes
54 import org.skyscreamer.jsonassert.JSONAssert
55 import org.skyscreamer.jsonassert.JSONCompareMode
56 import org.springframework.beans.factory.annotation.Autowired
57 import org.springframework.boot.web.server.LocalServerPort
58 import org.springframework.core.env.ConfigurableEnvironment
59 import org.springframework.core.env.MapPropertySource
60 import org.springframework.test.context.ActiveProfiles
61 import org.springframework.test.context.support.TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME
62 import org.yaml.snakeyaml.Yaml
63 import java.nio.file.Paths
64 import kotlin.test.AfterTest
65 import kotlin.test.BeforeTest
66 import kotlin.test.Test
67 import kotlin.test.assertNotNull
68
69 fun String.prefixIfNot(prefix: String) =
70     if (this.startsWith(prefix)) this else "$prefix$this"
71
72 @ActiveProfiles("uat")
73 @Suppress("MemberVisibilityCanBePrivate")
74 class UatServicesTest : BaseUatTest() {
75
76     companion object {
77
78         private const val BLUEPRINT_NAME = "pnf_config"
79         private val BLUEPRINT_BASE_DIR = Paths.get(UAT_BLUEPRINTS_BASE_DIR, BLUEPRINT_NAME)
80         private val UAT_PATH = BLUEPRINT_BASE_DIR.resolve(UAT_SPECIFICATION_FILE)
81         private val wireMockMarker = markerOf(COLOR_WIREMOCK)
82     }
83
84     @Autowired
85     lateinit var mapper: ObjectMapper
86
87     @Autowired
88     lateinit var environment: ConfigurableEnvironment
89
90     private val ephemeralProperties = mutableSetOf<String>()
91     private val startedMockServers = mutableListOf<WireMockServer>()
92
93     private fun setProperties(properties: Map<String, String>) {
94         inlinedPropertySource().putAll(properties)
95         ephemeralProperties += properties.keys
96     }
97
98     @AfterTest
99     fun resetProperties() {
100         val source = inlinedPropertySource()
101         ephemeralProperties.forEach { key -> source.remove(key) }
102         ephemeralProperties.clear()
103     }
104
105     @AfterTest
106     fun stopMockServers() {
107         startedMockServers.forEach { mockServer ->
108             try {
109                 mockServer.checkForUnmatchedRequests()
110             } finally {
111                 mockServer.stop()
112             }
113         }
114         startedMockServers.clear()
115     }
116
117     private fun inlinedPropertySource(): MutableMap<String, Any> =
118         (environment.propertySources[INLINED_PROPERTIES_PROPERTY_SOURCE_NAME] as MapPropertySource).source
119
120     @LocalServerPort
121     var localServerPort: Int = 0
122
123     // use lazy evaluation to postpone until localServerPort is injected by Spring
124     val baseUrl: String by lazy {
125         "http://127.0.0.1:$localServerPort"
126     }
127
128     lateinit var httpClient: CloseableHttpClient
129
130     @BeforeTest
131     fun setupHttpClient() {
132         val defaultHeaders = listOf(
133             BasicHeader(
134                 org.apache.http.HttpHeaders.AUTHORIZATION,
135                 TestSecuritySettings.clientAuthToken()
136             )
137         )
138         httpClient = HttpClientBuilder.create()
139             .setDefaultHeaders(defaultHeaders)
140             .build()
141     }
142
143     @Test
144     fun `verify service validates candidate UAT`() {
145         // GIVEN
146         val cbaBytes = compressToBytes(BLUEPRINT_BASE_DIR)
147         val multipartEntity = MultipartEntityBuilder.create()
148             .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
149             .addBinaryBody("cba", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip")
150             .build()
151         val request = HttpPost("$baseUrl/api/v1/uat/verify").apply {
152             entity = multipartEntity
153         }
154
155         // WHEN
156         httpClient.execute(request) { response ->
157
158             // THEN
159             val statusLine = response.statusLine
160             assertThat(statusLine.statusCode, CoreMatchers.equalTo(HttpStatus.SC_OK))
161         }
162     }
163
164     @Test
165     fun `spy service generates complete UAT from bare UAT`() {
166         // GIVEN
167         val uatSpec = UAT_PATH.toFile().readText()
168         val fullUat = UatDefinition.load(mapper, uatSpec)
169         val expectedJson = mapper.writeValueAsString(fullUat)
170
171         val bareUatBytes = fullUat.toBare().dump(mapper).toByteArray()
172
173         fullUat.externalServices.forEach { service ->
174             val mockServer = createMockServer(service)
175             mockServer.start()
176             startedMockServers += mockServer
177             setPropertiesForMockServer(service, mockServer)
178         }
179
180         val cbaBytes = compressToBytes(BLUEPRINT_BASE_DIR)
181         val multipartEntity = MultipartEntityBuilder.create()
182             .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
183             .addBinaryBody("cba", cbaBytes, ContentType.DEFAULT_BINARY, "cba.zip")
184             .addBinaryBody("uat", bareUatBytes, ContentType.DEFAULT_BINARY, "uat.yaml")
185             .build()
186         val request = HttpPost("$baseUrl/api/v1/uat/spy").apply {
187             entity = multipartEntity
188         }
189
190         // WHEN
191         httpClient.execute(request) { response ->
192
193             // THEN
194             val statusLine = response.statusLine
195             assertThat(statusLine.statusCode, CoreMatchers.equalTo(HttpStatus.SC_OK))
196             val entity = response.entity
197             assertNotNull(entity)
198             val contentType = ContentType.get(entity)
199             assertThat(contentType.mimeType, equalToIgnoringCase("text/vnd.yaml"))
200             val yamlResponse = entity.content.bufferedReader().readText()
201             val jsonResponse = yamlToJson(yamlResponse)
202             JSONAssert.assertEquals(expectedJson, jsonResponse, JSONCompareMode.LENIENT)
203         }
204     }
205
206     private fun createMockServer(service: ServiceDefinition): WireMockServer {
207         val mockServer = WireMockServer(
208             wireMockConfig()
209                 .dynamicPort()
210                 .notifier(MarkedSlf4jNotifier(wireMockMarker))
211         )
212         service.expectations.forEach { expectation ->
213
214             val request = expectation.request
215             // WebTestClient always use absolute path, prefixing with "/" if necessary
216             val urlPattern = urlEqualTo(request.path.prefixIfNot("/"))
217             val mappingBuilder: MappingBuilder = request(request.method, urlPattern)
218             request.headers.forEach { (key, value) ->
219                 mappingBuilder.withHeader(key, equalTo(value))
220             }
221             if (request.body != null) {
222                 mappingBuilder.withRequestBody(equalToJson(mapper.writeValueAsString(request.body), true, true))
223             }
224
225             for (response in expectation.responses) {
226                 val responseDefinitionBuilder: ResponseDefinitionBuilder = aResponse()
227                     .withStatus(response.status)
228                 if (response.body != null) {
229                     responseDefinitionBuilder.withBody(mapper.writeValueAsBytes(response.body))
230                         .withHeaders(
231                             HttpHeaders(
232                                 response.headers.entries.map { e -> HttpHeader(e.key, e.value) }
233                             )
234                         )
235                 }
236
237                 // TODO: MockServer verification for multiple responses should be done using Wiremock scenarios
238                 mappingBuilder.willReturn(responseDefinitionBuilder)
239             }
240
241             mockServer.stubFor(mappingBuilder)
242         }
243         return mockServer
244     }
245
246     private fun setPropertiesForMockServer(service: ServiceDefinition, mockServer: WireMockServer) {
247         val selector = service.selector
248         val httpPort = mockServer.port()
249         val properties = mapOf(
250             "blueprintsprocessor.restclient.$selector.type" to "basic-auth",
251             "blueprintsprocessor.restclient.$selector.url" to "http://localhost:$httpPort/",
252             // TODO credentials should be validated
253             "blueprintsprocessor.restclient.$selector.username" to "admin",
254             "blueprintsprocessor.restclient.$selector.password" to "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U"
255         )
256         setProperties(properties)
257     }
258
259     /**
260      * Borrowed from com.github.tomakehurst.wiremock.junit.WireMockRule.checkForUnmatchedRequests
261      */
262     private fun WireMockServer.checkForUnmatchedRequests() {
263         val unmatchedRequests = findAllUnmatchedRequests()
264         if (unmatchedRequests.isNotEmpty()) {
265             val nearMisses = findNearMissesForAllUnmatchedRequests()
266             if (nearMisses.isEmpty()) {
267                 throw VerificationException.forUnmatchedRequests(unmatchedRequests)
268             } else {
269                 throw VerificationException.forUnmatchedNearMisses(nearMisses)
270             }
271         }
272     }
273
274     private fun yamlToJson(yaml: String): String {
275         val map: Map<String, Any> = Yaml().load(yaml)
276         return mapper.writeValueAsString(map)
277     }
278 }