2 * Copyright © 2017-2018 AT&T Intellectual Property.
3 * Copyright (C) 2019 Nordix Foundation
4 * Modifications Copyright © 2019 Huawei.
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
21 import com.fasterxml.jackson.databind.JsonNode
22 import com.fasterxml.jackson.databind.ObjectMapper
23 import kotlinx.coroutines.CoroutineStart
24 import kotlinx.coroutines.async
25 import kotlinx.coroutines.runBlocking
26 import org.junit.After
27 import org.junit.Before
29 import org.junit.runner.RunWith
30 import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertiesService
31 import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintPropertyConfiguration
32 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BluePrintRestLibConfiguration
33 import org.springframework.beans.factory.annotation.Autowired
34 import org.springframework.boot.autoconfigure.EnableAutoConfiguration
35 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
36 import org.springframework.boot.test.context.SpringBootTest
37 import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
38 import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory
39 import org.springframework.boot.web.server.WebServer
40 import org.springframework.context.annotation.Bean
41 import org.springframework.http.HttpMethod
42 import org.springframework.http.HttpStatus
43 import org.springframework.http.ResponseEntity
44 import org.springframework.http.server.reactive.HttpHandler
45 import org.springframework.security.config.web.server.ServerHttpSecurity
46 import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
47 import org.springframework.security.core.userdetails.User
48 import org.springframework.security.core.userdetails.UserDetails
49 import org.springframework.security.web.server.SecurityWebFilterChain
50 import org.springframework.test.context.ContextConfiguration
51 import org.springframework.test.context.TestPropertySource
52 import org.springframework.test.context.junit4.SpringRunner
53 import org.springframework.web.bind.annotation.DeleteMapping
54 import org.springframework.web.bind.annotation.GetMapping
55 import org.springframework.web.bind.annotation.PatchMapping
56 import org.springframework.web.bind.annotation.PathVariable
57 import org.springframework.web.bind.annotation.PostMapping
58 import org.springframework.web.bind.annotation.PutMapping
59 import org.springframework.web.bind.annotation.RequestHeader
60 import org.springframework.web.bind.annotation.RequestMapping
61 import org.springframework.web.bind.annotation.RequestParam
62 import org.springframework.web.bind.annotation.RestController
63 import kotlin.test.assertEquals
64 import kotlin.test.assertNotNull
66 @RunWith(SpringRunner::class)
67 @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class])
68 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
69 @ContextConfiguration(
71 BluePrintRestLibConfiguration::class, SampleController::class,
72 SecurityConfiguration::class,
73 BluePrintPropertyConfiguration::class, BluePrintPropertiesService::class
80 "server.ssl.enabled=true",
81 "server.ssl.key-store=classpath:keystore.p12",
82 "server.ssl.key-store-password=changeit",
83 "server.ssl.keyStoreType=PKCS12",
84 "server.ssl.keyAlias=tomcat",
85 "blueprintsprocessor.restclient.sample.type=basic-auth",
86 "blueprintsprocessor.restclient.sample.url=http://127.0.0.1:9081",
87 "blueprintsprocessor.restclient.sample.username=admin",
88 "blueprintsprocessor.restclient.sample.password=jans",
89 "blueprintsprocessor.restclient.test.type=ssl-basic-auth",
90 "blueprintsprocessor.restclient.test.url=https://localhost:8443",
91 "blueprintsprocessor.restclient.test.username=admin",
92 "blueprintsprocessor.restclient.test.password=jans",
93 "blueprintsprocessor.restclient.test.keyStoreInstance=PKCS12",
94 "blueprintsprocessor.restclient.test.sslTrust=src/test/resources/keystore.p12",
95 "blueprintsprocessor.restclient.test.sslTrustPassword=changeit"
98 class RestClientServiceTest {
101 lateinit var bluePrintRestLibPropertyService: BluePrintRestLibPropertyService
104 lateinit var httpHandler: HttpHandler
106 lateinit var http: WebServer
108 fun localPort() = http.port
112 // Second Http server required for non-SSL requests to be processed along with the https server.
113 val factory: ReactiveWebServerFactory = NettyReactiveWebServerFactory(9081)
114 this.http = factory.getWebServer(this.httpHandler)
124 fun testGetQueryParam() {
125 val restClientService = bluePrintRestLibPropertyService
126 .blueprintWebClientService("sample")
127 val response = restClientService.exchangeResource(
128 HttpMethod.GET.name, "/sample/query?id=3", ""
131 "query with id:3", response.body,
132 "failed to get query param response"
138 val restClientService = bluePrintRestLibPropertyService
139 .blueprintWebClientService("sample")
140 val response = restClientService.exchangeResource(
141 HttpMethod.PATCH.name, "/sample/name", ""
144 "Patch request successful", response.body,
145 "failed to get patch response"
151 val restClientService = bluePrintRestLibPropertyService
152 .blueprintWebClientService("sample")
153 val headers = mutableMapOf<String, String>()
154 headers["X-Transaction-Id"] = "1234"
155 val response = restClientService.exchangeResource(
159 assertNotNull(response.body, "failed to get response")
163 fun testSimpleBasicAuth() {
164 val json: String = "{\n" +
165 " \"type\" : \"basic-auth\",\n" +
166 " \"url\" : \"http://localhost:9081\",\n" +
167 " \"username\" : \"admin\",\n" +
168 " \"password\" : \"jans\"\n" +
170 val mapper = ObjectMapper()
171 val actualObj: JsonNode = mapper.readTree(json)
172 val restClientService = bluePrintRestLibPropertyService
173 .blueprintWebClientService(actualObj)
174 lateinit var res: String
176 val get = async(start = CoroutineStart.LAZY) {
177 restClientService.exchangeNB(
185 assertNotNull(res, "failed to get response")
186 assertEquals(res, "Basic request arrived successfully")
190 fun testSampleAaiReq() {
191 val restClientService = bluePrintRestLibPropertyService
192 .blueprintWebClientService("test")
193 val headers = mutableMapOf<String, String>()
194 headers["X-TransactionId"] = "9999"
195 headers["X-FromAppId"] = "AAI"
197 " \"customer\": {\n" +
198 " \"global-customer-id\": \"ONSDEMOBJHKCustomer\",\n" +
199 " \"subscriber-name\": \"ONSDEMOBJHKCustomer\",\n" +
200 " \"subscriber-type\": \"CUST\",\n" +
201 " \"resource-version\": \"1552985011163\"\n" +
204 lateinit var res1: Customer
205 lateinit var res2: Customer
206 lateinit var res3: String
207 lateinit var res4: String
208 lateinit var res5: String
209 lateinit var res6: String
211 val get1 = async(start = CoroutineStart.LAZY) {
212 restClientService.exchangeNB(
214 "/sample/aai/v22/business/customers", "", headers,
219 val get2 = async(start = CoroutineStart.LAZY) {
220 restClientService.exchangeNB(
222 "/sample/aai/v22/business/customers", "", headers,
227 val post = async(start = CoroutineStart.LAZY) {
228 restClientService.exchangeNB(
229 HttpMethod.POST.name,
230 "/sample/aai/v22/business/customers", post1, headers,
235 val put = async(start = CoroutineStart.LAZY) {
236 restClientService.exchangeNB(
238 "/sample/aai/v22/business/customers", post1, headers,
243 val patch = async(start = CoroutineStart.LAZY) {
244 restClientService.exchangeNB(
245 HttpMethod.PATCH.name,
246 "/sample/aai/v22/business/customers", post1, headers,
251 val delete = async(start = CoroutineStart.LAZY) {
252 restClientService.exchangeNB(
253 HttpMethod.DELETE.name,
254 "/sample/aai/v22/business/customers", "", headers,
270 res6 = delete.await()
272 assertNotNull(res1, "failed to get response")
273 assertNotNull(res2, "failed to get response")
274 assertEquals(res1.id, "ONSDEMOBJHKCustomer")
275 assertEquals(res1.name, "ONSDEMOBJHKCustomer")
276 assertEquals(res1.type, "CUST")
277 assertEquals(res1.resource, "1552985011163")
278 assertEquals(res2.id, "ONSDEMOBJHKCustomer")
279 assertEquals(res2.name, "ONSDEMOBJHKCustomer")
280 assertEquals(res2.type, "CUST")
281 assertEquals(res2.resource, "1552985011163")
282 assertEquals(res3, "The message is successfully posted")
283 assertEquals(res4, "The put request is success")
284 assertEquals(res5, "The patch request is success")
285 assertEquals(res6, "The message is successfully deleted")
288 fun testDeleteWith204StatusAndResponseAsString() {
289 val restClientService = bluePrintRestLibPropertyService
290 .blueprintWebClientService("sample")
291 val headers = mutableMapOf<String, String>()
292 headers["X-Transaction-Id"] = "1234"
293 val response = restClientService.exchangeResource(
294 HttpMethod.DELETE.name,
297 assertEquals(response.status, 204)
298 assertEquals(response.body, "")
302 fun testDeleteWith204StatusAndResponseAsCustomerWithDefaultConstructor() {
303 val restClientService = bluePrintRestLibPropertyService
304 .blueprintWebClientService("sample")
305 val headers = mutableMapOf<String, String>()
306 headers["X-Transaction-Id"] = "1234"
308 val response = restClientService.exchangeNB(
309 HttpMethod.DELETE.name,
310 "/sample/customersWithDefaultConstructor", "", headers, CustomerWithDefaultConstructor::class.java
312 assertEquals(response.status, 204)
313 assertEquals(response.body, CustomerWithDefaultConstructor())
318 fun testDeleteWith204StatusAndResponseAsCustomerWithoutDefaultConstructor() {
319 val restClientService = bluePrintRestLibPropertyService
320 .blueprintWebClientService("sample")
321 val headers = mutableMapOf<String, String>()
322 headers["X-Transaction-Id"] = "1234"
324 val response = restClientService.exchangeNB(
325 HttpMethod.DELETE.name,
326 "/sample/customersWithoutDefaultConstructor", "", headers, CustomerWithoutDefaultConstructor::class.java
328 assertEquals(response.status, 204)
329 assertEquals(response.body, CustomerWithoutDefaultConstructor(""))
335 * Sample controller code for testing both http and https requests.
338 @RequestMapping("/sample")
339 open class SampleController {
342 fun getName(): String = "Sample Controller"
343 @DeleteMapping("/name")
344 fun deleteNameWith204StatusAndEmptyBody(): ResponseEntity<Unit> {
345 return ResponseEntity(HttpStatus.NO_CONTENT)
348 @GetMapping("/query")
349 fun getQuery(@RequestParam("id") id: String): String =
352 @GetMapping("/path/{id}/get")
353 fun getPathParam(@PathVariable("id") id: String): String =
356 @PatchMapping("/name")
357 fun patchName(): String = "Patch request successful"
359 @GetMapping("/basic")
360 fun getBasic(): String = "Basic request arrived successfully"
362 @GetMapping("/aai/v22/business/customers")
364 @RequestHeader(name = "X-TransactionId", required = true)
366 @RequestHeader(name = "X-FromAppId", required = true)
369 if (transId != "9999" || appId != "AAI") {
373 " \"id\": \"ONSDEMOBJHKCustomer\",\n" +
374 " \"name\": \"ONSDEMOBJHKCustomer\",\n" +
375 " \"type\": \"CUST\",\n" +
376 " \"resource\": \"1552985011163\"\n" +
380 @PostMapping("/aai/v22/business/customers")
381 fun postAaiCustomers(
382 @RequestHeader(name = "X-TransactionId", required = true)
384 @RequestHeader(name = "X-FromAppId", required = true)
387 if (transId != "9999" || appId != "AAI") {
390 return "The message is successfully posted"
393 @PutMapping("/aai/v22/business/customers")
395 @RequestHeader(name = "X-TransactionId", required = true)
397 @RequestHeader(name = "X-FromAppId", required = true)
400 if (transId != "9999" || appId != "AAI") {
403 return "The put request is success"
406 @PatchMapping("/aai/v22/business/customers")
407 fun patchAaiCustomers(
408 @RequestHeader(name = "X-TransactionId", required = true)
410 @RequestHeader(name = "X-FromAppId", required = true)
413 if (transId != "9999" || appId != "AAI") {
416 return "The patch request is success"
419 @DeleteMapping("/aai/v22/business/customers")
420 fun deleteAaiCustomers(
421 @RequestHeader(name = "X-TransactionId", required = true)
423 @RequestHeader(name = "X-FromAppId", required = true)
426 if (transId != "9999" || appId != "AAI") {
429 return "The message is successfully deleted"
431 @DeleteMapping("/customersWithDefaultConstructor")
432 fun deleteCustomersWith204StatusAndResponseAsCustomerWithDefaultConstructor():
433 ResponseEntity<CustomerWithDefaultConstructor> {
434 return ResponseEntity(CustomerWithDefaultConstructor(), HttpStatus.NO_CONTENT)
437 @DeleteMapping("/customersWithoutDefaultConstructor")
438 fun deleteCustomersWith204StatusAndResponseAsCustomerWithoutDefaultConstructor():
439 ResponseEntity<CustomerWithoutDefaultConstructor> {
440 return ResponseEntity(CustomerWithoutDefaultConstructor(""), HttpStatus.NO_CONTENT)
445 * Security configuration required for basic authentication with username and
446 * password for any request in the server.
448 open class SecurityConfiguration {
451 open fun userDetailsService(): MapReactiveUserDetailsService {
452 val user: UserDetails = User.withDefaultPasswordEncoder()
457 return MapReactiveUserDetailsService(user)
461 open fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
464 .authorizeExchange().anyExchange().authenticated()
471 * Data class required for response
480 data class CustomerWithoutDefaultConstructor(
484 data class CustomerWithDefaultConstructor(
487 constructor() : this("")