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