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