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