Fix incorrect encoding for query params
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / commons / rest-lib / src / test / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / rest / service / RestClientServiceTest.kt
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  * Copyright (C) 2019 Nordix Foundation
4  * Modifications Copyright © 2019 Huawei.
5  *
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
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  */
18
19 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
20
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
28 import org.junit.Test
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.server.reactive.HttpHandler
43 import org.springframework.security.config.web.server.ServerHttpSecurity
44 import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
45 import org.springframework.security.core.userdetails.User
46 import org.springframework.security.core.userdetails.UserDetails
47 import org.springframework.security.web.server.SecurityWebFilterChain
48 import org.springframework.test.context.ContextConfiguration
49 import org.springframework.test.context.TestPropertySource
50 import org.springframework.test.context.junit4.SpringRunner
51 import org.springframework.web.bind.annotation.DeleteMapping
52 import org.springframework.web.bind.annotation.GetMapping
53 import org.springframework.web.bind.annotation.PatchMapping
54 import org.springframework.web.bind.annotation.PathVariable
55 import org.springframework.web.bind.annotation.PostMapping
56 import org.springframework.web.bind.annotation.PutMapping
57 import org.springframework.web.bind.annotation.RequestHeader
58 import org.springframework.web.bind.annotation.RequestMapping
59 import org.springframework.web.bind.annotation.RequestParam
60 import org.springframework.web.bind.annotation.RestController
61 import kotlin.test.assertEquals
62 import kotlin.test.assertNotNull
63
64 @RunWith(SpringRunner::class)
65 @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class])
66 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
67 @ContextConfiguration(
68     classes = [BluePrintRestLibConfiguration::class, SampleController::class,
69         SecurityConfiguration::class,
70         BluePrintPropertyConfiguration::class, BluePrintPropertiesService::class]
71 )
72 @TestPropertySource(
73     properties =
74     [
75         "server.port=8443",
76         "server.ssl.enabled=true",
77         "server.ssl.key-store=classpath:keystore.p12",
78         "server.ssl.key-store-password=changeit",
79         "server.ssl.keyStoreType=PKCS12",
80         "server.ssl.keyAlias=tomcat",
81         "blueprintsprocessor.restclient.sample.type=basic-auth",
82         "blueprintsprocessor.restclient.sample.url=http://127.0.0.1:8081",
83         "blueprintsprocessor.restclient.sample.username=admin",
84         "blueprintsprocessor.restclient.sample.password=jans",
85         "blueprintsprocessor.restclient.test.type=ssl-basic-auth",
86         "blueprintsprocessor.restclient.test.url=https://localhost:8443",
87         "blueprintsprocessor.restclient.test.username=admin",
88         "blueprintsprocessor.restclient.test.password=jans",
89         "blueprintsprocessor.restclient.test.keyStoreInstance=PKCS12",
90         "blueprintsprocessor.restclient.test.sslTrust=src/test/resources/keystore.p12",
91         "blueprintsprocessor.restclient.test.sslTrustPassword=changeit"
92     ]
93 )
94 class RestClientServiceTest {
95
96     @Autowired
97     lateinit var bluePrintRestLibPropertyService: BluePrintRestLibPropertyService
98
99     @Autowired
100     lateinit var httpHandler: HttpHandler
101
102     lateinit var http: WebServer
103
104     fun localPort() = http.port
105
106     @Before
107     fun start() {
108         // Second Http server required for non-SSL requests to be processed along with the https server.
109         val factory: ReactiveWebServerFactory = NettyReactiveWebServerFactory(8081)
110         this.http = factory.getWebServer(this.httpHandler)
111         this.http.start()
112     }
113
114     @After
115     fun stop() {
116         this.http.stop()
117     }
118
119     @Test
120     fun testGetQueryParam() {
121         val restClientService = bluePrintRestLibPropertyService
122                 .blueprintWebClientService("sample")
123         val response = restClientService.exchangeResource(
124                 HttpMethod.GET.name, "/sample/query?id=3", ""
125         )
126         assertEquals(
127                 "query with id:3", response.body,
128                 "failed to get query param response"
129         )
130     }
131
132     @Test
133     fun testGetPathParamWithWhitespace() {
134         val restClientService = bluePrintRestLibPropertyService
135                 .blueprintWebClientService("sample")
136         val response = restClientService.exchangeResource(
137                 HttpMethod.GET.name, "/sample/path/id 3/get", ""
138         )
139         assertEquals(
140                 "path param id:id 3", response.body,
141                 "failed to get query param response"
142         )
143     }
144
145     @Test
146     fun testPatch() {
147         val restClientService = bluePrintRestLibPropertyService
148             .blueprintWebClientService("sample")
149         val response = restClientService.exchangeResource(
150             HttpMethod.PATCH.name, "/sample/name", ""
151         )
152         assertEquals(
153             "Patch request successful", response.body,
154             "failed to get patch response"
155         )
156     }
157
158     @Test
159     fun testBaseAuth() {
160         val restClientService = bluePrintRestLibPropertyService
161             .blueprintWebClientService("sample")
162         val headers = mutableMapOf<String, String>()
163         headers["X-Transaction-Id"] = "1234"
164         val response = restClientService.exchangeResource(
165             HttpMethod.GET.name,
166             "/sample/name", ""
167         )
168         assertNotNull(response.body, "failed to get response")
169     }
170
171     @Test
172     fun testSimpleBasicAuth() {
173         val json: String = "{\n" +
174                 "  \"type\" : \"basic-auth\",\n" +
175                 "  \"url\" : \"http://localhost:8081\",\n" +
176                 "  \"username\" : \"admin\",\n" +
177                 "  \"password\" : \"jans\"\n" +
178                 "}"
179         val mapper = ObjectMapper()
180         val actualObj: JsonNode = mapper.readTree(json)
181         val restClientService = bluePrintRestLibPropertyService
182             .blueprintWebClientService(actualObj)
183         lateinit var res: String
184         runBlocking {
185             val get = async(start = CoroutineStart.LAZY) {
186                 restClientService.exchangeNB(
187                     HttpMethod.GET.name,
188                     "/sample/basic", ""
189                 ).body
190             }
191             get.start()
192             res = get.await()
193         }
194         assertNotNull(res, "failed to get response")
195         assertEquals(res, "Basic request arrived successfully")
196     }
197
198     @Test
199     fun testSampleAaiReq() {
200         val restClientService = bluePrintRestLibPropertyService
201             .blueprintWebClientService("test")
202         val headers = mutableMapOf<String, String>()
203         headers["X-TransactionId"] = "9999"
204         headers["X-FromAppId"] = "AAI"
205         val post1 = "{\n" +
206                 "  \"customer\": {\n" +
207                 "    \"global-customer-id\": \"ONSDEMOBJHKCustomer\",\n" +
208                 "    \"subscriber-name\": \"ONSDEMOBJHKCustomer\",\n" +
209                 "    \"subscriber-type\": \"CUST\",\n" +
210                 "    \"resource-version\": \"1552985011163\"\n" +
211                 "  }\n" +
212                 "}"
213         lateinit var res1: Customer
214         lateinit var res2: Customer
215         lateinit var res3: String
216         lateinit var res4: String
217         lateinit var res5: String
218         lateinit var res6: String
219         runBlocking {
220             val get1 = async(start = CoroutineStart.LAZY) {
221                 restClientService.exchangeNB(
222                     HttpMethod.GET.name,
223                     "/sample/aai/v14/business/customers", "", headers,
224                     Customer::class.java
225                 ).body
226             }
227
228             val get2 = async(start = CoroutineStart.LAZY) {
229                 restClientService.exchangeNB(
230                     HttpMethod.GET.name,
231                     "/sample/aai/v14/business/customers", "", headers,
232                     Customer::class.java
233                 ).body
234             }
235
236             val post = async(start = CoroutineStart.LAZY) {
237                 restClientService.exchangeNB(
238                     HttpMethod.POST.name,
239                     "/sample/aai/v14/business/customers", post1, headers,
240                     String::class.java
241                 ).body
242             }
243
244             val put = async(start = CoroutineStart.LAZY) {
245                 restClientService.exchangeNB(
246                     HttpMethod.PUT.name,
247                     "/sample/aai/v14/business/customers", post1, headers,
248                     String::class.java
249                 ).body
250             }
251
252             val patch = async(start = CoroutineStart.LAZY) {
253                 restClientService.exchangeNB(
254                     HttpMethod.PATCH.name,
255                     "/sample/aai/v14/business/customers", post1, headers,
256                     String::class.java
257                 ).body
258             }
259
260             val delete = async(start = CoroutineStart.LAZY) {
261                 restClientService.exchangeNB(
262                     HttpMethod.DELETE.name,
263                     "/sample/aai/v14/business/customers", "", headers,
264                     String::class.java
265                 ).body
266             }
267
268             get1.start()
269             get2.start()
270             post.start()
271             put.start()
272             patch.start()
273             delete.start()
274             res1 = get1.await()
275             res2 = get2.await()
276             res3 = post.await()
277             res4 = put.await()
278             res5 = patch.await()
279             res6 = delete.await()
280         }
281         assertNotNull(res1, "failed to get response")
282         assertNotNull(res2, "failed to get response")
283         assertEquals(res1.id, "ONSDEMOBJHKCustomer")
284         assertEquals(res1.name, "ONSDEMOBJHKCustomer")
285         assertEquals(res1.type, "CUST")
286         assertEquals(res1.resource, "1552985011163")
287         assertEquals(res2.id, "ONSDEMOBJHKCustomer")
288         assertEquals(res2.name, "ONSDEMOBJHKCustomer")
289         assertEquals(res2.type, "CUST")
290         assertEquals(res2.resource, "1552985011163")
291         assertEquals(res3, "The message is successfully posted")
292         assertEquals(res4, "The put request is success")
293         assertEquals(res5, "The patch request is success")
294         assertEquals(res6, "The message is successfully deleted")
295     }
296 }
297
298 /**
299  * Sample controller code for testing both http and https requests.
300  */
301 @RestController
302 @RequestMapping("/sample")
303 open class SampleController {
304
305     @GetMapping("/name")
306     fun getName(): String = "Sample Controller"
307
308     @GetMapping("/query")
309     fun getQuery(@RequestParam("id") id: String): String =
310             "query with id:$id"
311
312     @GetMapping("/path/{id}/get")
313     fun getPathParam(@PathVariable("id") id: String): String =
314             "path param id:$id"
315
316     @PatchMapping("/name")
317     fun patchName(): String = "Patch request successful"
318
319     @GetMapping("/basic")
320     fun getBasic(): String = "Basic request arrived successfully"
321
322     @GetMapping("/aai/v14/business/customers")
323     fun getAaiCustomers(
324         @RequestHeader(name = "X-TransactionId", required = true)
325         transId: String,
326         @RequestHeader(name = "X-FromAppId", required = true)
327         appId: String
328     ): String {
329         if (transId != "9999" || appId != "AAI") {
330             return ""
331         }
332         return "{\n" +
333                 "  \"id\": \"ONSDEMOBJHKCustomer\",\n" +
334                 "  \"name\": \"ONSDEMOBJHKCustomer\",\n" +
335                 "  \"type\": \"CUST\",\n" +
336                 "  \"resource\": \"1552985011163\"\n" +
337                 "}"
338     }
339
340     @PostMapping("/aai/v14/business/customers")
341     fun postAaiCustomers(
342         @RequestHeader(name = "X-TransactionId", required = true)
343         transId: String,
344         @RequestHeader(name = "X-FromAppId", required = true)
345         appId: String
346     ): String {
347         if (transId != "9999" || appId != "AAI") {
348             return ""
349         }
350         return "The message is successfully posted"
351     }
352
353     @PutMapping("/aai/v14/business/customers")
354     fun putAaiCustomers(
355         @RequestHeader(name = "X-TransactionId", required = true)
356         transId: String,
357         @RequestHeader(name = "X-FromAppId", required = true)
358         appId: String
359     ): String {
360         if (transId != "9999" || appId != "AAI") {
361             return ""
362         }
363         return "The put request is success"
364     }
365
366     @PatchMapping("/aai/v14/business/customers")
367     fun patchAaiCustomers(
368         @RequestHeader(name = "X-TransactionId", required = true)
369         transId: String,
370         @RequestHeader(name = "X-FromAppId", required = true)
371         appId: String
372     ): String {
373         if (transId != "9999" || appId != "AAI") {
374             return ""
375         }
376         return "The patch request is success"
377     }
378
379     @DeleteMapping("/aai/v14/business/customers")
380     fun deleteAaiCustomers(
381         @RequestHeader(name = "X-TransactionId", required = true)
382         transId: String,
383         @RequestHeader(name = "X-FromAppId", required = true)
384         appId: String
385     ): String {
386         if (transId != "9999" || appId != "AAI") {
387             return ""
388         }
389         return "The message is successfully deleted"
390     }
391 }
392
393 /**
394  * Security configuration required for basic authentication with username and
395  * password for any request in the server.
396  */
397 open class SecurityConfiguration {
398
399     @Bean
400     open fun userDetailsService(): MapReactiveUserDetailsService {
401         val user: UserDetails = User.withDefaultPasswordEncoder()
402             .username("admin")
403             .password("jans")
404             .roles("USER")
405             .build()
406         return MapReactiveUserDetailsService(user)
407     }
408
409     @Bean
410     open fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
411         return http
412             .csrf().disable()
413             .authorizeExchange().anyExchange().authenticated()
414             .and().httpBasic()
415             .and().build()
416     }
417 }
418
419 /**
420  * Data class required for response
421  */
422 data class Customer(
423     val id: String,
424     val name: String,
425     val type: String,
426     val resource: String
427 )