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() {
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)
84 lateinit var mapper: ObjectMapper
87 lateinit var environment: ConfigurableEnvironment
89 private val ephemeralProperties = mutableSetOf<String>()
90 private val startedMockServers = mutableListOf<WireMockServer>()
92 private fun setProperties(properties: Map<String, String>) {
93 inlinedPropertySource().putAll(properties)
94 ephemeralProperties += properties.keys
98 fun resetProperties() {
99 val source = inlinedPropertySource()
100 ephemeralProperties.forEach { key -> source.remove(key) }
101 ephemeralProperties.clear()
105 fun stopMockServers() {
106 startedMockServers.forEach { mockServer ->
108 mockServer.checkForUnmatchedRequests()
113 startedMockServers.clear()
116 private fun inlinedPropertySource(): MutableMap<String, Any> =
117 (environment.propertySources[INLINED_PROPERTIES_PROPERTY_SOURCE_NAME] as MapPropertySource).source
120 var localServerPort: Int = 0
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"
127 lateinit var httpClient: CloseableHttpClient
130 fun setupHttpClient() {
131 val defaultHeaders = listOf(
133 org.apache.http.HttpHeaders.AUTHORIZATION,
134 TestSecuritySettings.clientAuthToken()
137 httpClient = HttpClientBuilder.create()
138 .setDefaultHeaders(defaultHeaders)
143 fun `verify service validates candidate UAT`() {
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")
150 val request = HttpPost("$baseUrl/api/v1/uat/verify").apply {
151 entity = multipartEntity
155 httpClient.execute(request) { response ->
158 val statusLine = response.statusLine
159 assertThat(statusLine.statusCode, CoreMatchers.equalTo(HttpStatus.SC_OK))
164 fun `spy service generates complete UAT from bare UAT`() {
166 val uatSpec = UAT_PATH.toFile().readText()
167 val fullUat = UatDefinition.load(mapper, uatSpec)
168 val expectedJson = mapper.writeValueAsString(fullUat)
170 val bareUatBytes = fullUat.toBare().dump(mapper).toByteArray()
172 fullUat.externalServices.forEach { service ->
173 val mockServer = createMockServer(service)
175 startedMockServers += mockServer
176 setPropertiesForMockServer(service, mockServer)
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")
185 val request = HttpPost("$baseUrl/api/v1/uat/spy").apply {
186 entity = multipartEntity
190 httpClient.execute(request) { response ->
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)
205 private fun createMockServer(service: ServiceDefinition): WireMockServer {
206 val mockServer = WireMockServer(
209 .notifier(MarkedSlf4jNotifier(wireMockMarker))
211 service.expectations.forEach { expectation ->
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))
220 if (request.body != null) {
221 mappingBuilder.withRequestBody(equalToJson(mapper.writeValueAsString(request.body), true, true))
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) }))
233 // TODO: MockServer verification for multiple responses should be done using Wiremock scenarios
234 mappingBuilder.willReturn(responseDefinitionBuilder)
237 mockServer.stubFor(mappingBuilder)
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"
252 setProperties(properties)
256 * Borrowed from com.github.tomakehurst.wiremock.junit.WireMockRule.checkForUnmatchedRequests
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)
265 throw VerificationException.forUnmatchedNearMisses(nearMisses)
270 private fun yamlToJson(yaml: String): String {
271 val map: Map<String, Any> = Yaml().load(yaml)
272 return mapper.writeValueAsString(map)