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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
20 package org.onap.ccsdk.cds.blueprintsprocessor.uat
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
69 fun String.prefixIfNot(prefix: String) =
70 if (this.startsWith(prefix)) this else "$prefix$this"
72 @ActiveProfiles("uat")
73 @Suppress("MemberVisibilityCanBePrivate")
74 class UatServicesTest : BaseUatTest() {
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)
85 lateinit var mapper: ObjectMapper
88 lateinit var environment: ConfigurableEnvironment
90 private val ephemeralProperties = mutableSetOf<String>()
91 private val startedMockServers = mutableListOf<WireMockServer>()
93 private fun setProperties(properties: Map<String, String>) {
94 inlinedPropertySource().putAll(properties)
95 ephemeralProperties += properties.keys
99 fun resetProperties() {
100 val source = inlinedPropertySource()
101 ephemeralProperties.forEach { key -> source.remove(key) }
102 ephemeralProperties.clear()
106 fun stopMockServers() {
107 startedMockServers.forEach { mockServer ->
109 mockServer.checkForUnmatchedRequests()
114 startedMockServers.clear()
117 private fun inlinedPropertySource(): MutableMap<String, Any> =
118 (environment.propertySources[INLINED_PROPERTIES_PROPERTY_SOURCE_NAME] as MapPropertySource).source
121 var localServerPort: Int = 0
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"
128 lateinit var httpClient: CloseableHttpClient
131 fun setupHttpClient() {
132 val defaultHeaders = listOf(
134 org.apache.http.HttpHeaders.AUTHORIZATION,
135 TestSecuritySettings.clientAuthToken()
138 httpClient = HttpClientBuilder.create()
139 .setDefaultHeaders(defaultHeaders)
144 fun `verify service validates candidate UAT`() {
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")
151 val request = HttpPost("$baseUrl/api/v1/uat/verify").apply {
152 entity = multipartEntity
156 httpClient.execute(request) { response ->
159 val statusLine = response.statusLine
160 assertThat(statusLine.statusCode, CoreMatchers.equalTo(HttpStatus.SC_OK))
165 fun `spy service generates complete UAT from bare UAT`() {
167 val uatSpec = UAT_PATH.toFile().readText()
168 val fullUat = UatDefinition.load(mapper, uatSpec)
169 val expectedJson = mapper.writeValueAsString(fullUat)
171 val bareUatBytes = fullUat.toBare().dump(mapper).toByteArray()
173 fullUat.externalServices.forEach { service ->
174 val mockServer = createMockServer(service)
176 startedMockServers += mockServer
177 setPropertiesForMockServer(service, mockServer)
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")
186 val request = HttpPost("$baseUrl/api/v1/uat/spy").apply {
187 entity = multipartEntity
191 httpClient.execute(request) { response ->
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)
206 private fun createMockServer(service: ServiceDefinition): WireMockServer {
207 val mockServer = WireMockServer(
210 .notifier(MarkedSlf4jNotifier(wireMockMarker))
212 service.expectations.forEach { expectation ->
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))
221 if (request.body != null) {
222 mappingBuilder.withRequestBody(equalToJson(mapper.writeValueAsString(request.body), true, true))
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))
232 response.headers.entries.map { e -> HttpHeader(e.key, e.value) }
237 // TODO: MockServer verification for multiple responses should be done using Wiremock scenarios
238 mappingBuilder.willReturn(responseDefinitionBuilder)
241 mockServer.stubFor(mappingBuilder)
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"
256 setProperties(properties)
260 * Borrowed from com.github.tomakehurst.wiremock.junit.WireMockRule.checkForUnmatchedRequests
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)
269 throw VerificationException.forUnmatchedNearMisses(nearMisses)
274 private fun yamlToJson(yaml: String): String {
275 val map: Map<String, Any> = Yaml().load(yaml)
276 return mapper.writeValueAsString(map)