Merge SW Upgrade Blueprint into PNF_AAI and create one UAT BP for PNF
[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         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             // WebTestClient always use absolute path, prefixing with "/" if necessary
215             val urlPattern = urlEqualTo(request.path.prefixIfNot("/"))
216             val mappingBuilder: MappingBuilder = request(request.method, urlPattern)
217             request.headers.forEach { (key, value) ->
218                 mappingBuilder.withHeader(key, equalTo(value))
219             }
220             if (request.body != null) {
221                 mappingBuilder.withRequestBody(equalToJson(mapper.writeValueAsString(request.body), true, true))
222             }
223
224             for (response in expectation.responses) {
225                 val responseDefinitionBuilder: ResponseDefinitionBuilder = aResponse()
226                         .withStatus(response.status)
227                 if (response.body != null) {
228                     responseDefinitionBuilder.withBody(mapper.writeValueAsBytes(response.body))
229                         .withHeaders(HttpHeaders(
230                             response.headers.entries.map { e -> HttpHeader(e.key, e.value) }))
231                 }
232
233                 // TODO: MockServer verification for multiple responses should be done using Wiremock scenarios
234                 mappingBuilder.willReturn(responseDefinitionBuilder)
235             }
236
237             mockServer.stubFor(mappingBuilder)
238         }
239         return mockServer
240     }
241
242     private fun setPropertiesForMockServer(service: ServiceDefinition, mockServer: WireMockServer) {
243         val selector = service.selector
244         val httpPort = mockServer.port()
245         val properties = mapOf(
246             "blueprintsprocessor.restclient.$selector.type" to "basic-auth",
247             "blueprintsprocessor.restclient.$selector.url" to "http://localhost:$httpPort/",
248             // TODO credentials should be validated
249             "blueprintsprocessor.restclient.$selector.username" to "admin",
250             "blueprintsprocessor.restclient.$selector.password" to "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U"
251         )
252         setProperties(properties)
253     }
254
255     /**
256      * Borrowed from com.github.tomakehurst.wiremock.junit.WireMockRule.checkForUnmatchedRequests
257      */
258     private fun WireMockServer.checkForUnmatchedRequests() {
259         val unmatchedRequests = findAllUnmatchedRequests()
260         if (unmatchedRequests.isNotEmpty()) {
261             val nearMisses = findNearMissesForAllUnmatchedRequests()
262             if (nearMisses.isEmpty()) {
263                 throw VerificationException.forUnmatchedRequests(unmatchedRequests)
264             } else {
265                 throw VerificationException.forUnmatchedNearMisses(nearMisses)
266             }
267         }
268     }
269
270     private fun yamlToJson(yaml: String): String {
271         val map: Map<String, Any> = Yaml().load(yaml)
272         return mapper.writeValueAsString(map)
273     }
274 }